diff --git a/Dockerfile b/Dockerfile
index 9b75311..3002884 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,6 +9,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install -ldflags '-extldflags "-sta
FROM scratch
COPY --from=app-builder /go/bin/main /ticket-pimp
COPY --from=app-builder /go/src/ticket-pimp/cmd/prod.env /
+COPY --from=app-builder /go/src/ticket-pimp/internal/storage/migrate/* /internal/storage/migrate/
# the tls certificates:
# NB: this pulls directly from the upstream image, which already has ca-certificates:
COPY --from=alpine:latest /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
diff --git a/client/discord/discord.go b/client/discord/discord.go
index 83960fb..5e2d06f 100644
--- a/client/discord/discord.go
+++ b/client/discord/discord.go
@@ -1,6 +1,7 @@
package discord
import (
+ "errors"
"fmt"
"log"
"os"
@@ -17,41 +18,95 @@ func initBotWith(token string) *discordgo.Session {
if err != nil {
log.Fatalf("unable to create discord session: %v", err)
}
+
return discord
}
type DiscordOptions struct {
- AppConfig *domain.Config
+ Config *domain.Config
Controller *controller.WorkflowController
}
+func checkPrivateMessaging(s *discordgo.Session, i *discordgo.InteractionCreate) error {
+ dchan, err := s.Channel(i.ChannelID)
+ if err != nil {
+ return err
+ }
+
+ if dchan.Type == discordgo.ChannelTypeDM {
+ s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
+ Type: discordgo.InteractionResponseChannelMessageWithSource,
+ Data: &discordgo.InteractionResponseData{
+ Content: "Yo, fella! I'm not working in private!",
+ },
+ })
+ return errors.New("no private messages! lol")
+ }
+
+ return nil
+}
+
func Run(conf domain.Config, opts DiscordOptions) error {
token := conf.Discord.Token
- session := initBotWith(token)
+ s := initBotWith(token)
- router := handler.InitRouter(*opts.Controller, &conf.Discord)
+ router := handler.InitRouter(*opts.Controller, &conf.Discord, &conf.Telegram)
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
}
- session.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
- if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok {
- h(s, i)
+ componentsHandlers := map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
+ "task_start": router.Components[0].Handler,
+ "task_close": router.Components[0].Handler,
+ }
+
+ s.AddHandler(router.ListenPosts)
+
+ s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
+ err := checkPrivateMessaging(s, i)
+ if err != nil {
+ return
+ }
+
+ 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)
+ }
}
})
- if err := session.Open(); err != nil {
+ if err := s.Open(); err != nil {
return fmt.Errorf("cannot open the session: %v", err)
}
+ // UPDATE FORUM IF NEEDED:
+
+ // forum, err := session.Channel(os.Getenv("TASKS_CHANNEL"))
+ forum, err := s.Channel(conf.Discord.IsProjectChannel)
+ if err != nil {
+ log.Print(err)
+ }
+
+ _, err = s.ChannelEditComplex(forum.ID, &discordgo.ChannelEdit{
+ AvailableTags: &router.Tags,
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
log.Println("Adding commands...")
var cmds []*discordgo.ApplicationCommand
var logString []string
- for _, h := range router.Routes {
- cmd, err := session.ApplicationCommandCreate(session.State.User.ID, "", &h.Command)
+ for _, h := range router.Commands {
+ cmd, err := s.ApplicationCommandCreate(s.State.User.ID, "1103928338898235462", &h.Command)
if err != nil {
log.Panicf("Cannot create '%v' command: %v", h.Command.Name, err)
}
@@ -61,7 +116,8 @@ func Run(conf domain.Config, opts DiscordOptions) error {
log.Println("Following commands added:")
log.Println(logString)
- defer session.Close()
+
+ defer s.Close()
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt)
<-stop
@@ -69,11 +125,10 @@ func Run(conf domain.Config, opts DiscordOptions) error {
log.Println("Removing commands...")
for _, h := range cmds {
- err := session.ApplicationCommandDelete(session.State.User.ID, "", h.ID)
+ err := s.ApplicationCommandDelete(s.State.User.ID, "", h.ID)
if err != nil {
log.Panicf("Cannot delete '%v' command: %v", h.Name, err)
}
}
-
return nil
}
diff --git a/client/discord/handler/handle_external_task.go b/client/discord/handler/handle_external_task.go
new file mode 100644
index 0000000..b41dd5c
--- /dev/null
+++ b/client/discord/handler/handle_external_task.go
@@ -0,0 +1,249 @@
+package handler
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "ticket-pimp/internal/domain"
+
+ "github.com/bwmarrin/discordgo"
+ "github.com/imroc/req/v3"
+)
+
+func (c *client) setFlag(s *discordgo.Session, i *discordgo.InteractionCreate, tag *discordgo.ForumTag) error {
+
+ th, err := s.Channel(i.ChannelID)
+ if err != nil {
+ return err
+ }
+
+ forum, err := s.Channel(th.ParentID)
+ if err != nil {
+ return err
+ }
+
+ // Проверка на существование тега в списке тегов:
+ if len(forum.AvailableTags) != 0 {
+ for _, some := range forum.AvailableTags {
+ if some.Name == tag.Name {
+ _, err := s.ChannelEditComplex(i.ChannelID, &discordgo.ChannelEdit{
+ AppliedTags: &[]string{some.ID},
+ })
+ if err != nil {
+ return err
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+func (c *client) ListenPosts(s *discordgo.Session, th *discordgo.ThreadCreate) {
+
+ // Check if thread starter is not a bot, and thread started at the tasks channel;
+ if th.ParentID != c.conf.IsTaskForum || th.OwnerID == s.State.User.ID {
+ return
+ }
+
+ msgs, _ := s.ChannelMessages(th.ID, 1, "", "", "")
+
+ msg, _ := s.ChannelMessage(th.ID, msgs[0].ID)
+
+ if msg.Author.ID == s.State.User.ID {
+ return
+ }
+
+ content := th.Name
+ content += "\n" + msg.Content
+
+ user, _ := s.GuildMember(th.GuildID, msg.Author.ID)
+
+ t, err := c.controller.WriteTaskToDB(&domain.Task{
+ Description: content,
+ Creator: user.User.Mention(),
+ })
+ if err != nil {
+ s.ChannelMessageSend(th.ID, fmt.Sprintf("unable to write task to db, %v", err))
+ return
+ }
+
+ // [x] -- Отредактировать Thread name как для задачи
+ _, err = s.ChannelEditComplex(th.ID, &discordgo.ChannelEdit{
+ Name: fmt.Sprintf("Task ID: %d, by %s", t.ID, t.Creator),
+ })
+ if err != nil {
+ log.Printf("th edition is not complete: %v", err)
+ }
+
+ // Fix the original task message:
+ taskMessage, err := s.ChannelMessageSendComplex(th.ID, &discordgo.MessageSend{
+ Content: content,
+ 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",
+ },
+ },
+ },
+ },
+ })
+ if err != nil {
+ log.Printf("th start message edition is not complete: %v", err)
+ }
+
+ err = c.controller.UpdateTasksMessageID(context.TODO(), taskMessage.ID, t.ID)
+ if err != nil {
+ s.ChannelMessageSend(th.ID, fmt.Sprintf("unable to update task at the db, %v", err))
+ return
+ }
+}
+
+func (c *client) HandleTaskButtons() Component {
+ return Component{
+ Handler: c.handleTaskButton,
+ }
+}
+
+func (c *client) handleTaskButton(s *discordgo.Session, i *discordgo.InteractionCreate) {
+
+ // Send an empty interaction response; ----------------------------------------------------------------
+ s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
+ Type: discordgo.InteractionResponseUpdateMessage,
+ Data: &discordgo.InteractionResponseData{},
+ })
+
+ // Get assignee value; ---------------------------------------------------------------------------------
+ user := i.Member.User.Mention()
+
+ var (
+ opt int = -1
+ doneButtonIsDisabled bool = false
+ state domain.TaskState = domain.NewTaskState()
+ message string
+ )
+
+ // Check what flow was touched: -------------------------------------------------------------------------
+ switch i.Interaction.MessageComponentData().CustomID {
+ case "task_start":
+ opt = 0
+ doneButtonIsDisabled = false
+ state = domain.InrpogressTaskState()
+ message = "взята в работу"
+ case "task_close":
+ opt = 1
+ doneButtonIsDisabled = true
+ state = domain.DoneTaskState()
+ message = "выполнена"
+ }
+
+ // Send the task update to db --------------------------------------------------------------------------
+ convertable, err := c.controller.UpdateTask(i.Message.ID, opt, user)
+ if err != nil {
+ s.ChannelMessageSend(i.ChannelID, fmt.Sprintf("Unable to update task at the db w/ error: %v", err))
+ return
+ }
+
+ // Map DB's response to domain.Task: -------------------------------------------------------------------
+ task := convertable.
+ ExtractDomain()
+
+ newContent := task.DiscordMessage(state)
+
+ // Send message to the creator in Telegram: -------------------------------------------------------------
+
+ if task.CreatorLink != "" {
+ c.sendTelegramMessageToCreator(
+ task.CreatorLink,
+ fmt.Sprintf("Task ID: %d %s", task.ID, message))
+ }
+
+ // Send a message to the thread about the task was started: ---------------------------------------------
+ _, err = s.ChannelMessageSendComplex(i.ChannelID, &discordgo.MessageSend{
+ Content: newContent,
+ })
+ if err != nil {
+ log.Printf("error while sending start task message: %v", err)
+ }
+
+ // Fix the original task message: ----------------------------------------------------------------------
+ _, err = s.ChannelMessageEditComplex(&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: doneButtonIsDisabled,
+ CustomID: "task_close",
+ },
+ },
+ },
+ },
+ })
+ if err != nil {
+ log.Printf("th start message edition is not complete: %v", err)
+ }
+
+ // Устанавливаем тэги статуса на тред ---------------------------------------------------------------------
+ err = c.setFlag(s, i, &c.Tags[opt])
+ if err != nil {
+ log.Printf("error while `start` tag setting: %v", err)
+ }
+}
+
+type TelegramMessage struct {
+ ChatID string `json:"chat_id"`
+ Text string `json:"text"`
+ DisableNotification bool `json:"disable_notification"`
+ ParseMode string `json:"parse_mode"`
+ DisablePreview bool `json:"disable_web_page_preview"`
+}
+
+func (c *client) sendTelegramMessageToCreator(tgChatID string, text string) {
+
+ http := req.C()
+ http.R().
+ SetBody(&TelegramMessage{
+ ChatID: tgChatID,
+ Text: text,
+ DisableNotification: true,
+ ParseMode: "HTML",
+ DisablePreview: true,
+ }).
+ Post("https://api.telegram.org/bot" + c.tgConf.Token + "/sendMessage")
+ // [HTTP Kit Marlerino]::POST(
+ // "https://api.telegram.org/bot" + Config.botToken +
+ // "/sendMessage",
+ // "",
+ // Object(
+ // "chat_id",
+ // thisRow.[Creator ID].ToText(),
+ // "text",
+ // Format(
+ // "{2} взята в работу",
+ // thisRow.ObjectLink().ToText(),
+ // "Задача"
+ // ),
+ // "disable_notification",
+ // true,
+ // "parse_mode",
+ // "HTML",
+ // "disable_web_page_preview",
+ // true
+ // )
+ // )
+
+}
diff --git a/client/discord/handler/handle_folder.go b/client/discord/handler/handle_folder.go
index f9fcc8c..ab7a6e7 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 (c *client) CreateFolderHandler(nameMinLenght int) Command {
const (
nameOption string = "folder_name"
)
- return route{
+ return Command{
Command: discordgo.ApplicationCommand{
Name: "folder",
@@ -28,61 +28,70 @@ func (h *router) CreateFolderHandler(nameMinLenght int) route {
},
},
- 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)
-
- // Определение переменной для ответа
- var result string = "unexpected result"
-
- // Определение выбранных вариантов ответа
- options := i.ApplicationCommandData().Options
-
- optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options))
- for _, opt := range options {
- optionMap[opt.Name] = opt
- }
-
- // Creating request:
- var req controller.FolderRequest
- name, insertedValueNotNil := optionMap[nameOption]
- dchan, err := s.Channel(i.ChannelID)
-
- if err != nil {
- log.Printf("error while identifying channel: %v", err)
- } else {
-
- if dchan.ParentID == h.conf.IsProjectChannel {
- req.ChannelID = dchan.ID
- if insertedValueNotNil {
- req.InsertedName = name.StringValue()
- }
-
- } else {
- req.ChannelID = ""
- if insertedValueNotNil {
- req.InsertedName = name.StringValue()
- }
- }
- }
-
- // Making request:
- resp := h.controller.CreateFolder(context.TODO(), req)
- if resp.Project == nil {
- result = "Надо написать имя для папки, или создать папку из проекта!"
- } else {
- result = resp.Project.DiscordString() + "Errors: " + resp.Message.Error()
- }
-
- h.defaultFollowUp(result, s, i)
- },
+ Handler: c.createFolderHandler,
}
}
+
+func (c *client) createFolderHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
+ const (
+ nameOption string = "folder_name"
+ )
+
+ // Моментальный ответ для избежания столкновения с протуханием токена
+ initialResponse := discordgo.InteractionResponse{
+ Type: discordgo.InteractionResponseChannelMessageWithSource,
+ Data: &discordgo.InteractionResponseData{
+ Flags: discordgo.MessageFlagsEphemeral,
+ Content: "👩🍳 Cooking your query..",
+ },
+ }
+
+ s.InteractionRespond(i.Interaction, &initialResponse)
+
+ // Определение переменной для ответа
+ var result string = "unexpected result"
+
+ // Определение выбранных вариантов ответа
+ options := i.ApplicationCommandData().Options
+
+ optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options))
+ for _, opt := range options {
+ optionMap[opt.Name] = opt
+ }
+
+ // Creating request:
+ var req controller.FolderRequest
+ name, insertedValueNotNil := optionMap[nameOption]
+ dchan, err := s.Channel(i.ChannelID)
+
+ if err != nil {
+ log.Printf("error while identifying channel: %v", err)
+ } else {
+
+ if dchan.ParentID == c.conf.IsProjectChannel {
+ req.ChannelID = dchan.ID
+ if insertedValueNotNil {
+ req.InsertedName = name.StringValue()
+ }
+
+ } else {
+ req.ChannelID = ""
+ if insertedValueNotNil {
+ req.InsertedName = name.StringValue()
+ }
+ }
+ }
+
+ // Making request:
+ resp := c.controller.CreateFolder(context.TODO(), req)
+ if resp.Project == nil {
+ result = "Надо написать имя для папки, или создать папку из проекта!"
+ } else {
+ result = resp.Project.DiscordString()
+ if resp.Message != nil {
+ result += "Errors: " + resp.Message.Error()
+ }
+ }
+
+ c.defaultFollowUp(result, s, i)
+}
diff --git a/client/discord/handler/handle_git.go b/client/discord/handler/handle_git.go
index a9149c3..f41ff60 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 (c *client) CreateRepoHandler(repoNameMinLength int) Command {
const (
repoType = "repo_type"
projectRepo = "project_repo"
@@ -16,10 +16,10 @@ func (h *router) CreateRepoHandler(repoNameMinLength int) route {
nameOption = "repo_name"
)
- return route{
+ return Command{
Command: discordgo.ApplicationCommand{
Name: "repo",
- Description: "Command for repository creation",
+ Description: "Creates repository of selected type. Name used for projects channels only",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
@@ -46,67 +46,82 @@ func (h *router) CreateRepoHandler(repoNameMinLength int) route {
},
},
},
- 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)
-
- // Определение переменной для ответа
- var result string = "unexpected result"
-
- // Access options in the order provided by the user.
- options := i.ApplicationCommandData().Options
-
- // Or convert the slice into a map
- optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options))
- for _, opt := range options {
- optionMap[opt.Name] = opt
- }
-
- // Creating request:
- var req controller.GitRequest
- name, insertedValueNotNil := optionMap[nameOption]
- isBuild := optionMap[repoType]
- switch isBuild.StringValue() {
- case buildRepo:
- req.IsBuildGit = true
- case projectRepo:
- req.IsBuildGit = false
- }
- dchan, err := s.Channel(i.ChannelID)
-
- if err != nil {
- log.Printf("error while identifying channel: %v", err)
- } else {
-
- if dchan.ParentID == h.conf.IsProjectChannel {
- req.ChannelID = dchan.ID
- if insertedValueNotNil {
- req.InsertedName = name.StringValue()
- }
- } else {
- req.ChannelID = ""
- if insertedValueNotNil {
- req.InsertedName = name.StringValue()
- }
- }
- }
-
- // Making request:
- resp := h.controller.CreateGit(context.TODO(), req)
- if resp.Project == nil {
- result = resp.Message.Error()
- } else {
- result = resp.Project.DiscordString() + "Errors: " + resp.Message.Error()
- }
- h.defaultFollowUp(result, s, i)
- },
+ Handler: c.createRepoHandler,
}
}
+
+func (c *client) createRepoHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
+ const (
+ repoType = "repo_type"
+ projectRepo = "project_repo"
+ buildRepo = "build_repo"
+ nameOption = "repo_name"
+ )
+ // Моментальный ответ для избежания столкновения с протуханием токена
+ initialResponse := discordgo.InteractionResponse{
+ Type: discordgo.InteractionResponseChannelMessageWithSource,
+ Data: &discordgo.InteractionResponseData{
+ Flags: discordgo.MessageFlagsEphemeral,
+ Content: "👩🍳 Cooking your query..",
+ },
+ }
+
+ s.InteractionRespond(i.Interaction, &initialResponse)
+
+ // Определение переменной для ответа
+ var result string = "unexpected result"
+
+ // Access options in the order provided by the user.
+ options := i.ApplicationCommandData().Options
+
+ // Or convert the slice into a map
+ optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options))
+ for _, opt := range options {
+ optionMap[opt.Name] = opt
+ }
+
+ // Creating request:
+ var req controller.GitRequest
+ name, insertedValueNotNil := optionMap[nameOption]
+ isBuild := optionMap[repoType]
+ switch isBuild.StringValue() {
+ case buildRepo:
+ req.IsBuildGit = true
+ case projectRepo:
+ req.IsBuildGit = false
+ }
+ dchan, err := s.Channel(i.ChannelID)
+
+ if err != nil {
+ log.Printf("error while identifying channel: %v", err)
+ } else {
+
+ if dchan.ParentID == c.conf.IsProjectChannel {
+ req.ChannelID = dchan.ID
+ if insertedValueNotNil {
+ req.InsertedName = name.StringValue()
+ }
+ } else {
+ req.ChannelID = ""
+ if insertedValueNotNil {
+ req.InsertedName = name.StringValue()
+ }
+ }
+ }
+
+ // Making request:
+ resp := c.controller.CreateGit(context.TODO(), req)
+ if resp.Project == nil {
+ if resp.Message != nil {
+ result = resp.Message.Error()
+ }
+ } else {
+
+ result = resp.Project.DiscordString()
+ if resp.Message != nil {
+ result += "Errors: " + resp.Message.Error()
+ }
+
+ }
+ c.defaultFollowUp(result, s, i)
+}
diff --git a/client/discord/handler/handle_ping.go b/client/discord/handler/handle_ping.go
index b44b88c..4f91795 100644
--- a/client/discord/handler/handle_ping.go
+++ b/client/discord/handler/handle_ping.go
@@ -6,25 +6,25 @@ import (
"github.com/bwmarrin/discordgo"
)
-func (h *router) Ping() route {
- return route{
+func (c *client) Ping() Command {
+ return Command{
Command: discordgo.ApplicationCommand{
Name: "ping",
Description: "pongs in a reply",
},
- Handler: func(s *discordgo.Session, i *discordgo.InteractionCreate) {
- log.Println("ok, I'm here..")
-
- err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
- Type: discordgo.InteractionResponseChannelMessageWithSource,
- Data: &discordgo.InteractionResponseData{
- Content: "`pong`",
- Title: "Pong reply",
- },
- })
- if err != nil {
- log.Println(err)
- }
- },
+ Handler: c.ping,
+ }
+}
+
+func (c *client) ping(s *discordgo.Session, i *discordgo.InteractionCreate) {
+
+ err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
+ Type: discordgo.InteractionResponseChannelMessageWithSource,
+ Data: &discordgo.InteractionResponseData{
+ Content: "Pong to: " + i.Member.User.Mention(),
+ },
+ })
+ if err != nil {
+ log.Println(err)
}
}
diff --git a/client/discord/handler/handle_ticket.go b/client/discord/handler/handle_ticket.go
index 0e71c33..f904e5d 100644
--- a/client/discord/handler/handle_ticket.go
+++ b/client/discord/handler/handle_ticket.go
@@ -9,130 +9,138 @@ import (
"github.com/bwmarrin/discordgo"
)
-func (h *router) GetInfo() route {
- return route{
+func (c *client) GetInfo() Command {
+ return Command{
Command: discordgo.ApplicationCommand{
Name: "info",
Description: "Get project's info",
},
- 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)
-
- var result string
-
- // Get channel from the request
- dchan, err := s.Channel(i.ChannelID)
- if err != nil {
- result = "unable to get channel from the message"
- } else {
- project, err := h.controller.GetProjectByChannelID(context.TODO(), dchan.ID)
- if err != nil {
- result = err.Error()
- } else {
- result = project.DiscordString()
- if err != nil {
- result += "Errors: " + err.Error()
- }
- }
-
- }
-
- h.defaultFollowUp(result, s, i)
- },
+ Handler: c.getInfo,
}
}
-func (h *router) InitProjectFromChannel(minLength int) route {
- const (
- keyOption = "key"
- )
- return route{
+func (c *client) getInfo(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)
+
+ var result string
+
+ // Get channel from the request
+ dchan, err := s.Channel(i.ChannelID)
+ if err != nil {
+ result = "unable to get channel from the message"
+ } else {
+ project, err := c.controller.GetProjectByChannelID(context.TODO(), dchan.ID)
+ if err != nil {
+ result = err.Error()
+ } else {
+ if project != nil {
+ result = project.DiscordString()
+ if err != nil {
+ result += "Errors: " + err.Error()
+ }
+ } else {
+ result = "Something wrong with retrieving project from db"
+ }
+
+ }
+ }
+
+ c.defaultFollowUp(result, s, i)
+}
+
+func (c *client) InitProjectFromChannel(minLength int) Command {
+ return Command{
Command: discordgo.ApplicationCommand{
Name: "init_project",
Description: "Connect project with Coda ID",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
- Name: keyOption,
+ Name: "key",
Description: "Project's key from Coda.io",
Required: true,
MinLength: &minLength,
},
},
},
- 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)
-
- var result string
-
- // Get channel from the request
- dchan, err := s.Channel(i.ChannelID)
- if err != nil {
- result = "unable to get channel from the message"
- } else {
- if dchan.ParentID != h.conf.IsProjectChannel {
- // Sending result:
- _, err := s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
- Content: "This channel is not at the project's group",
- })
-
- if err != nil {
- s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
- Content: fmt.Sprintf("Something went wrong: %v", err),
- })
- return
- }
- return
- }
-
- // Access options in the order provided by the user.
- options := i.ApplicationCommandData().Options
-
- // Or convert the slice into a map
- optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options))
- for _, opt := range options {
- optionMap[opt.Name] = opt
- }
-
- if option, ok := optionMap[keyOption]; ok {
- var errMsg error = nil
-
- project, err := h.controller.InitProjectInChannel(context.TODO(), i.ChannelID, option.StringValue())
- if err != nil {
- result = fmt.Sprintf("unable to init project: %v", err)
- } else {
- result = project.DiscordString() + "Errors: " + errMsg.Error()
- }
- }
- }
-
- h.defaultFollowUp(result, s, i)
- },
+ Handler: c.initProjectFromChannel,
}
}
-func (h *router) CreateTicketHandler(repoNameMinLength int) route {
- return route{
+func (c *client) initProjectFromChannel(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)
+
+ var result string
+
+ // Get channel from the request
+ dchan, err := s.Channel(i.ChannelID)
+ if err != nil {
+ result = "unable to get channel from the message"
+ } else {
+ if dchan.ParentID != c.conf.IsProjectChannel {
+ // Sending result:
+ _, err := s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
+ Content: "This channel is not at the project's group",
+ })
+
+ if err != nil {
+ s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
+ Content: fmt.Sprintf("Something went wrong: %v", err),
+ })
+ return
+ }
+ return
+ }
+
+ // Access options in the order provided by the user.
+ options := i.ApplicationCommandData().Options
+
+ // Or convert the slice into a map
+ optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options))
+ for _, opt := range options {
+ optionMap[opt.Name] = opt
+ }
+
+ if option, ok := optionMap["key"]; ok {
+ var errMsg error = nil
+
+ project, err := c.controller.InitProjectInChannel(context.TODO(), i.ChannelID, option.StringValue())
+ if err != nil {
+ result = fmt.Sprintf("unable to init project: %v", err)
+ } else {
+ result = project.DiscordString()
+ if errMsg != nil {
+ result += "Errors: " + errMsg.Error()
+ }
+ }
+ }
+ }
+
+ c.defaultFollowUp(result, s, i)
+}
+
+func (c *client) CreateTicketHandler(repoNameMinLength int) Command {
+ return Command{
Command: discordgo.ApplicationCommand{
Name: "project",
Description: "Create new development ticket",
@@ -146,65 +154,67 @@ func (h *router) CreateTicketHandler(repoNameMinLength int) route {
},
},
},
- 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)
-
- var result string
- // Access options in the order provided by the user.
- options := i.ApplicationCommandData().Options
-
- // Or convert the slice into a map
- optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options))
- for _, opt := range options {
- optionMap[opt.Name] = opt
- }
-
- if option, ok := optionMap["project_name"]; ok {
- dchan, err := s.GuildChannelCreate(i.GuildID, option.StringValue(), discordgo.ChannelTypeGuildText)
- if err != nil {
- result = fmt.Sprintf("chan creation problem: %v\n", err)
- } else {
- p, err := h.controller.ProjectCreate(context.TODO(), domain.Project{
- ChannelID: dchan.ID,
- })
- if err != nil {
- result = fmt.Sprintf("unable to create project: %v\n", err)
- _, err := s.ChannelDelete(dchan.ID)
- if err != nil {
- result += fmt.Sprintf("\nunable to clean channel: %v\n", err)
- }
- } else {
- edit := discordgo.ChannelEdit{
- Name: p.ShortName,
- ParentID: "1150719794853716028",
- }
-
- dchan, err = s.ChannelEdit(dchan.ID, &edit)
- if err != nil {
- result = fmt.Sprintf("channel created, but unable to edit: %v\n", err)
-
- } else {
- _, err = s.ChannelMessageSend(dchan.ID, "Hello!")
- if err != nil {
- log.Printf("message send problem: %v\n", err)
- }
- result = dchan.ID
- }
- }
- }
- }
-
- h.defaultFollowUp(result, s, i)
- },
+ Handler: c.createTicketHandler,
}
}
+
+func (c *client) createTicketHandler(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)
+
+ var result string
+ // Access options in the order provided by the user.
+ options := i.ApplicationCommandData().Options
+
+ // Or convert the slice into a map
+ optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options))
+ for _, opt := range options {
+ optionMap[opt.Name] = opt
+ }
+
+ if option, ok := optionMap["project_name"]; ok {
+ dchan, err := s.GuildChannelCreate(i.GuildID, option.StringValue(), discordgo.ChannelTypeGuildText)
+ if err != nil {
+ result = fmt.Sprintf("chan creation problem: %v\n", err)
+ } else {
+ p, err := c.controller.ProjectCreate(context.TODO(), domain.Project{
+ ChannelID: dchan.ID,
+ })
+ if err != nil {
+ result = fmt.Sprintf("unable to create project: %v\n", err)
+ _, err := s.ChannelDelete(dchan.ID)
+ if err != nil {
+ result += fmt.Sprintf("\nunable to clean channel: %v\n", err)
+ }
+ } else {
+ edit := discordgo.ChannelEdit{
+ Name: p.ShortName,
+ ParentID: "1150719794853716028",
+ }
+
+ dchan, err = s.ChannelEdit(dchan.ID, &edit)
+ if err != nil {
+ result = fmt.Sprintf("channel %s created, but unable to edit follow up message: %v\n", p.ShortName, err)
+
+ } else {
+ _, err = s.ChannelMessageSend(dchan.ID, "Hello!")
+ if err != nil {
+ log.Printf("message send problem: %v\n", err)
+ }
+ result = "Project " + p.ShortName + "Was created"
+ }
+ }
+ }
+ }
+
+ c.defaultFollowUp(result, s, i)
+}
diff --git a/client/discord/handler/handler.go b/client/discord/handler/handler.go
index 96398e4..cb64fc3 100644
--- a/client/discord/handler/handler.go
+++ b/client/discord/handler/handler.go
@@ -8,18 +8,25 @@ import (
"github.com/bwmarrin/discordgo"
)
-type router struct {
- Routes []route
+type client struct {
+ Commands []Command
+ Components []Component
+
+ Tags []discordgo.ForumTag
+
controller controller.WorkflowController
conf *domain.DiscordConfig
+ tgConf *domain.TelegramConfig
}
// Подключение роутов к Discord боту
-func InitRouter(wc controller.WorkflowController, conf *domain.DiscordConfig) *router {
+func InitRouter(wc controller.WorkflowController, conf *domain.DiscordConfig, tgConf *domain.TelegramConfig) *client {
- var r router
- r.Routes = append(
- r.Routes,
+ var r client
+ r.controller = wc
+ r.conf = conf
+
+ r.Commands = append(r.Commands,
r.CreateRepoHandler(3),
r.CreateFolderHandler(3),
r.Ping(),
@@ -27,18 +34,42 @@ func InitRouter(wc controller.WorkflowController, conf *domain.DiscordConfig) *r
r.InitProjectFromChannel(3),
r.GetInfo(),
)
- r.controller = wc
- r.conf = conf
+ r.Components = append(r.Components,
+ r.HandleTaskButtons(),
+ )
+
+ r.Tags = append(
+ r.Tags,
+ discordgo.ForumTag{
+ Name: "В работе",
+ Moderated: true,
+ EmojiName: "👩🍳",
+ },
+
+ discordgo.ForumTag{
+ Name: "Готово",
+ Moderated: true,
+ EmojiName: "✅",
+ })
+ r.tgConf = tgConf
return &r
}
-type route struct {
+//
+// Подключение роутов к Discord боту
+
+type Command struct {
Command discordgo.ApplicationCommand
Handler func(s *discordgo.Session, i *discordgo.InteractionCreate)
}
-func (h *router) defaultFollowUp(answer string, s *discordgo.Session, i *discordgo.InteractionCreate) {
+type Component struct {
+ Component discordgo.MessageComponent
+ Handler func(s *discordgo.Session, i *discordgo.InteractionCreate)
+}
+
+func (h *client) defaultFollowUp(answer string, s *discordgo.Session, i *discordgo.InteractionCreate) {
// Sending result:
_, err := s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
diff --git a/client/telegram/handler/handle_farmtask.go b/client/telegram/handler/handle_farmtask.go
index 87e5e35..128c120 100644
--- a/client/telegram/handler/handle_farmtask.go
+++ b/client/telegram/handler/handle_farmtask.go
@@ -2,8 +2,7 @@ package handler
import (
"context"
- "fmt"
- "log"
+ "strconv"
"strings"
"ticket-pimp/internal/domain"
@@ -13,9 +12,17 @@ import (
func (h *Handler) FarmTaskHandler(ctx context.Context, mu *tgb.MessageUpdate) error {
- msgID := mu.Message.ID
+ var (
+ taskText string = ""
+ answer string = ""
+ )
- taskText := strings.TrimSpace(strings.Replace(mu.Text, "/task", "", 1))
+ msgID := mu.Message.ID
+ if mu.Caption != "" {
+ taskText = strings.TrimSpace(strings.Replace(mu.Caption, "/task", "", 1))
+ } else {
+ taskText = strings.TrimSpace(strings.Replace(mu.Text, "/task", "", 1))
+ }
var summaryTail string
@@ -38,33 +45,47 @@ func (h *Handler) FarmTaskHandler(ctx context.Context, mu *tgb.MessageUpdate) er
mu.Chat.ID.PeerID(),
)
- id, err := h.coda.CreateTask(t.Summary, t.Description, t.Creator, t.CreatorLink)
- if err != nil {
- answer := errorAnswer(err.Error())
- h.LogMessage(ctx, mu, answer)
- return mu.Answer(answer).ParseMode(tg.HTML).DoVoid(ctx)
- }
- if id == "" {
- answer := errorAnswer("task wasn't created")
- h.LogMessage(ctx, mu, answer)
- return mu.Answer(answer).ParseMode(tg.HTML).DoVoid(ctx)
- }
+ conv, err := h.controller.InitTask(t)
- err = mu.Answer(fmt.Sprintf("Задача с id: %s была создана, жду ссылку", id)).DoVoid(ctx)
- if err != nil {
- log.Println("бот не смог ответить про создание задачи")
- }
-
- url, err := h.coda.GetRowLink(id)
if err != nil {
answer := err.Error()
h.LogMessage(ctx, mu, answer)
return err
}
- t.URL = url
- answer := tg.HTML.Text(
- tg.HTML.Line(tg.HTML.Link("🤘 Задача", t.URL), "была создана!"))
+ i := strconv.Itoa(int(conv.ID))
+ answer = tg.HTML.Text(
+ tg.HTML.Line(
+ tg.HTML.Bold("Task ID: "),
+ tg.HTML.Code(i),
+ tg.HTML.Text(" was created"),
+ ),
+ tg.HTML.Line(
+ "Заходи в наш",
+ tg.HTML.Link("discord", "https://discord.gg/RHdzK3kUr7"),
+ "чтобы отслеживать статус по задаче",
+ ),
+ )
+ if mu.Caption != "" {
+ answer = tg.HTML.Text(
+ tg.HTML.Line(
+ tg.HTML.Bold("I'm unable to work with files, but"),
+ ),
+ tg.HTML.Line(
+ tg.HTML.Italic(
+ tg.HTML.Bold("Task ID: "),
+ tg.HTML.Code(i),
+ tg.HTML.Text(" was created")),
+ ),
+
+ tg.HTML.Line(
+ "Заходи в наш",
+ tg.HTML.Link("discord", "https://discord.gg/RHdzK3kUr7"),
+ "чтобы отслеживать статус по задаче",
+ ),
+ )
+ }
+
h.LogMessage(ctx, mu, answer)
return mu.Answer(answer).
ReplyToMessageID(msgID).ParseMode(tg.HTML).DisableWebPagePreview(true).DoVoid(ctx)
diff --git a/client/telegram/handler/handler.go b/client/telegram/handler/handler.go
index 7aa7f0e..da3a326 100644
--- a/client/telegram/handler/handler.go
+++ b/client/telegram/handler/handler.go
@@ -1,26 +1,30 @@
package handler
import (
+ "ticket-pimp/internal/controller"
"ticket-pimp/internal/services"
)
type Handler struct {
- git services.IGit
- cloud services.ICloud
- coda services.ICoda
- key string
- id string
+ git services.IGit
+ cloud services.ICloud
+ coda services.ICoda
+ key string
+ id string
+ controller *controller.WorkflowController
}
func NewHandler(
git services.IGit,
cloud services.ICloud,
coda services.ICoda,
+ controller *controller.WorkflowController,
) *Handler {
return &Handler{
- git: git,
- cloud: cloud,
- coda: coda,
+ git: git,
+ cloud: cloud,
+ coda: coda,
+ controller: controller,
}
}
diff --git a/client/telegram/handler/handler_test.go b/client/telegram/handler/handler_test.go
deleted file mode 100644
index d871058..0000000
--- a/client/telegram/handler/handler_test.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package handler
-
-import (
- "testing"
- "ticket-pimp/internal/domain"
-)
-
-type test struct {
- arg domain.Git
- expected string
-}
-
-var tests = []test{
- {domain.Git{
- Name: "text",
- FullName: "",
- Private: false,
- Url: "",
- CloneUrl: "",
- HtmlUrl: "https://reddit.com/",
- SshUrl: "",
- }, "Repo text has been created!"},
-}
-
-func TestPrepareAnswer(t *testing.T) {
-
- for _, test := range tests {
- g := newGit(&test.arg)
-
- if output := g.PrepareAnswer(); output != test.expected {
- t.Errorf("Output %q not equal to expected %q", output, test.expected)
- }
- }
-}
diff --git a/client/telegram/telegram.go b/client/telegram/telegram.go
index d756363..63fb7d8 100644
--- a/client/telegram/telegram.go
+++ b/client/telegram/telegram.go
@@ -4,21 +4,20 @@ import (
"context"
"log"
"ticket-pimp/client/telegram/handler"
+ "ticket-pimp/internal/controller"
"ticket-pimp/internal/domain"
"ticket-pimp/internal/services"
"github.com/mr-linch/go-tg"
"github.com/mr-linch/go-tg/tgb"
-
- tickets "ticket-pimp/internal/storage/db"
)
type TelegramOptions struct {
- TicketsRepo *tickets.Queries
GitService *services.Git
CloudService *services.Cloud
Coda *services.Coda
AppConfig *domain.Config
+ Controller *controller.WorkflowController
}
// runTgBot ...
@@ -35,6 +34,7 @@ func Run(ctx context.Context, opts TelegramOptions) error {
opts.GitService,
opts.CloudService,
opts.Coda,
+ opts.Controller,
)
router := tgb.NewRouter().
diff --git a/cmd/main.go b/cmd/main.go
index 5e3387b..42329fa 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -16,11 +16,18 @@ import (
"ticket-pimp/client/telegram"
"github.com/jackc/pgx/v5/pgxpool"
+ "github.com/jackc/pgx/v5/stdlib"
+ migrate "github.com/rubenv/sql-migrate"
+)
+
+const (
+ envfile = "prod.env"
+ migrationfile = "internal/storage/migrate"
)
func main() {
log.Print("started")
- config := domain.InitConfig("develop.env")
+ config := domain.InitConfig(envfile)
run(config)
}
@@ -29,17 +36,40 @@ func run(conf domain.Config) {
defer cancel()
// -- DB connection init -- START
+ connString := fmt.Sprintf(
+ "postgresql://%s:%s@%s:%s/%s",
+ conf.DB.User, conf.DB.Pass, conf.DB.Host, conf.DB.Port, conf.DB.Name,
+ )
conn, err := pgxpool.New(
ctx,
- fmt.Sprintf(
- "postgresql://%s:%s@%s:%s/%s",
- conf.DB.User, conf.DB.Pass, conf.DB.Host, conf.DB.Port, conf.DB.Name,
- ))
+ connString)
if err != nil {
log.Fatalf("DB connection failed: %v", err)
}
// -- DB connection init -- END
+ // Aply migrations:
+
+ dbConnConfig, err := pgxpool.ParseConfig(connString)
+ if err != nil {
+ log.Fatalf("unable to parse connString: %v", err)
+ }
+
+ migrations := &migrate.FileMigrationSource{
+ Dir: migrationfile,
+ }
+
+ db := stdlib.OpenDB(*dbConnConfig.ConnConfig)
+
+ const dialect = "postgres"
+ n, err := migrate.Exec(db, dialect, migrations, migrate.Up)
+ if err != nil {
+ log.Fatalf("unable to handle migrations: %v", err)
+ }
+ fmt.Printf("Applied %d migrations!\n", n)
+
+ //
+
gitService := services.NewGit(conf.Git)
cloudService := services.NewCloud(conf.Cloud)
codaService := services.NewCodaClient(conf.Coda)
@@ -55,7 +85,7 @@ func run(conf domain.Config) {
go func() {
opts := discord.DiscordOptions{
Controller: controller,
- AppConfig: &conf,
+ Config: &conf,
}
if err := discord.Run(conf, opts); err != nil {
log.Fatalf("discord bot cannot be runned: %v", err)
@@ -63,11 +93,11 @@ func run(conf domain.Config) {
}()
opts := telegram.TelegramOptions{
- // TicketsRepo: db,
GitService: gitService,
CloudService: cloudService,
Coda: codaService,
AppConfig: &conf,
+ Controller: controller,
}
if err := telegram.Run(ctx, opts); err != nil {
diff --git a/compose.yaml b/compose.yaml
new file mode 100644
index 0000000..abeda0f
--- /dev/null
+++ b/compose.yaml
@@ -0,0 +1,25 @@
+services:
+ ticket-pimp:
+ container_name: pimp
+ image: naudachu/ticket-pimp
+ ports:
+ - "8080:8080"
+ depends_on:
+ postgres:
+ condition: service_healthy
+ postgres:
+ container_name: db
+ image: "postgres:16.1-alpine3.18"
+ environment:
+ POSTGRES_DB: "tickets"
+ POSTGRES_USER: "postgres"
+ POSTGRES_PASSWORD: "postgres"
+ volumes:
+ - db:./postgres-data:/var/lib/postgresql/data
+ ports:
+ - "5432:5432"
+ healthcheck:
+ test: [ "CMD", "pg_isready", "-q", "-d", "tickets", "-U", "postgres" ]
+ interval: 10s
+ timeout: 5s
+ retries: 5
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 6b32eba..c4183a4 100644
--- a/go.mod
+++ b/go.mod
@@ -11,6 +11,7 @@ require (
)
require (
+ github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
@@ -19,12 +20,15 @@ require (
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
+ github.com/jackc/pgx v3.6.2+incompatible // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/onsi/ginkgo/v2 v2.10.0 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
github.com/quic-go/quic-go v0.35.1 // indirect
+ github.com/rubenv/sql-migrate v1.5.2 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
golang.org/x/crypto v0.10.0 // indirect
diff --git a/go.sum b/go.sum
index a42806f..683817c 100644
--- a/go.sum
+++ b/go.sum
@@ -3,6 +3,8 @@ github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
+github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
@@ -27,6 +29,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
+github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
@@ -38,6 +42,8 @@ github.com/mr-linch/go-tg v0.9.1/go.mod h1:276w69YW4pEo3ZYta+LQe4v/ut2w2h1ksP4zi
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
@@ -48,8 +54,11 @@ github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8G
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62poo=
github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
+github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0=
+github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -105,3 +114,4 @@ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/controller/control_git.go b/internal/controller/control_git.go
index c9e1f0d..077a509 100644
--- a/internal/controller/control_git.go
+++ b/internal/controller/control_git.go
@@ -92,8 +92,13 @@ func (wc *WorkflowController) createGitForExistingProject(ctx context.Context, r
}
func (wc *WorkflowController) CreateGit(ctx context.Context, req GitRequest) *ProjectResponse {
+ if req.ChannelID == "" {
+ return &ProjectResponse{
+ Project: nil,
+ Message: errors.New("empty channel string"),
+ }
+ }
- // [ ] Валидация на пустой канал?
p, err := wc.GetProjectByChannelID(ctx, req.ChannelID)
if err != nil {
return &ProjectResponse{
@@ -116,7 +121,6 @@ func (wc *WorkflowController) CreateGit(ctx context.Context, req GitRequest) *Pr
Message: errors.New("build git already exists"),
}
} else {
- // [x]
return wc.createGitForExistingProject(ctx, req, p)
}
case p != nil && !req.IsBuildGit:
@@ -126,7 +130,6 @@ func (wc *WorkflowController) CreateGit(ctx context.Context, req GitRequest) *Pr
Message: errors.New("project git already exists"),
}
} else {
- // [x]
return wc.createGitForExistingProject(ctx, req, p)
}
default:
diff --git a/internal/controller/control_project.go b/internal/controller/control_project.go
index bc95ff6..d04cbb3 100644
--- a/internal/controller/control_project.go
+++ b/internal/controller/control_project.go
@@ -86,10 +86,10 @@ func (wc *WorkflowController) GetProjectByChannelID(ctx context.Context, id stri
return &proj, nil
}
+// Saves current channel as project's channel;
func (wc *WorkflowController) InitProjectInChannel(ctx context.Context, channelID string, key string) (*domain.Project, error) {
dbTicket, err := wc.q.GetTicketByChannelID(ctx, pgtype.Text{String: channelID, Valid: true})
if err == pgx.ErrNoRows {
- // [ ] Логика инициализации проекта
dbTicket, err = wc.q.CreateTicket(
ctx,
db.CreateTicketParams{
diff --git a/internal/controller/control_task.go b/internal/controller/control_task.go
new file mode 100644
index 0000000..b992ca7
--- /dev/null
+++ b/internal/controller/control_task.go
@@ -0,0 +1,153 @@
+package controller
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "ticket-pimp/internal/domain"
+ "ticket-pimp/internal/storage/db"
+ "time"
+
+ "github.com/bwmarrin/discordgo"
+ "github.com/jackc/pgx/v5/pgtype"
+)
+
+// WriteTaskToDB
+/*
+Makes an SQL query to create new tasks row from domain.Task entity
+ - Creator field: telegram nickname or Discord's Mention();
+ - Creator link (tg ID) in telegram case;
+ - Description from telegram/discord message bodies;
+*/
+func (wc *WorkflowController) WriteTaskToDB(t *domain.Task) (*domain.Task, error) {
+ dbtask, err := wc.q.InsertTask(context.TODO(), db.InsertTaskParams{
+ Creator: pgtype.Text{String: t.Creator, Valid: true},
+ CreatorLink: pgtype.Text{
+ String: t.CreatorLink,
+ Valid: true,
+ },
+ Description: pgtype.Text{
+ String: t.Description,
+ Valid: true,
+ },
+ })
+ if err != nil {
+ return nil, fmt.Errorf("unable to create task at the db: %v", err)
+ }
+ // ------------------------------------------------------------------------------------
+
+ task := newConvertable(&dbtask).ExtractDomain()
+ return task, nil
+}
+
+// InitTask
+/*
+Runs the following:
+ - Use WriteTaskToDB method to make a new task row in the db;
+ - init new discord bot instance;
+ -
+
+Possible errors:
+ - db record couldn't be created;
+ - bot couldn't be inited;
+ - bot session couldn't be started;
+ - thread couldn't be started;
+ - first task message couldn't be edited;
+*/
+func (wc *WorkflowController) InitTask(t *domain.Task) (*domain.Task, error) {
+
+ task, err := wc.WriteTaskToDB(t)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create task at the db: %v", err)
+ }
+
+ // Инициализируем новый клиент дискорда
+ // [ ] Нездоровое получение параметров клиента из os..
+ var (
+ token = os.Getenv("DISCORD_TOKEN")
+ forumChannelID = os.Getenv("TASKS_CHANNEL")
+ )
+
+ s, err := discordgo.New("Bot " + token)
+ if err != nil {
+ return task, fmt.Errorf("unable to create discord session: %v", err)
+ }
+
+ if err := s.Open(); err != nil {
+ return task, fmt.Errorf("cannot open the session: %v", err)
+ }
+
+ msg := discordgo.MessageSend{
+ Content: task.DiscordMessage(domain.NewTaskState()),
+ 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",
+ },
+ },
+ },
+ },
+ }
+
+ th, err := s.ForumThreadStartComplex(
+ forumChannelID,
+ &discordgo.ThreadStart{
+ Name: fmt.Sprintf("Task ID: %d, by %s", task.ID, task.Creator),
+ },
+ &msg,
+ )
+ if err != nil {
+ return task, fmt.Errorf("unable to update channel: %v", err)
+ }
+
+ err = wc.UpdateTasksMessageID(context.TODO(), th.ID, task.ID)
+ if err != nil {
+ return task, fmt.Errorf("unable to set discord message to task: %v", err)
+ }
+
+ return task, nil
+}
+
+func (wc *WorkflowController) UpdateTasksMessageID(ctx context.Context, msgID string, taskID int32) error {
+ err := wc.q.UpdateTaskWithMessageID(context.TODO(), db.UpdateTaskWithMessageIDParams{
+ Messageid: pgtype.Text{String: msgID, Valid: true},
+ ID: taskID,
+ })
+ return err
+}
+
+func (wc *WorkflowController) UpdateTask(id string, opt int, user string) (*TaskConvertable, error) {
+ var (
+ err error
+ dbtask db.Task
+ )
+ switch opt {
+ case 0:
+ dbtask, err = wc.q.StartTask(context.TODO(), db.StartTaskParams{
+ UpdatedAt: pgtype.Timestamptz{Time: time.Now(), InfinityModifier: 0, Valid: true},
+ Assignee: pgtype.Text{String: user, Valid: true},
+ Messageid: pgtype.Text{String: id, Valid: true},
+ })
+ return &TaskConvertable{&dbtask}, err
+
+ case 1:
+ dbtask, err = wc.q.CloseTask(context.TODO(), db.CloseTaskParams{
+ DeletedAt: pgtype.Timestamptz{Time: time.Now(), InfinityModifier: 0, Valid: true},
+ Assignee: pgtype.Text{String: user, Valid: true},
+ Messageid: pgtype.Text{String: id, Valid: true},
+ })
+ return &TaskConvertable{&dbtask}, err
+ }
+
+ return &TaskConvertable{&dbtask}, nil
+}
diff --git a/internal/controller/control_workflow.go b/internal/controller/control_workflow.go
index c24fdbb..d1fd033 100644
--- a/internal/controller/control_workflow.go
+++ b/internal/controller/control_workflow.go
@@ -12,6 +12,13 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
+// FullProjectInit
+/*
+ Deprecated method to create a project with all related data:
+ - git;
+ - git for the project's build;
+ - cloud folder;
+*/
func (wc *WorkflowController) FullProjectInit(name, key, id string) (string, error) {
appKey := fmt.Sprintf("%s-%s", key, id)
diff --git a/internal/controller/controller.go b/internal/controller/controller.go
index c65d97d..ecea95d 100644
--- a/internal/controller/controller.go
+++ b/internal/controller/controller.go
@@ -5,6 +5,7 @@ import (
"ticket-pimp/internal/services"
"ticket-pimp/internal/storage/db"
+ "github.com/bwmarrin/discordgo"
"github.com/jackc/pgx/v5/pgxpool"
)
@@ -14,6 +15,7 @@ type WorkflowController struct {
ICoda services.ICoda
pool *pgxpool.Pool
q *db.Queries
+ ATags []discordgo.ForumTag
}
func NewWorkflowController(
@@ -35,3 +37,28 @@ type ProjectResponse struct {
Project *domain.Project
Message error
}
+
+type TaskConvertable struct {
+ *db.Task
+}
+
+func newConvertable(db *db.Task) *TaskConvertable {
+ return &TaskConvertable{
+ Task: db,
+ }
+}
+
+func (t *TaskConvertable) ExtractDomain() *domain.Task {
+ return &domain.Task{
+ ID: t.ID,
+ // Summary: "",
+ Description: t.Description.String,
+ Creator: t.Creator.String,
+ CreatorLink: t.CreatorLink.String,
+ Assignee: t.Assignee.String,
+ CreatedAt: t.CreatedAt.Time,
+ DeletedAt: t.DeletedAt.Time,
+ UpdatedAt: t.UpdatedAt.Time,
+ // URL: "",
+ }
+}
diff --git a/internal/domain/config.go b/internal/domain/config.go
index fb02231..b2f891a 100644
--- a/internal/domain/config.go
+++ b/internal/domain/config.go
@@ -50,6 +50,7 @@ type TelegramConfig struct {
type DiscordConfig struct {
Token string
IsProjectChannel string
+ IsTaskForum string
}
type ApplicationConfig struct {
@@ -95,6 +96,7 @@ func InitConfig(envFilePath string) Config {
Discord: DiscordConfig{
Token: os.Getenv("DISCORD_TOKEN"),
IsProjectChannel: os.Getenv("PROJECTS_CHANNEL_GROUP"),
+ IsTaskForum: os.Getenv("TASKS_CHANNEL"),
},
}
}
diff --git a/internal/domain/models.go b/internal/domain/models.go
index c41f1bd..48ec870 100644
--- a/internal/domain/models.go
+++ b/internal/domain/models.go
@@ -1,6 +1,9 @@
package domain
-import "fmt"
+import (
+ "fmt"
+ "time"
+)
type Folder struct {
Title string // k
@@ -51,14 +54,69 @@ func (r *Row) NewCell(col string, value string) *Row {
}
type Task struct {
+ ID int32
Summary string
Description string
Creator string
CreatorLink string
+ Assignee string
+
+ CreatedAt time.Time
+ DeletedAt time.Time
+ UpdatedAt time.Time
+
URL string
}
+type TaskState int
+
+const (
+ new TaskState = iota
+ inprogress
+ done
+)
+
+func NewTaskState() TaskState {
+ return TaskState(0)
+}
+
+func InrpogressTaskState() TaskState {
+ return TaskState(1)
+}
+
+func DoneTaskState() TaskState {
+ return TaskState(2)
+}
+
+// Creates a string for discordgo.DiscordMessage.Content
+// State: New task;
+func (t *Task) DiscordMessage(ts TaskState) string {
+ switch ts {
+ case new:
+ return fmt.Sprintf(
+ "Created at: %s \n>>> %s\n",
+ t.CreatedAt,
+ t.Description,
+ )
+ case inprogress:
+ return fmt.Sprintf(
+ "**TaskID: %d** Started by: %s\n🚀 Started at: %s\n",
+ t.ID,
+ t.Assignee,
+ t.UpdatedAt,
+ )
+ case done:
+ return fmt.Sprintf(
+ "**TaskID: %d** Closed by: %s\n✅ Closed at: %s\n",
+ t.ID,
+ t.Assignee,
+ t.DeletedAt,
+ )
+ }
+ return "task state not provided"
+}
+
func NewTask(summ, desc, c, cLink string) *Task {
return &Task{
Summary: summ,
@@ -68,10 +126,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 +137,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..6af8f66 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
+ Description pgtype.Text
+ Assignee pgtype.Text
+ 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..13685a7 100644
--- a/internal/storage/db/queries.sql.go
+++ b/internal/storage/db/queries.sql.go
@@ -11,6 +11,36 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
+const closeTask = `-- name: CloseTask :one
+UPDATE tasks
+SET deleted_at = $1, assignee = $2
+WHERE messageID = $3
+RETURNING id, creator, creator_link, messageid, description, assignee, created_at, deleted_at, updated_at
+`
+
+type CloseTaskParams struct {
+ DeletedAt pgtype.Timestamptz
+ Assignee pgtype.Text
+ Messageid pgtype.Text
+}
+
+func (q *Queries) CloseTask(ctx context.Context, arg CloseTaskParams) (Task, error) {
+ row := q.db.QueryRow(ctx, closeTask, arg.DeletedAt, arg.Assignee, arg.Messageid)
+ var i Task
+ err := row.Scan(
+ &i.ID,
+ &i.Creator,
+ &i.CreatorLink,
+ &i.Messageid,
+ &i.Description,
+ &i.Assignee,
+ &i.CreatedAt,
+ &i.DeletedAt,
+ &i.UpdatedAt,
+ )
+ return i, err
+}
+
const createTicket = `-- name: CreateTicket :one
INSERT INTO tickets (
key, channelID
@@ -72,6 +102,48 @@ func (q *Queries) GetConfig(ctx context.Context) (Appconfig, error) {
return i, err
}
+const getTaskByID = `-- name: GetTaskByID :one
+SELECT id, creator, creator_link, messageid, description, assignee, created_at, deleted_at, updated_at FROM tasks WHERE id = $1
+`
+
+func (q *Queries) GetTaskByID(ctx context.Context, id int32) (Task, error) {
+ row := q.db.QueryRow(ctx, getTaskByID, id)
+ var i Task
+ err := row.Scan(
+ &i.ID,
+ &i.Creator,
+ &i.CreatorLink,
+ &i.Messageid,
+ &i.Description,
+ &i.Assignee,
+ &i.CreatedAt,
+ &i.DeletedAt,
+ &i.UpdatedAt,
+ )
+ return i, err
+}
+
+const getTaskByMessage = `-- name: GetTaskByMessage :one
+SELECT id, creator, creator_link, messageid, description, assignee, created_at, deleted_at, updated_at FROM tasks WHERE messageID = $1
+`
+
+func (q *Queries) GetTaskByMessage(ctx context.Context, messageid pgtype.Text) (Task, error) {
+ row := q.db.QueryRow(ctx, getTaskByMessage, messageid)
+ var i Task
+ err := row.Scan(
+ &i.ID,
+ &i.Creator,
+ &i.CreatorLink,
+ &i.Messageid,
+ &i.Description,
+ &i.Assignee,
+ &i.CreatedAt,
+ &i.DeletedAt,
+ &i.UpdatedAt,
+ )
+ return i, err
+}
+
const getTicketByChannelID = `-- name: GetTicketByChannelID :one
SELECT id, key, channelid, project_git, build_git, folder, created_at, deleted_at, updated_at FROM tickets WHERE channelID = $1
`
@@ -114,6 +186,72 @@ func (q *Queries) GetTicketByID(ctx context.Context, id int32) (Ticket, error) {
return i, err
}
+const insertTask = `-- name: InsertTask :one
+INSERT INTO tasks (
+ creator, creator_link, description
+) VALUES (
+ $1, $2, $3
+ )
+ RETURNING id, creator, creator_link, messageid, description, assignee, created_at, deleted_at, updated_at
+`
+
+type InsertTaskParams struct {
+ Creator pgtype.Text
+ CreatorLink pgtype.Text
+ Description pgtype.Text
+}
+
+func (q *Queries) InsertTask(ctx context.Context, arg InsertTaskParams) (Task, error) {
+ row := q.db.QueryRow(ctx, insertTask, arg.Creator, arg.CreatorLink, arg.Description)
+ var i Task
+ err := row.Scan(
+ &i.ID,
+ &i.Creator,
+ &i.CreatorLink,
+ &i.Messageid,
+ &i.Description,
+ &i.Assignee,
+ &i.CreatedAt,
+ &i.DeletedAt,
+ &i.UpdatedAt,
+ )
+ return i, err
+}
+
+const listTasksByCreator = `-- name: ListTasksByCreator :many
+SELECT id, creator, creator_link, messageid, description, assignee, created_at, deleted_at, updated_at FROM tasks WHERE creator_link = $1 AND deleted_at is NULL
+`
+
+func (q *Queries) ListTasksByCreator(ctx context.Context, creatorLink pgtype.Text) ([]Task, error) {
+ rows, err := q.db.Query(ctx, listTasksByCreator, creatorLink)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []Task
+ for rows.Next() {
+ var i Task
+ if err := rows.Scan(
+ &i.ID,
+ &i.Creator,
+ &i.CreatorLink,
+ &i.Messageid,
+ &i.Description,
+ &i.Assignee,
+ &i.CreatedAt,
+ &i.DeletedAt,
+ &i.UpdatedAt,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
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 +333,52 @@ func (q *Queries) SetNewConfig(ctx context.Context) (Appconfig, error) {
return i, err
}
+const startTask = `-- name: StartTask :one
+UPDATE tasks
+SET updated_at = $1, assignee = $2
+WHERE messageID = $3
+RETURNING id, creator, creator_link, messageid, description, assignee, created_at, deleted_at, updated_at
+`
+
+type StartTaskParams struct {
+ UpdatedAt pgtype.Timestamptz
+ Assignee pgtype.Text
+ Messageid pgtype.Text
+}
+
+func (q *Queries) StartTask(ctx context.Context, arg StartTaskParams) (Task, error) {
+ row := q.db.QueryRow(ctx, startTask, arg.UpdatedAt, arg.Assignee, arg.Messageid)
+ var i Task
+ err := row.Scan(
+ &i.ID,
+ &i.Creator,
+ &i.CreatorLink,
+ &i.Messageid,
+ &i.Description,
+ &i.Assignee,
+ &i.CreatedAt,
+ &i.DeletedAt,
+ &i.UpdatedAt,
+ )
+ return i, err
+}
+
+const updateTaskWithMessageID = `-- name: UpdateTaskWithMessageID :exec
+UPDATE tasks
+SET messageID = $1
+WHERE id = $2
+`
+
+type UpdateTaskWithMessageIDParams struct {
+ Messageid pgtype.Text
+ ID int32
+}
+
+func (q *Queries) UpdateTaskWithMessageID(ctx context.Context, arg UpdateTaskWithMessageIDParams) error {
+ _, err := q.db.Exec(ctx, updateTaskWithMessageID, arg.Messageid, arg.ID)
+ return err
+}
+
const updateTicketBuildGit = `-- name: UpdateTicketBuildGit :one
UPDATE tickets
SET build_git = $1, updated_at = $2
diff --git a/internal/storage/dbconfig.yml b/internal/storage/dbconfig.yml
index 14a3c91..f38dded 100644
--- a/internal/storage/dbconfig.yml
+++ b/internal/storage/dbconfig.yml
@@ -1,4 +1,4 @@
development:
dialect: postgres
- datasource: host=localhost dbname=tickets user=postgres password=postgres sslmode=disable
+ datasource: host=postgres dbname=tickets user=postgres password=postgres sslmode=disable
dir: migrate
\ No newline at end of file
diff --git a/internal/storage/migrate/0001_init_tickets.sql b/internal/storage/migrate/0001_init_tickets.sql
deleted file mode 100644
index 5a67451..0000000
--- a/internal/storage/migrate/0001_init_tickets.sql
+++ /dev/null
@@ -1,15 +0,0 @@
--- +migrate Up
-CREATE TABLE tickets (
- id SERIAL PRIMARY KEY,
- key VARCHAR(10),
- channelID VARCHAR(255),
- project_git VARCHAR(255),
- build_git VARCHAR(255),
- folder VARCHAR(255),
- created_at TIMESTAMPTZ DEFAULT current_timestamp,
- deleted_at TIMESTAMPTZ,
- updated_at TIMESTAMPTZ
-);
-
--- +migrate Down
-DROP TABLE tickets;
\ No newline at end of file
diff --git a/internal/storage/migrate/0001_initial_migration.sql b/internal/storage/migrate/0001_initial_migration.sql
new file mode 100644
index 0000000..fa83763
--- /dev/null
+++ b/internal/storage/migrate/0001_initial_migration.sql
@@ -0,0 +1,39 @@
+-- +migrate Up
+CREATE TABLE appconfig (
+ ticket_key VARCHAR(5),
+ ticket_id INT
+);
+
+INSERT INTO appconfig (ticket_key, ticket_id) VALUES ('xpp', 1);
+
+CREATE TABLE tickets (
+ id SERIAL PRIMARY KEY,
+ key VARCHAR(10),
+ channelID VARCHAR(255),
+ project_git VARCHAR(255),
+ build_git VARCHAR(255),
+ folder VARCHAR(255),
+
+ created_at TIMESTAMPTZ DEFAULT current_timestamp,
+ deleted_at TIMESTAMPTZ,
+ updated_at TIMESTAMPTZ
+);
+
+CREATE TABLE tasks (
+ id SERIAL PRIMARY KEY,
+ creator VARCHAR(255),
+ creator_link VARCHAR(255),
+ messageID VARCHAR(255),
+
+ description TEXT,
+ assignee VARCHAR(255),
+
+ created_at TIMESTAMPTZ DEFAULT current_timestamp,
+ deleted_at TIMESTAMPTZ,
+ updated_at TIMESTAMPTZ
+);
+
+-- +migrate Down
+DROP TABLE tickets;
+DROP TABLE appconfig;
+DROP TABLE tasks;
\ No newline at end of file
diff --git a/internal/storage/migrate/0002_init_config.sql b/internal/storage/migrate/0002_init_config.sql
deleted file mode 100644
index f8dc281..0000000
--- a/internal/storage/migrate/0002_init_config.sql
+++ /dev/null
@@ -1,11 +0,0 @@
--- +migrate Up
-CREATE TABLE appconfig (
- ticket_key VARCHAR(5),
- ticket_id INT
-);
-
--- +migrate Up
-INSERT INTO appconfig (ticket_key, ticket_id) VALUES ('DAP', 100);
-
--- +migrate Down
-DROP TABLE appconfig;
\ No newline at end of file
diff --git a/internal/storage/sqlc/queries.sql b/internal/storage/sqlc/queries.sql
index d5320ee..cae5e62 100644
--- a/internal/storage/sqlc/queries.sql
+++ b/internal/storage/sqlc/queries.sql
@@ -52,4 +52,38 @@ 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 (
+ creator, creator_link, description
+) VALUES (
+ $1, $2, $3
+ )
+ RETURNING *;
+
+-- name: UpdateTaskWithMessageID :exec
+UPDATE tasks
+SET messageID = $1
+WHERE id = $2;
+
+-- name: StartTask :one
+UPDATE tasks
+SET updated_at = $1, assignee = $2
+WHERE messageID = $3
+RETURNING *;
+
+-- name: CloseTask :one
+UPDATE tasks
+SET deleted_at = $1, assignee = $2
+WHERE messageID = $3
+RETURNING *;
+
+-- name: GetTaskByMessage :one
+SELECT * FROM tasks WHERE messageID = $1;
+
+-- name: ListTasksByCreator :many
+SELECT * FROM tasks WHERE creator_link = $1 AND deleted_at is NULL;
+
+-- name: GetTaskByID :one
+SELECT * FROM tasks WHERE id = $1;
\ No newline at end of file