Merge branch 'dev'
This commit is contained in:
commit
696d0f2233
|
|
@ -1,3 +1,5 @@
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
.github
|
||||||
|
docker/
|
||||||
**/**/*.env
|
**/**/*.env
|
||||||
docker/**
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package adapters
|
||||||
|
|
||||||
|
import "ticket-pimp/internal/domain"
|
||||||
|
|
||||||
|
type IDummyTelegram interface {
|
||||||
|
DummyNotification(id string, text string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IGit interface {
|
||||||
|
CreateRepo(name string) (*domain.Git, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ICloud interface {
|
||||||
|
CreateFolder(name string) domain.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
type ICoda interface {
|
||||||
|
ListDocs()
|
||||||
|
CreateApp(task domain.CodaApplication) (string, error)
|
||||||
|
CreateTask(title string, desc string, creatorName string, creatorID string) (string, error)
|
||||||
|
GetRowLink(id string) (string, error)
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,127 @@
|
||||||
package discord
|
package discord
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"ticket-pimp/client/discord/handler"
|
"ticket-pimp/client/discord/discord_handler"
|
||||||
|
"ticket-pimp/client/discord/discord_router"
|
||||||
|
|
||||||
"ticket-pimp/internal/controller"
|
"ticket-pimp/internal/controller"
|
||||||
"ticket-pimp/internal/domain"
|
"ticket-pimp/internal/domain"
|
||||||
|
"ticket-pimp/internal/external"
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
minLength int = 3
|
||||||
|
repoType string = "repo_type"
|
||||||
|
projectRepo string = "project_repo"
|
||||||
|
buildRepo string = "build_repo"
|
||||||
|
nameOption string = "name"
|
||||||
|
tagsPreset = [3]discordgo.ForumTag{
|
||||||
|
{
|
||||||
|
Name: "В работе",
|
||||||
|
Moderated: true,
|
||||||
|
EmojiName: "👩🍳",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Готово",
|
||||||
|
Moderated: true,
|
||||||
|
EmojiName: "✅",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Не начат",
|
||||||
|
Moderated: true,
|
||||||
|
EmojiName: "🚧",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
commands = []discordgo.ApplicationCommand{
|
||||||
|
{
|
||||||
|
Name: "ping",
|
||||||
|
Description: "pongs in a reply",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "coda_ticket",
|
||||||
|
Description: "Creates ticket in Coda.io w/ provided info",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "init_project",
|
||||||
|
Description: "Connect project with Coda ID",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "key",
|
||||||
|
Description: "Project's key from Coda.io",
|
||||||
|
Required: true,
|
||||||
|
MinLength: &minLength,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "project",
|
||||||
|
Description: "Create new development ticket",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "project_name",
|
||||||
|
Description: "Temporary project name",
|
||||||
|
Required: true,
|
||||||
|
MinLength: &minLength,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "info",
|
||||||
|
Description: "Get project's info",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "repo",
|
||||||
|
Description: "Creates repository of selected type. Name used for projects channels only",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: repoType,
|
||||||
|
Description: "The type of repo",
|
||||||
|
Required: true,
|
||||||
|
Choices: []*discordgo.ApplicationCommandOptionChoice{
|
||||||
|
{
|
||||||
|
Name: "Unity project repo",
|
||||||
|
Value: projectRepo,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "XCode build repo",
|
||||||
|
Value: buildRepo,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: nameOption,
|
||||||
|
Description: "Type the repository's name",
|
||||||
|
Required: false,
|
||||||
|
MinLength: &minLength,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "folder",
|
||||||
|
Description: "Command for cloud folder creation",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: nameOption,
|
||||||
|
Description: "Type the folder's name",
|
||||||
|
Required: false,
|
||||||
|
MinLength: &minLength,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func initBotWith(token string) *discordgo.Session {
|
func initBotWith(token string) *discordgo.Session {
|
||||||
discord, err := discordgo.New("Bot " + token)
|
discord, err := discordgo.New("Bot " + token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -27,95 +136,137 @@ type DiscordOptions struct {
|
||||||
Controller *controller.WorkflowController
|
Controller *controller.WorkflowController
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPrivateMessaging(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
func updateForum(conf *domain.Config, s *discordgo.Session) ([]discordgo.ForumTag, error) {
|
||||||
dchan, err := s.Channel(i.ChannelID)
|
|
||||||
|
log.Println("Updating forum chan...")
|
||||||
|
|
||||||
|
// Get tasks channel instance:
|
||||||
|
forum, err := s.Channel(conf.Discord.IsTaskForum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if dchan.Type == discordgo.ChannelTypeDM {
|
// Map all pre-set tags
|
||||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
var tagsMap = map[string]discordgo.ForumTag{}
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
for _, t := range forum.AvailableTags {
|
||||||
Data: &discordgo.InteractionResponseData{
|
tagsMap[t.Name] = t
|
||||||
Content: "Yo, fella! I'm not working in private!",
|
}
|
||||||
},
|
|
||||||
|
// Result tags array
|
||||||
|
tags := forum.AvailableTags
|
||||||
|
|
||||||
|
// Check if preset tag exists into current channel..
|
||||||
|
for i := 0; i < len(tagsPreset); i++ {
|
||||||
|
_, ok := tagsMap[tagsPreset[i].Name]
|
||||||
|
if !ok {
|
||||||
|
// .. and append them if they aren't
|
||||||
|
tags = append(tags, tagsPreset[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dchan, err := s.ChannelEditComplex(forum.ID, &discordgo.ChannelEdit{
|
||||||
|
AvailableTags: &tags,
|
||||||
})
|
})
|
||||||
return errors.New("no private messages! lol")
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
log.Printf("Channel %s with ID %s propagated by tags:", dchan.Name, dchan.ID)
|
||||||
|
for _, t := range dchan.AvailableTags {
|
||||||
|
fmt.Printf("N: %s, ID: %s", t.Name, t.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update config w/ tags:
|
||||||
|
confTags := make(map[domain.TaskState]string)
|
||||||
|
|
||||||
|
for _, tag := range dchan.AvailableTags {
|
||||||
|
switch tag.Name {
|
||||||
|
case "Не начат":
|
||||||
|
confTags[domain.State(0)] = tag.ID
|
||||||
|
case "В работе":
|
||||||
|
confTags[domain.State(1)] = tag.ID
|
||||||
|
case "Готово":
|
||||||
|
confTags[domain.State(2)] = tag.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.Discord.Tags = confTags
|
||||||
|
|
||||||
|
return dchan.AvailableTags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Run(conf domain.Config, opts DiscordOptions) error {
|
func commandRegistration(s *discordgo.Session, commands []discordgo.ApplicationCommand) []*discordgo.ApplicationCommand {
|
||||||
token := conf.Discord.Token
|
log.Println("Adding commands...")
|
||||||
|
var cmds []*discordgo.ApplicationCommand
|
||||||
s := initBotWith(token)
|
for _, cmd := range commands {
|
||||||
|
cmd, err := s.ApplicationCommandCreate(s.State.User.ID, "", &cmd)
|
||||||
router := handler.InitRouter(*opts.Controller, &conf.Discord, &conf.Telegram)
|
|
||||||
|
|
||||||
commandHandlers := map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){}
|
|
||||||
for _, handler := range router.Commands {
|
|
||||||
commandHandlers[handler.Command.Name] = handler.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
componentsHandlers := map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
|
|
||||||
"task_start": router.Components[0].Handler,
|
|
||||||
"task_close": router.Components[0].Handler,
|
|
||||||
}
|
|
||||||
|
|
||||||
s.AddHandler(router.ListenPosts)
|
|
||||||
|
|
||||||
s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
||||||
err := checkPrivateMessaging(s, i)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
log.Panicf("Cannot create '%v' command: %v", cmd.Name, err)
|
||||||
|
}
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
log.Println(cmd.Name + " command added")
|
||||||
|
}
|
||||||
|
return cmds
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(conf *domain.Config, opts DiscordOptions) error {
|
||||||
|
|
||||||
|
// bot init
|
||||||
|
s := initBotWith(conf.Discord.Token)
|
||||||
|
|
||||||
|
// Init new handler
|
||||||
|
h := discord_handler.New(
|
||||||
|
opts.Controller,
|
||||||
|
&conf.Discord,
|
||||||
|
external.NewDummyClient(conf.Telegram),
|
||||||
|
)
|
||||||
|
|
||||||
|
r := discord_router.NewApp(s)
|
||||||
|
|
||||||
|
var commonMw = []discord_router.Middleware{
|
||||||
|
h.WithInitialResponse,
|
||||||
|
h.RejectPM,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch i.Type {
|
r.Use(commonMw...).
|
||||||
case discordgo.InteractionApplicationCommand:
|
Route("ping", h.Ping).
|
||||||
if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok {
|
Route("project", h.CreateProject).
|
||||||
h(s, i)
|
Route("info", h.ProjectInfo).
|
||||||
}
|
Route("repo", h.CreateGit).
|
||||||
case discordgo.InteractionMessageComponent:
|
Route("folder", h.CreateFolder).
|
||||||
if h, ok := componentsHandlers[i.MessageComponentData().CustomID]; ok {
|
Route("init_project", h.InitChannelAsProject).
|
||||||
h(s, i)
|
Route("coda_ticket", h.CreateCoda)
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
// and components
|
||||||
|
r.
|
||||||
|
/*Use().*/ // Combining into group duplicates replies
|
||||||
|
Route("task_start", h.HandleTaskButtons).
|
||||||
|
Route("task_close", h.HandleTaskButtons)
|
||||||
|
|
||||||
|
// Add posts listener
|
||||||
|
s.AddHandler(h.ListenPosts)
|
||||||
|
|
||||||
|
// Add interactions handlers
|
||||||
|
s.AddHandler(r.Serve)
|
||||||
|
|
||||||
|
// session opening
|
||||||
if err := s.Open(); err != nil {
|
if err := s.Open(); err != nil {
|
||||||
return fmt.Errorf("cannot open the session: %v", err)
|
return fmt.Errorf("cannot open the session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UPDATE FORUM IF NEEDED:
|
// Forum update
|
||||||
|
tags, err := updateForum(conf, s)
|
||||||
forum, err := s.Channel(conf.Discord.IsProjectChannel)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Println(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.ChannelEditComplex(forum.ID, &discordgo.ChannelEdit{
|
//Update handler with tags:
|
||||||
AvailableTags: &router.Tags,
|
h.SetAvailableTags(tags)
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Adding commands...")
|
// commands registration
|
||||||
var cmds []*discordgo.ApplicationCommand
|
cmds := commandRegistration(s, commands)
|
||||||
var logString []string
|
|
||||||
for _, h := range router.Commands {
|
|
||||||
cmd, err := s.ApplicationCommandCreate(s.State.User.ID, "", &h.Command)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Cannot create '%v' command: %v", h.Command.Name, err)
|
|
||||||
}
|
|
||||||
cmds = append(cmds, cmd)
|
|
||||||
logString = append(logString, cmd.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Following commands added:")
|
|
||||||
log.Println(logString)
|
|
||||||
|
|
||||||
|
// gracefull shutdown
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
stop := make(chan os.Signal, 1)
|
stop := make(chan os.Signal, 1)
|
||||||
signal.Notify(stop, os.Interrupt)
|
signal.Notify(stop, os.Interrupt)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package discord_handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) defaultFollowUp(answer string, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
|
||||||
|
// Sending result:
|
||||||
|
/*_, err := */
|
||||||
|
s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
|
||||||
|
Content: answer,
|
||||||
|
})
|
||||||
|
|
||||||
|
// if err != nil {
|
||||||
|
// s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
|
||||||
|
// Content: fmt.Sprintf("Something went wrong: %v", err),
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// setFlag
|
||||||
|
// sets tag with In progress and Done text to discords channel;
|
||||||
|
func (h *Handler) 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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,648 @@
|
||||||
|
package discord_handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"ticket-pimp/adapters"
|
||||||
|
router "ticket-pimp/client/discord/discord_router"
|
||||||
|
"ticket-pimp/internal/controller"
|
||||||
|
"ticket-pimp/internal/domain"
|
||||||
|
"ticket-pimp/internal/helpers"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
controller *controller.WorkflowController
|
||||||
|
conf *domain.DiscordConfig
|
||||||
|
telegramDummyClient adapters.IDummyTelegram
|
||||||
|
tags []discordgo.ForumTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(
|
||||||
|
controller *controller.WorkflowController,
|
||||||
|
conf *domain.DiscordConfig,
|
||||||
|
tg adapters.IDummyTelegram,
|
||||||
|
) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
controller: controller,
|
||||||
|
conf: conf,
|
||||||
|
telegramDummyClient: tg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) RejectPM(f router.HandlerFunc) router.HandlerFunc {
|
||||||
|
return func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
|
||||||
|
dchan, err := s.Channel(i.ChannelID)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if dchan.Type != discordgo.ChannelTypeDM {
|
||||||
|
f(s, i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respondWithReject(s, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func respondWithReject(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
|
||||||
|
var content = "Yo, fella! I'm not working in private!"
|
||||||
|
|
||||||
|
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Content: content,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
||||||
|
Content: &content,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("unable to edit answer with unknown error %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Моментальный ответ для избежания столкновения с протуханием токена
|
||||||
|
func (h *Handler) WithInitialResponse(f router.HandlerFunc) router.HandlerFunc {
|
||||||
|
return func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
|
||||||
|
initialResponse := discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Flags: discordgo.MessageFlagsEphemeral,
|
||||||
|
Content: "👩🍳 Cooking your query..",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.InteractionRespond(i.Interaction, &initialResponse)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f(s, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) SetAvailableTags(tags []discordgo.ForumTag) {
|
||||||
|
h.tags = append(h.tags, tags...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Ping(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
|
||||||
|
var content string = fmt.Sprintf(
|
||||||
|
"**Pong to:** %s\n**App ID:** %s\n**Guild ID:** %s\nC**hannelID:** %s",
|
||||||
|
i.Member.User.Mention(),
|
||||||
|
i.AppID,
|
||||||
|
i.GuildID,
|
||||||
|
i.ChannelID,
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err := s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
||||||
|
Content: &content,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenPosts
|
||||||
|
/*
|
||||||
|
..listens to new posts in specific channel
|
||||||
|
to act them like a task
|
||||||
|
*/
|
||||||
|
func (h *Handler) 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 != h.conf.IsTaskForum || th.OwnerID == s.State.User.ID {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all messages from the channel:
|
||||||
|
msgs, _ := s.ChannelMessages(th.ID, 1, "", "", "")
|
||||||
|
|
||||||
|
// Take the first one:
|
||||||
|
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 := h.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отредактировать Thread name как для задачи
|
||||||
|
appliedTags := []string{
|
||||||
|
h.conf.Tags[domain.NewTaskState()],
|
||||||
|
// h.tags[2].ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.ChannelEditComplex(th.ID, &discordgo.ChannelEdit{
|
||||||
|
Name: fmt.Sprintf("Task ID: %d, by %s", t.ID, t.Creator),
|
||||||
|
AppliedTags: &appliedTags,
|
||||||
|
})
|
||||||
|
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 = h.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleTaskButtons
|
||||||
|
// .. handler function to work with the Action Buttons over a task
|
||||||
|
func (h *Handler) HandleTaskButtons(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
|
||||||
|
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{},
|
||||||
|
})
|
||||||
|
_ = err
|
||||||
|
|
||||||
|
// Get assignee value; ---------------------------------------------------------------------------------
|
||||||
|
user := i.Member.User.Mention()
|
||||||
|
|
||||||
|
var (
|
||||||
|
opt int = -1
|
||||||
|
doneButtonIsDisabled bool = false
|
||||||
|
state domain.TaskState = domain.NewTaskState()
|
||||||
|
message string
|
||||||
|
tag discordgo.ForumTag
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check what flow was touched: -------------------------------------------------------------------------
|
||||||
|
switch i.Interaction.MessageComponentData().CustomID {
|
||||||
|
case "task_start":
|
||||||
|
opt = 0
|
||||||
|
doneButtonIsDisabled = false
|
||||||
|
state = domain.InrpogressTaskState()
|
||||||
|
message = "взята в работу"
|
||||||
|
tag = h.tags[0]
|
||||||
|
case "task_close":
|
||||||
|
opt = 1
|
||||||
|
doneButtonIsDisabled = true
|
||||||
|
state = domain.DoneTaskState()
|
||||||
|
message = "выполнена"
|
||||||
|
tag = h.tags[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the task update to db --------------------------------------------------------------------------
|
||||||
|
convertable, err := h.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 != "" {
|
||||||
|
h.telegramDummyClient.DummyNotification(
|
||||||
|
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 = h.setFlag(s, i, &tag)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error while `start` tag setting: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFolder
|
||||||
|
/*
|
||||||
|
- creates project's cloud folder;
|
||||||
|
- writed folder link to db;
|
||||||
|
*/
|
||||||
|
func (h *Handler) CreateFolder(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 == 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()
|
||||||
|
if resp.Message != nil {
|
||||||
|
result += "Errors: " + resp.Message.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.defaultFollowUp(result, s, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateGit
|
||||||
|
/*
|
||||||
|
-creates project's git repository;
|
||||||
|
- writed git link to db;
|
||||||
|
*/
|
||||||
|
func (h *Handler) CreateGit(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
|
||||||
|
const (
|
||||||
|
typeOfRepo = "repo_type"
|
||||||
|
projectRepoType = "project_repo"
|
||||||
|
buildRepoType = "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[typeOfRepo]
|
||||||
|
|
||||||
|
switch isBuild.StringValue() {
|
||||||
|
case buildRepoType:
|
||||||
|
req.IsBuildGit = true
|
||||||
|
case projectRepoType:
|
||||||
|
req.IsBuildGit = false
|
||||||
|
}
|
||||||
|
|
||||||
|
dchan, err := s.Channel(i.ChannelID)
|
||||||
|
if err != nil {
|
||||||
|
h.defaultFollowUp("error while identifying channel: %v", s, i)
|
||||||
|
return
|
||||||
|
} 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 {
|
||||||
|
if resp.Message != nil {
|
||||||
|
result = resp.Message.Error()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
result = resp.Project.DiscordString()
|
||||||
|
if resp.Message != nil {
|
||||||
|
result += "Errors: " + resp.Message.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
h.defaultFollowUp(result, s, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProjectInfo
|
||||||
|
/*
|
||||||
|
Message in chat with related project information
|
||||||
|
*/
|
||||||
|
func (h *Handler) ProjectInfo(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if project != nil {
|
||||||
|
result = project.DiscordString()
|
||||||
|
if err != nil {
|
||||||
|
result += "Errors: " + err.Error()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = "Something wrong with retrieving project from db"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.defaultFollowUp(result, s, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitChannelAsProject
|
||||||
|
/*
|
||||||
|
- makes channel-project raw in the db storage;
|
||||||
|
*/
|
||||||
|
func (h *Handler) InitChannelAsProject(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
|
||||||
|
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["key"]; 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()
|
||||||
|
if errMsg != nil {
|
||||||
|
result += "Errors: " + errMsg.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.defaultFollowUp(result, s, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateProject
|
||||||
|
/*
|
||||||
|
- creates new proejct in the db;
|
||||||
|
- creates new channel and writes it to db;
|
||||||
|
*/
|
||||||
|
func (h *Handler) CreateProject(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Project's title from the request:
|
||||||
|
var projectTitle string
|
||||||
|
if option, ok := optionMap["project_name"]; !ok {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
projectTitle = option.StringValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create channel with specified title
|
||||||
|
dchan, err := s.GuildChannelCreate(i.GuildID, projectTitle, discordgo.ChannelTypeGuildText)
|
||||||
|
if err != nil {
|
||||||
|
result = fmt.Sprintf("chan creation problem: %v\n", err)
|
||||||
|
h.defaultFollowUp(result, s, i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create DB raw with new project:
|
||||||
|
p, err := h.controller.ProjectCreate(context.TODO(), domain.Project{
|
||||||
|
ChannelID: dchan.ID,
|
||||||
|
Name: projectTitle,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
result = fmt.Sprintf("unable to create project: %v\n", err)
|
||||||
|
|
||||||
|
// Revert channel creation:
|
||||||
|
_, err := s.ChannelDelete(dchan.ID)
|
||||||
|
if err != nil {
|
||||||
|
result += fmt.Sprintf("\nunable to clean channel: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.defaultFollowUp(result, s, i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit created channel:
|
||||||
|
edit := discordgo.ChannelEdit{
|
||||||
|
Name: p.Key + "-" + helpers.Cut(projectTitle),
|
||||||
|
ParentID: h.conf.IsProjectChannel,
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Key, err)
|
||||||
|
h.defaultFollowUp(result, s, i)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отправить сообщение о создании проекта:
|
||||||
|
_, err = s.ChannelMessageSend(dchan.ID, p.DiscordString())
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("message send problem: %v\n", err)
|
||||||
|
result = "Project was created, but there is some problem with init channel message"
|
||||||
|
h.defaultFollowUp(result, s, i)
|
||||||
|
}
|
||||||
|
result = fmt.Sprintf("Project was created: https://discord.com/channels/%s/%s", i.GuildID, dchan.ID)
|
||||||
|
|
||||||
|
h.defaultFollowUp(result, s, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCoda
|
||||||
|
/*
|
||||||
|
- sends request to Coda.io;
|
||||||
|
*/
|
||||||
|
func (h *Handler) CreateCoda(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
|
||||||
|
// Get channel from the request
|
||||||
|
dchan, err := s.Channel(i.ChannelID)
|
||||||
|
if err != nil {
|
||||||
|
h.defaultFollowUp("unable to get channel from the message", s, i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if dchan.ParentID != h.conf.IsProjectChannel {
|
||||||
|
h.defaultFollowUp("This channel is not at the project's group", s, i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//[ ] достать проект из базы и послать в коду
|
||||||
|
result, err := h.controller.CreateCoda(i.GuildID, dchan.ID)
|
||||||
|
if err != nil {
|
||||||
|
h.defaultFollowUp(fmt.Sprintf("unable to create coda: %v", err), s, i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
result += fmt.Sprintf("\nexecuted w/ error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.defaultFollowUp(result, s, i)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
package discord_router
|
||||||
|
|
||||||
|
import "github.com/bwmarrin/discordgo"
|
||||||
|
|
||||||
|
type HandlerFunc func(*discordgo.Session, *discordgo.InteractionCreate)
|
||||||
|
|
||||||
|
type RouteEntry struct {
|
||||||
|
CommandName string
|
||||||
|
Handler HandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *RouteEntry) Match(i *discordgo.InteractionCreate) bool {
|
||||||
|
switch i.Type {
|
||||||
|
case discordgo.InteractionApplicationCommand:
|
||||||
|
if i.ApplicationCommandData().Name != re.CommandName {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case discordgo.InteractionMessageComponent:
|
||||||
|
if i.MessageComponentData().CustomID != re.CommandName {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type Router struct {
|
||||||
|
session *discordgo.Session
|
||||||
|
routes []RouteEntry
|
||||||
|
group []Group
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewApp(s *discordgo.Session) *Router {
|
||||||
|
return &Router{
|
||||||
|
session: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Route(cmd string, handlerFunc HandlerFunc) *Router {
|
||||||
|
|
||||||
|
r.routes = append(r.routes, RouteEntry{
|
||||||
|
CommandName: cmd,
|
||||||
|
Handler: handlerFunc,
|
||||||
|
})
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Serve(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
for _, e := range r.routes {
|
||||||
|
ok := e.Match(i)
|
||||||
|
if ok {
|
||||||
|
e.Handler(s, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, g := range r.group {
|
||||||
|
for _, e := range g.routes {
|
||||||
|
ok := e.Match(i)
|
||||||
|
if ok {
|
||||||
|
if len(g.middleware) < 1 {
|
||||||
|
e.Handler(s, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapped := e.Handler
|
||||||
|
|
||||||
|
// loop in reverse to preserve middleware order
|
||||||
|
for i := len(g.middleware) - 1; i >= 0; i-- {
|
||||||
|
wrapped = g.middleware[i](wrapped)
|
||||||
|
}
|
||||||
|
wrapped(s, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Middleware func(HandlerFunc) HandlerFunc
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
routes []RouteEntry
|
||||||
|
middleware []Middleware
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Use(m ...Middleware) *Group {
|
||||||
|
|
||||||
|
r.group = append(r.group, Group{
|
||||||
|
routes: []RouteEntry{},
|
||||||
|
middleware: m,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &r.group[len(r.group)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) Route(cmd string, handlerFunc HandlerFunc) *Group {
|
||||||
|
|
||||||
|
g.routes = append(g.routes, RouteEntry{
|
||||||
|
CommandName: cmd,
|
||||||
|
Handler: handlerFunc,
|
||||||
|
})
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
@ -1,249 +0,0 @@
|
||||||
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
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"ticket-pimp/internal/controller"
|
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *client) CreateFolderHandler(nameMinLenght int) Command {
|
|
||||||
const (
|
|
||||||
nameOption string = "folder_name"
|
|
||||||
)
|
|
||||||
return Command{
|
|
||||||
|
|
||||||
Command: discordgo.ApplicationCommand{
|
|
||||||
Name: "folder",
|
|
||||||
Description: "Command for cloud folder creation",
|
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionString,
|
|
||||||
Name: nameOption,
|
|
||||||
Description: "Type the folder's name",
|
|
||||||
Required: false,
|
|
||||||
MinLength: &nameMinLenght,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"ticket-pimp/internal/controller"
|
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *client) CreateRepoHandler(repoNameMinLength int) Command {
|
|
||||||
const (
|
|
||||||
repoType = "repo_type"
|
|
||||||
projectRepo = "project_repo"
|
|
||||||
buildRepo = "build_repo"
|
|
||||||
nameOption = "repo_name"
|
|
||||||
)
|
|
||||||
|
|
||||||
return Command{
|
|
||||||
Command: discordgo.ApplicationCommand{
|
|
||||||
Name: "repo",
|
|
||||||
Description: "Creates repository of selected type. Name used for projects channels only",
|
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionString,
|
|
||||||
Name: repoType,
|
|
||||||
Description: "The type of repo",
|
|
||||||
Required: true,
|
|
||||||
Choices: []*discordgo.ApplicationCommandOptionChoice{
|
|
||||||
{
|
|
||||||
Name: "Unity project repo",
|
|
||||||
Value: projectRepo,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "XCode build repo",
|
|
||||||
Value: buildRepo,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionString,
|
|
||||||
Name: nameOption,
|
|
||||||
Description: "Type the repository's name",
|
|
||||||
Required: false,
|
|
||||||
MinLength: &repoNameMinLength,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *client) Ping() Command {
|
|
||||||
return Command{
|
|
||||||
Command: discordgo.ApplicationCommand{
|
|
||||||
Name: "ping",
|
|
||||||
Description: "pongs in a reply",
|
|
||||||
},
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,220 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"ticket-pimp/internal/domain"
|
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *client) GetInfo() Command {
|
|
||||||
return Command{
|
|
||||||
Command: discordgo.ApplicationCommand{
|
|
||||||
Name: "info",
|
|
||||||
Description: "Get project's info",
|
|
||||||
},
|
|
||||||
Handler: c.getInfo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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: "key",
|
|
||||||
Description: "Project's key from Coda.io",
|
|
||||||
Required: true,
|
|
||||||
MinLength: &minLength,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Handler: c.initProjectFromChannel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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",
|
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionString,
|
|
||||||
Name: "project_name",
|
|
||||||
Description: "Temporary project name",
|
|
||||||
Required: true,
|
|
||||||
MinLength: &repoNameMinLength,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"ticket-pimp/internal/controller"
|
|
||||||
"ticket-pimp/internal/domain"
|
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
|
||||||
)
|
|
||||||
|
|
||||||
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, tgConf *domain.TelegramConfig) *client {
|
|
||||||
|
|
||||||
var r client
|
|
||||||
r.controller = wc
|
|
||||||
r.conf = conf
|
|
||||||
|
|
||||||
r.Commands = append(r.Commands,
|
|
||||||
r.CreateRepoHandler(3),
|
|
||||||
r.CreateFolderHandler(3),
|
|
||||||
r.Ping(),
|
|
||||||
r.CreateTicketHandler(3),
|
|
||||||
r.InitProjectFromChannel(3),
|
|
||||||
r.GetInfo(),
|
|
||||||
)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Подключение роутов к Discord боту
|
|
||||||
|
|
||||||
type Command struct {
|
|
||||||
Command discordgo.ApplicationCommand
|
|
||||||
Handler func(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{
|
|
||||||
Content: answer,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
|
|
||||||
Content: fmt.Sprintf("Something went wrong: %v", err),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,19 +3,19 @@ package telegram
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"ticket-pimp/client/telegram/handler"
|
"ticket-pimp/client/telegram/telegram_handler"
|
||||||
"ticket-pimp/internal/controller"
|
"ticket-pimp/internal/controller"
|
||||||
"ticket-pimp/internal/domain"
|
"ticket-pimp/internal/domain"
|
||||||
"ticket-pimp/internal/services"
|
"ticket-pimp/internal/external"
|
||||||
|
|
||||||
"github.com/mr-linch/go-tg"
|
"github.com/mr-linch/go-tg"
|
||||||
"github.com/mr-linch/go-tg/tgb"
|
"github.com/mr-linch/go-tg/tgb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TelegramOptions struct {
|
type TelegramOptions struct {
|
||||||
GitService *services.Git
|
GitService *external.Git
|
||||||
CloudService *services.Cloud
|
CloudService *external.Cloud
|
||||||
Coda *services.Coda
|
Coda *external.Coda
|
||||||
AppConfig *domain.Config
|
AppConfig *domain.Config
|
||||||
Controller *controller.WorkflowController
|
Controller *controller.WorkflowController
|
||||||
}
|
}
|
||||||
|
|
@ -30,7 +30,7 @@ func Run(ctx context.Context, opts TelegramOptions) error {
|
||||||
log.Print("Start telegram bot init..")
|
log.Print("Start telegram bot init..")
|
||||||
client := tg.New(opts.AppConfig.Telegram.Token)
|
client := tg.New(opts.AppConfig.Telegram.Token)
|
||||||
|
|
||||||
h := handler.NewHandler(
|
h := telegram_handler.NewHandler(
|
||||||
opts.GitService,
|
opts.GitService,
|
||||||
opts.CloudService,
|
opts.CloudService,
|
||||||
opts.Coda,
|
opts.Coda,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package handler
|
package telegram_handler
|
||||||
|
|
||||||
// func (h *Handler) DevelopmentTaskHandler(ctx context.Context, mu *tgb.MessageUpdate) error {
|
// func (h *Handler) DevelopmentTaskHandler(ctx context.Context, mu *tgb.MessageUpdate) error {
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package handler
|
package telegram_handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package handler
|
package telegram_handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package handler
|
package telegram_handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package handler
|
package telegram_handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
package handler
|
package telegram_handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"ticket-pimp/adapters"
|
||||||
"ticket-pimp/internal/controller"
|
"ticket-pimp/internal/controller"
|
||||||
"ticket-pimp/internal/services"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
git services.IGit
|
git adapters.IGit
|
||||||
cloud services.ICloud
|
cloud adapters.ICloud
|
||||||
coda services.ICoda
|
coda adapters.ICoda
|
||||||
key string
|
key string
|
||||||
id string
|
id string
|
||||||
controller *controller.WorkflowController
|
controller *controller.WorkflowController
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandler(
|
func NewHandler(
|
||||||
git services.IGit,
|
git adapters.IGit,
|
||||||
cloud services.ICloud,
|
cloud adapters.ICloud,
|
||||||
coda services.ICoda,
|
coda adapters.ICoda,
|
||||||
controller *controller.WorkflowController,
|
controller *controller.WorkflowController,
|
||||||
) *Handler {
|
) *Handler {
|
||||||
|
|
||||||
125
cmd/main.go
125
cmd/main.go
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -10,16 +11,19 @@ import (
|
||||||
|
|
||||||
"ticket-pimp/internal/controller"
|
"ticket-pimp/internal/controller"
|
||||||
"ticket-pimp/internal/domain"
|
"ticket-pimp/internal/domain"
|
||||||
"ticket-pimp/internal/services"
|
"ticket-pimp/internal/external"
|
||||||
|
|
||||||
"ticket-pimp/client/discord"
|
"ticket-pimp/client/discord"
|
||||||
"ticket-pimp/client/telegram"
|
"ticket-pimp/client/telegram"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
"github.com/jackc/pgx/v5/stdlib"
|
"github.com/jackc/pgx/v5/stdlib"
|
||||||
|
"github.com/pkg/errors"
|
||||||
migrate "github.com/rubenv/sql-migrate"
|
migrate "github.com/rubenv/sql-migrate"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
const (
|
const (
|
||||||
envfile = "prod.env"
|
envfile = "prod.env"
|
||||||
migrationfile = "../internal/storage/migrate"
|
migrationfile = "../internal/storage/migrate"
|
||||||
|
|
@ -27,31 +31,45 @@ const (
|
||||||
// envfile = "../docker/prod.env"
|
// envfile = "../docker/prod.env"
|
||||||
// migrationfile = "../internal/storage/migrate"
|
// migrationfile = "../internal/storage/migrate"
|
||||||
)
|
)
|
||||||
|
=======
|
||||||
|
const migrationfile = "../internal/storage/migrate"
|
||||||
|
>>>>>>> dev
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Print("started")
|
log.Print("started")
|
||||||
config := domain.InitConfig(envfile)
|
|
||||||
run(config)
|
env := flag.Int("env", -1, "0 for development env file, 1 for production environment run, missing flag for build")
|
||||||
|
flag.Parse()
|
||||||
|
var envPath string
|
||||||
|
|
||||||
|
switch *env {
|
||||||
|
case 0:
|
||||||
|
envPath = ".env"
|
||||||
|
case 1:
|
||||||
|
envPath = "../docker/prod.env"
|
||||||
|
default:
|
||||||
|
envPath = "prod.env"
|
||||||
|
}
|
||||||
|
|
||||||
|
config := domain.InitConfig(envPath)
|
||||||
|
run(&config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(conf domain.Config) {
|
func Go(ctx context.Context, fns ...func(context.Context) error) error {
|
||||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill, syscall.SIGTERM)
|
group, ctx := errgroup.WithContext(ctx)
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// -- DB connection init -- START
|
for _, fn := range fns {
|
||||||
connString := fmt.Sprintf(
|
fn := fn
|
||||||
"postgresql://%s:%s@%s:%s/%s",
|
group.Go(func() error {
|
||||||
conf.DB.User, conf.DB.Pass, conf.DB.Host, conf.DB.Port, conf.DB.Name,
|
return fn(ctx)
|
||||||
)
|
})
|
||||||
conn, err := pgxpool.New(
|
|
||||||
ctx,
|
|
||||||
connString)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("DB connection failed: %v", err)
|
|
||||||
}
|
}
|
||||||
// -- DB connection init -- END
|
|
||||||
|
|
||||||
// Aply migrations:
|
return group.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyMigrations(connString string) {
|
||||||
|
// Apply migrations:
|
||||||
|
|
||||||
dbConnConfig, err := pgxpool.ParseConfig(connString)
|
dbConnConfig, err := pgxpool.ParseConfig(connString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -71,42 +89,69 @@ func run(conf domain.Config) {
|
||||||
}
|
}
|
||||||
fmt.Printf("Applied %d migrations!\n", n)
|
fmt.Printf("Applied %d migrations!\n", n)
|
||||||
|
|
||||||
db.Close()
|
err = db.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("unable to close db connection")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
func run(c *domain.Config) {
|
||||||
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
gitService := services.NewGit(conf.Git)
|
// -- DB connection init -- START
|
||||||
cloudService := services.NewCloud(conf.Cloud)
|
connString := fmt.Sprintf(
|
||||||
codaService := services.NewCodaClient(conf.Coda)
|
"postgresql://%s:%s@%s:%s/%s",
|
||||||
|
c.DB.User, c.DB.Pass, c.DB.Host, c.DB.Port, c.DB.Name,
|
||||||
|
)
|
||||||
|
conn, err := pgxpool.New(
|
||||||
|
ctx,
|
||||||
|
connString)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("DB connection failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Инициализация контроллера:
|
// Apply migrations:
|
||||||
controller := controller.NewWorkflowController(
|
applyMigrations(connString)
|
||||||
gitService,
|
|
||||||
cloudService,
|
// Init services instances:
|
||||||
codaService,
|
git := external.NewGit(c.Git)
|
||||||
|
cloud := external.NewCloud(c.Cloud)
|
||||||
|
coda := external.NewCoda(c.Coda)
|
||||||
|
|
||||||
|
// Controller instance init:
|
||||||
|
controller := controller.NewApp(
|
||||||
|
git,
|
||||||
|
cloud,
|
||||||
|
coda,
|
||||||
conn,
|
conn,
|
||||||
|
c,
|
||||||
)
|
)
|
||||||
|
|
||||||
go func() {
|
Go(ctx,
|
||||||
|
func(ctx context.Context) error {
|
||||||
opts := discord.DiscordOptions{
|
opts := discord.DiscordOptions{
|
||||||
Controller: controller,
|
Controller: controller,
|
||||||
Config: &conf,
|
Config: c,
|
||||||
}
|
}
|
||||||
if err := discord.Run(conf, opts); err != nil {
|
if err := discord.Run(c, opts); err != nil {
|
||||||
log.Fatalf("discord bot cannot be runned: %v", err)
|
return errors.Errorf("discord bot cannot be runned: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
return nil
|
||||||
|
},
|
||||||
|
func(ctx context.Context) error {
|
||||||
opts := telegram.TelegramOptions{
|
opts := telegram.TelegramOptions{
|
||||||
GitService: gitService,
|
GitService: git,
|
||||||
CloudService: cloudService,
|
CloudService: cloud,
|
||||||
Coda: codaService,
|
Coda: coda,
|
||||||
AppConfig: &conf,
|
AppConfig: c,
|
||||||
Controller: controller,
|
Controller: controller,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := telegram.Run(ctx, opts); err != nil {
|
if err := telegram.Run(ctx, opts); err != nil {
|
||||||
log.Fatalf("telegram bot cannot be runned: %v", err)
|
return errors.Errorf("telegram bot cannot be runned: %v", err)
|
||||||
defer os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
go.mod
7
go.mod
|
|
@ -8,6 +8,9 @@ require (
|
||||||
github.com/jackc/pgx/v5 v5.4.3
|
github.com/jackc/pgx/v5 v5.4.3
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/mr-linch/go-tg v0.9.1
|
github.com/mr-linch/go-tg v0.9.1
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/rubenv/sql-migrate v1.5.2
|
||||||
|
golang.org/x/sync v0.2.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
@ -20,22 +23,18 @@ require (
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // 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/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.10.0 // 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/qpack v0.4.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-19 v0.3.2 // 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/qtls-go1-20 v0.2.2 // indirect
|
||||||
github.com/quic-go/quic-go v0.35.1 // 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/stretchr/testify v1.8.4 // indirect
|
||||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
|
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
|
||||||
golang.org/x/crypto v0.10.0 // indirect
|
golang.org/x/crypto v0.10.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||||
golang.org/x/mod v0.10.0 // indirect
|
golang.org/x/mod v0.10.0 // indirect
|
||||||
golang.org/x/net v0.11.0 // indirect
|
golang.org/x/net v0.11.0 // indirect
|
||||||
golang.org/x/sync v0.2.0 // indirect
|
|
||||||
golang.org/x/sys v0.9.0 // indirect
|
golang.org/x/sys v0.9.0 // indirect
|
||||||
golang.org/x/text v0.10.0 // indirect
|
golang.org/x/text v0.10.0 // indirect
|
||||||
golang.org/x/tools v0.9.3 // indirect
|
golang.org/x/tools v0.9.3 // indirect
|
||||||
|
|
|
||||||
21
go.sum
21
go.sum
|
|
@ -6,8 +6,12 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
|
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-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-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
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=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
|
github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU=
|
||||||
|
github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0=
|
||||||
|
github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
|
@ -29,14 +33,20 @@ 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/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 h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
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 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
||||||
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
||||||
|
github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI=
|
||||||
|
github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY=
|
||||||
|
github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||||
github.com/mr-linch/go-tg v0.9.1 h1:4KNe7zwFG6svgM9w6pIcH3R7QWa6hIK8tCisQiFRCpU=
|
github.com/mr-linch/go-tg v0.9.1 h1:4KNe7zwFG6svgM9w6pIcH3R7QWa6hIK8tCisQiFRCpU=
|
||||||
github.com/mr-linch/go-tg v0.9.1/go.mod h1:276w69YW4pEo3ZYta+LQe4v/ut2w2h1ksP4ziBWkK98=
|
github.com/mr-linch/go-tg v0.9.1/go.mod h1:276w69YW4pEo3ZYta+LQe4v/ut2w2h1ksP4ziBWkK98=
|
||||||
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
|
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
|
||||||
|
|
@ -46,6 +56,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
|
||||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||||
|
|
@ -54,11 +65,12 @@ 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/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 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62poo=
|
||||||
github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
|
github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0=
|
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/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is=
|
||||||
|
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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 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.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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
|
@ -95,6 +107,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||||
|
|
@ -112,6 +125,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
||||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"ticket-pimp/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (wc *WorkflowController) CreateCoda(guildID string, chanID string) (string, error) {
|
||||||
|
|
||||||
|
p, err := wc.GetProjectByChannelID(context.TODO(), chanID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
requestResult, err := wc.ICoda.CreateApp(domain.CodaApplication{
|
||||||
|
ID: p.Key,
|
||||||
|
Summary: p.Name,
|
||||||
|
URL: fmt.Sprintf("https://discord.com/channels/%s/%s", guildID, chanID),
|
||||||
|
Git: p.ProjectGit,
|
||||||
|
GitBuild: p.BuildGit,
|
||||||
|
Folder: p.Folder,
|
||||||
|
})
|
||||||
|
|
||||||
|
return requestResult, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -35,13 +35,13 @@ func (wc *WorkflowController) CreateFolder(ctx context.Context, req FolderReques
|
||||||
|
|
||||||
if project != nil {
|
if project != nil {
|
||||||
switch {
|
switch {
|
||||||
case project.Cloud != "":
|
case project.Folder != "":
|
||||||
return &ProjectResponse{
|
return &ProjectResponse{
|
||||||
Project: project,
|
Project: project,
|
||||||
Message: nil,
|
Message: nil,
|
||||||
}
|
}
|
||||||
case project.ShortName != "":
|
case project.Key != "":
|
||||||
name = project.ShortName
|
name = project.Key
|
||||||
case req.InsertedName != "":
|
case req.InsertedName != "":
|
||||||
name = req.InsertedName
|
name = req.InsertedName
|
||||||
}
|
}
|
||||||
|
|
@ -65,11 +65,12 @@ func (wc *WorkflowController) CreateFolder(ctx context.Context, req FolderReques
|
||||||
result = ProjectResponse{
|
result = ProjectResponse{
|
||||||
Project: &domain.Project{
|
Project: &domain.Project{
|
||||||
ID: string(dbticket.ID),
|
ID: string(dbticket.ID),
|
||||||
ShortName: dbticket.Key.String,
|
Name: dbticket.Title.String,
|
||||||
|
Key: dbticket.Key.String,
|
||||||
ChannelID: dbticket.Channelid.String,
|
ChannelID: dbticket.Channelid.String,
|
||||||
ProjectGit: dbticket.ProjectGit.String,
|
ProjectGit: dbticket.ProjectGit.String,
|
||||||
BuildGit: dbticket.BuildGit.String,
|
BuildGit: dbticket.BuildGit.String,
|
||||||
Cloud: dbticket.Folder.String,
|
Folder: dbticket.Folder.String,
|
||||||
},
|
},
|
||||||
Message: response.ErrMessage,
|
Message: response.ErrMessage,
|
||||||
}
|
}
|
||||||
|
|
@ -78,7 +79,7 @@ func (wc *WorkflowController) CreateFolder(ctx context.Context, req FolderReques
|
||||||
response := wc.ICloud.CreateFolder(req.InsertedName)
|
response := wc.ICloud.CreateFolder(req.InsertedName)
|
||||||
result = ProjectResponse{
|
result = ProjectResponse{
|
||||||
Project: &domain.Project{
|
Project: &domain.Project{
|
||||||
Cloud: response.Folder.PrivateURL,
|
Folder: response.Folder.PrivateURL,
|
||||||
},
|
},
|
||||||
Message: response.ErrMessage,
|
Message: response.ErrMessage,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ func (wc *WorkflowController) createGitForExistingProject(ctx context.Context, r
|
||||||
dbticket db.Ticket
|
dbticket db.Ticket
|
||||||
)
|
)
|
||||||
switch {
|
switch {
|
||||||
case p.ShortName != "":
|
case p.Key != "":
|
||||||
name = p.ShortName
|
name = p.Key
|
||||||
if req.IsBuildGit {
|
if req.IsBuildGit {
|
||||||
name += "-build"
|
name += "-build"
|
||||||
}
|
}
|
||||||
|
|
@ -81,11 +81,12 @@ func (wc *WorkflowController) createGitForExistingProject(ctx context.Context, r
|
||||||
return &ProjectResponse{
|
return &ProjectResponse{
|
||||||
Project: &domain.Project{
|
Project: &domain.Project{
|
||||||
ID: string(dbticket.ID),
|
ID: string(dbticket.ID),
|
||||||
ShortName: dbticket.Key.String,
|
Name: dbticket.Title.String,
|
||||||
|
Key: dbticket.Key.String,
|
||||||
ChannelID: dbticket.Channelid.String,
|
ChannelID: dbticket.Channelid.String,
|
||||||
ProjectGit: dbticket.ProjectGit.String,
|
ProjectGit: dbticket.ProjectGit.String,
|
||||||
BuildGit: dbticket.BuildGit.String,
|
BuildGit: dbticket.BuildGit.String,
|
||||||
Cloud: dbticket.Folder.String,
|
Folder: dbticket.Folder.String,
|
||||||
},
|
},
|
||||||
Message: err,
|
Message: err,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package controller
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"ticket-pimp/internal/domain"
|
"ticket-pimp/internal/domain"
|
||||||
"ticket-pimp/internal/storage/db"
|
"ticket-pimp/internal/storage/db"
|
||||||
|
|
||||||
|
|
@ -41,17 +42,19 @@ func (wc *WorkflowController) ProjectCreate(ctx context.Context, project domain.
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ShortName = fmt.Sprintf(
|
project.Key = fmt.Sprintf(
|
||||||
"%s-%d",
|
"%s-%d",
|
||||||
appconfig.TicketKey.String,
|
appconfig.TicketKey.String,
|
||||||
appconfig.TicketID.Int32,
|
appconfig.TicketID.Int32,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Set ID from the DB raw:
|
||||||
project.ID = string(appconfig.TicketID.Int32)
|
project.ID = string(appconfig.TicketID.Int32)
|
||||||
|
|
||||||
projectRow, err := qtx.CreateTicket(ctx, db.CreateTicketParams{
|
projectRow, err := qtx.CreateTicket(ctx, db.CreateTicketParams{
|
||||||
Key: pgtype.Text{String: project.ShortName, Valid: true},
|
Key: pgtype.Text{String: project.Key, Valid: true},
|
||||||
Channelid: pgtype.Text{String: project.ChannelID, Valid: true},
|
Channelid: pgtype.Text{String: project.ChannelID, Valid: true},
|
||||||
|
Title: pgtype.Text{String: project.Name, Valid: true},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback(ctx)
|
tx.Rollback(ctx)
|
||||||
|
|
@ -65,25 +68,24 @@ func (wc *WorkflowController) ProjectCreate(ctx context.Context, project domain.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wc *WorkflowController) GetProjectByChannelID(ctx context.Context, id string) (*domain.Project, error) {
|
func (wc *WorkflowController) GetProjectByChannelID(ctx context.Context, id string) (*domain.Project, error) {
|
||||||
var proj domain.Project
|
|
||||||
dbTicket, err := wc.q.GetTicketByChannelID(ctx, pgtype.Text{String: id, Valid: true})
|
dbTicket, err := wc.q.GetTicketByChannelID(ctx, pgtype.Text{String: id, Valid: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == pgx.ErrNoRows {
|
if err == pgx.ErrNoRows {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
}
|
||||||
proj = domain.Project{
|
|
||||||
ID: string(dbTicket.ID),
|
return &domain.Project{
|
||||||
ShortName: dbTicket.Key.String,
|
ID: strconv.Itoa(int(dbTicket.ID)),
|
||||||
Name: dbTicket.Key.String,
|
Key: dbTicket.Key.String,
|
||||||
|
Name: dbTicket.Title.String,
|
||||||
ChannelID: dbTicket.Channelid.String,
|
ChannelID: dbTicket.Channelid.String,
|
||||||
ProjectGit: dbTicket.ProjectGit.String,
|
ProjectGit: dbTicket.ProjectGit.String,
|
||||||
BuildGit: dbTicket.BuildGit.String,
|
BuildGit: dbTicket.BuildGit.String,
|
||||||
Cloud: dbTicket.Folder.String,
|
Folder: dbTicket.Folder.String,
|
||||||
}
|
}, nil
|
||||||
}
|
|
||||||
return &proj, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saves current channel as project's channel;
|
// Saves current channel as project's channel;
|
||||||
|
|
@ -108,11 +110,11 @@ func (wc *WorkflowController) InitProjectInChannel(ctx context.Context, channelI
|
||||||
|
|
||||||
return &domain.Project{
|
return &domain.Project{
|
||||||
ID: string(dbTicket.ID),
|
ID: string(dbTicket.ID),
|
||||||
ShortName: dbTicket.Key.String,
|
Key: dbTicket.Key.String,
|
||||||
Name: dbTicket.Key.String,
|
Name: dbTicket.Key.String,
|
||||||
ChannelID: dbTicket.Channelid.String,
|
ChannelID: dbTicket.Channelid.String,
|
||||||
ProjectGit: dbTicket.ProjectGit.String,
|
ProjectGit: dbTicket.ProjectGit.String,
|
||||||
BuildGit: dbTicket.BuildGit.String,
|
BuildGit: dbTicket.BuildGit.String,
|
||||||
Cloud: dbTicket.Folder.String,
|
Folder: dbTicket.Folder.String,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package controller
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"ticket-pimp/internal/domain"
|
"ticket-pimp/internal/domain"
|
||||||
"ticket-pimp/internal/storage/db"
|
"ticket-pimp/internal/storage/db"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -42,12 +41,11 @@ func (wc *WorkflowController) WriteTaskToDB(t *domain.Task) (*domain.Task, error
|
||||||
|
|
||||||
// InitTask
|
// InitTask
|
||||||
/*
|
/*
|
||||||
Runs the following:
|
Runs the following:
|
||||||
- Use WriteTaskToDB method to make a new task row in the db;
|
- Use WriteTaskToDB method to make a new task row in the db;
|
||||||
- init new discord bot instance;
|
- init new discord bot instance;
|
||||||
-
|
|
||||||
|
|
||||||
Possible errors:
|
Possible errors:
|
||||||
- db record couldn't be created;
|
- db record couldn't be created;
|
||||||
- bot couldn't be inited;
|
- bot couldn't be inited;
|
||||||
- bot session couldn't be started;
|
- bot session couldn't be started;
|
||||||
|
|
@ -61,14 +59,7 @@ func (wc *WorkflowController) InitTask(t *domain.Task) (*domain.Task, error) {
|
||||||
return nil, fmt.Errorf("unable to create task at the db: %v", err)
|
return nil, fmt.Errorf("unable to create task at the db: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Инициализируем новый клиент дискорда
|
s, err := discordgo.New("Bot " + wc.conf.Discord.Token)
|
||||||
// [ ] Нездоровое получение параметров клиента из os..
|
|
||||||
var (
|
|
||||||
token = os.Getenv("DISCORD_TOKEN")
|
|
||||||
forumChannelID = os.Getenv("TASKS_CHANNEL")
|
|
||||||
)
|
|
||||||
|
|
||||||
s, err := discordgo.New("Bot " + token)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return task, fmt.Errorf("unable to create discord session: %v", err)
|
return task, fmt.Errorf("unable to create discord session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -100,9 +91,10 @@ func (wc *WorkflowController) InitTask(t *domain.Task) (*domain.Task, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
th, err := s.ForumThreadStartComplex(
|
th, err := s.ForumThreadStartComplex(
|
||||||
forumChannelID,
|
wc.conf.Discord.IsTaskForum,
|
||||||
&discordgo.ThreadStart{
|
&discordgo.ThreadStart{
|
||||||
Name: fmt.Sprintf("Task ID: %d, by %s", task.ID, task.Creator),
|
Name: fmt.Sprintf("Task ID: %d, by %s", task.ID, task.Creator),
|
||||||
|
AppliedTags: []string{wc.conf.Discord.Tags[domain.NewTaskState()]},
|
||||||
},
|
},
|
||||||
&msg,
|
&msg,
|
||||||
)
|
)
|
||||||
|
|
@ -126,25 +118,34 @@ func (wc *WorkflowController) UpdateTasksMessageID(ctx context.Context, msgID st
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wc *WorkflowController) UpdateTask(id string, opt int, user string) (*TaskConvertable, error) {
|
// UpdateTask
|
||||||
|
/*
|
||||||
|
- updates task by message Id
|
||||||
|
- with an action:
|
||||||
|
0 for 'in progress' state;
|
||||||
|
1 for 'done' state;
|
||||||
|
- and assignee..
|
||||||
|
*/
|
||||||
|
func (wc *WorkflowController) UpdateTask(messageId string, opt int, assignee string) (*TaskConvertable, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
dbtask db.Task
|
dbtask db.Task
|
||||||
)
|
)
|
||||||
|
|
||||||
switch opt {
|
switch opt {
|
||||||
case 0:
|
case 0:
|
||||||
dbtask, err = wc.q.StartTask(context.TODO(), db.StartTaskParams{
|
dbtask, err = wc.q.StartTask(context.TODO(), db.StartTaskParams{
|
||||||
UpdatedAt: pgtype.Timestamptz{Time: time.Now(), InfinityModifier: 0, Valid: true},
|
UpdatedAt: pgtype.Timestamptz{Time: time.Now(), InfinityModifier: 0, Valid: true},
|
||||||
Assignee: pgtype.Text{String: user, Valid: true},
|
Assignee: pgtype.Text{String: assignee, Valid: true},
|
||||||
Messageid: pgtype.Text{String: id, Valid: true},
|
Messageid: pgtype.Text{String: messageId, Valid: true},
|
||||||
})
|
})
|
||||||
return &TaskConvertable{&dbtask}, err
|
return &TaskConvertable{&dbtask}, err
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
dbtask, err = wc.q.CloseTask(context.TODO(), db.CloseTaskParams{
|
dbtask, err = wc.q.CloseTask(context.TODO(), db.CloseTaskParams{
|
||||||
DeletedAt: pgtype.Timestamptz{Time: time.Now(), InfinityModifier: 0, Valid: true},
|
DeletedAt: pgtype.Timestamptz{Time: time.Now(), InfinityModifier: 0, Valid: true},
|
||||||
Assignee: pgtype.Text{String: user, Valid: true},
|
Assignee: pgtype.Text{String: assignee, Valid: true},
|
||||||
Messageid: pgtype.Text{String: id, Valid: true},
|
Messageid: pgtype.Text{String: messageId, Valid: true},
|
||||||
})
|
})
|
||||||
return &TaskConvertable{&dbtask}, err
|
return &TaskConvertable{&dbtask}, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"ticket-pimp/adapters"
|
||||||
"ticket-pimp/internal/domain"
|
"ticket-pimp/internal/domain"
|
||||||
"ticket-pimp/internal/services"
|
|
||||||
"ticket-pimp/internal/storage/db"
|
"ticket-pimp/internal/storage/db"
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WorkflowController struct {
|
type WorkflowController struct {
|
||||||
IGit services.IGit
|
IGit adapters.IGit
|
||||||
ICloud services.ICloud
|
ICloud adapters.ICloud
|
||||||
ICoda services.ICoda
|
ICoda adapters.ICoda
|
||||||
pool *pgxpool.Pool
|
pool *pgxpool.Pool
|
||||||
q *db.Queries
|
q *db.Queries
|
||||||
ATags []discordgo.ForumTag
|
conf *domain.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWorkflowController(
|
func NewApp(
|
||||||
git services.IGit,
|
git adapters.IGit,
|
||||||
cloud services.ICloud,
|
cloud adapters.ICloud,
|
||||||
coda services.ICoda,
|
coda adapters.ICoda,
|
||||||
pool *pgxpool.Pool,
|
pool *pgxpool.Pool,
|
||||||
|
conf *domain.Config,
|
||||||
) *WorkflowController {
|
) *WorkflowController {
|
||||||
return &WorkflowController{
|
return &WorkflowController{
|
||||||
IGit: git,
|
IGit: git,
|
||||||
|
|
@ -30,6 +30,7 @@ func NewWorkflowController(
|
||||||
ICoda: coda,
|
ICoda: coda,
|
||||||
pool: pool,
|
pool: pool,
|
||||||
q: db.New(pool),
|
q: db.New(pool),
|
||||||
|
conf: conf,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ type DiscordConfig struct {
|
||||||
Token string
|
Token string
|
||||||
IsProjectChannel string
|
IsProjectChannel string
|
||||||
IsTaskForum string
|
IsTaskForum string
|
||||||
|
Tags map[TaskState]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApplicationConfig struct {
|
type ApplicationConfig struct {
|
||||||
|
|
@ -62,8 +63,9 @@ type ApplicationConfig struct {
|
||||||
|
|
||||||
// InitConfig
|
// InitConfig
|
||||||
// InitConfig function reads provided file and setup envirmental variables;
|
// InitConfig function reads provided file and setup envirmental variables;
|
||||||
func InitConfig(envFilePath string) Config {
|
func InitConfig(env string) Config {
|
||||||
err := godotenv.Load(envFilePath)
|
log.Printf("loading ENV from: %s", env)
|
||||||
|
err := godotenv.Load(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error while loading env file")
|
log.Fatal("Error while loading env file")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -11,6 +12,11 @@ type Folder struct {
|
||||||
PrivateURL string // http://domain/apps/files/?dir=/temp/k OR http://domain/f/3333
|
PrivateURL string // http://domain/apps/files/?dir=/temp/k OR http://domain/f/3333
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Folder *Folder
|
||||||
|
ErrMessage error
|
||||||
|
}
|
||||||
|
|
||||||
type CodaApplication struct {
|
type CodaApplication struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Summary string `json:"summary"`
|
Summary string `json:"summary"`
|
||||||
|
|
@ -77,6 +83,18 @@ const (
|
||||||
done
|
done
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func State(i int) TaskState {
|
||||||
|
switch i {
|
||||||
|
case 0:
|
||||||
|
return new
|
||||||
|
case 1:
|
||||||
|
return inprogress
|
||||||
|
case 2:
|
||||||
|
return done
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
func NewTaskState() TaskState {
|
func NewTaskState() TaskState {
|
||||||
return TaskState(0)
|
return TaskState(0)
|
||||||
}
|
}
|
||||||
|
|
@ -138,22 +156,31 @@ type Git struct {
|
||||||
|
|
||||||
type Project struct {
|
type Project struct {
|
||||||
ID string `json:"id"` //15
|
ID string `json:"id"` //15
|
||||||
ShortName string `json:"shortName"` //key-15
|
Key string `json:"shortName"` //key-15
|
||||||
Name string `json:"name"` //default project name
|
Name string `json:"name"` //default project name
|
||||||
ChannelID string `json:"channel_id"` //123412341234
|
ChannelID string `json:"channel_id"` //123412341234
|
||||||
|
|
||||||
ProjectGit string `json:"project_git"` //https://github.com/mobilerino/dap-108
|
ProjectGit string `json:"project_git"` //https://github.com/mobilerino/dap-108
|
||||||
BuildGit string `json:"build_git"` //https://github.com/mobilerino/dap-108-build
|
BuildGit string `json:"build_git"` //https://github.com/mobilerino/dap-108-build
|
||||||
Cloud string `json:"cloud"` //http://82.151.222.22:7000/f/86658
|
Folder string `json:"cloud"` //http://82.151.222.22:7000/f/86658
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Project) DiscordString() string {
|
func (p *Project) DiscordString() string {
|
||||||
|
|
||||||
return fmt.Sprintf(
|
var builder strings.Builder
|
||||||
"## Project info:\n> 🔑 key: %s\n> 📂 folder: %s\n> 👾 project git: %s\n> 🚀 build git: %s\n",
|
builder.WriteString(fmt.Sprintf("## Project `%s`:\n> 🔑 key: %s", p.Name, p.Key))
|
||||||
p.ShortName,
|
|
||||||
p.Cloud,
|
if p.Folder != "" {
|
||||||
p.ProjectGit,
|
builder.WriteString(fmt.Sprintf("\n> 📂 folder: %s", p.Folder))
|
||||||
p.BuildGit,
|
}
|
||||||
)
|
|
||||||
|
if p.ProjectGit != "" {
|
||||||
|
builder.WriteString(fmt.Sprintf("\n> 👾 project git: %s", p.ProjectGit))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.BuildGit != "" {
|
||||||
|
builder.WriteString(fmt.Sprintf("\n> 🚀 build git: %s", p.BuildGit))
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package domain
|
|
||||||
|
|
||||||
type TgUser struct {
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
TgLink string
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package services
|
package external
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package services
|
package external
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
@ -16,10 +16,6 @@ type Cloud struct {
|
||||||
Config domain.CloudConfig
|
Config domain.CloudConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type ICloud interface {
|
|
||||||
CreateFolder(name string) Response
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCloud(conf domain.CloudConfig) *Cloud {
|
func NewCloud(conf domain.CloudConfig) *Cloud {
|
||||||
|
|
||||||
client := NewClient().
|
client := NewClient().
|
||||||
|
|
@ -35,13 +31,8 @@ func NewCloud(conf domain.CloudConfig) *Cloud {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
func (c *Cloud) CreateFolder(name string) domain.Response {
|
||||||
Folder *domain.Folder
|
var R domain.Response
|
||||||
ErrMessage error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cloud) CreateFolder(name string) Response {
|
|
||||||
var R Response
|
|
||||||
|
|
||||||
rootDir := c.Config.RootDir
|
rootDir := c.Config.RootDir
|
||||||
user := c.Config.User
|
user := c.Config.User
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package services
|
package external
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
"ticket-pimp/internal/domain"
|
"ticket-pimp/internal/domain"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -14,14 +16,7 @@ type Coda struct {
|
||||||
Config domain.CodaConfig
|
Config domain.CodaConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type ICoda interface {
|
func NewCoda(conf domain.CodaConfig) *Coda {
|
||||||
ListDocs()
|
|
||||||
CreateApp(task domain.CodaApplication)
|
|
||||||
CreateTask(title string, desc string, creatorName string, creatorID string) (string, error)
|
|
||||||
GetRowLink(id string) (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCodaClient(conf domain.CodaConfig) *Coda {
|
|
||||||
|
|
||||||
client := NewClient().
|
client := NewClient().
|
||||||
SetTimeout(15 * time.Second).
|
SetTimeout(15 * time.Second).
|
||||||
|
|
@ -54,14 +49,49 @@ func (c *Coda) ListDocs() {
|
||||||
log.Print(resp)
|
log.Print(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Coda) CreateApp(task domain.CodaApplication) {
|
type CodaWebhookResponse struct {
|
||||||
resp, _ := c.R().
|
ReqID string `json:"requestId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Coda) CreateApp(task domain.CodaApplication) (string, error) {
|
||||||
|
|
||||||
|
var whResponse CodaWebhookResponse
|
||||||
|
c.R().
|
||||||
SetBody(task).
|
SetBody(task).
|
||||||
SetContentType("application/json").
|
SetContentType("application/json").
|
||||||
|
SetSuccessResult(&whResponse).
|
||||||
SetBearerAuthToken(c.Config.Develop).
|
SetBearerAuthToken(c.Config.Develop).
|
||||||
Post("/docs/Ic3IZpQ3Wk/hooks/automation/grid-auto-NlUwM7F7Cr")
|
Post("/docs/Ic3IZpQ3Wk/hooks/automation/grid-auto-NlUwM7F7Cr")
|
||||||
|
|
||||||
fmt.Print(resp)
|
if whResponse.ReqID == "" {
|
||||||
|
return "", errors.New("coda responded w/o mutate id")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// mutate string
|
||||||
|
sep string = ":"
|
||||||
|
)
|
||||||
|
|
||||||
|
if !strings.Contains(whResponse.ReqID, sep) {
|
||||||
|
return "", fmt.Errorf("unexpected coda response: %s", whResponse.ReqID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// arr := strings.Split(whResponse.ReqID, sep)
|
||||||
|
// if arr[0] == "mutate" {
|
||||||
|
// mutate = arr[1]
|
||||||
|
// }
|
||||||
|
|
||||||
|
// mutateResponse, err := c.R().
|
||||||
|
// SetContentType("application/json").
|
||||||
|
// SetBearerAuthToken(c.Config.Develop).
|
||||||
|
// Get(fmt.Sprintf("/mutationStatus/%s", mutate))
|
||||||
|
|
||||||
|
// if err != nil {
|
||||||
|
// return "", fmt.Errorf("unable to get coda mutate result: %s", mutate)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _ = mutateResponse
|
||||||
|
return whResponse.ReqID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Coda) CreateTask(title string, desc string, creatorName string, creatorID string) (string, error) {
|
func (c *Coda) CreateTask(title string, desc string, creatorName string, creatorID string) (string, error) {
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package external
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ticket-pimp/internal/domain"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DummyTelegram struct {
|
||||||
|
*CommonClient
|
||||||
|
config domain.TelegramConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDummyClient(conf domain.TelegramConfig) *DummyTelegram {
|
||||||
|
|
||||||
|
client := NewClient().
|
||||||
|
SetTimeout(5 * time.Second)
|
||||||
|
|
||||||
|
return &DummyTelegram{
|
||||||
|
CommonClient: &CommonClient{
|
||||||
|
Client: client,
|
||||||
|
},
|
||||||
|
config: conf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (tg *DummyTelegram) DummyNotification(id string, text string) {
|
||||||
|
tg.R().
|
||||||
|
SetBody(&TelegramMessage{
|
||||||
|
ChatID: id,
|
||||||
|
Text: text,
|
||||||
|
DisableNotification: true,
|
||||||
|
ParseMode: "HTML",
|
||||||
|
DisablePreview: true,
|
||||||
|
}).
|
||||||
|
Post("https://api.telegram.org/bot" + tg.config.Token + "/sendMessage")
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package services
|
package external
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -13,10 +13,6 @@ type Git struct {
|
||||||
conf *domain.GitConfig
|
conf *domain.GitConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type IGit interface {
|
|
||||||
CreateRepo(name string) (*domain.Git, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGit(conf domain.GitConfig) *Git {
|
func NewGit(conf domain.GitConfig) *Git {
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
"Accept": "application/vnd.github+json",
|
"Accept": "application/vnd.github+json",
|
||||||
|
|
@ -90,7 +86,6 @@ func (gb *Git) defaultGroupAsCollaborator(git *domain.Git) (*domain.Git, error)
|
||||||
Perm: "push",
|
Perm: "push",
|
||||||
}
|
}
|
||||||
|
|
||||||
// respURL := "/orgs/mobilerino/teams/devs/repos/mobilerino/" + git.Name
|
|
||||||
respURL := fmt.Sprintf("/orgs/%s/teams/devs/repos/%s/%s", gb.conf.OrgName, gb.conf.OrgName, git.Name)
|
respURL := fmt.Sprintf("/orgs/%s/teams/devs/repos/%s/%s", gb.conf.OrgName, gb.conf.OrgName, git.Name)
|
||||||
|
|
||||||
resp, _ := gb.R().
|
resp, _ := gb.R().
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- +migrate Up
|
||||||
|
ALTER TABLE tickets
|
||||||
|
ADD COLUMN title VARCHAR(100);
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
ALTER TABLE tickets
|
||||||
|
DROP COLUMN title;
|
||||||
|
|
@ -9,9 +9,9 @@ RETURNING *;
|
||||||
|
|
||||||
-- name: CreateTicket :one
|
-- name: CreateTicket :one
|
||||||
INSERT INTO tickets (
|
INSERT INTO tickets (
|
||||||
key, channelID
|
key, channelID, title
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1, $2
|
$1, $2, $3
|
||||||
)
|
)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue