- initial commit

This commit is contained in:
naudachu 2023-11-13 19:10:56 +05:00
parent e1acfdf308
commit da119ab4d5
14 changed files with 370 additions and 88 deletions

View File

@ -33,13 +33,25 @@ func Run(conf domain.Config, opts DiscordOptions) error {
router := handler.InitRouter(*opts.Controller, &conf.Discord) router := handler.InitRouter(*opts.Controller, &conf.Discord)
commandHandlers := map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){} 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 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) { session.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok { switch i.Type {
h(s, i) 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...") log.Println("Adding commands...")
var cmds []*discordgo.ApplicationCommand var cmds []*discordgo.ApplicationCommand
var logString []string var logString []string
for _, h := range router.Routes { for _, h := range router.Commands {
cmd, err := session.ApplicationCommandCreate(session.State.User.ID, "", &h.Command) cmd, err := session.ApplicationCommandCreate(session.State.User.ID, "", &h.Command)
if err != nil { if err != nil {
log.Panicf("Cannot create '%v' command: %v", h.Command.Name, err) log.Panicf("Cannot create '%v' command: %v", h.Command.Name, err)

View File

@ -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)
},
}
}

View File

@ -8,11 +8,11 @@ import (
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
) )
func (h *router) CreateFolderHandler(nameMinLenght int) route { func (h *router) CreateFolderHandler(nameMinLenght int) CommandRoute {
const ( const (
nameOption string = "folder_name" nameOption string = "folder_name"
) )
return route{ return CommandRoute{
Command: discordgo.ApplicationCommand{ Command: discordgo.ApplicationCommand{
Name: "folder", Name: "folder",
@ -79,7 +79,10 @@ func (h *router) CreateFolderHandler(nameMinLenght int) route {
if resp.Project == nil { if resp.Project == nil {
result = "Надо написать имя для папки, или создать папку из проекта!" result = "Надо написать имя для папки, или создать папку из проекта!"
} else { } 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) h.defaultFollowUp(result, s, i)

View File

@ -8,7 +8,7 @@ import (
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
) )
func (h *router) CreateRepoHandler(repoNameMinLength int) route { func (h *router) CreateRepoHandler(repoNameMinLength int) CommandRoute {
const ( const (
repoType = "repo_type" repoType = "repo_type"
projectRepo = "project_repo" projectRepo = "project_repo"
@ -16,7 +16,7 @@ func (h *router) CreateRepoHandler(repoNameMinLength int) route {
nameOption = "repo_name" nameOption = "repo_name"
) )
return route{ return CommandRoute{
Command: discordgo.ApplicationCommand{ Command: discordgo.ApplicationCommand{
Name: "repo", Name: "repo",
Description: "Command for repository creation", Description: "Command for repository creation",

View File

@ -6,8 +6,8 @@ import (
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
) )
func (h *router) Ping() route { func (h *router) Ping() CommandRoute {
return route{ return CommandRoute{
Command: discordgo.ApplicationCommand{ Command: discordgo.ApplicationCommand{
Name: "ping", Name: "ping",
Description: "pongs in a reply", Description: "pongs in a reply",

View File

@ -9,8 +9,8 @@ import (
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
) )
func (h *router) GetInfo() route { func (h *router) GetInfo() CommandRoute {
return route{ return CommandRoute{
Command: discordgo.ApplicationCommand{ Command: discordgo.ApplicationCommand{
Name: "info", Name: "info",
Description: "Get project's 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 ( const (
keyOption = "key" keyOption = "key"
) )
return route{ return CommandRoute{
Command: discordgo.ApplicationCommand{ Command: discordgo.ApplicationCommand{
Name: "init_project", Name: "init_project",
Description: "Connect project with Coda ID", Description: "Connect project with Coda ID",
@ -121,7 +121,10 @@ func (h *router) InitProjectFromChannel(minLength int) route {
if err != nil { if err != nil {
result = fmt.Sprintf("unable to init project: %v", err) result = fmt.Sprintf("unable to init project: %v", err)
} else { } 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 { func (h *router) CreateTicketHandler(repoNameMinLength int) CommandRoute {
return route{ return CommandRoute{
Command: discordgo.ApplicationCommand{ Command: discordgo.ApplicationCommand{
Name: "project", Name: "project",
Description: "Create new development ticket", Description: "Create new development ticket",

View File

@ -9,7 +9,8 @@ import (
) )
type router struct { type router struct {
Routes []route Commands []CommandRoute
Components []ComponentRoute
controller controller.WorkflowController controller controller.WorkflowController
conf *domain.DiscordConfig conf *domain.DiscordConfig
} }
@ -18,14 +19,18 @@ type router struct {
func InitRouter(wc controller.WorkflowController, conf *domain.DiscordConfig) *router { func InitRouter(wc controller.WorkflowController, conf *domain.DiscordConfig) *router {
var r router var r router
r.Routes = append( r.Commands = append(r.Commands,
r.Routes, // r.CreateRepoHandler(3),
r.CreateRepoHandler(3), // r.CreateFolderHandler(3),
r.CreateFolderHandler(3), // r.Ping(),
r.Ping(), // r.CreateTicketHandler(3),
r.CreateTicketHandler(3), // r.InitProjectFromChannel(3),
r.InitProjectFromChannel(3), // r.GetInfo(),
r.GetInfo(), r.CreateExternalTask(),
)
r.Components = append(r.Components,
r.StartTask(),
r.CloseTask(),
) )
r.controller = wc r.controller = wc
r.conf = conf r.conf = conf
@ -33,11 +38,16 @@ func InitRouter(wc controller.WorkflowController, conf *domain.DiscordConfig) *r
return &r return &r
} }
type route struct { type CommandRoute struct {
Command discordgo.ApplicationCommand Command discordgo.ApplicationCommand
Handler func(s *discordgo.Session, i *discordgo.InteractionCreate) 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) { func (h *router) defaultFollowUp(answer string, s *discordgo.Session, i *discordgo.InteractionCreate) {
// Sending result: // Sending result:

View File

@ -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},
})
}
}

View File

@ -68,10 +68,6 @@ func NewTask(summ, desc, c, cLink string) *Task {
} }
} }
type ConversionLog struct {
Advertiser []string
}
type Git struct { type Git struct {
Name string `json:"name"` // "poop" Name string `json:"name"` // "poop"
FullName string `json:"full_name"` // "developer/poop" FullName string `json:"full_name"` // "developer/poop"
@ -83,69 +79,23 @@ type Git struct {
} }
type Project struct { type Project struct {
ID string `json:"id"` ID string `json:"id"` //15
ShortName string `json:"shortName"` ShortName string `json:"shortName"` //key-15
Name string `json:"name"` Name string `json:"name"` //default project name
ChannelID string `json:"channel_id"` ChannelID string `json:"channel_id"` //123412341234
ProjectGit string `json:"project_git"` ProjectGit string `json:"project_git"` //https://github.com/mobilerino/dap-108
BuildGit string `json:"build_git"` BuildGit string `json:"build_git"` //https://github.com/mobilerino/dap-108-build
Cloud string `json:"cloud"` Cloud string `json:"cloud"` //http://82.151.222.22:7000/f/86658
} }
func (p *Project) DiscordString() string { func (p *Project) DiscordString() string {
return fmt.Sprintf( 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.ShortName,
p.Cloud, p.Cloud,
p.ProjectGit, p.ProjectGit,
p.BuildGit, 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
}

View File

@ -13,6 +13,18 @@ type Appconfig struct {
TicketID pgtype.Int4 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 { type Ticket struct {
ID int32 ID int32
Key pgtype.Text Key pgtype.Text

View File

@ -11,6 +11,35 @@ import (
"github.com/jackc/pgx/v5/pgtype" "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 const createTicket = `-- name: CreateTicket :one
INSERT INTO tickets ( INSERT INTO tickets (
key, channelID key, channelID
@ -114,6 +143,32 @@ func (q *Queries) GetTicketByID(ctx context.Context, id int32) (Ticket, error) {
return i, err 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 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 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 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 const updateTicketBuildGit = `-- name: UpdateTicketBuildGit :one
UPDATE tickets UPDATE tickets
SET build_git = $1, updated_at = $2 SET build_git = $1, updated_at = $2

View File

@ -5,7 +5,7 @@ CREATE TABLE appconfig (
); );
-- +migrate Up -- +migrate Up
INSERT INTO appconfig (ticket_key, ticket_id) VALUES ('DAP', 100); INSERT INTO appconfig (ticket_key, ticket_id) VALUES ('P', 100);
-- +migrate Down -- +migrate Down
DROP TABLE appconfig; DROP TABLE appconfig;

View File

@ -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;

View File

@ -53,3 +53,23 @@ UPDATE tickets SET deleted_at = current_timestamp WHERE id = $1;
-- name: DeleteTicketByKey :exec -- name: DeleteTicketByKey :exec
UPDATE tickets SET deleted_at = current_timestamp WHERE key = $1; 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 *;