From da119ab4d5a2f69aeaabbb66439375d835973326 Mon Sep 17 00:00:00 2001 From: naudachu Date: Mon, 13 Nov 2023 19:10:56 +0500 Subject: [PATCH] - initial commit --- client/discord/discord.go | 20 +++- .../discord/handler/handle_external_task.go | 92 +++++++++++++++++++ client/discord/handler/handle_folder.go | 9 +- client/discord/handler/handle_git.go | 4 +- client/discord/handler/handle_ping.go | 4 +- client/discord/handler/handle_ticket.go | 17 ++-- client/discord/handler/handler.go | 30 ++++-- internal/controller/control_task.go | 79 ++++++++++++++++ internal/domain/models.go | 66 ++----------- internal/storage/db/models.go | 12 +++ internal/storage/db/queries.sql.go | 84 +++++++++++++++++ internal/storage/migrate/0002_init_config.sql | 2 +- internal/storage/migrate/003_init_tasks.sql | 17 ++++ internal/storage/sqlc/queries.sql | 22 ++++- 14 files changed, 370 insertions(+), 88 deletions(-) create mode 100644 client/discord/handler/handle_external_task.go create mode 100644 internal/controller/control_task.go create mode 100644 internal/storage/migrate/003_init_tasks.sql diff --git a/client/discord/discord.go b/client/discord/discord.go index 83960fb..c009d6e 100644 --- a/client/discord/discord.go +++ b/client/discord/discord.go @@ -33,13 +33,25 @@ func Run(conf domain.Config, opts DiscordOptions) error { router := handler.InitRouter(*opts.Controller, &conf.Discord) commandHandlers := map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){} - for _, handler := range router.Routes { + for _, handler := range router.Commands { commandHandlers[handler.Command.Name] = handler.Handler } + componentsHandlers := map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){ + "task_start": router.Components[0].Handler, + "task_close": router.Components[1].Handler, + } + session.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { - if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok { - h(s, i) + switch i.Type { + case discordgo.InteractionApplicationCommand: + if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok { + h(s, i) + } + case discordgo.InteractionMessageComponent: + if h, ok := componentsHandlers[i.MessageComponentData().CustomID]; ok { + h(s, i) + } } }) @@ -50,7 +62,7 @@ func Run(conf domain.Config, opts DiscordOptions) error { log.Println("Adding commands...") var cmds []*discordgo.ApplicationCommand var logString []string - for _, h := range router.Routes { + for _, h := range router.Commands { cmd, err := session.ApplicationCommandCreate(session.State.User.ID, "", &h.Command) if err != nil { log.Panicf("Cannot create '%v' command: %v", h.Command.Name, err) diff --git a/client/discord/handler/handle_external_task.go b/client/discord/handler/handle_external_task.go new file mode 100644 index 0000000..fd65c84 --- /dev/null +++ b/client/discord/handler/handle_external_task.go @@ -0,0 +1,92 @@ +package handler + +import ( + "log" + + "github.com/bwmarrin/discordgo" +) + +func (h *router) CreateExternalTask() CommandRoute { + return CommandRoute{ + + Command: discordgo.ApplicationCommand{ + Name: "test", + Description: "Buttons test", + }, + + Handler: func(s *discordgo.Session, i *discordgo.InteractionCreate) { + + // Моментальный ответ для избежания столкновения с протуханием токена + initialResponse := discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Flags: discordgo.MessageFlagsEphemeral, + Content: "👩‍🍳 Cooking your query..", + }, + } + + s.InteractionRespond(i.Interaction, &initialResponse) + + go func() { + h.controller.InitTask("something like a default description") + }() + }, + } +} + +func (h *router) StartTask() ComponentRoute { + return ComponentRoute{ + Handler: func(s *discordgo.Session, i *discordgo.InteractionCreate) { + + h.controller.UpdateTask(i.Message.ID, 0) + + newMsg := discordgo.MessageEdit{ + Channel: i.ChannelID, + ID: i.Message.ID, + Components: []discordgo.MessageComponent{ + discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{ + discordgo.Button{ + Label: "Close", + Style: discordgo.DangerButton, + Disabled: false, + CustomID: "task_close", + }, + }, + }, + }, + } + + editedM, err := s.ChannelMessageEditComplex(&newMsg) + if err != nil { + log.Println("edition NOT complete, ", err) + } + + log.Print(editedM) + + }, + } +} + +func (h *router) CloseTask() ComponentRoute { + return ComponentRoute{ + Handler: func(s *discordgo.Session, i *discordgo.InteractionCreate) { + + h.controller.UpdateTask(i.Message.ID, 1) + + newMsg := discordgo.MessageEdit{ + Channel: i.ChannelID, + ID: i.Message.ID, + Components: []discordgo.MessageComponent{}, + } + + editedM, err := s.ChannelMessageEditComplex(&newMsg) + if err != nil { + log.Println("edition NOT complete, ", err) + } + + log.Print(editedM) + + }, + } +} diff --git a/client/discord/handler/handle_folder.go b/client/discord/handler/handle_folder.go index f9fcc8c..82d860e 100644 --- a/client/discord/handler/handle_folder.go +++ b/client/discord/handler/handle_folder.go @@ -8,11 +8,11 @@ import ( "github.com/bwmarrin/discordgo" ) -func (h *router) CreateFolderHandler(nameMinLenght int) route { +func (h *router) CreateFolderHandler(nameMinLenght int) CommandRoute { const ( nameOption string = "folder_name" ) - return route{ + return CommandRoute{ Command: discordgo.ApplicationCommand{ Name: "folder", @@ -79,7 +79,10 @@ func (h *router) CreateFolderHandler(nameMinLenght int) route { if resp.Project == nil { result = "Надо написать имя для папки, или создать папку из проекта!" } else { - result = resp.Project.DiscordString() + "Errors: " + resp.Message.Error() + result = resp.Project.DiscordString() + if resp.Message != nil { + result += "Errors: " + resp.Message.Error() + } } h.defaultFollowUp(result, s, i) diff --git a/client/discord/handler/handle_git.go b/client/discord/handler/handle_git.go index a9149c3..6a62152 100644 --- a/client/discord/handler/handle_git.go +++ b/client/discord/handler/handle_git.go @@ -8,7 +8,7 @@ import ( "github.com/bwmarrin/discordgo" ) -func (h *router) CreateRepoHandler(repoNameMinLength int) route { +func (h *router) CreateRepoHandler(repoNameMinLength int) CommandRoute { const ( repoType = "repo_type" projectRepo = "project_repo" @@ -16,7 +16,7 @@ func (h *router) CreateRepoHandler(repoNameMinLength int) route { nameOption = "repo_name" ) - return route{ + return CommandRoute{ Command: discordgo.ApplicationCommand{ Name: "repo", Description: "Command for repository creation", diff --git a/client/discord/handler/handle_ping.go b/client/discord/handler/handle_ping.go index b44b88c..833d11c 100644 --- a/client/discord/handler/handle_ping.go +++ b/client/discord/handler/handle_ping.go @@ -6,8 +6,8 @@ import ( "github.com/bwmarrin/discordgo" ) -func (h *router) Ping() route { - return route{ +func (h *router) Ping() CommandRoute { + return CommandRoute{ Command: discordgo.ApplicationCommand{ Name: "ping", Description: "pongs in a reply", diff --git a/client/discord/handler/handle_ticket.go b/client/discord/handler/handle_ticket.go index 0e71c33..2e7f790 100644 --- a/client/discord/handler/handle_ticket.go +++ b/client/discord/handler/handle_ticket.go @@ -9,8 +9,8 @@ import ( "github.com/bwmarrin/discordgo" ) -func (h *router) GetInfo() route { - return route{ +func (h *router) GetInfo() CommandRoute { + return CommandRoute{ Command: discordgo.ApplicationCommand{ Name: "info", Description: "Get project's info", @@ -52,11 +52,11 @@ func (h *router) GetInfo() route { } } -func (h *router) InitProjectFromChannel(minLength int) route { +func (h *router) InitProjectFromChannel(minLength int) CommandRoute { const ( keyOption = "key" ) - return route{ + return CommandRoute{ Command: discordgo.ApplicationCommand{ Name: "init_project", Description: "Connect project with Coda ID", @@ -121,7 +121,10 @@ func (h *router) InitProjectFromChannel(minLength int) route { if err != nil { result = fmt.Sprintf("unable to init project: %v", err) } else { - result = project.DiscordString() + "Errors: " + errMsg.Error() + result = project.DiscordString() + if errMsg != nil { + result += "Errors: " + errMsg.Error() + } } } } @@ -131,8 +134,8 @@ func (h *router) InitProjectFromChannel(minLength int) route { } } -func (h *router) CreateTicketHandler(repoNameMinLength int) route { - return route{ +func (h *router) CreateTicketHandler(repoNameMinLength int) CommandRoute { + return CommandRoute{ Command: discordgo.ApplicationCommand{ Name: "project", Description: "Create new development ticket", diff --git a/client/discord/handler/handler.go b/client/discord/handler/handler.go index 96398e4..d6ddaf9 100644 --- a/client/discord/handler/handler.go +++ b/client/discord/handler/handler.go @@ -9,7 +9,8 @@ import ( ) type router struct { - Routes []route + Commands []CommandRoute + Components []ComponentRoute controller controller.WorkflowController conf *domain.DiscordConfig } @@ -18,14 +19,18 @@ type router struct { func InitRouter(wc controller.WorkflowController, conf *domain.DiscordConfig) *router { var r router - r.Routes = append( - r.Routes, - r.CreateRepoHandler(3), - r.CreateFolderHandler(3), - r.Ping(), - r.CreateTicketHandler(3), - r.InitProjectFromChannel(3), - r.GetInfo(), + r.Commands = append(r.Commands, + // r.CreateRepoHandler(3), + // r.CreateFolderHandler(3), + // r.Ping(), + // r.CreateTicketHandler(3), + // r.InitProjectFromChannel(3), + // r.GetInfo(), + r.CreateExternalTask(), + ) + r.Components = append(r.Components, + r.StartTask(), + r.CloseTask(), ) r.controller = wc r.conf = conf @@ -33,11 +38,16 @@ func InitRouter(wc controller.WorkflowController, conf *domain.DiscordConfig) *r return &r } -type route struct { +type CommandRoute struct { Command discordgo.ApplicationCommand Handler func(s *discordgo.Session, i *discordgo.InteractionCreate) } +type ComponentRoute struct { + Component discordgo.MessageComponent + Handler func(s *discordgo.Session, i *discordgo.InteractionCreate) +} + func (h *router) defaultFollowUp(answer string, s *discordgo.Session, i *discordgo.InteractionCreate) { // Sending result: diff --git a/internal/controller/control_task.go b/internal/controller/control_task.go new file mode 100644 index 0000000..a6ef764 --- /dev/null +++ b/internal/controller/control_task.go @@ -0,0 +1,79 @@ +package controller + +import ( + "context" + "log" + "os" + "ticket-pimp/internal/storage/db" + "time" + + "github.com/bwmarrin/discordgo" + "github.com/jackc/pgx/v5/pgtype" +) + +func (wc *WorkflowController) InitTask(description string) { + var ( + token = os.Getenv("DISCORD_TOKEN") + channel = os.Getenv("TASKS_CHANNEL") + ) + discord, err := discordgo.New("Bot " + token) + if err != nil { + log.Fatalf("unable to create discord session: %v", err) + // [ ] Что делать, если не получилось создать задачу? + } + + if err := discord.Open(); err != nil { + // [ ] Что делать, если не получилось создать задачу? + log.Printf("cannot open the session: %v", err) + } + + msg := discordgo.MessageSend{ + Content: description, + Components: []discordgo.MessageComponent{ + discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{ + discordgo.Button{ + Label: "Start", + Style: discordgo.SuccessButton, + Disabled: false, + CustomID: "task_start", + }, + discordgo.Button{ + Label: "Close", + Style: discordgo.DangerButton, + Disabled: true, + CustomID: "task_close", + }, + }, + }, + }, + } + + st, err := discord.ChannelMessageSendComplex(channel, &msg) + if err != nil { + log.Println("unable to send task message") + } + + t, err := wc.q.InsertTask(context.TODO(), pgtype.Text{String: st.ID, Valid: true}) + if err != nil { + log.Println("unable to insert task") + } + + _ = t + +} + +func (wc *WorkflowController) UpdateTask(id string, opt int) { + switch opt { + case 0: + wc.q.StartTask(context.TODO(), db.StartTaskParams{ + StartedAt: pgtype.Timestamptz{Time: time.Now(), InfinityModifier: 0, Valid: true}, + Messageid: pgtype.Text{String: id, Valid: true}, + }) + case 1: + wc.q.CloseTask(context.TODO(), db.CloseTaskParams{ + ClosedAt: pgtype.Timestamptz{Time: time.Now(), InfinityModifier: 0, Valid: true}, + Messageid: pgtype.Text{String: id, Valid: true}, + }) + } +} diff --git a/internal/domain/models.go b/internal/domain/models.go index c41f1bd..fa222a8 100644 --- a/internal/domain/models.go +++ b/internal/domain/models.go @@ -68,10 +68,6 @@ func NewTask(summ, desc, c, cLink string) *Task { } } -type ConversionLog struct { - Advertiser []string -} - type Git struct { Name string `json:"name"` // "poop" FullName string `json:"full_name"` // "developer/poop" @@ -83,69 +79,23 @@ type Git struct { } type Project struct { - ID string `json:"id"` - ShortName string `json:"shortName"` - Name string `json:"name"` - ChannelID string `json:"channel_id"` + ID string `json:"id"` //15 + ShortName string `json:"shortName"` //key-15 + Name string `json:"name"` //default project name + ChannelID string `json:"channel_id"` //123412341234 - ProjectGit string `json:"project_git"` - BuildGit string `json:"build_git"` - Cloud string `json:"cloud"` + ProjectGit string `json:"project_git"` //https://github.com/mobilerino/dap-108 + BuildGit string `json:"build_git"` //https://github.com/mobilerino/dap-108-build + Cloud string `json:"cloud"` //http://82.151.222.22:7000/f/86658 } func (p *Project) DiscordString() string { return fmt.Sprintf( - "## Project info:\n> 🔑 key: %s\n> 📂 folder: %s\n> 👾 project git: %s\n>🚀 build git: %s\n", + "## Project info:\n> 🔑 key: %s\n> 📂 folder: %s\n> 👾 project git: %s\n> 🚀 build git: %s\n", p.ShortName, p.Cloud, p.ProjectGit, p.BuildGit, ) } - -type ProjectID struct { - ID string `json:"id"` -} - -type IssueCreateRequest struct { - ProjectID ProjectID `json:"project"` - Key string `json:"idReadable"` - ID string `json:"id"` - Summary string `json:"summary"` - Description string `json:"description"` -} - -// [ ] try `,omitempty` to remove extra struct; - -type IssueUpdateRequest struct { - IssueCreateRequest - CustomFields []CustomField `json:"customFields"` -} - -type CustomField struct { - Name string `json:"name"` - Type string `json:"$type"` - Value string `json:"value"` -} - -type ProjectsList struct { - Projects []Project -} - -// Find needed project.ID in the project's list -func (plist *ProjectsList) FindProjectByName(searchName string) (string, error) { - - projectID := "" - - for _, elem := range plist.Projects { - if elem.ShortName == searchName { - projectID = elem.ID - } - } - - if projectID == "" { - return "", fmt.Errorf("project %s doesn't exist", searchName) - } - return projectID, nil -} diff --git a/internal/storage/db/models.go b/internal/storage/db/models.go index a8f702f..06edc5e 100644 --- a/internal/storage/db/models.go +++ b/internal/storage/db/models.go @@ -13,6 +13,18 @@ type Appconfig struct { TicketID pgtype.Int4 } +type Task struct { + ID int32 + Creator pgtype.Text + CreatorLink pgtype.Text + Messageid pgtype.Text + StartedAt pgtype.Timestamptz + ClosedAt pgtype.Timestamptz + CreatedAt pgtype.Timestamptz + DeletedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} + type Ticket struct { ID int32 Key pgtype.Text diff --git a/internal/storage/db/queries.sql.go b/internal/storage/db/queries.sql.go index a425106..b72b3be 100644 --- a/internal/storage/db/queries.sql.go +++ b/internal/storage/db/queries.sql.go @@ -11,6 +11,35 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +const closeTask = `-- name: CloseTask :one +UPDATE tasks +SET closed_at = $1 +WHERE messageID = $2 +RETURNING id, creator, creator_link, messageid, started_at, closed_at, created_at, deleted_at, updated_at +` + +type CloseTaskParams struct { + ClosedAt pgtype.Timestamptz + Messageid pgtype.Text +} + +func (q *Queries) CloseTask(ctx context.Context, arg CloseTaskParams) (Task, error) { + row := q.db.QueryRow(ctx, closeTask, arg.ClosedAt, arg.Messageid) + var i Task + err := row.Scan( + &i.ID, + &i.Creator, + &i.CreatorLink, + &i.Messageid, + &i.StartedAt, + &i.ClosedAt, + &i.CreatedAt, + &i.DeletedAt, + &i.UpdatedAt, + ) + return i, err +} + const createTicket = `-- name: CreateTicket :one INSERT INTO tickets ( key, channelID @@ -114,6 +143,32 @@ func (q *Queries) GetTicketByID(ctx context.Context, id int32) (Ticket, error) { return i, err } +const insertTask = `-- name: InsertTask :one +INSERT INTO tasks ( + messageID +) VALUES ( + $1 + ) + RETURNING id, creator, creator_link, messageid, started_at, closed_at, created_at, deleted_at, updated_at +` + +func (q *Queries) InsertTask(ctx context.Context, messageid pgtype.Text) (Task, error) { + row := q.db.QueryRow(ctx, insertTask, messageid) + var i Task + err := row.Scan( + &i.ID, + &i.Creator, + &i.CreatorLink, + &i.Messageid, + &i.StartedAt, + &i.ClosedAt, + &i.CreatedAt, + &i.DeletedAt, + &i.UpdatedAt, + ) + return i, err +} + const listTickets = `-- name: ListTickets :many SELECT id, key, channelid, project_git, build_git, folder, created_at, deleted_at, updated_at FROM tickets WHERE deleted_at IS NULL ` @@ -195,6 +250,35 @@ func (q *Queries) SetNewConfig(ctx context.Context) (Appconfig, error) { return i, err } +const startTask = `-- name: StartTask :one +UPDATE tasks +SET started_at = $1 +WHERE messageID = $2 +RETURNING id, creator, creator_link, messageid, started_at, closed_at, created_at, deleted_at, updated_at +` + +type StartTaskParams struct { + StartedAt pgtype.Timestamptz + Messageid pgtype.Text +} + +func (q *Queries) StartTask(ctx context.Context, arg StartTaskParams) (Task, error) { + row := q.db.QueryRow(ctx, startTask, arg.StartedAt, arg.Messageid) + var i Task + err := row.Scan( + &i.ID, + &i.Creator, + &i.CreatorLink, + &i.Messageid, + &i.StartedAt, + &i.ClosedAt, + &i.CreatedAt, + &i.DeletedAt, + &i.UpdatedAt, + ) + return i, err +} + const updateTicketBuildGit = `-- name: UpdateTicketBuildGit :one UPDATE tickets SET build_git = $1, updated_at = $2 diff --git a/internal/storage/migrate/0002_init_config.sql b/internal/storage/migrate/0002_init_config.sql index f8dc281..b5fd33e 100644 --- a/internal/storage/migrate/0002_init_config.sql +++ b/internal/storage/migrate/0002_init_config.sql @@ -5,7 +5,7 @@ CREATE TABLE appconfig ( ); -- +migrate Up -INSERT INTO appconfig (ticket_key, ticket_id) VALUES ('DAP', 100); +INSERT INTO appconfig (ticket_key, ticket_id) VALUES ('P', 100); -- +migrate Down DROP TABLE appconfig; \ No newline at end of file diff --git a/internal/storage/migrate/003_init_tasks.sql b/internal/storage/migrate/003_init_tasks.sql new file mode 100644 index 0000000..c47f75b --- /dev/null +++ b/internal/storage/migrate/003_init_tasks.sql @@ -0,0 +1,17 @@ +-- +migrate Up +CREATE TABLE tasks ( + id SERIAL PRIMARY KEY, + creator VARCHAR(255), + creator_link VARCHAR(255), + messageID VARCHAR(255), + + started_at TIMESTAMPTZ, + closed_at TIMESTAMPTZ, + + created_at TIMESTAMPTZ DEFAULT current_timestamp, + deleted_at TIMESTAMPTZ, + updated_at TIMESTAMPTZ +); + +-- +migrate Down +DROP TABLE tasks; \ No newline at end of file diff --git a/internal/storage/sqlc/queries.sql b/internal/storage/sqlc/queries.sql index d5320ee..11f57a1 100644 --- a/internal/storage/sqlc/queries.sql +++ b/internal/storage/sqlc/queries.sql @@ -52,4 +52,24 @@ UPDATE tickets SET project_git = $1, build_git = $2, folder = $3 WHERE id = $4; UPDATE tickets SET deleted_at = current_timestamp WHERE id = $1; -- name: DeleteTicketByKey :exec -UPDATE tickets SET deleted_at = current_timestamp WHERE key = $1; \ No newline at end of file +UPDATE tickets SET deleted_at = current_timestamp WHERE key = $1; + +-- name: InsertTask :one +INSERT INTO tasks ( + messageID +) VALUES ( + $1 + ) + RETURNING *; + +-- name: StartTask :one +UPDATE tasks +SET started_at = $1 +WHERE messageID = $2 +RETURNING *; + +-- name: CloseTask :one +UPDATE tasks +SET closed_at = $1 +WHERE messageID = $2 +RETURNING *; \ No newline at end of file