diff --git a/Dockerfile b/Dockerfile index f69a1c9..9b75311 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,6 @@ RUN apk add git RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install -ldflags '-extldflags "-static"' -tags timetzdata ./cmd/main.go FROM scratch -# the test program: COPY --from=app-builder /go/bin/main /ticket-pimp COPY --from=app-builder /go/src/ticket-pimp/cmd/prod.env / # the tls certificates: diff --git a/cmd/main.go b/cmd/main.go index f750f95..c764e4e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -62,7 +62,6 @@ func run(conf domain.Config) { } }() - // go func() { opts := telegram.TelegramOptions{ // TicketsRepo: db, GitService: gitService, @@ -75,6 +74,4 @@ func run(conf domain.Config) { log.Fatalf("telegram bot cannot be runned: %v", err) defer os.Exit(1) } - // }() - } diff --git a/discord/discord.go b/discord/discord.go index c2db60a..911569d 100644 --- a/discord/discord.go +++ b/discord/discord.go @@ -12,12 +12,12 @@ import ( "github.com/bwmarrin/discordgo" ) -func initBotWith(token string) (*discordgo.Session, error) { +func initBotWith(token string) *discordgo.Session { discord, err := discordgo.New("Bot " + token) if err != nil { - return nil, err + log.Fatalf("unable to create discord session: %v", err) } - return discord, nil + return discord } type DiscordOptions struct { @@ -28,10 +28,7 @@ type DiscordOptions struct { func Run(conf domain.Config, opts DiscordOptions) error { token := conf.Discord.Token - session, err := initBotWith(token) - if err != nil { - return err - } + session := initBotWith(token) router := handler.InitRouter(*opts.Controller) @@ -41,7 +38,6 @@ func Run(conf domain.Config, opts DiscordOptions) error { } session.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { - if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok { h(s, i) } @@ -73,7 +69,7 @@ func Run(conf domain.Config, opts DiscordOptions) error { log.Println("Removing commands...") for _, h := range cmds { - err = session.ApplicationCommandDelete(session.State.User.ID, "", h.ID) + err := session.ApplicationCommandDelete(session.State.User.ID, "", h.ID) if err != nil { log.Panicf("Cannot delete '%v' command: %v", h.Name, err) } diff --git a/discord/handler/handle_folder.go b/discord/handler/handle_folder.go new file mode 100644 index 0000000..0373596 --- /dev/null +++ b/discord/handler/handle_folder.go @@ -0,0 +1,107 @@ +package handler + +import ( + "context" + "fmt" + "log" + "strconv" + "ticket-pimp/internal/controller" + + "github.com/bwmarrin/discordgo" +) + +func (h *router) CreateFolderHandler(nameMinLenght int) route { + const ( + nameOption string = "folder_name" + ) + return route{ + + 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: func(s *discordgo.Session, i *discordgo.InteractionCreate) { + // Моментальный ответ для избежания столкновения с протуханием токена + initialResponse := discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Flags: discordgo.MessageFlagsEphemeral, + Content: "Cooking your query..", + }, + } + + s.InteractionRespond(i.Interaction, &initialResponse) + + // Определение переменной для ответа + var result string = "unexpected result" + + // Определение выбранных вариантов ответа + options := i.ApplicationCommandData().Options + + optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options)) + for _, opt := range options { + optionMap[opt.Name] = opt + } + + // Creating request: + var req controller.FolderRequest + name, insertedValueNotNil := optionMap[nameOption] + dchan, err := s.Channel(i.ChannelID) + + if err != nil { + log.Printf("error while identifying channel: %v", err) + } else { + + if dchan.ParentID == strconv.Itoa(1150719794853716028) { + 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 = fmt.Sprintf( + "## Project info:\n🔑 key: %s\n📂 folder: %s\n👾 project git: %s\n🚀 build git: %s\n\nErrors: %v", + resp.Project.ShortName, + resp.Project.Cloud, + resp.Project.ProjectGit, + resp.Project.BuildGit, + resp.Message, + ) + } + + // Sending result: + _, err = s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ + Content: result, + }) + + if err != nil { + s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ + Content: fmt.Sprintf("Something went wrong: %v", err), + }) + return + } + }, + } +} diff --git a/discord/handler/handle_git.go b/discord/handler/handle_git.go new file mode 100644 index 0000000..5816b9f --- /dev/null +++ b/discord/handler/handle_git.go @@ -0,0 +1,131 @@ +package handler + +import ( + "context" + "fmt" + "log" + "strconv" + "ticket-pimp/internal/controller" + + "github.com/bwmarrin/discordgo" +) + +func (h *router) CreateRepoHandler(repoNameMinLength int) route { + const ( + repoType = "repo_type" + projectRepo = "project_repo" + buildRepo = "build_repo" + nameOption = "repo_name" + ) + + return route{ + Command: discordgo.ApplicationCommand{ + Name: "repo", + Description: "Command for repository creation", + 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: func(s *discordgo.Session, i *discordgo.InteractionCreate) { + // Моментальный ответ для избежания столкновения с протуханием токена + initialResponse := discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Flags: discordgo.MessageFlagsEphemeral, + Content: "Cooking your query..", + }, + } + + s.InteractionRespond(i.Interaction, &initialResponse) + + // Определение переменной для ответа + var result string = "unexpected result" + + // Access options in the order provided by the user. + options := i.ApplicationCommandData().Options + + // Or convert the slice into a map + optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options)) + for _, opt := range options { + optionMap[opt.Name] = opt + } + + // Creating request: + var req controller.GitRequest + name, insertedValueNotNil := optionMap[nameOption] + isBuild := optionMap[repoType] + switch isBuild.StringValue() { + case buildRepo: + req.IsBuildGit = true + case projectRepo: + req.IsBuildGit = false + } + dchan, err := s.Channel(i.ChannelID) + + if err != nil { + log.Printf("error while identifying channel: %v", err) + } else { + + if dchan.ParentID == strconv.Itoa(1150719794853716028) { + req.ChannelID = dchan.ID + if insertedValueNotNil { + req.InsertedName = name.StringValue() + } + } else { + req.ChannelID = "" + if insertedValueNotNil { + req.InsertedName = name.StringValue() + } + } + } + + // Making request: + resp := h.controller.CreateGit(context.TODO(), req) + if resp.Project == nil { + result = resp.Message.Error() + } else { + result = fmt.Sprintf( + "## Project info:\n🔑 key: %s\n📂 folder: %s\n👾 project git: %s\n🚀 build git: %s\n\nErrors: %v", + resp.Project.ShortName, + resp.Project.Cloud, + resp.Project.ProjectGit, + resp.Project.BuildGit, + resp.Message, + ) + } + // Sending result: + _, err = s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ + Content: result, + }) + + if err != nil { + s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ + Content: fmt.Sprintf("Something went wrong: %v", err), + }) + return + } + }, + } +} diff --git a/discord/handler/handle_ping.go b/discord/handler/handle_ping.go new file mode 100644 index 0000000..b44b88c --- /dev/null +++ b/discord/handler/handle_ping.go @@ -0,0 +1,30 @@ +package handler + +import ( + "log" + + "github.com/bwmarrin/discordgo" +) + +func (h *router) Ping() route { + return route{ + Command: discordgo.ApplicationCommand{ + Name: "ping", + Description: "pongs in a reply", + }, + Handler: func(s *discordgo.Session, i *discordgo.InteractionCreate) { + log.Println("ok, I'm here..") + + err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "`pong`", + Title: "Pong reply", + }, + }) + if err != nil { + log.Println(err) + } + }, + } +} diff --git a/discord/handler/handle_ticket.go b/discord/handler/handle_ticket.go new file mode 100644 index 0000000..1d1d0f6 --- /dev/null +++ b/discord/handler/handle_ticket.go @@ -0,0 +1,78 @@ +package handler + +import ( + "context" + "fmt" + "log" + "ticket-pimp/internal/domain" + + "github.com/bwmarrin/discordgo" +) + +func (h *router) CreateTicketHandler(repoNameMinLength int) route { + return route{ + 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: func(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 + } + + if option, ok := optionMap["project_name"]; ok { + dchan, err := s.GuildChannelCreate(i.GuildID, option.StringValue(), discordgo.ChannelTypeGuildText) + if err != nil { + result = fmt.Sprintf("chan creation problem: %v\n", err) + } else { + p, err := h.controller.ProjectCreate(context.TODO(), domain.Project{ + ChannelID: dchan.ID, + }) + if err != nil { + result = fmt.Sprintf("unable to create project: %v\n", err) + } else { + edit := discordgo.ChannelEdit{ + Name: p.ShortName, + ParentID: "1150719794853716028", + } + + dchan, err = s.ChannelEdit(dchan.ID, &edit) + if err != nil { + result = fmt.Sprintf("channel created, but unable to edit: %v\n", err) + + } else { + _, err = s.ChannelMessageSend(dchan.ID, "Hello!") + if err != nil { + log.Printf("message send problem: %v\n", err) + } + result = dchan.ID + } + } + } + } + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + // Ignore type for now, they will be discussed in "responses" + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: result, + }, + }) + }, + } +} diff --git a/discord/handler/handler.go b/discord/handler/handler.go index b954eee..b1421c8 100644 --- a/discord/handler/handler.go +++ b/discord/handler/handler.go @@ -1,11 +1,7 @@ package handler import ( - "context" - "fmt" - "log" "ticket-pimp/internal/controller" - "ticket-pimp/internal/domain" "github.com/bwmarrin/discordgo" ) @@ -21,10 +17,10 @@ func InitRouter(wc controller.WorkflowController) *router { var r router r.Routes = append( r.Routes, - // r.CreateRepoHandler(3), + r.CreateRepoHandler(3), r.CreateFolderHandler(3), r.Ping(), - // r.CreateTicketHandler(3), + r.CreateTicketHandler(3), ) r.controller = wc @@ -35,284 +31,3 @@ type route struct { Command discordgo.ApplicationCommand Handler func(s *discordgo.Session, i *discordgo.InteractionCreate) } - -func (h *router) Ping() route { - return route{ - Command: discordgo.ApplicationCommand{ - Name: "ping", - Description: "pongs in a reply", - }, - Handler: func(s *discordgo.Session, i *discordgo.InteractionCreate) { - log.Println("ok, I'm here..") - - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseDeferredMessageUpdate, - Data: &discordgo.InteractionResponseData{ - Content: "`pong`", - Title: "Pong reply", - }, - }) - }, - } -} - -func (h *router) CreateFolderHandler(nameMinLenght int) route { - const ( - nameOption string = "folder_name" - ) - return route{ - 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: func(s *discordgo.Session, i *discordgo.InteractionCreate) { - var result string - - resp := discordgo.InteractionResponse{ - - Type: discordgo.InteractionResponseUpdateMessage, - Data: &discordgo.InteractionResponseData{ - Content: "Folder is going to be created..", - }, - } - err := s.InteractionRespond(i.Interaction, &resp) - if err != nil { - log.Println(err) - return - } - - options := i.ApplicationCommandData().Options - - optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options)) - for _, opt := range options { - optionMap[opt.Name] = opt - } - - var str string = "" - - project, err := h.controller.GetProjectByChannelID(context.TODO(), i.ChannelID) - if err != nil { - result = fmt.Sprintf("unable to retrieve project from db, error: %v", err) - } else { - if project == nil { - if option, ok := optionMap[nameOption]; ok { - str = option.StringValue() - } else { - str = "" - } - } else { - str = project.ShortName - } - - if str == "" { - result = "Ты, либо в проекте директорию создавай, либо имя напиши, блет!" - } else { - - f, err := h.controller.ICloud.CreateFolder(str) - if err != nil { - result = fmt.Sprintf("error while cloud folder creation: %v", err) - } else { - result = fmt.Sprint(f.PrivateURL) - } - } - } - - // resp = discordgo.InteractionResponse{ - // // Ignore type for now, they will be discussed in "responses" - // Type: discordgo.InteractionResponseUpdateMessage, - // Data: &discordgo.InteractionResponseData{ - // Content: result, - // Title: "📂 Folder was created", - // }, - // } - - webhookEdit := discordgo.WebhookEdit{ - Content: &result, - } - s.InteractionResponseEdit(i.Interaction, &webhookEdit) - - // discerr := s.InteractionRespond(i.Interaction, &resp) - // if discerr != nil { - // log.Println(discerr) - // } - }, - } -} - -func (h *router) CreateRepoHandler(repoNameMinLength int) route { - const ( - projectRepo = "project_repo" - buildRepo = "build_repo" - ) - - return route{ - Command: discordgo.ApplicationCommand{ - Name: "repo", - Description: "Command for repository creation", - Options: []*discordgo.ApplicationCommandOption{ - { - Type: discordgo.ApplicationCommandOptionString, - Name: "repo_type", - 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: "repo_name", - Description: "Type the repository's name", - Required: false, - MinLength: &repoNameMinLength, - }, - }, - }, - Handler: func(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 - } - - var str string = "" - - project, err := h.controller.GetProjectByChannelID(context.TODO(), i.ChannelID) - if err != nil { - result = fmt.Sprintf("unable to retrieve project from db, error: %v", err) - } else { - var suffix string - if option, ok := optionMap["repo_type"]; ok { - switch option.Value { - case projectRepo: - suffix = "" - case buildRepo: - suffix = "-build" - } - } - - if project == nil { - if option, ok := optionMap["repo_name"]; ok { - str = option.StringValue() - } else { - str = "" - } - } else { - str = project.ShortName - } - - if str == "" { - result = "Ты, либо в проекте репо создавай, либо имя напиши, блет!" - } else { - str = str + suffix - - // var g *domain.Git - - g, err := h.controller.IGit.CreateRepo(str) - if err != nil { - result = fmt.Sprintf("error while repo creation: %v", err) - } else { - result = "🚀 " + g.HtmlUrl + " was created" - } - } - } - - resp := &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: result, - }, - } - - s.InteractionRespond(i.Interaction, resp) - }, - } -} - -func (h *router) CreateTicketHandler(repoNameMinLength int) route { - return route{ - 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: func(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 - } - - if option, ok := optionMap["project_name"]; ok { - dchan, err := s.GuildChannelCreate(i.GuildID, option.StringValue(), discordgo.ChannelTypeGuildText) - if err != nil { - result = fmt.Sprintf("chan creation problem: %v\n", err) - } else { - p, err := h.controller.ProjectCreate(context.TODO(), domain.Project{ - ChannelID: dchan.ID, - }) - if err != nil { - result = fmt.Sprintf("unable to create project: %v\n", err) - } else { - edit := discordgo.ChannelEdit{ - Name: p.ShortName, - ParentID: "1150719794853716028", - } - - dchan, err = s.ChannelEdit(dchan.ID, &edit) - if err != nil { - result = fmt.Sprintf("channel created, but unable to edit: %v\n", err) - - } else { - _, err = s.ChannelMessageSend(dchan.ID, "Hello!") - if err != nil { - log.Printf("message send problem: %v\n", err) - } - result = dchan.ID - } - } - } - } - - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - // Ignore type for now, they will be discussed in "responses" - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: result, - }, - }) - }, - } -} diff --git a/internal/controller/control_folder.go b/internal/controller/control_folder.go new file mode 100644 index 0000000..7a8f61b --- /dev/null +++ b/internal/controller/control_folder.go @@ -0,0 +1,94 @@ +package controller + +import ( + "context" + "errors" + "fmt" + "log" + "ticket-pimp/internal/domain" + "ticket-pimp/internal/storage/db" + "time" + + "github.com/jackc/pgx/v5/pgtype" +) + +type FolderRequest struct { + ChannelID string + InsertedName string +} + +func (wc *WorkflowController) CreateFolder(ctx context.Context, req FolderRequest) *ProjectResponse { + + project, err := wc.GetProjectByChannelID(ctx, req.ChannelID) + if err != nil { + return &ProjectResponse{ + Project: nil, + Message: fmt.Errorf("unable to retrieve project from db: %v", err), + } + } + + var ( + name string + dbticket db.Ticket + result ProjectResponse + ) + + if project != nil { + switch { + case project.Cloud != "": + return &ProjectResponse{ + Project: project, + Message: nil, + } + case project.ShortName != "": + name = project.ShortName + case req.InsertedName != "": + name = req.InsertedName + } + response := wc.ICloud.CreateFolder(name) + + dbticket, err = wc.q.UpdateTicketFolder( + ctx, + db.UpdateTicketFolderParams{ + Folder: pgtype.Text{String: response.Folder.PrivateURL, Valid: true}, + UpdatedAt: pgtype.Timestamptz{Time: time.Now(), InfinityModifier: 0, Valid: true}, + Channelid: pgtype.Text{String: req.ChannelID, Valid: true}, + }) + if err != nil { + log.Printf("unable to scan row from db: %v", err) + return &ProjectResponse{ + Project: project, + Message: fmt.Errorf("unable to update project: %v", err), + } + } + + result = ProjectResponse{ + Project: &domain.Project{ + ID: string(dbticket.ID), + ShortName: dbticket.Key.String, + ChannelID: dbticket.Channelid.String, + ProjectGit: dbticket.ProjectGit.String, + BuildGit: dbticket.BuildGit.String, + Cloud: dbticket.Folder.String, + }, + Message: response.ErrMessage, + } + } else { + if req.InsertedName != "" { + response := wc.ICloud.CreateFolder(req.InsertedName) + result = ProjectResponse{ + Project: &domain.Project{ + Cloud: response.Folder.PrivateURL, + }, + Message: response.ErrMessage, + } + } else { + return &ProjectResponse{ + Project: nil, + Message: errors.New("передано пустое имя"), + } + } + } + + return &result +} diff --git a/internal/controller/control_git.go b/internal/controller/control_git.go new file mode 100644 index 0000000..c9e1f0d --- /dev/null +++ b/internal/controller/control_git.go @@ -0,0 +1,168 @@ +package controller + +import ( + "context" + "errors" + "fmt" + "log" + "ticket-pimp/internal/domain" + "ticket-pimp/internal/storage/db" + "time" + + "github.com/jackc/pgx/v5/pgtype" +) + +type GitRequest struct { + ChannelID string + InsertedName string + IsBuildGit bool +} + +func (wc *WorkflowController) createGitForExistingProject(ctx context.Context, req GitRequest, p *domain.Project) *ProjectResponse { + var ( + name string = "" + dbticket db.Ticket + ) + switch { + case p.ShortName != "": + name = p.ShortName + if req.IsBuildGit { + name += "-build" + } + case req.InsertedName != "": + name = req.InsertedName + if req.IsBuildGit { + name += "-build" + } + } + + // response := wc.ICloud.CreateFolder(name) + git, err := wc.IGit.CreateRepo(name) + if err != nil { + return &ProjectResponse{ + Project: p, + Message: fmt.Errorf("unable to create git w/ an error: %v", err), + } + } + + if req.IsBuildGit { + dbticket, err = wc.q.UpdateTicketBuildGit( + ctx, + db.UpdateTicketBuildGitParams{ + BuildGit: pgtype.Text{String: git.HtmlUrl, Valid: true}, + UpdatedAt: pgtype.Timestamptz{Time: time.Now(), InfinityModifier: 0, Valid: true}, + Channelid: pgtype.Text{String: req.ChannelID, Valid: true}, + }) + if err != nil { + log.Printf("unable to scan row from db: %v", err) + return &ProjectResponse{ + Project: p, + Message: fmt.Errorf("unable to update project: %v", err), + } + } + } else { + dbticket, err = wc.q.UpdateTicketProjectGit( + ctx, + db.UpdateTicketProjectGitParams{ + ProjectGit: pgtype.Text{String: git.HtmlUrl, Valid: true}, + UpdatedAt: pgtype.Timestamptz{Time: time.Now(), InfinityModifier: 0, Valid: true}, + Channelid: pgtype.Text{String: req.ChannelID, Valid: true}, + }, + ) + if err != nil { + log.Printf("unable to scan row from db: %v", err) + return &ProjectResponse{ + Project: p, + Message: fmt.Errorf("unable to update project: %v", err), + } + } + } + + return &ProjectResponse{ + Project: &domain.Project{ + ID: string(dbticket.ID), + ShortName: dbticket.Key.String, + ChannelID: dbticket.Channelid.String, + ProjectGit: dbticket.ProjectGit.String, + BuildGit: dbticket.BuildGit.String, + Cloud: dbticket.Folder.String, + }, + Message: err, + } +} + +func (wc *WorkflowController) CreateGit(ctx context.Context, req GitRequest) *ProjectResponse { + + // [ ] Валидация на пустой канал? + p, err := wc.GetProjectByChannelID(ctx, req.ChannelID) + if err != nil { + return &ProjectResponse{ + Project: nil, + Message: fmt.Errorf("unable to retrieve project from db: %v", err), + } + } + + // var ( + // name string + // dbticket db.Ticket + // result ProjectResponse + // ) + + switch { + case p != nil && req.IsBuildGit: + if p.BuildGit != "" { + return &ProjectResponse{ + Project: p, + Message: errors.New("build git already exists"), + } + } else { + // [x] + return wc.createGitForExistingProject(ctx, req, p) + } + case p != nil && !req.IsBuildGit: + if p.ProjectGit != "" { + return &ProjectResponse{ + Project: p, + Message: errors.New("project git already exists"), + } + } else { + // [x] + return wc.createGitForExistingProject(ctx, req, p) + } + default: + if req.InsertedName != "" { + + if req.IsBuildGit { + req.InsertedName += "-build" + } + + git, err := wc.IGit.CreateRepo(req.InsertedName) + if err != nil || git == nil { + return &ProjectResponse{ + Project: nil, + Message: err, + } + } else { + if req.IsBuildGit { + return &ProjectResponse{ + Project: &domain.Project{ + BuildGit: git.HtmlUrl, + }, + Message: err, + } + } + return &ProjectResponse{ + Project: &domain.Project{ + ProjectGit: git.HtmlUrl, + }, + Message: err, + } + } + } else { + return &ProjectResponse{ + Project: nil, + Message: errors.New("передано пустое имя"), + } + } + } +} diff --git a/internal/controller/project.go b/internal/controller/control_project.go similarity index 86% rename from internal/controller/project.go rename to internal/controller/control_project.go index ee68bfe..54094f3 100644 --- a/internal/controller/project.go +++ b/internal/controller/control_project.go @@ -74,10 +74,13 @@ func (wc *WorkflowController) GetProjectByChannelID(ctx context.Context, id stri return nil, err } else { proj = domain.Project{ - ID: string(dbTicket.ID), - ShortName: dbTicket.Key.String, - Name: dbTicket.Key.String, - ChannelID: dbTicket.Channelid.String, + ID: string(dbTicket.ID), + ShortName: dbTicket.Key.String, + Name: dbTicket.Key.String, + ChannelID: dbTicket.Channelid.String, + ProjectGit: dbTicket.ProjectGit.String, + BuildGit: dbTicket.BuildGit.String, + Cloud: dbTicket.Folder.String, } } return &proj, nil diff --git a/internal/controller/control_workflow.go b/internal/controller/control_workflow.go new file mode 100644 index 0000000..c24fdbb --- /dev/null +++ b/internal/controller/control_workflow.go @@ -0,0 +1,84 @@ +package controller + +import ( + "context" + "fmt" + "log" + "strings" + "sync" + "ticket-pimp/internal/domain" + "ticket-pimp/internal/storage/db" + + "github.com/jackc/pgx/v5/pgtype" +) + +func (wc *WorkflowController) FullProjectInit(name, key, id string) (string, error) { + + appKey := fmt.Sprintf("%s-%s", key, id) + + var ( + git, gitBuild *domain.Git + cloud *domain.Folder + ) + + var wg sync.WaitGroup + wg.Add(3) + + go func(ref **domain.Git) { + defer wg.Done() + *ref, _ = wc.IGit.CreateRepo(appKey) + }(&git) + + go func(ref **domain.Git) { + defer wg.Done() + *ref, _ = wc.IGit.CreateRepo(appKey + "-build") + }(&gitBuild) + + go func(ref **domain.Folder) { + defer wg.Done() + + *ref = wc.ICloud.CreateFolder(appKey).Folder + }(&cloud) + + wg.Wait() + + var gitResult, gitBuildResult, cloudResult string + + if git == nil { + gitResult = "cannot create git" + } else { + gitResult = git.HtmlUrl + } + + if gitBuild == nil { + gitBuildResult = "cannot create git" + } else { + gitBuildResult = fmt.Sprintf("ssh://%s/%s.git", gitBuild.SshUrl, gitBuild.FullName) + } + + if cloud == nil { + cloudResult = "cannot create folder" + } else { + cloudResult = cloud.PrivateURL + } + ctx := context.TODO() + + insertedTicket, err := wc.q.CreateTicket(ctx, db.CreateTicketParams{ + Key: pgtype.Text{String: appKey, Valid: true}, + Channelid: pgtype.Text{}, + }) + if err != nil { + log.Fatal(err) + } + log.Print(insertedTicket) + + wc.ICoda.CreateApp(domain.CodaApplication{ + ID: appKey, + Summary: strings.TrimSpace(name), + Git: gitResult, + GitBuild: gitBuildResult, + Folder: cloudResult, + }) + + return appKey, nil +} diff --git a/internal/controller/controller.go b/internal/controller/controller.go index 93a86d1..c65d97d 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -1,16 +1,10 @@ package controller import ( - "context" - "fmt" - "log" - "strings" - "sync" "ticket-pimp/internal/domain" "ticket-pimp/internal/services" "ticket-pimp/internal/storage/db" - "github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgxpool" ) @@ -37,72 +31,7 @@ func NewWorkflowController( } } -func (wc *WorkflowController) FullProjectInit(name, key, id string) (string, error) { - - appKey := fmt.Sprintf("%s-%s", key, id) - - var ( - git, gitBuild *domain.Git - cloud *domain.Folder - ) - - var wg sync.WaitGroup - wg.Add(3) - - go func(ref **domain.Git) { - defer wg.Done() - *ref, _ = wc.IGit.CreateRepo(appKey) - }(&git) - - go func(ref **domain.Git) { - defer wg.Done() - *ref, _ = wc.IGit.CreateRepo(appKey + "-build") - }(&gitBuild) - - go func(ref **domain.Folder) { - defer wg.Done() - *ref, _ = wc.ICloud.CreateFolder(appKey) - }(&cloud) - - wg.Wait() - - var gitResult, gitBuildResult, cloudResult string - - if git == nil { - gitResult = "cannot create git" - } else { - gitResult = git.HtmlUrl - } - - if gitBuild == nil { - gitBuildResult = "cannot create git" - } else { - gitBuildResult = fmt.Sprintf("ssh://%s/%s.git", gitBuild.SshUrl, gitBuild.FullName) - } - - if cloud == nil { - cloudResult = "cannot create folder" - } else { - cloudResult = cloud.PrivateURL - } - ctx := context.TODO() - - insertedTicket, err := wc.q.CreateTicket(ctx, db.CreateTicketParams{ - Key: pgtype.Text{String: appKey, Valid: true}, - Channelid: pgtype.Text{}, - }) - if err != nil { - log.Fatal(err) - } - log.Print(insertedTicket) - - wc.ICoda.CreateApp(domain.CodaApplication{ - ID: appKey, - Summary: strings.TrimSpace(name), - Git: gitResult, - GitBuild: gitBuildResult, - Folder: cloudResult, - }) - - return appKey, nil +type ProjectResponse struct { + Project *domain.Project + Message error } diff --git a/internal/controller/tickets_config.go b/internal/controller/tickets_config.go deleted file mode 100644 index b4a5392..0000000 --- a/internal/controller/tickets_config.go +++ /dev/null @@ -1,22 +0,0 @@ -package controller - -import ( - "context" - "ticket-pimp/internal/domain" - db "ticket-pimp/internal/storage/db" -) - -type IConfigController interface { - Get(context.Context) (domain.ApplicationConfig, error) - Update(context.Context) (domain.ApplicationConfig, error) -} - -type AppConfig struct { - db *db.Queries -} - -func NewAppConfig(db *db.Queries) AppConfig { - return AppConfig{ - db: db, - } -} diff --git a/internal/domain/models.go b/internal/domain/models.go index 96f5a99..8dff715 100644 --- a/internal/domain/models.go +++ b/internal/domain/models.go @@ -87,6 +87,10 @@ type Project struct { ShortName string `json:"shortName"` Name string `json:"name"` ChannelID string `json:"channel_id"` + + ProjectGit string `json:"project_git"` + BuildGit string `json:"build_git"` + Cloud string `json:"cloud"` } type ProjectID struct { diff --git a/internal/helpers/error_message.go b/internal/helpers/error_message.go new file mode 100644 index 0000000..970e15c --- /dev/null +++ b/internal/helpers/error_message.go @@ -0,0 +1,5 @@ +package helpers + +type ErrorMessage struct { + Message string `json:"message"` +} diff --git a/internal/helpers/helpers.go b/internal/helpers/helpers.go index 8a12bfd..9c600e0 100644 --- a/internal/helpers/helpers.go +++ b/internal/helpers/helpers.go @@ -7,7 +7,7 @@ import ( "strings" ) -func GitNaming(input string) string { +func ValidNaming(input string) string { // Remove leading and trailing whitespace input = strings.TrimSpace(input) diff --git a/internal/helpers/helpers_test.go b/internal/helpers/helpers_test.go index 3e877a4..53e8112 100644 --- a/internal/helpers/helpers_test.go +++ b/internal/helpers/helpers_test.go @@ -18,7 +18,7 @@ var tests = []test{ func TestGitNaming(t *testing.T) { for _, test := range tests { - if output := GitNaming(test.arg); output != test.expected { + if output := ValidNaming(test.arg); output != test.expected { t.Errorf("Output %q not equal to expected %q", output, test.expected) } } diff --git a/internal/services/cloud.go b/internal/services/cloud.go index 8c61a6f..cd37732 100644 --- a/internal/services/cloud.go +++ b/internal/services/cloud.go @@ -1,8 +1,11 @@ package services import ( + "errors" "fmt" + "log" "strconv" + "strings" "ticket-pimp/internal/domain" "ticket-pimp/internal/helpers" "time" @@ -14,7 +17,7 @@ type Cloud struct { } type ICloud interface { - CreateFolder(name string) (*domain.Folder, error) + CreateFolder(name string) Response } func NewCloud(conf domain.CloudConfig) *Cloud { @@ -32,40 +35,76 @@ func NewCloud(conf domain.CloudConfig) *Cloud { } } -func (c *Cloud) CreateFolder(name string) (*domain.Folder, error) { +type Response struct { + Folder *domain.Folder + ErrMessage error +} + +func (c *Cloud) CreateFolder(name string) Response { + var R Response + rootDir := c.Config.RootDir user := c.Config.User davPath := "/remote.php/dav/files/" parentPath := "/apps/files/?dir=" - name = helpers.GitNaming(name) + name = helpers.ValidNaming(name) - cloud := domain.Folder{ + R.Folder = &domain.Folder{ Title: name, PrivateURL: "", } + // cloud := domain.Folder{ + // Title: name, + // PrivateURL: "", + // } + requestPath := davPath + user + rootDir + name - cloud.PathTo = parentPath + rootDir + name + R.Folder.PathTo = parentPath + rootDir + name - resp, _ := c.R(). + var errMessage helpers.ErrorMessage + + resp, err := c.R(). + SetErrorResult(&errMessage). Send("MKCOL", requestPath) + if err != nil { // Error handling. + log.Println("error:", err) + + // Херовая обработка ошибки: + // error while cloud folder creation: bad response, raw content: + // + // + // Sabre\DAV\Exception\MethodNotAllowed + // The resource you tried to create already exists + // + if strings.Contains(err.Error(), "already exists") { + R.Folder.PrivateURL = c.BaseURL + R.Folder.PathTo + R.ErrMessage = errors.New("guess, that folder already exists") + // Try to set short URL to the d entity + if err := c.setPrivateURL(requestPath, R.Folder); err != nil { + R.ErrMessage = err + return R + } + return R + } + return R + } + if resp.IsSuccessState() { // Set stupid URL to the d entity - cloud.PrivateURL = c.BaseURL + cloud.PathTo + R.Folder.PrivateURL = c.BaseURL + R.Folder.PathTo // Try to set short URL to the d entity - if err := c.setPrivateURL(requestPath, &cloud); err != nil { - return &cloud, err + if err := c.setPrivateURL(requestPath, R.Folder); err != nil { + return R } - } else { - fmt.Println(resp.Status) } - return &cloud, nil + return R } func (c *Cloud) setPrivateURL(requestPath string, cloud *domain.Folder) error { diff --git a/internal/services/git.go b/internal/services/git.go index 01388d6..0f1c1a7 100644 --- a/internal/services/git.go +++ b/internal/services/git.go @@ -55,7 +55,7 @@ type gitCreateRequest struct { } func (gb *Git) newRepo(name string) (*domain.Git, error) { - name = helpers.GitNaming(name) + name = helpers.ValidNaming(name) payload := gitCreateRequest{ Name: name, diff --git a/internal/storage/db/queries.sql.go b/internal/storage/db/queries.sql.go new file mode 100644 index 0000000..a425106 --- /dev/null +++ b/internal/storage/db/queries.sql.go @@ -0,0 +1,307 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 +// source: queries.sql + +package db + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const createTicket = `-- name: CreateTicket :one +INSERT INTO tickets ( + key, channelID +) VALUES ( + $1, $2 + ) + RETURNING id, key, channelid, project_git, build_git, folder, created_at, deleted_at, updated_at +` + +type CreateTicketParams struct { + Key pgtype.Text + Channelid pgtype.Text +} + +func (q *Queries) CreateTicket(ctx context.Context, arg CreateTicketParams) (Ticket, error) { + row := q.db.QueryRow(ctx, createTicket, arg.Key, arg.Channelid) + var i Ticket + err := row.Scan( + &i.ID, + &i.Key, + &i.Channelid, + &i.ProjectGit, + &i.BuildGit, + &i.Folder, + &i.CreatedAt, + &i.DeletedAt, + &i.UpdatedAt, + ) + return i, err +} + +const deleteTicketByID = `-- name: DeleteTicketByID :exec +UPDATE tickets SET deleted_at = current_timestamp WHERE id = $1 +` + +func (q *Queries) DeleteTicketByID(ctx context.Context, id int32) error { + _, err := q.db.Exec(ctx, deleteTicketByID, id) + return err +} + +const deleteTicketByKey = `-- name: DeleteTicketByKey :exec +UPDATE tickets SET deleted_at = current_timestamp WHERE key = $1 +` + +func (q *Queries) DeleteTicketByKey(ctx context.Context, key pgtype.Text) error { + _, err := q.db.Exec(ctx, deleteTicketByKey, key) + return err +} + +const getConfig = `-- name: GetConfig :one +SELECT ticket_key, ticket_id +FROM appconfig +` + +func (q *Queries) GetConfig(ctx context.Context) (Appconfig, error) { + row := q.db.QueryRow(ctx, getConfig) + var i Appconfig + err := row.Scan(&i.TicketKey, &i.TicketID) + return i, err +} + +const getTicketByChannelID = `-- name: GetTicketByChannelID :one +SELECT id, key, channelid, project_git, build_git, folder, created_at, deleted_at, updated_at FROM tickets WHERE channelID = $1 +` + +func (q *Queries) GetTicketByChannelID(ctx context.Context, channelid pgtype.Text) (Ticket, error) { + row := q.db.QueryRow(ctx, getTicketByChannelID, channelid) + var i Ticket + err := row.Scan( + &i.ID, + &i.Key, + &i.Channelid, + &i.ProjectGit, + &i.BuildGit, + &i.Folder, + &i.CreatedAt, + &i.DeletedAt, + &i.UpdatedAt, + ) + return i, err +} + +const getTicketByID = `-- name: GetTicketByID :one +SELECT id, key, channelid, project_git, build_git, folder, created_at, deleted_at, updated_at FROM tickets WHERE id = $1 +` + +func (q *Queries) GetTicketByID(ctx context.Context, id int32) (Ticket, error) { + row := q.db.QueryRow(ctx, getTicketByID, id) + var i Ticket + err := row.Scan( + &i.ID, + &i.Key, + &i.Channelid, + &i.ProjectGit, + &i.BuildGit, + &i.Folder, + &i.CreatedAt, + &i.DeletedAt, + &i.UpdatedAt, + ) + return i, err +} + +const listTickets = `-- name: ListTickets :many +SELECT id, key, channelid, project_git, build_git, folder, created_at, deleted_at, updated_at FROM tickets WHERE deleted_at IS NULL +` + +func (q *Queries) ListTickets(ctx context.Context) ([]Ticket, error) { + rows, err := q.db.Query(ctx, listTickets) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Ticket + for rows.Next() { + var i Ticket + if err := rows.Scan( + &i.ID, + &i.Key, + &i.Channelid, + &i.ProjectGit, + &i.BuildGit, + &i.Folder, + &i.CreatedAt, + &i.DeletedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listTicketsWithDeleted = `-- name: ListTicketsWithDeleted :many +SELECT id, key, channelid, project_git, build_git, folder, created_at, deleted_at, updated_at FROM tickets +` + +func (q *Queries) ListTicketsWithDeleted(ctx context.Context) ([]Ticket, error) { + rows, err := q.db.Query(ctx, listTicketsWithDeleted) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Ticket + for rows.Next() { + var i Ticket + if err := rows.Scan( + &i.ID, + &i.Key, + &i.Channelid, + &i.ProjectGit, + &i.BuildGit, + &i.Folder, + &i.CreatedAt, + &i.DeletedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const setNewConfig = `-- name: SetNewConfig :one +UPDATE appconfig +SET ticket_id = ticket_id + 1 +RETURNING ticket_key, ticket_id +` + +func (q *Queries) SetNewConfig(ctx context.Context) (Appconfig, error) { + row := q.db.QueryRow(ctx, setNewConfig) + var i Appconfig + err := row.Scan(&i.TicketKey, &i.TicketID) + return i, err +} + +const updateTicketBuildGit = `-- name: UpdateTicketBuildGit :one +UPDATE tickets +SET build_git = $1, updated_at = $2 +WHERE channelID = $3 +RETURNING id, key, channelid, project_git, build_git, folder, created_at, deleted_at, updated_at +` + +type UpdateTicketBuildGitParams struct { + BuildGit pgtype.Text + UpdatedAt pgtype.Timestamptz + Channelid pgtype.Text +} + +func (q *Queries) UpdateTicketBuildGit(ctx context.Context, arg UpdateTicketBuildGitParams) (Ticket, error) { + row := q.db.QueryRow(ctx, updateTicketBuildGit, arg.BuildGit, arg.UpdatedAt, arg.Channelid) + var i Ticket + err := row.Scan( + &i.ID, + &i.Key, + &i.Channelid, + &i.ProjectGit, + &i.BuildGit, + &i.Folder, + &i.CreatedAt, + &i.DeletedAt, + &i.UpdatedAt, + ) + return i, err +} + +const updateTicketByID = `-- name: UpdateTicketByID :exec +UPDATE tickets SET project_git = $1, build_git = $2, folder = $3 WHERE id = $4 +` + +type UpdateTicketByIDParams struct { + ProjectGit pgtype.Text + BuildGit pgtype.Text + Folder pgtype.Text + ID int32 +} + +func (q *Queries) UpdateTicketByID(ctx context.Context, arg UpdateTicketByIDParams) error { + _, err := q.db.Exec(ctx, updateTicketByID, + arg.ProjectGit, + arg.BuildGit, + arg.Folder, + arg.ID, + ) + return err +} + +const updateTicketFolder = `-- name: UpdateTicketFolder :one +UPDATE tickets +SET folder = $1, updated_at = $2 +WHERE channelID = $3 +RETURNING id, key, channelid, project_git, build_git, folder, created_at, deleted_at, updated_at +` + +type UpdateTicketFolderParams struct { + Folder pgtype.Text + UpdatedAt pgtype.Timestamptz + Channelid pgtype.Text +} + +func (q *Queries) UpdateTicketFolder(ctx context.Context, arg UpdateTicketFolderParams) (Ticket, error) { + row := q.db.QueryRow(ctx, updateTicketFolder, arg.Folder, arg.UpdatedAt, arg.Channelid) + var i Ticket + err := row.Scan( + &i.ID, + &i.Key, + &i.Channelid, + &i.ProjectGit, + &i.BuildGit, + &i.Folder, + &i.CreatedAt, + &i.DeletedAt, + &i.UpdatedAt, + ) + return i, err +} + +const updateTicketProjectGit = `-- name: UpdateTicketProjectGit :one +UPDATE tickets +SET project_git = $1, updated_at = $2 +WHERE channelID = $3 +RETURNING id, key, channelid, project_git, build_git, folder, created_at, deleted_at, updated_at +` + +type UpdateTicketProjectGitParams struct { + ProjectGit pgtype.Text + UpdatedAt pgtype.Timestamptz + Channelid pgtype.Text +} + +func (q *Queries) UpdateTicketProjectGit(ctx context.Context, arg UpdateTicketProjectGitParams) (Ticket, error) { + row := q.db.QueryRow(ctx, updateTicketProjectGit, arg.ProjectGit, arg.UpdatedAt, arg.Channelid) + var i Ticket + err := row.Scan( + &i.ID, + &i.Key, + &i.Channelid, + &i.ProjectGit, + &i.BuildGit, + &i.Folder, + &i.CreatedAt, + &i.DeletedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/internal/storage/sqlc/queries.sql b/internal/storage/sqlc/queries.sql index b9911aa..d5320ee 100644 --- a/internal/storage/sqlc/queries.sql +++ b/internal/storage/sqlc/queries.sql @@ -15,6 +15,24 @@ INSERT INTO tickets ( ) RETURNING *; +-- name: UpdateTicketFolder :one +UPDATE tickets +SET folder = $1, updated_at = $2 +WHERE channelID = $3 +RETURNING *; + +-- name: UpdateTicketProjectGit :one +UPDATE tickets +SET project_git = $1, updated_at = $2 +WHERE channelID = $3 +RETURNING *; + +-- name: UpdateTicketBuildGit :one +UPDATE tickets +SET build_git = $1, updated_at = $2 +WHERE channelID = $3 +RETURNING *; + -- name: ListTickets :many SELECT * FROM tickets WHERE deleted_at IS NULL; diff --git a/telegram/handler/handle_folder.go b/telegram/handler/handle_folder.go index 460d7af..fa88377 100644 --- a/telegram/handler/handle_folder.go +++ b/telegram/handler/handle_folder.go @@ -17,10 +17,10 @@ func (h *Handler) NewFolderHandler(ctx context.Context, mu *tgb.MessageUpdate) e return errors.New("empty command provided") } - cloud, err := h.cloud.CreateFolder(str) + resp := h.cloud.CreateFolder(str) - if err != nil { - answer := errorAnswer(err.Error()) + if resp.ErrMessage != nil { + answer := errorAnswer(resp.ErrMessage.Error()) h.LogMessage(ctx, mu, answer) return mu.Answer(answer).ParseMode(tg.HTML).DoVoid(ctx) } @@ -28,7 +28,7 @@ func (h *Handler) NewFolderHandler(ctx context.Context, mu *tgb.MessageUpdate) e answer := tg.HTML.Text( tg.HTML.Line( "✨ Shiny folder", - tg.HTML.Link(cloud.Title, cloud.PrivateURL), + tg.HTML.Link(resp.Folder.Title, resp.Folder.PrivateURL), "has been created!", ), ) diff --git a/telegram/telegram.go b/telegram/telegram.go index 521012d..83f6403 100644 --- a/telegram/telegram.go +++ b/telegram/telegram.go @@ -45,6 +45,7 @@ func Run(ctx context.Context, opts TelegramOptions) error { // Message(h.NewFolderHandler, tgb.TextHasPrefix("/folder")). Message(h.FarmTaskHandler, tgb.TextHasPrefix("/task")) + log.Print("Success init. Start poller.") return tgb.NewPoller( router, client,