commit
fc8b825de0
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
// "<a href='{1}'>{2}</a> взята в работу",
|
||||
// thisRow.ObjectLink().ToText(),
|
||||
// "Задача"
|
||||
// ),
|
||||
// "disable_notification",
|
||||
// true,
|
||||
// "parse_mode",
|
||||
// "HTML",
|
||||
// "disable_web_page_preview",
|
||||
// true
|
||||
// )
|
||||
// )
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <a href=\"https://reddit.com/\">text</a> 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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().
|
||||
|
|
|
|||
44
cmd/main.go
44
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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
4
go.mod
4
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
|
||||
|
|
|
|||
10
go.sum
10
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=
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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: "",
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
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;
|
||||
Loading…
Reference in New Issue