Merge pull request #22 from naudachu/discord-bot-integration
Discord bot integration
This commit is contained in:
commit
c881540795
|
|
@ -1,2 +1,3 @@
|
||||||
.vscode/
|
.vscode/
|
||||||
**/**/*.env
|
**/**/*.env
|
||||||
|
docker/**
|
||||||
|
|
@ -7,9 +7,9 @@ RUN apk add git
|
||||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install -ldflags '-extldflags "-static"' -tags timetzdata ./cmd/main.go
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install -ldflags '-extldflags "-static"' -tags timetzdata ./cmd/main.go
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
# the test program:
|
|
||||||
COPY --from=app-builder /go/bin/main /ticket-pimp
|
COPY --from=app-builder /go/bin/main /ticket-pimp
|
||||||
COPY --from=app-builder /go/src/ticket-pimp/cmd/prod.env /
|
COPY --from=app-builder /go/src/ticket-pimp/cmd/prod.env /
|
||||||
|
COPY --from=app-builder /go/src/ticket-pimp/internal/storage/migrate/* /internal/storage/migrate/
|
||||||
# the tls certificates:
|
# the tls certificates:
|
||||||
# NB: this pulls directly from the upstream image, which already has ca-certificates:
|
# NB: this pulls directly from the upstream image, which already has ca-certificates:
|
||||||
COPY --from=alpine:latest /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
COPY --from=alpine:latest /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mr-linch/go-tg"
|
|
||||||
"github.com/mr-linch/go-tg/tgb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *Handler) DevelopmentTaskHandler(ctx context.Context, mu *tgb.MessageUpdate) error {
|
|
||||||
|
|
||||||
str := strings.Replace(mu.Text, "/new", "", 1)
|
|
||||||
|
|
||||||
if str == "" {
|
|
||||||
return errors.New("empty command provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
issueKeyStr, err := h.workflow.Workflow(str, h.key, h.id)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
answer := errorAnswer(err.Error())
|
|
||||||
h.LogMessage(ctx, mu, answer)
|
|
||||||
return mu.Answer(answer).ParseMode(tg.HTML).DoVoid(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
i, err := strconv.Atoi(h.id)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("problem with conversion id to int")
|
|
||||||
}
|
|
||||||
h.id = strconv.Itoa(i + 1)
|
|
||||||
|
|
||||||
answer := newTicketAnswer(issueKeyStr)
|
|
||||||
h.LogMessage(ctx, mu, answer)
|
|
||||||
return mu.Answer(answer).ParseMode(tg.HTML).DoVoid(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTicketAnswer(name string) string {
|
|
||||||
return tg.HTML.Text(
|
|
||||||
tg.HTML.Line(
|
|
||||||
"🤘 Ticket ",
|
|
||||||
name,
|
|
||||||
" has been created!",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
"ticket-pimp/internal/domain"
|
|
||||||
|
|
||||||
"github.com/mr-linch/go-tg"
|
|
||||||
"github.com/mr-linch/go-tg/tgb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *Handler) FarmTaskHandler(ctx context.Context, mu *tgb.MessageUpdate) error {
|
|
||||||
|
|
||||||
msgID := mu.Message.ID
|
|
||||||
|
|
||||||
taskText := strings.TrimSpace(strings.Replace(mu.Text, "/task", "", 1))
|
|
||||||
|
|
||||||
var summaryTail string
|
|
||||||
|
|
||||||
sentances := strings.Split(taskText, "\n")
|
|
||||||
if len(sentances) < 2 {
|
|
||||||
words := strings.Split(taskText, " ")
|
|
||||||
if len(words) > 2 {
|
|
||||||
summaryTail = strings.Join(words[0:2], " ")
|
|
||||||
} else {
|
|
||||||
summaryTail = strings.Join(words, " ")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
summaryTail = sentances[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
t := domain.NewTask(
|
|
||||||
summaryTail,
|
|
||||||
taskText,
|
|
||||||
mu.From.Username.PeerID(),
|
|
||||||
mu.Chat.ID.PeerID(),
|
|
||||||
)
|
|
||||||
|
|
||||||
id, err := h.coda.CreateTask(t.Summary, t.Description, t.Creator, t.CreatorLink)
|
|
||||||
if err != nil {
|
|
||||||
answer := errorAnswer(err.Error())
|
|
||||||
h.LogMessage(ctx, mu, answer)
|
|
||||||
return mu.Answer(answer).ParseMode(tg.HTML).DoVoid(ctx)
|
|
||||||
}
|
|
||||||
if id == "" {
|
|
||||||
answer := errorAnswer("task wasn't created")
|
|
||||||
h.LogMessage(ctx, mu, answer)
|
|
||||||
return mu.Answer(answer).ParseMode(tg.HTML).DoVoid(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = mu.Answer(fmt.Sprintf("Задача с id: %s была создана, жду ссылку", id)).DoVoid(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("бот не смог ответить про создание задачи")
|
|
||||||
}
|
|
||||||
|
|
||||||
url, err := h.coda.GetRowLink(id)
|
|
||||||
if err != nil {
|
|
||||||
answer := err.Error()
|
|
||||||
h.LogMessage(ctx, mu, answer)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.URL = url
|
|
||||||
|
|
||||||
answer := tg.HTML.Text(
|
|
||||||
tg.HTML.Line(tg.HTML.Link("🤘 Задача", t.URL), "была создана!"))
|
|
||||||
h.LogMessage(ctx, mu, answer)
|
|
||||||
return mu.Answer(answer).
|
|
||||||
ReplyToMessageID(msgID).ParseMode(tg.HTML).DisableWebPagePreview(true).DoVoid(ctx)
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"ticket-pimp/bot/controller"
|
|
||||||
"ticket-pimp/internal/services"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Handler struct {
|
|
||||||
workflow controller.IWorkflowController
|
|
||||||
git services.IGit
|
|
||||||
cloud services.ICloud
|
|
||||||
coda services.ICoda
|
|
||||||
key string
|
|
||||||
id string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHandler(
|
|
||||||
git services.IGit,
|
|
||||||
cloud services.ICloud,
|
|
||||||
coda services.ICoda,
|
|
||||||
) *Handler {
|
|
||||||
|
|
||||||
return &Handler{
|
|
||||||
workflow: controller.NewWorkflowController(git, cloud, coda),
|
|
||||||
git: git,
|
|
||||||
cloud: cloud,
|
|
||||||
coda: coda,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"ticket-pimp/internal/domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
type test struct {
|
|
||||||
arg domain.Git
|
|
||||||
expected string
|
|
||||||
}
|
|
||||||
|
|
||||||
var tests = []test{
|
|
||||||
{domain.Git{
|
|
||||||
Name: "text",
|
|
||||||
FullName: "",
|
|
||||||
Private: false,
|
|
||||||
Url: "",
|
|
||||||
CloneUrl: "",
|
|
||||||
HtmlUrl: "https://reddit.com/",
|
|
||||||
SshUrl: "",
|
|
||||||
}, "Repo <a href=\"https://reddit.com/\">text</a> has been created!"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrepareAnswer(t *testing.T) {
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
g := newGit(&test.arg)
|
|
||||||
|
|
||||||
if output := g.PrepareAnswer(); output != test.expected {
|
|
||||||
t.Errorf("Output %q not equal to expected %q", output, test.expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
package discord
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"ticket-pimp/client/discord/handler"
|
||||||
|
"ticket-pimp/internal/controller"
|
||||||
|
"ticket-pimp/internal/domain"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initBotWith(token string) *discordgo.Session {
|
||||||
|
discord, err := discordgo.New("Bot " + token)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to create discord session: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return discord
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscordOptions struct {
|
||||||
|
Config *domain.Config
|
||||||
|
Controller *controller.WorkflowController
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPrivateMessaging(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||||
|
dchan, err := s.Channel(i.ChannelID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if dchan.Type == discordgo.ChannelTypeDM {
|
||||||
|
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Content: "Yo, fella! I'm not working in private!",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return errors.New("no private messages! lol")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(conf domain.Config, opts DiscordOptions) error {
|
||||||
|
token := conf.Discord.Token
|
||||||
|
|
||||||
|
s := initBotWith(token)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch i.Type {
|
||||||
|
case discordgo.InteractionApplicationCommand:
|
||||||
|
if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok {
|
||||||
|
h(s, i)
|
||||||
|
}
|
||||||
|
case discordgo.InteractionMessageComponent:
|
||||||
|
if h, ok := componentsHandlers[i.MessageComponentData().CustomID]; ok {
|
||||||
|
h(s, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := s.Open(); err != nil {
|
||||||
|
return fmt.Errorf("cannot open the session: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UPDATE FORUM IF NEEDED:
|
||||||
|
|
||||||
|
// forum, err := session.Channel(os.Getenv("TASKS_CHANNEL"))
|
||||||
|
forum, err := s.Channel(conf.Discord.IsProjectChannel)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.ChannelEditComplex(forum.ID, &discordgo.ChannelEdit{
|
||||||
|
AvailableTags: &router.Tags,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Adding commands...")
|
||||||
|
var cmds []*discordgo.ApplicationCommand
|
||||||
|
var logString []string
|
||||||
|
for _, h := range router.Commands {
|
||||||
|
cmd, err := s.ApplicationCommandCreate(s.State.User.ID, "1103928338898235462", &h.Command)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("Cannot create '%v' command: %v", h.Command.Name, err)
|
||||||
|
}
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
logString = append(logString, cmd.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Following commands added:")
|
||||||
|
log.Println(logString)
|
||||||
|
|
||||||
|
defer s.Close()
|
||||||
|
stop := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(stop, os.Interrupt)
|
||||||
|
<-stop
|
||||||
|
log.Println("Graceful shutdown")
|
||||||
|
|
||||||
|
log.Println("Removing commands...")
|
||||||
|
for _, h := range cmds {
|
||||||
|
err := s.ApplicationCommandDelete(s.State.User.ID, "", h.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("Cannot delete '%v' command: %v", h.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,249 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"ticket-pimp/internal/domain"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
"github.com/imroc/req/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *client) setFlag(s *discordgo.Session, i *discordgo.InteractionCreate, tag *discordgo.ForumTag) error {
|
||||||
|
|
||||||
|
th, err := s.Channel(i.ChannelID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
forum, err := s.Channel(th.ParentID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка на существование тега в списке тегов:
|
||||||
|
if len(forum.AvailableTags) != 0 {
|
||||||
|
for _, some := range forum.AvailableTags {
|
||||||
|
if some.Name == tag.Name {
|
||||||
|
_, err := s.ChannelEditComplex(i.ChannelID, &discordgo.ChannelEdit{
|
||||||
|
AppliedTags: &[]string{some.ID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) ListenPosts(s *discordgo.Session, th *discordgo.ThreadCreate) {
|
||||||
|
|
||||||
|
// Check if thread starter is not a bot, and thread started at the tasks channel;
|
||||||
|
if th.ParentID != c.conf.IsTaskForum || th.OwnerID == s.State.User.ID {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs, _ := s.ChannelMessages(th.ID, 1, "", "", "")
|
||||||
|
|
||||||
|
msg, _ := s.ChannelMessage(th.ID, msgs[0].ID)
|
||||||
|
|
||||||
|
if msg.Author.ID == s.State.User.ID {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content := th.Name
|
||||||
|
content += "\n" + msg.Content
|
||||||
|
|
||||||
|
user, _ := s.GuildMember(th.GuildID, msg.Author.ID)
|
||||||
|
|
||||||
|
t, err := c.controller.WriteTaskToDB(&domain.Task{
|
||||||
|
Description: content,
|
||||||
|
Creator: user.User.Mention(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.ChannelMessageSend(th.ID, fmt.Sprintf("unable to write task to db, %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// [x] -- Отредактировать Thread name как для задачи
|
||||||
|
_, err = s.ChannelEditComplex(th.ID, &discordgo.ChannelEdit{
|
||||||
|
Name: fmt.Sprintf("Task ID: %d, by %s", t.ID, t.Creator),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("th edition is not complete: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix the original task message:
|
||||||
|
taskMessage, err := s.ChannelMessageSendComplex(th.ID, &discordgo.MessageSend{
|
||||||
|
Content: content,
|
||||||
|
Components: []discordgo.MessageComponent{
|
||||||
|
discordgo.ActionsRow{
|
||||||
|
Components: []discordgo.MessageComponent{
|
||||||
|
discordgo.Button{
|
||||||
|
Label: "Start",
|
||||||
|
Style: discordgo.SuccessButton,
|
||||||
|
Disabled: false,
|
||||||
|
CustomID: "task_start",
|
||||||
|
},
|
||||||
|
discordgo.Button{
|
||||||
|
Label: "Close",
|
||||||
|
Style: discordgo.DangerButton,
|
||||||
|
Disabled: true,
|
||||||
|
CustomID: "task_close",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("th start message edition is not complete: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.controller.UpdateTasksMessageID(context.TODO(), taskMessage.ID, t.ID)
|
||||||
|
if err != nil {
|
||||||
|
s.ChannelMessageSend(th.ID, fmt.Sprintf("unable to update task at the db, %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) HandleTaskButtons() Component {
|
||||||
|
return Component{
|
||||||
|
Handler: c.handleTaskButton,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) handleTaskButton(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
|
||||||
|
// Send an empty interaction response; ----------------------------------------------------------------
|
||||||
|
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseUpdateMessage,
|
||||||
|
Data: &discordgo.InteractionResponseData{},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get assignee value; ---------------------------------------------------------------------------------
|
||||||
|
user := i.Member.User.Mention()
|
||||||
|
|
||||||
|
var (
|
||||||
|
opt int = -1
|
||||||
|
doneButtonIsDisabled bool = false
|
||||||
|
state domain.TaskState = domain.NewTaskState()
|
||||||
|
message string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check what flow was touched: -------------------------------------------------------------------------
|
||||||
|
switch i.Interaction.MessageComponentData().CustomID {
|
||||||
|
case "task_start":
|
||||||
|
opt = 0
|
||||||
|
doneButtonIsDisabled = false
|
||||||
|
state = domain.InrpogressTaskState()
|
||||||
|
message = "взята в работу"
|
||||||
|
case "task_close":
|
||||||
|
opt = 1
|
||||||
|
doneButtonIsDisabled = true
|
||||||
|
state = domain.DoneTaskState()
|
||||||
|
message = "выполнена"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the task update to db --------------------------------------------------------------------------
|
||||||
|
convertable, err := c.controller.UpdateTask(i.Message.ID, opt, user)
|
||||||
|
if err != nil {
|
||||||
|
s.ChannelMessageSend(i.ChannelID, fmt.Sprintf("Unable to update task at the db w/ error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map DB's response to domain.Task: -------------------------------------------------------------------
|
||||||
|
task := convertable.
|
||||||
|
ExtractDomain()
|
||||||
|
|
||||||
|
newContent := task.DiscordMessage(state)
|
||||||
|
|
||||||
|
// Send message to the creator in Telegram: -------------------------------------------------------------
|
||||||
|
|
||||||
|
if task.CreatorLink != "" {
|
||||||
|
c.sendTelegramMessageToCreator(
|
||||||
|
task.CreatorLink,
|
||||||
|
fmt.Sprintf("Task ID: %d %s", task.ID, message))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a message to the thread about the task was started: ---------------------------------------------
|
||||||
|
_, err = s.ChannelMessageSendComplex(i.ChannelID, &discordgo.MessageSend{
|
||||||
|
Content: newContent,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error while sending start task message: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix the original task message: ----------------------------------------------------------------------
|
||||||
|
_, err = s.ChannelMessageEditComplex(&discordgo.MessageEdit{
|
||||||
|
Channel: i.ChannelID,
|
||||||
|
ID: i.Message.ID,
|
||||||
|
Components: []discordgo.MessageComponent{
|
||||||
|
discordgo.ActionsRow{
|
||||||
|
Components: []discordgo.MessageComponent{
|
||||||
|
discordgo.Button{
|
||||||
|
Label: "Close",
|
||||||
|
Style: discordgo.DangerButton,
|
||||||
|
Disabled: doneButtonIsDisabled,
|
||||||
|
CustomID: "task_close",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("th start message edition is not complete: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Устанавливаем тэги статуса на тред ---------------------------------------------------------------------
|
||||||
|
err = c.setFlag(s, i, &c.Tags[opt])
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error while `start` tag setting: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TelegramMessage struct {
|
||||||
|
ChatID string `json:"chat_id"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
DisableNotification bool `json:"disable_notification"`
|
||||||
|
ParseMode string `json:"parse_mode"`
|
||||||
|
DisablePreview bool `json:"disable_web_page_preview"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) sendTelegramMessageToCreator(tgChatID string, text string) {
|
||||||
|
|
||||||
|
http := req.C()
|
||||||
|
http.R().
|
||||||
|
SetBody(&TelegramMessage{
|
||||||
|
ChatID: tgChatID,
|
||||||
|
Text: text,
|
||||||
|
DisableNotification: true,
|
||||||
|
ParseMode: "HTML",
|
||||||
|
DisablePreview: true,
|
||||||
|
}).
|
||||||
|
Post("https://api.telegram.org/bot" + c.tgConf.Token + "/sendMessage")
|
||||||
|
// [HTTP Kit Marlerino]::POST(
|
||||||
|
// "https://api.telegram.org/bot" + Config.botToken +
|
||||||
|
// "/sendMessage",
|
||||||
|
// "",
|
||||||
|
// Object(
|
||||||
|
// "chat_id",
|
||||||
|
// thisRow.[Creator ID].ToText(),
|
||||||
|
// "text",
|
||||||
|
// Format(
|
||||||
|
// "<a href='{1}'>{2}</a> взята в работу",
|
||||||
|
// thisRow.ObjectLink().ToText(),
|
||||||
|
// "Задача"
|
||||||
|
// ),
|
||||||
|
// "disable_notification",
|
||||||
|
// true,
|
||||||
|
// "parse_mode",
|
||||||
|
// "HTML",
|
||||||
|
// "disable_web_page_preview",
|
||||||
|
// true
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,220 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
// func (h *Handler) DevelopmentTaskHandler(ctx context.Context, mu *tgb.MessageUpdate) error {
|
||||||
|
|
||||||
|
// str := strings.Replace(mu.Text, "/new", "", 1)
|
||||||
|
|
||||||
|
// if str == "" {
|
||||||
|
// return errors.New("empty command provided")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// issueKeyStr, err := h.workflow.Workflow(str, h.key, h.id)
|
||||||
|
|
||||||
|
// if err != nil {
|
||||||
|
// answer := errorAnswer(err.Error())
|
||||||
|
// h.LogMessage(ctx, mu, answer)
|
||||||
|
// return mu.Answer(answer).ParseMode(tg.HTML).DoVoid(ctx)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// i, err := strconv.Atoi(h.id)
|
||||||
|
// if err != nil {
|
||||||
|
// return errors.New("problem with conversion id to int")
|
||||||
|
// }
|
||||||
|
// h.id = strconv.Itoa(i + 1)
|
||||||
|
|
||||||
|
// answer := newTicketAnswer(issueKeyStr)
|
||||||
|
// h.LogMessage(ctx, mu, answer)
|
||||||
|
// return mu.Answer(answer).ParseMode(tg.HTML).DoVoid(ctx)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func newTicketAnswer(name string) string {
|
||||||
|
// return tg.HTML.Text(
|
||||||
|
// tg.HTML.Line(
|
||||||
|
// "🤘 Ticket ",
|
||||||
|
// name,
|
||||||
|
// " has been created!",
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"ticket-pimp/internal/domain"
|
||||||
|
|
||||||
|
"github.com/mr-linch/go-tg"
|
||||||
|
"github.com/mr-linch/go-tg/tgb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) FarmTaskHandler(ctx context.Context, mu *tgb.MessageUpdate) error {
|
||||||
|
|
||||||
|
var (
|
||||||
|
taskText string = ""
|
||||||
|
answer string = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
msgID := mu.Message.ID
|
||||||
|
if mu.Caption != "" {
|
||||||
|
taskText = strings.TrimSpace(strings.Replace(mu.Caption, "/task", "", 1))
|
||||||
|
} else {
|
||||||
|
taskText = strings.TrimSpace(strings.Replace(mu.Text, "/task", "", 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
var summaryTail string
|
||||||
|
|
||||||
|
sentances := strings.Split(taskText, "\n")
|
||||||
|
if len(sentances) < 2 {
|
||||||
|
words := strings.Split(taskText, " ")
|
||||||
|
if len(words) > 2 {
|
||||||
|
summaryTail = strings.Join(words[0:2], " ")
|
||||||
|
} else {
|
||||||
|
summaryTail = strings.Join(words, " ")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
summaryTail = sentances[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
t := domain.NewTask(
|
||||||
|
summaryTail,
|
||||||
|
taskText,
|
||||||
|
mu.From.Username.PeerID(),
|
||||||
|
mu.Chat.ID.PeerID(),
|
||||||
|
)
|
||||||
|
|
||||||
|
conv, err := h.controller.InitTask(t)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
answer := err.Error()
|
||||||
|
h.LogMessage(ctx, mu, answer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
i := strconv.Itoa(int(conv.ID))
|
||||||
|
answer = tg.HTML.Text(
|
||||||
|
tg.HTML.Line(
|
||||||
|
tg.HTML.Bold("Task ID: "),
|
||||||
|
tg.HTML.Code(i),
|
||||||
|
tg.HTML.Text(" was created"),
|
||||||
|
),
|
||||||
|
tg.HTML.Line(
|
||||||
|
"Заходи в наш",
|
||||||
|
tg.HTML.Link("discord", "https://discord.gg/RHdzK3kUr7"),
|
||||||
|
"чтобы отслеживать статус по задаче",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if mu.Caption != "" {
|
||||||
|
answer = tg.HTML.Text(
|
||||||
|
tg.HTML.Line(
|
||||||
|
tg.HTML.Bold("I'm unable to work with files, but"),
|
||||||
|
),
|
||||||
|
tg.HTML.Line(
|
||||||
|
tg.HTML.Italic(
|
||||||
|
tg.HTML.Bold("Task ID: "),
|
||||||
|
tg.HTML.Code(i),
|
||||||
|
tg.HTML.Text(" was created")),
|
||||||
|
),
|
||||||
|
|
||||||
|
tg.HTML.Line(
|
||||||
|
"Заходи в наш",
|
||||||
|
tg.HTML.Link("discord", "https://discord.gg/RHdzK3kUr7"),
|
||||||
|
"чтобы отслеживать статус по задаче",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.LogMessage(ctx, mu, answer)
|
||||||
|
return mu.Answer(answer).
|
||||||
|
ReplyToMessageID(msgID).ParseMode(tg.HTML).DisableWebPagePreview(true).DoVoid(ctx)
|
||||||
|
}
|
||||||
|
|
@ -17,10 +17,10 @@ func (h *Handler) NewFolderHandler(ctx context.Context, mu *tgb.MessageUpdate) e
|
||||||
return errors.New("empty command provided")
|
return errors.New("empty command provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
cloud, err := h.cloud.CreateFolder(str)
|
resp := h.cloud.CreateFolder(str)
|
||||||
|
|
||||||
if err != nil {
|
if resp.ErrMessage != nil {
|
||||||
answer := errorAnswer(err.Error())
|
answer := errorAnswer(resp.ErrMessage.Error())
|
||||||
h.LogMessage(ctx, mu, answer)
|
h.LogMessage(ctx, mu, answer)
|
||||||
return mu.Answer(answer).ParseMode(tg.HTML).DoVoid(ctx)
|
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(
|
answer := tg.HTML.Text(
|
||||||
tg.HTML.Line(
|
tg.HTML.Line(
|
||||||
"✨ Shiny folder",
|
"✨ Shiny folder",
|
||||||
tg.HTML.Link(cloud.Title, cloud.PrivateURL),
|
tg.HTML.Link(resp.Folder.Title, resp.Folder.PrivateURL),
|
||||||
"has been created!",
|
"has been created!",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -19,12 +19,12 @@ type git struct {
|
||||||
ssh string
|
ssh string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGit(domain *domain.Git) *git {
|
func newGit(g *domain.Git) *git {
|
||||||
return &git{
|
return &git{
|
||||||
name: domain.Name,
|
name: g.Name,
|
||||||
url: domain.HtmlUrl,
|
url: g.HtmlUrl,
|
||||||
git: domain.CloneUrl,
|
git: g.CloneUrl,
|
||||||
ssh: fmt.Sprintf("ssh://%s/%s.git", domain.SshUrl, domain.FullName),
|
ssh: fmt.Sprintf("ssh://%s/%s.git", g.SshUrl, g.FullName),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ticket-pimp/internal/controller"
|
||||||
|
"ticket-pimp/internal/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
git services.IGit
|
||||||
|
cloud services.ICloud
|
||||||
|
coda services.ICoda
|
||||||
|
key string
|
||||||
|
id string
|
||||||
|
controller *controller.WorkflowController
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(
|
||||||
|
git services.IGit,
|
||||||
|
cloud services.ICloud,
|
||||||
|
coda services.ICoda,
|
||||||
|
controller *controller.WorkflowController,
|
||||||
|
) *Handler {
|
||||||
|
|
||||||
|
return &Handler{
|
||||||
|
git: git,
|
||||||
|
cloud: cloud,
|
||||||
|
coda: coda,
|
||||||
|
controller: controller,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package telegram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"ticket-pimp/client/telegram/handler"
|
||||||
|
"ticket-pimp/internal/controller"
|
||||||
|
"ticket-pimp/internal/domain"
|
||||||
|
"ticket-pimp/internal/services"
|
||||||
|
|
||||||
|
"github.com/mr-linch/go-tg"
|
||||||
|
"github.com/mr-linch/go-tg/tgb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TelegramOptions struct {
|
||||||
|
GitService *services.Git
|
||||||
|
CloudService *services.Cloud
|
||||||
|
Coda *services.Coda
|
||||||
|
AppConfig *domain.Config
|
||||||
|
Controller *controller.WorkflowController
|
||||||
|
}
|
||||||
|
|
||||||
|
// runTgBot ...
|
||||||
|
// ..function creates new Telegram BOT instance
|
||||||
|
// ..throw env variables through bot's handlers
|
||||||
|
// ..setup tg bot router;
|
||||||
|
// and finally returns tgb.Poller
|
||||||
|
func Run(ctx context.Context, opts TelegramOptions) error {
|
||||||
|
|
||||||
|
log.Print("Start telegram bot init..")
|
||||||
|
client := tg.New(opts.AppConfig.Telegram.Token)
|
||||||
|
|
||||||
|
h := handler.NewHandler(
|
||||||
|
opts.GitService,
|
||||||
|
opts.CloudService,
|
||||||
|
opts.Coda,
|
||||||
|
opts.Controller,
|
||||||
|
)
|
||||||
|
|
||||||
|
router := tgb.NewRouter().
|
||||||
|
Message(h.Init, tgb.Command("init")).
|
||||||
|
Message(h.PingHandler, tgb.Command("ping")).
|
||||||
|
// Message(h.DevelopmentTaskHandler, tgb.TextHasPrefix("/new")).
|
||||||
|
// Message(h.NewRepoHandler, tgb.TextHasPrefix("/repo")).
|
||||||
|
// Message(h.NewFolderHandler, tgb.TextHasPrefix("/folder")).
|
||||||
|
Message(h.FarmTaskHandler, tgb.TextHasPrefix("/task"))
|
||||||
|
|
||||||
|
log.Print("Success init. Start poller.")
|
||||||
|
return tgb.NewPoller(
|
||||||
|
router,
|
||||||
|
client,
|
||||||
|
).Run(ctx)
|
||||||
|
}
|
||||||
148
cmd/main.go
148
cmd/main.go
|
|
@ -7,77 +7,101 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
"ticket-pimp/bot/handler"
|
|
||||||
|
"ticket-pimp/internal/controller"
|
||||||
|
"ticket-pimp/internal/domain"
|
||||||
"ticket-pimp/internal/services"
|
"ticket-pimp/internal/services"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"ticket-pimp/client/discord"
|
||||||
"github.com/mr-linch/go-tg"
|
"ticket-pimp/client/telegram"
|
||||||
"github.com/mr-linch/go-tg/tgb"
|
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
"github.com/jackc/pgx/v5/stdlib"
|
||||||
|
migrate "github.com/rubenv/sql-migrate"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
envfile = "prod.env"
|
||||||
|
migrationfile = "internal/storage/migrate"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Print("started")
|
log.Print("started")
|
||||||
env("prod.env")
|
config := domain.InitConfig(envfile)
|
||||||
ctx := context.Background()
|
run(config)
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, os.Kill, syscall.SIGTERM)
|
func run(conf domain.Config) {
|
||||||
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := runBot(ctx); err != nil {
|
// -- DB connection init -- START
|
||||||
fmt.Println(err)
|
connString := fmt.Sprintf(
|
||||||
|
"postgresql://%s:%s@%s:%s/%s",
|
||||||
|
conf.DB.User, conf.DB.Pass, conf.DB.Host, conf.DB.Port, conf.DB.Name,
|
||||||
|
)
|
||||||
|
conn, err := pgxpool.New(
|
||||||
|
ctx,
|
||||||
|
connString)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("DB connection failed: %v", err)
|
||||||
|
}
|
||||||
|
// -- DB connection init -- END
|
||||||
|
|
||||||
|
// Aply migrations:
|
||||||
|
|
||||||
|
dbConnConfig, err := pgxpool.ParseConfig(connString)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to parse connString: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
migrations := &migrate.FileMigrationSource{
|
||||||
|
Dir: migrationfile,
|
||||||
|
}
|
||||||
|
|
||||||
|
db := stdlib.OpenDB(*dbConnConfig.ConnConfig)
|
||||||
|
|
||||||
|
const dialect = "postgres"
|
||||||
|
n, err := migrate.Exec(db, dialect, migrations, migrate.Up)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to handle migrations: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Applied %d migrations!\n", n)
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
gitService := services.NewGit(conf.Git)
|
||||||
|
cloudService := services.NewCloud(conf.Cloud)
|
||||||
|
codaService := services.NewCodaClient(conf.Coda)
|
||||||
|
|
||||||
|
// Инициализация контроллера:
|
||||||
|
controller := controller.NewWorkflowController(
|
||||||
|
gitService,
|
||||||
|
cloudService,
|
||||||
|
codaService,
|
||||||
|
conn,
|
||||||
|
)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
opts := discord.DiscordOptions{
|
||||||
|
Controller: controller,
|
||||||
|
Config: &conf,
|
||||||
|
}
|
||||||
|
if err := discord.Run(conf, opts); err != nil {
|
||||||
|
log.Fatalf("discord bot cannot be runned: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
opts := telegram.TelegramOptions{
|
||||||
|
GitService: gitService,
|
||||||
|
CloudService: cloudService,
|
||||||
|
Coda: codaService,
|
||||||
|
AppConfig: &conf,
|
||||||
|
Controller: controller,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := telegram.Run(ctx, opts); err != nil {
|
||||||
|
log.Fatalf("telegram bot cannot be runned: %v", err)
|
||||||
defer os.Exit(1)
|
defer os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// env
|
|
||||||
// env function reads provided file and setup envirmental variables;
|
|
||||||
func env(envFilePath string) {
|
|
||||||
err := godotenv.Load(envFilePath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error while loading env file")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// runBot ...
|
|
||||||
// ..function creates new Telegram BOT instance
|
|
||||||
// ..throw env variables through bot's handlers
|
|
||||||
// ..setup tg bot router;
|
|
||||||
// and finally returns tgb.Poller
|
|
||||||
func runBot(ctx context.Context) error {
|
|
||||||
|
|
||||||
client := tg.New(os.Getenv("TG_API"))
|
|
||||||
|
|
||||||
gitService := services.NewGit(
|
|
||||||
os.Getenv("GIT_BASE_URL"),
|
|
||||||
os.Getenv("GIT_TOKEN"),
|
|
||||||
)
|
|
||||||
|
|
||||||
cloudService := services.NewCloud(
|
|
||||||
os.Getenv("CLOUD_BASE_URL"),
|
|
||||||
os.Getenv("CLOUD_USER"),
|
|
||||||
os.Getenv("CLOUD_PASS"),
|
|
||||||
)
|
|
||||||
|
|
||||||
coda := services.NewCodaClient(
|
|
||||||
os.Getenv("CODA_TOKEN1"),
|
|
||||||
)
|
|
||||||
|
|
||||||
h := handler.NewHandler(
|
|
||||||
gitService,
|
|
||||||
cloudService,
|
|
||||||
coda,
|
|
||||||
)
|
|
||||||
|
|
||||||
router := tgb.NewRouter().
|
|
||||||
Message(h.Init, tgb.Command("init")).
|
|
||||||
Message(h.PingHandler, tgb.Command("ping")).
|
|
||||||
Message(h.DevelopmentTaskHandler, tgb.TextHasPrefix("/new")).
|
|
||||||
Message(h.NewRepoHandler, tgb.TextHasPrefix("/repo")).
|
|
||||||
Message(h.NewFolderHandler, tgb.TextHasPrefix("/folder")).
|
|
||||||
Message(h.FarmTaskHandler, tgb.TextHasPrefix("/task"))
|
|
||||||
|
|
||||||
return tgb.NewPoller(
|
|
||||||
router,
|
|
||||||
client,
|
|
||||||
).Run(ctx)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
services:
|
||||||
|
ticket-pimp:
|
||||||
|
container_name: pimp
|
||||||
|
image: naudachu/ticket-pimp
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
postgres:
|
||||||
|
container_name: db
|
||||||
|
image: "postgres:16.1-alpine3.18"
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: "tickets"
|
||||||
|
POSTGRES_USER: "postgres"
|
||||||
|
POSTGRES_PASSWORD: "postgres"
|
||||||
|
volumes:
|
||||||
|
- db:./postgres-data:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "pg_isready", "-q", "-d", "tickets", "-U", "postgres" ]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
FROM adoptopenjdk/openjdk11-openj9
|
|
||||||
|
|
||||||
ARG GITBUCKET_HOME
|
|
||||||
|
|
||||||
# create home
|
|
||||||
RUN mkdir -p $GITBUCKET_HOME
|
|
||||||
|
|
||||||
# mark volumes
|
|
||||||
VOLUME $GITBUCKET_HOME/repositories
|
|
||||||
VOLUME $GITBUCKET_HOME/data
|
|
||||||
VOLUME $GITBUCKET_HOME/gist
|
|
||||||
VOLUME $GITBUCKET_HOME/plugins
|
|
||||||
|
|
||||||
# Port for web page and Port for SSH access to git repository (Optional)
|
|
||||||
EXPOSE 8080 8443 29418
|
|
||||||
|
|
||||||
COPY server-WIN-DOMAIN-CA.cer /
|
|
||||||
COPY SSLPoke.java /
|
|
||||||
|
|
||||||
# ADD https://github.com/gitbucket/gitbucket/releases/download/4.38.4/gitbucket.war $GITBUCKET_HOME/gitbucket.war
|
|
||||||
COPY gitbucket.war $GITBUCKET_HOME
|
|
||||||
|
|
||||||
RUN keytool -importcert -file /server-WIN-DOMAIN-CA.cer -alias "server-WIN-DOMAIN-CA" -cacerts -storepass changeit -noprompt
|
|
||||||
|
|
||||||
# set environment
|
|
||||||
WORKDIR $GITBUCKET_HOME
|
|
||||||
|
|
||||||
CMD ["sh", "-c", "java $JAVA_OPTS -jar $GITBUCKET_HOME/gitbucket.war"]
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import javax.net.ssl.SSLParameters;
|
|
||||||
import javax.net.ssl.SSLSocket;
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
import java.io.*;
|
|
||||||
|
|
||||||
/** Establish a SSL connection to a host and port, writes a byte and
|
|
||||||
* prints the response. See
|
|
||||||
* http://confluence.atlassian.com/display/JIRA/Connecting+to+SSL+services
|
|
||||||
*/
|
|
||||||
public class SSLPoke {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
if (args.length != 2) {
|
|
||||||
System.out.println("Usage: "+SSLPoke.class.getName()+" <host> <port>");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
|
|
||||||
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(args[0], Integer.parseInt(args[1]));
|
|
||||||
|
|
||||||
SSLParameters sslparams = new SSLParameters();
|
|
||||||
sslparams.setEndpointIdentificationAlgorithm("HTTPS");
|
|
||||||
sslsocket.setSSLParameters(sslparams);
|
|
||||||
|
|
||||||
InputStream in = sslsocket.getInputStream();
|
|
||||||
OutputStream out = sslsocket.getOutputStream();
|
|
||||||
|
|
||||||
// Write a test byte to get a reaction :)
|
|
||||||
out.write(1);
|
|
||||||
|
|
||||||
while (in.available() > 0) {
|
|
||||||
System.out.print(in.read());
|
|
||||||
}
|
|
||||||
System.out.println("Successfully connected");
|
|
||||||
|
|
||||||
} catch (Exception exception) {
|
|
||||||
exception.printStackTrace();
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
version: "3"
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
mysql:
|
|
||||||
driver: local
|
|
||||||
|
|
||||||
services:
|
|
||||||
gitbucket:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
args:
|
|
||||||
GITBUCKET_HOME: /mnt/gitbucket
|
|
||||||
container_name: gitbucket_server
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- ${HTTP_PORT}:8080
|
|
||||||
- ${HTTPS_PORT}:8443
|
|
||||||
# Optional for SSH:
|
|
||||||
- ${SSH_PORT}:29418
|
|
||||||
depends_on:
|
|
||||||
mariadb:
|
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
|
||||||
- JAVA_OPTS=-Dcom.sun.net.ssl.checkRevocation=false -Dcom.sun.security.enableAIAcaIssuers=true -Docsp.enable=fase -Dtrust_all_cert=true -Djdk.tls.client.protocols=TLSv1.2 -Dcom.sun.net.ssl.enableECC=false
|
|
||||||
- GITBUCKET_HOME=/mnt/gitbucket
|
|
||||||
- GITBUCKET_CONNECTORS=http
|
|
||||||
- GITBUCKET_HOST=0.0.0.0
|
|
||||||
- GITBUCKET_PORT=8080
|
|
||||||
- GITBUCKET_SECUREPORT=8443
|
|
||||||
- GITBUCKET_REDIRECTHTTPS=false
|
|
||||||
- GITBUCKET_PREFIX=/
|
|
||||||
- GITBUCKET_MAXFILESIZE=4294967296
|
|
||||||
- GITBUCKET_UPLOADTIMEOUT=120000
|
|
||||||
- GITBUCKET_JETTYIDLETIMEOUT=600000
|
|
||||||
- GITBUCKET_DB_URL=jdbc:mariadb://mariadb/gitbucket?useUnicode=true&characterEncoding=utf8
|
|
||||||
- GITBUCKET_DB_USER=gitbucket
|
|
||||||
- GITBUCKET_DB_PASSWORD=gitbucket
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "/usr/bin/healthcheck"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 5
|
|
||||||
volumes:
|
|
||||||
- ../disks/disk1/gitbucket_data/repositories/:/mnt/gitbucket/repositories/
|
|
||||||
- ../disks/disk1/gitbucket_data/data/:/mnt/gitbucket/data/
|
|
||||||
- ../disks/disk1/gitbucket_data/gist/:/mnt/gitbucket/gist/
|
|
||||||
- ../disks/disk1/gitbucket_data/plugins/:/mnt/gitbucket/plugins/
|
|
||||||
|
|
||||||
mariadb:
|
|
||||||
image: mariadb:10.6
|
|
||||||
container_name: gitbucket_mariadb
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- ${DB_PORT}:3306
|
|
||||||
environment:
|
|
||||||
- MYSQL_ROOT_PASSWORD=gitbucket
|
|
||||||
- MYSQL_USER=gitbucket
|
|
||||||
- MYSQL_PASSWORD=gitbucket
|
|
||||||
- MYSQL_DATABASE=gitbucket
|
|
||||||
command: ["--max-allowed-packet=128M", "--innodb-log-file-size=64M"]
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "mysqladmin", "ping", "-u", "root", "--password=gitbucket"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
volumes:
|
|
||||||
- mysql:/var/lib/mysql
|
|
||||||
Binary file not shown.
|
|
@ -1,22 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDuTCCAqGgAwIBAgIQcZXcA798CYtB1LtewpzPijANBgkqhkiG9w0BAQsFADBu
|
|
||||||
MRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxHjAcBgoJkiaJk/IsZAEZFg5tYXJsZXJp
|
|
||||||
bm9ncm91cDEWMBQGCgmSJomT8ixkARkWBnNlcnZlcjEdMBsGA1UEAxMUc2VydmVy
|
|
||||||
LVdJTi1ET01BSU4tQ0EwIBcNMjIwODE1MTI1NTM3WhgPMjEyMjA4MTUxMzA1Mzda
|
|
||||||
MG4xFTATBgoJkiaJk/IsZAEZFgVsb2NhbDEeMBwGCgmSJomT8ixkARkWDm1hcmxl
|
|
||||||
cmlub2dyb3VwMRYwFAYKCZImiZPyLGQBGRYGc2VydmVyMR0wGwYDVQQDExRzZXJ2
|
|
||||||
ZXItV0lOLURPTUFJTi1DQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
|
||||||
AMBJwWymu7Csp+y8EMiPNo5drsQE58+enNCk13OIemQON5LZwY++BnvIrHvnaCof
|
|
||||||
Cdb9U2QyWV+Iehv/lNXcBkT6dJCTxUVmn6rXMmwNnBJmGuy0D6APIEPVBO3S7+/R
|
|
||||||
E4az8ikpVezbqAw/LiSyVMEQFaAEK8RlleBScLw9gpXmcWKljCrO/z+2wO1EGI3z
|
|
||||||
a1+qFLynQLeyusRj6rvo0U4RTyXdKaA++Ojhx3DV1NopHoSJ7CqXR728ubWK0fvY
|
|
||||||
0JGMg2ijS1cle7yC96+9ZSjdtktI/9M+8SG/tbpXcpW1bzGM0wCWOos1hLehks7V
|
|
||||||
c1pBEuVGSKSdtyF/FK2C7YUCAwEAAaNRME8wCwYDVR0PBAQDAgGGMA8GA1UdEwEB
|
|
||||||
/wQFMAMBAf8wHQYDVR0OBBYEFL+/yzfaRM/Rcrc9oYEzZf3STypqMBAGCSsGAQQB
|
|
||||||
gjcVAQQDAgEAMA0GCSqGSIb3DQEBCwUAA4IBAQA7j2n9ZeKDwbmWCM2P1F9+6EZ8
|
|
||||||
+RZ+PEdxqs3aDo6S0Ckr/iopHA9PoexKGVEQN6bzaYxb0XTEbpJ89oI299weJF+W
|
|
||||||
63zID/lpEzsaSlTWpuj8KBudVoKsJmwJK2akQ2T3MmSiz7drtFOyh5wl9vhoi2cv
|
|
||||||
H3xhpqOYgh6iRwfwXMLo2YyS4vG6UZtq1GnU/drwVb1gHCoMdz7sFIdxlaw85Ub+
|
|
||||||
dlicykBNu60dbvM/iI0qSG25PerjEoOWZAglrPWpsQ0Zy+ChV0lWSp6FIYHjT3Rj
|
|
||||||
3TUo18LwIUKyorRErOTO4vgYHpqaA/RkAfl0C3cUujMw9eAp1fyd9FH0UcDo
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
version: "3"
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
mysql:
|
|
||||||
driver: local
|
|
||||||
redis:
|
|
||||||
driver: local
|
|
||||||
|
|
||||||
services:
|
|
||||||
owncloud:
|
|
||||||
image: owncloud/server:${OWNCLOUD_VERSION}
|
|
||||||
container_name: owncloud_server
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- ${HTTP_PORT}:8080
|
|
||||||
depends_on:
|
|
||||||
- mariadb
|
|
||||||
- redis
|
|
||||||
environment:
|
|
||||||
- OWNCLOUD_DOMAIN=${OWNCLOUD_DOMAIN}
|
|
||||||
- OWNCLOUD_TRUSTED_DOMAINS=${OWNCLOUD_TRUSTED_DOMAINS}
|
|
||||||
- OWNCLOUD_DB_TYPE=mysql
|
|
||||||
- OWNCLOUD_DB_NAME=owncloud
|
|
||||||
- OWNCLOUD_DB_USERNAME=owncloud
|
|
||||||
- OWNCLOUD_DB_PASSWORD=owncloud
|
|
||||||
- OWNCLOUD_DB_HOST=mariadb
|
|
||||||
- OWNCLOUD_ADMIN_USERNAME=${ADMIN_USERNAME}
|
|
||||||
- OWNCLOUD_ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
|
||||||
- OWNCLOUD_MYSQL_UTF8MB4=true
|
|
||||||
- OWNCLOUD_REDIS_ENABLED=true
|
|
||||||
- OWNCLOUD_REDIS_HOST=redis
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "/usr/bin/healthcheck"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 5
|
|
||||||
volumes:
|
|
||||||
- ../disks/disk1/owncloud_data/:/mnt/data
|
|
||||||
- ../disks/:/mnt/disks
|
|
||||||
|
|
||||||
mariadb:
|
|
||||||
image: mariadb:10.6 # minimum required ownCloud version is 10.9
|
|
||||||
container_name: owncloud_mariadb
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- ${DB_PORT}:3306
|
|
||||||
environment:
|
|
||||||
- MYSQL_ROOT_PASSWORD=owncloud
|
|
||||||
- MYSQL_USER=owncloud
|
|
||||||
- MYSQL_PASSWORD=owncloud
|
|
||||||
- MYSQL_DATABASE=owncloud
|
|
||||||
command: ["--max-allowed-packet=128M", "--innodb-log-file-size=64M"]
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "mysqladmin", "ping", "-u", "root", "--password=owncloud"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
volumes:
|
|
||||||
- mysql:/var/lib/mysql
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: redis:6
|
|
||||||
container_name: owncloud_redis
|
|
||||||
restart: always
|
|
||||||
command: ["--databases", "1"]
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
volumes:
|
|
||||||
- redis:/data
|
|
||||||
44
go.mod
44
go.mod
|
|
@ -3,65 +3,41 @@ module ticket-pimp
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/bwmarrin/discordgo v0.27.1
|
||||||
github.com/imroc/req/v3 v3.35.2
|
github.com/imroc/req/v3 v3.35.2
|
||||||
|
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
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||||
github.com/artsafin/coda-go-client v1.0.4 // indirect
|
|
||||||
github.com/bwmarrin/discordgo v0.27.1 // indirect
|
|
||||||
github.com/bytedance/sonic v1.9.2 // indirect
|
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/deepmap/oapi-codegen v1.13.0 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
|
||||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
|
||||||
github.com/go-playground/validator/v10 v10.14.1 // indirect
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
|
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
|
||||||
github.com/gorilla/websocket v1.4.2 // indirect
|
github.com/gorilla/websocket v1.4.2 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/labstack/echo/v4 v4.10.2 // indirect
|
github.com/jackc/pgx v3.6.2+incompatible // indirect
|
||||||
github.com/labstack/gommon v0.4.0 // indirect
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/onsi/ginkgo/v2 v2.10.0 // indirect
|
github.com/onsi/ginkgo/v2 v2.10.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // 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/stretchr/objx v0.5.0 // 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/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2 // indirect
|
|
||||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
|
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
|
||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
|
||||||
golang.org/x/arch v0.3.0 // 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
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
123
go.sum
123
go.sum
|
|
@ -1,54 +1,21 @@
|
||||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
|
||||||
github.com/artsafin/coda-go-client v1.0.4 h1:FiqMEMgWVpGKEGYBvdhH4lJHPD569vIaOFbF0Q0YX2U=
|
|
||||||
github.com/artsafin/coda-go-client v1.0.4/go.mod h1:WFyZA4RSVNCAnLoQEDMerb+eKxbxMP6+CqbcHmabSHU=
|
|
||||||
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
|
||||||
github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
|
github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
|
||||||
github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
|
||||||
github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM=
|
|
||||||
github.com/bytedance/sonic v1.9.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/deepmap/oapi-codegen v1.13.0 h1:cnFHelhsRQbYvanCUAbRSn/ZpkUb1HPRlQcu8YqSORQ=
|
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
|
||||||
github.com/deepmap/oapi-codegen v1.13.0/go.mod h1:Amy7tbubKY9qkZOXqymI3Z6xSbndmu+atMJheLdyg44=
|
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
|
||||||
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-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
|
||||||
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
|
|
||||||
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
|
||||||
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/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
|
||||||
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=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
|
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
|
||||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
|
@ -58,43 +25,24 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/imroc/req/v3 v3.35.2 h1:psklb5GyhKugTsGQy6seydK/8vMKBJj4R0Bwc/OQahw=
|
github.com/imroc/req/v3 v3.35.2 h1:psklb5GyhKugTsGQy6seydK/8vMKBJj4R0Bwc/OQahw=
|
||||||
github.com/imroc/req/v3 v3.35.2/go.mod h1:c8dXW9N3SJn/DuKVjHHmryV2WO7At9bFtnu1rloiFoo=
|
github.com/imroc/req/v3 v3.35.2/go.mod h1:c8dXW9N3SJn/DuKVjHHmryV2WO7At9bFtnu1rloiFoo=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
|
||||||
|
github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
|
||||||
|
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
||||||
|
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||||
|
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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
|
||||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
|
||||||
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
|
|
||||||
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
|
|
||||||
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
|
|
||||||
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
|
||||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
|
||||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
|
||||||
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
|
||||||
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
|
|
||||||
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
|
||||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
|
||||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
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=
|
||||||
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
|
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
|
||||||
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
|
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
|
||||||
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=
|
||||||
|
|
@ -106,44 +54,22 @@ 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/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0=
|
||||||
|
github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
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/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=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2 h1:VsBj3UD2xyAOu7kJw6O/2jjG2UXLFoBzihqDU9Ofg9M=
|
|
||||||
github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
|
||||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
|
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
|
||||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
|
||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
|
||||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
|
||||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
|
||||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|
||||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
|
||||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
|
||||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
|
||||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||||
|
|
@ -155,32 +81,22 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|
||||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||||
|
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
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/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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|
@ -193,14 +109,9 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
|
||||||
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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
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.0-20210107192922-496545a6307b/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=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
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 {
|
||||||
|
if req.ChannelID == "" {
|
||||||
|
return &ProjectResponse{
|
||||||
|
Project: nil,
|
||||||
|
Message: errors.New("empty channel string"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := wc.GetProjectByChannelID(ctx, req.ChannelID)
|
||||||
|
if err != nil {
|
||||||
|
return &ProjectResponse{
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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("передано пустое имя"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"ticket-pimp/internal/domain"
|
||||||
|
"ticket-pimp/internal/storage/db"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (wc *WorkflowController) Get(ctx context.Context) (*domain.ApplicationConfig, error) {
|
||||||
|
c, err := wc.q.GetConfig(ctx)
|
||||||
|
return &domain.ApplicationConfig{
|
||||||
|
Key: c.TicketKey.String,
|
||||||
|
ID: int(c.TicketID.Int32),
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *WorkflowController) NewKey(ctx context.Context) (*domain.ApplicationConfig, error) {
|
||||||
|
c, err := wc.q.SetNewConfig(ctx)
|
||||||
|
return &domain.ApplicationConfig{
|
||||||
|
Key: c.TicketKey.String,
|
||||||
|
ID: int(c.TicketID.Int32),
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *WorkflowController) ProjectCreate(ctx context.Context, project domain.Project) (*domain.Project, error) {
|
||||||
|
|
||||||
|
tx, err := wc.pool.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
|
qtx := wc.q.WithTx(tx)
|
||||||
|
|
||||||
|
appconfig, err := qtx.SetNewConfig(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
project.ShortName = fmt.Sprintf(
|
||||||
|
"%s-%d",
|
||||||
|
appconfig.TicketKey.String,
|
||||||
|
appconfig.TicketID.Int32,
|
||||||
|
)
|
||||||
|
|
||||||
|
project.ID = string(appconfig.TicketID.Int32)
|
||||||
|
|
||||||
|
projectRow, err := qtx.CreateTicket(ctx, db.CreateTicketParams{
|
||||||
|
Key: pgtype.Text{String: project.ShortName, Valid: true},
|
||||||
|
Channelid: pgtype.Text{String: project.ChannelID, Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback(ctx)
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
tx.Commit(ctx)
|
||||||
|
fmt.Println(projectRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &project, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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})
|
||||||
|
if err != nil {
|
||||||
|
if err == pgx.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
proj = domain.Project{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saves current channel as project's channel;
|
||||||
|
func (wc *WorkflowController) InitProjectInChannel(ctx context.Context, channelID string, key string) (*domain.Project, error) {
|
||||||
|
dbTicket, err := wc.q.GetTicketByChannelID(ctx, pgtype.Text{String: channelID, Valid: true})
|
||||||
|
if err == pgx.ErrNoRows {
|
||||||
|
dbTicket, err = wc.q.CreateTicket(
|
||||||
|
ctx,
|
||||||
|
db.CreateTicketParams{
|
||||||
|
Key: pgtype.Text{String: key, Valid: true},
|
||||||
|
Channelid: pgtype.Text{String: channelID, Valid: true},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &domain.Project{
|
||||||
|
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,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"ticket-pimp/internal/domain"
|
||||||
|
"ticket-pimp/internal/storage/db"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WriteTaskToDB
|
||||||
|
/*
|
||||||
|
Makes an SQL query to create new tasks row from domain.Task entity
|
||||||
|
- Creator field: telegram nickname or Discord's Mention();
|
||||||
|
- Creator link (tg ID) in telegram case;
|
||||||
|
- Description from telegram/discord message bodies;
|
||||||
|
*/
|
||||||
|
func (wc *WorkflowController) WriteTaskToDB(t *domain.Task) (*domain.Task, error) {
|
||||||
|
dbtask, err := wc.q.InsertTask(context.TODO(), db.InsertTaskParams{
|
||||||
|
Creator: pgtype.Text{String: t.Creator, Valid: true},
|
||||||
|
CreatorLink: pgtype.Text{
|
||||||
|
String: t.CreatorLink,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
Description: pgtype.Text{
|
||||||
|
String: t.Description,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to create task at the db: %v", err)
|
||||||
|
}
|
||||||
|
// ------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
task := newConvertable(&dbtask).ExtractDomain()
|
||||||
|
return task, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitTask
|
||||||
|
/*
|
||||||
|
Runs the following:
|
||||||
|
- Use WriteTaskToDB method to make a new task row in the db;
|
||||||
|
- init new discord bot instance;
|
||||||
|
-
|
||||||
|
|
||||||
|
Possible errors:
|
||||||
|
- db record couldn't be created;
|
||||||
|
- bot couldn't be inited;
|
||||||
|
- bot session couldn't be started;
|
||||||
|
- thread couldn't be started;
|
||||||
|
- first task message couldn't be edited;
|
||||||
|
*/
|
||||||
|
func (wc *WorkflowController) InitTask(t *domain.Task) (*domain.Task, error) {
|
||||||
|
|
||||||
|
task, err := wc.WriteTaskToDB(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to create task at the db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализируем новый клиент дискорда
|
||||||
|
// [ ] Нездоровое получение параметров клиента из os..
|
||||||
|
var (
|
||||||
|
token = os.Getenv("DISCORD_TOKEN")
|
||||||
|
forumChannelID = os.Getenv("TASKS_CHANNEL")
|
||||||
|
)
|
||||||
|
|
||||||
|
s, err := discordgo.New("Bot " + token)
|
||||||
|
if err != nil {
|
||||||
|
return task, fmt.Errorf("unable to create discord session: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Open(); err != nil {
|
||||||
|
return task, fmt.Errorf("cannot open the session: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := discordgo.MessageSend{
|
||||||
|
Content: task.DiscordMessage(domain.NewTaskState()),
|
||||||
|
Components: []discordgo.MessageComponent{
|
||||||
|
discordgo.ActionsRow{
|
||||||
|
Components: []discordgo.MessageComponent{
|
||||||
|
discordgo.Button{
|
||||||
|
Label: "Start",
|
||||||
|
Style: discordgo.SuccessButton,
|
||||||
|
Disabled: false,
|
||||||
|
CustomID: "task_start",
|
||||||
|
},
|
||||||
|
discordgo.Button{
|
||||||
|
Label: "Close",
|
||||||
|
Style: discordgo.DangerButton,
|
||||||
|
Disabled: true,
|
||||||
|
CustomID: "task_close",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
th, err := s.ForumThreadStartComplex(
|
||||||
|
forumChannelID,
|
||||||
|
&discordgo.ThreadStart{
|
||||||
|
Name: fmt.Sprintf("Task ID: %d, by %s", task.ID, task.Creator),
|
||||||
|
},
|
||||||
|
&msg,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return task, fmt.Errorf("unable to update channel: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wc.UpdateTasksMessageID(context.TODO(), th.ID, task.ID)
|
||||||
|
if err != nil {
|
||||||
|
return task, fmt.Errorf("unable to set discord message to task: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return task, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *WorkflowController) UpdateTasksMessageID(ctx context.Context, msgID string, taskID int32) error {
|
||||||
|
err := wc.q.UpdateTaskWithMessageID(context.TODO(), db.UpdateTaskWithMessageIDParams{
|
||||||
|
Messageid: pgtype.Text{String: msgID, Valid: true},
|
||||||
|
ID: taskID,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *WorkflowController) UpdateTask(id string, opt int, user string) (*TaskConvertable, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
dbtask db.Task
|
||||||
|
)
|
||||||
|
switch opt {
|
||||||
|
case 0:
|
||||||
|
dbtask, err = wc.q.StartTask(context.TODO(), db.StartTaskParams{
|
||||||
|
UpdatedAt: pgtype.Timestamptz{Time: time.Now(), InfinityModifier: 0, Valid: true},
|
||||||
|
Assignee: pgtype.Text{String: user, Valid: true},
|
||||||
|
Messageid: pgtype.Text{String: id, Valid: true},
|
||||||
|
})
|
||||||
|
return &TaskConvertable{&dbtask}, err
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
dbtask, err = wc.q.CloseTask(context.TODO(), db.CloseTaskParams{
|
||||||
|
DeletedAt: pgtype.Timestamptz{Time: time.Now(), InfinityModifier: 0, Valid: true},
|
||||||
|
Assignee: pgtype.Text{String: user, Valid: true},
|
||||||
|
Messageid: pgtype.Text{String: id, Valid: true},
|
||||||
|
})
|
||||||
|
return &TaskConvertable{&dbtask}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TaskConvertable{&dbtask}, nil
|
||||||
|
}
|
||||||
|
|
@ -1,37 +1,25 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"ticket-pimp/internal/domain"
|
"ticket-pimp/internal/domain"
|
||||||
"ticket-pimp/internal/services"
|
"ticket-pimp/internal/storage/db"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WorkflowController struct {
|
// FullProjectInit
|
||||||
iGit services.IGit
|
/*
|
||||||
iCloud services.ICloud
|
Deprecated method to create a project with all related data:
|
||||||
iCoda services.ICoda
|
- git;
|
||||||
}
|
- git for the project's build;
|
||||||
|
- cloud folder;
|
||||||
func NewWorkflowController(
|
*/
|
||||||
git services.IGit,
|
func (wc *WorkflowController) FullProjectInit(name, key, id string) (string, error) {
|
||||||
cloud services.ICloud,
|
|
||||||
coda services.ICoda,
|
|
||||||
) *WorkflowController {
|
|
||||||
return &WorkflowController{
|
|
||||||
iGit: git,
|
|
||||||
iCloud: cloud,
|
|
||||||
|
|
||||||
iCoda: coda,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type IWorkflowController interface {
|
|
||||||
Workflow(name, key, id string) (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wc *WorkflowController) Workflow(name, key, id string) (string, error) {
|
|
||||||
|
|
||||||
appKey := fmt.Sprintf("%s-%s", key, id)
|
appKey := fmt.Sprintf("%s-%s", key, id)
|
||||||
|
|
||||||
|
|
@ -45,17 +33,18 @@ func (wc *WorkflowController) Workflow(name, key, id string) (string, error) {
|
||||||
|
|
||||||
go func(ref **domain.Git) {
|
go func(ref **domain.Git) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
*ref, _ = wc.iGit.CreateRepo(appKey)
|
*ref, _ = wc.IGit.CreateRepo(appKey)
|
||||||
}(&git)
|
}(&git)
|
||||||
|
|
||||||
go func(ref **domain.Git) {
|
go func(ref **domain.Git) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
*ref, _ = wc.iGit.CreateRepo(appKey + "-build")
|
*ref, _ = wc.IGit.CreateRepo(appKey + "-build")
|
||||||
}(&gitBuild)
|
}(&gitBuild)
|
||||||
|
|
||||||
go func(ref **domain.Folder) {
|
go func(ref **domain.Folder) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
*ref, _ = wc.iCloud.CreateFolder(appKey)
|
|
||||||
|
*ref = wc.ICloud.CreateFolder(appKey).Folder
|
||||||
}(&cloud)
|
}(&cloud)
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
@ -79,8 +68,18 @@ func (wc *WorkflowController) Workflow(name, key, id string) (string, error) {
|
||||||
} else {
|
} else {
|
||||||
cloudResult = cloud.PrivateURL
|
cloudResult = cloud.PrivateURL
|
||||||
}
|
}
|
||||||
|
ctx := context.TODO()
|
||||||
|
|
||||||
wc.iCoda.CreateApp(domain.CodaApplication{
|
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,
|
ID: appKey,
|
||||||
Summary: strings.TrimSpace(name),
|
Summary: strings.TrimSpace(name),
|
||||||
Git: gitResult,
|
Git: gitResult,
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ticket-pimp/internal/domain"
|
||||||
|
"ticket-pimp/internal/services"
|
||||||
|
"ticket-pimp/internal/storage/db"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WorkflowController struct {
|
||||||
|
IGit services.IGit
|
||||||
|
ICloud services.ICloud
|
||||||
|
ICoda services.ICoda
|
||||||
|
pool *pgxpool.Pool
|
||||||
|
q *db.Queries
|
||||||
|
ATags []discordgo.ForumTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWorkflowController(
|
||||||
|
git services.IGit,
|
||||||
|
cloud services.ICloud,
|
||||||
|
coda services.ICoda,
|
||||||
|
pool *pgxpool.Pool,
|
||||||
|
) *WorkflowController {
|
||||||
|
return &WorkflowController{
|
||||||
|
IGit: git,
|
||||||
|
ICloud: cloud,
|
||||||
|
ICoda: coda,
|
||||||
|
pool: pool,
|
||||||
|
q: db.New(pool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProjectResponse struct {
|
||||||
|
Project *domain.Project
|
||||||
|
Message error
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskConvertable struct {
|
||||||
|
*db.Task
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConvertable(db *db.Task) *TaskConvertable {
|
||||||
|
return &TaskConvertable{
|
||||||
|
Task: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TaskConvertable) ExtractDomain() *domain.Task {
|
||||||
|
return &domain.Task{
|
||||||
|
ID: t.ID,
|
||||||
|
// Summary: "",
|
||||||
|
Description: t.Description.String,
|
||||||
|
Creator: t.Creator.String,
|
||||||
|
CreatorLink: t.CreatorLink.String,
|
||||||
|
Assignee: t.Assignee.String,
|
||||||
|
CreatedAt: t.CreatedAt.Time,
|
||||||
|
DeletedAt: t.DeletedAt.Time,
|
||||||
|
UpdatedAt: t.UpdatedAt.Time,
|
||||||
|
// URL: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Git GitConfig
|
||||||
|
Cloud CloudConfig
|
||||||
|
Coda CodaConfig
|
||||||
|
DB DBConfig
|
||||||
|
Telegram TelegramConfig
|
||||||
|
Discord DiscordConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type GitConfig struct {
|
||||||
|
BaseUrl string
|
||||||
|
Token string
|
||||||
|
User string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloudConfig struct {
|
||||||
|
BaseUrl string
|
||||||
|
User string
|
||||||
|
Pass string
|
||||||
|
RootDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CodaConfig struct {
|
||||||
|
Farm string
|
||||||
|
Develop string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DBConfig struct {
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
Name string
|
||||||
|
User string
|
||||||
|
Pass string
|
||||||
|
SslMode string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TelegramConfig struct {
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscordConfig struct {
|
||||||
|
Token string
|
||||||
|
IsProjectChannel string
|
||||||
|
IsTaskForum string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApplicationConfig struct {
|
||||||
|
Key string
|
||||||
|
ID int
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitConfig
|
||||||
|
// InitConfig function reads provided file and setup envirmental variables;
|
||||||
|
func InitConfig(envFilePath string) Config {
|
||||||
|
err := godotenv.Load(envFilePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error while loading env file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Config{
|
||||||
|
Git: GitConfig{
|
||||||
|
BaseUrl: os.Getenv("GIT_BASE_URL"),
|
||||||
|
Token: os.Getenv("GIT_TOKEN"),
|
||||||
|
User: os.Getenv("GIT_USER"),
|
||||||
|
},
|
||||||
|
Cloud: CloudConfig{
|
||||||
|
BaseUrl: os.Getenv("CLOUD_BASE_URL"),
|
||||||
|
User: os.Getenv("CLOUD_USER"),
|
||||||
|
Pass: os.Getenv("CLOUD_PASS"),
|
||||||
|
RootDir: os.Getenv("ROOTDIR"),
|
||||||
|
},
|
||||||
|
Coda: CodaConfig{
|
||||||
|
Farm: os.Getenv("CODA_TOKEN1"),
|
||||||
|
Develop: os.Getenv("CODA_TOKEN2"),
|
||||||
|
},
|
||||||
|
DB: DBConfig{
|
||||||
|
Host: os.Getenv("DB_HOST"),
|
||||||
|
Port: os.Getenv("DB_PORT"),
|
||||||
|
Name: os.Getenv("DB_NAME"),
|
||||||
|
User: os.Getenv("DB_USER"),
|
||||||
|
Pass: os.Getenv("DB_PASS"),
|
||||||
|
SslMode: os.Getenv("SSLMODE"),
|
||||||
|
},
|
||||||
|
Telegram: TelegramConfig{
|
||||||
|
Token: os.Getenv("TG_API"),
|
||||||
|
},
|
||||||
|
Discord: DiscordConfig{
|
||||||
|
Token: os.Getenv("DISCORD_TOKEN"),
|
||||||
|
IsProjectChannel: os.Getenv("PROJECTS_CHANNEL_GROUP"),
|
||||||
|
IsTaskForum: os.Getenv("TASKS_CHANNEL"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type Folder struct {
|
type Folder struct {
|
||||||
Title string // k
|
Title string // k
|
||||||
|
|
@ -51,14 +54,69 @@ func (r *Row) NewCell(col string, value string) *Row {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Task struct {
|
type Task struct {
|
||||||
|
ID int32
|
||||||
Summary string
|
Summary string
|
||||||
Description string
|
Description string
|
||||||
Creator string
|
Creator string
|
||||||
CreatorLink string
|
CreatorLink string
|
||||||
|
|
||||||
|
Assignee string
|
||||||
|
|
||||||
|
CreatedAt time.Time
|
||||||
|
DeletedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
|
||||||
URL string
|
URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TaskState int
|
||||||
|
|
||||||
|
const (
|
||||||
|
new TaskState = iota
|
||||||
|
inprogress
|
||||||
|
done
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewTaskState() TaskState {
|
||||||
|
return TaskState(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InrpogressTaskState() TaskState {
|
||||||
|
return TaskState(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DoneTaskState() TaskState {
|
||||||
|
return TaskState(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a string for discordgo.DiscordMessage.Content
|
||||||
|
// State: New task;
|
||||||
|
func (t *Task) DiscordMessage(ts TaskState) string {
|
||||||
|
switch ts {
|
||||||
|
case new:
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Created at: %s \n>>> %s\n",
|
||||||
|
t.CreatedAt,
|
||||||
|
t.Description,
|
||||||
|
)
|
||||||
|
case inprogress:
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"**TaskID: %d** Started by: %s\n🚀 Started at: %s\n",
|
||||||
|
t.ID,
|
||||||
|
t.Assignee,
|
||||||
|
t.UpdatedAt,
|
||||||
|
)
|
||||||
|
case done:
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"**TaskID: %d** Closed by: %s\n✅ Closed at: %s\n",
|
||||||
|
t.ID,
|
||||||
|
t.Assignee,
|
||||||
|
t.DeletedAt,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return "task state not provided"
|
||||||
|
}
|
||||||
|
|
||||||
func NewTask(summ, desc, c, cLink string) *Task {
|
func NewTask(summ, desc, c, cLink string) *Task {
|
||||||
return &Task{
|
return &Task{
|
||||||
Summary: summ,
|
Summary: summ,
|
||||||
|
|
@ -68,10 +126,6 @@ func NewTask(summ, desc, c, cLink string) *Task {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConversionLog struct {
|
|
||||||
Advertiser []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Git struct {
|
type Git struct {
|
||||||
Name string `json:"name"` // "poop"
|
Name string `json:"name"` // "poop"
|
||||||
FullName string `json:"full_name"` // "developer/poop"
|
FullName string `json:"full_name"` // "developer/poop"
|
||||||
|
|
@ -83,53 +137,23 @@ type Git struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Project struct {
|
type Project struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"` //15
|
||||||
ShortName string `json:"shortName"`
|
ShortName string `json:"shortName"` //key-15
|
||||||
Name string `json:"name"`
|
Name string `json:"name"` //default project name
|
||||||
|
ChannelID string `json:"channel_id"` //123412341234
|
||||||
|
|
||||||
|
ProjectGit string `json:"project_git"` //https://github.com/mobilerino/dap-108
|
||||||
|
BuildGit string `json:"build_git"` //https://github.com/mobilerino/dap-108-build
|
||||||
|
Cloud string `json:"cloud"` //http://82.151.222.22:7000/f/86658
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectID struct {
|
func (p *Project) DiscordString() string {
|
||||||
ID string `json:"id"`
|
|
||||||
}
|
return fmt.Sprintf(
|
||||||
|
"## Project info:\n> 🔑 key: %s\n> 📂 folder: %s\n> 👾 project git: %s\n> 🚀 build git: %s\n",
|
||||||
type IssueCreateRequest struct {
|
p.ShortName,
|
||||||
ProjectID ProjectID `json:"project"`
|
p.Cloud,
|
||||||
Key string `json:"idReadable"`
|
p.ProjectGit,
|
||||||
ID string `json:"id"`
|
p.BuildGit,
|
||||||
Summary string `json:"summary"`
|
)
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// [ ] try `,omitempty` to remove extra struct;
|
|
||||||
|
|
||||||
type IssueUpdateRequest struct {
|
|
||||||
IssueCreateRequest
|
|
||||||
CustomFields []CustomField `json:"customFields"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CustomField struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"$type"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProjectsList struct {
|
|
||||||
Projects []Project
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find needed project.ID in the project's list
|
|
||||||
func (plist *ProjectsList) FindProjectByName(searchName string) (string, error) {
|
|
||||||
|
|
||||||
projectID := ""
|
|
||||||
|
|
||||||
for _, elem := range plist.Projects {
|
|
||||||
if elem.ShortName == searchName {
|
|
||||||
projectID = elem.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if projectID == "" {
|
|
||||||
return "", fmt.Errorf("project %s doesn't exist", searchName)
|
|
||||||
}
|
|
||||||
return projectID, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package helpers
|
||||||
|
|
||||||
|
type ErrorMessage struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GitNaming(input string) string {
|
func Cut(input string) string {
|
||||||
// Remove leading and trailing whitespace
|
// Remove leading and trailing whitespace
|
||||||
input = strings.TrimSpace(input)
|
input = strings.TrimSpace(input)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ var tests = []test{
|
||||||
func TestGitNaming(t *testing.T) {
|
func TestGitNaming(t *testing.T) {
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
if output := GitNaming(test.arg); output != test.expected {
|
if output := Cut(test.arg); output != test.expected {
|
||||||
t.Errorf("Output %q not equal to expected %q", output, test.expected)
|
t.Errorf("Output %q not equal to expected %q", output, test.expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"ticket-pimp/internal/domain"
|
"ticket-pimp/internal/domain"
|
||||||
"ticket-pimp/internal/helpers"
|
"ticket-pimp/internal/helpers"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -11,60 +13,98 @@ import (
|
||||||
|
|
||||||
type Cloud struct {
|
type Cloud struct {
|
||||||
*CommonClient
|
*CommonClient
|
||||||
|
Config domain.CloudConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type ICloud interface {
|
type ICloud interface {
|
||||||
CreateFolder(name string) (*domain.Folder, error)
|
CreateFolder(name string) Response
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCloud(base, user, pass string) *Cloud {
|
func NewCloud(conf domain.CloudConfig) *Cloud {
|
||||||
|
|
||||||
client := NewClient().
|
client := NewClient().
|
||||||
SetTimeout(5*time.Second).
|
SetTimeout(5*time.Second).
|
||||||
SetCommonBasicAuth(user, pass).
|
SetCommonBasicAuth(conf.User, conf.Pass).
|
||||||
SetBaseURL(base)
|
SetBaseURL(conf.BaseUrl)
|
||||||
|
|
||||||
return &Cloud{
|
return &Cloud{
|
||||||
CommonClient: &CommonClient{
|
CommonClient: &CommonClient{
|
||||||
client,
|
client,
|
||||||
},
|
},
|
||||||
|
Config: conf,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cloud) CreateFolder(name string) (*domain.Folder, error) {
|
type Response struct {
|
||||||
rootDir := os.Getenv("ROOTDIR")
|
Folder *domain.Folder
|
||||||
user := os.Getenv("CLOUD_USER")
|
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/"
|
davPath := "/remote.php/dav/files/"
|
||||||
parentPath := "/apps/files/?dir="
|
parentPath := "/apps/files/?dir="
|
||||||
|
|
||||||
name = helpers.GitNaming(name)
|
name = helpers.Cut(name)
|
||||||
|
|
||||||
cloud := domain.Folder{
|
R.Folder = &domain.Folder{
|
||||||
Title: name,
|
Title: name,
|
||||||
PrivateURL: "",
|
PrivateURL: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cloud := domain.Folder{
|
||||||
|
// Title: name,
|
||||||
|
// PrivateURL: "",
|
||||||
|
// }
|
||||||
|
|
||||||
requestPath := davPath + user + rootDir + name
|
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)
|
Send("MKCOL", requestPath)
|
||||||
|
|
||||||
|
if err != nil { // Error handling.
|
||||||
|
log.Println("error:", err)
|
||||||
|
|
||||||
|
// Херовая обработка ошибки:
|
||||||
|
// error while cloud folder creation: bad response, raw content:
|
||||||
|
// <?xml version="1.0" encoding="utf-8"?>
|
||||||
|
// <d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns">
|
||||||
|
// <s:exception>Sabre\DAV\Exception\MethodNotAllowed</s:exception>
|
||||||
|
// <s:message>The resource you tried to create already exists</s:message>
|
||||||
|
// </d:error>
|
||||||
|
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() {
|
if resp.IsSuccessState() {
|
||||||
// Set stupid URL to the d entity
|
// 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
|
// Try to set short URL to the d entity
|
||||||
if err := c.setPrivateURL(requestPath, &cloud); err != nil {
|
if err := c.setPrivateURL(requestPath, R.Folder); err != nil {
|
||||||
return &cloud, err
|
return R
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
fmt.Println(resp.Status)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &cloud, nil
|
return R
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cloud) setPrivateURL(requestPath string, cloud *domain.Folder) error {
|
func (c *Cloud) setPrivateURL(requestPath string, cloud *domain.Folder) error {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package services
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"ticket-pimp/internal/domain"
|
"ticket-pimp/internal/domain"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -12,6 +11,7 @@ import (
|
||||||
|
|
||||||
type Coda struct {
|
type Coda struct {
|
||||||
*CommonClient
|
*CommonClient
|
||||||
|
Config domain.CodaConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type ICoda interface {
|
type ICoda interface {
|
||||||
|
|
@ -21,17 +21,18 @@ type ICoda interface {
|
||||||
GetRowLink(id string) (string, error)
|
GetRowLink(id string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCodaClient(token string) *Coda {
|
func NewCodaClient(conf domain.CodaConfig) *Coda {
|
||||||
|
|
||||||
client := NewClient().
|
client := NewClient().
|
||||||
SetTimeout(15 * time.Second).
|
SetTimeout(15 * time.Second).
|
||||||
SetCommonBearerAuthToken(token).
|
SetCommonBearerAuthToken(conf.Farm).
|
||||||
SetBaseURL("https://coda.io/apis/v1")
|
SetBaseURL("https://coda.io/apis/v1")
|
||||||
|
|
||||||
return &Coda{
|
return &Coda{
|
||||||
CommonClient: &CommonClient{
|
CommonClient: &CommonClient{
|
||||||
client,
|
client,
|
||||||
},
|
},
|
||||||
|
Config: conf,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +58,7 @@ func (c *Coda) CreateApp(task domain.CodaApplication) {
|
||||||
resp, _ := c.R().
|
resp, _ := c.R().
|
||||||
SetBody(task).
|
SetBody(task).
|
||||||
SetContentType("application/json").
|
SetContentType("application/json").
|
||||||
SetBearerAuthToken(os.Getenv("CODA_TOKEN2")).
|
SetBearerAuthToken(c.Config.Develop).
|
||||||
Post("/docs/Ic3IZpQ3Wk/hooks/automation/grid-auto-NlUwM7F7Cr")
|
Post("/docs/Ic3IZpQ3Wk/hooks/automation/grid-auto-NlUwM7F7Cr")
|
||||||
|
|
||||||
fmt.Print(resp)
|
fmt.Print(resp)
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@ type IGit interface {
|
||||||
CreateRepo(name string) (*domain.Git, error)
|
CreateRepo(name string) (*domain.Git, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGit(base, token string) *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",
|
||||||
"Authorization": "Token " + token,
|
"Authorization": "Token " + conf.Token,
|
||||||
"X-GitHub-Api-Version": "2022-11-28",
|
"X-GitHub-Api-Version": "2022-11-28",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +26,7 @@ func NewGit(base, token string) *Git {
|
||||||
client := NewClient().
|
client := NewClient().
|
||||||
SetTimeout(5 * time.Second).
|
SetTimeout(5 * time.Second).
|
||||||
SetCommonHeaders(headers).
|
SetCommonHeaders(headers).
|
||||||
SetBaseURL(base)
|
SetBaseURL(conf.BaseUrl)
|
||||||
|
|
||||||
return &Git{
|
return &Git{
|
||||||
CommonClient: &CommonClient{client},
|
CommonClient: &CommonClient{client},
|
||||||
|
|
@ -49,19 +49,15 @@ func (gb *Git) CreateRepo(name string) (*domain.Git, error) {
|
||||||
return repo, nil
|
return repo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type request struct {
|
type gitCreateRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Private bool `json:"private"`
|
Private bool `json:"private"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type permissionRequest struct {
|
|
||||||
Perm string `json:"permission"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gb *Git) newRepo(name string) (*domain.Git, error) {
|
func (gb *Git) newRepo(name string) (*domain.Git, error) {
|
||||||
name = helpers.GitNaming(name)
|
name = helpers.Cut(name)
|
||||||
|
|
||||||
payload := request{
|
payload := gitCreateRequest{
|
||||||
Name: name,
|
Name: name,
|
||||||
Private: true,
|
Private: true,
|
||||||
}
|
}
|
||||||
|
|
@ -81,13 +77,16 @@ func (gb *Git) newRepo(name string) (*domain.Git, error) {
|
||||||
return &git, nil
|
return &git, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type gitSetPermissionRequest struct {
|
||||||
|
Perm string `json:"permission"`
|
||||||
|
}
|
||||||
|
|
||||||
func (gb *Git) defaultGroupAsCollaborator(git *domain.Git) (*domain.Git, error) {
|
func (gb *Git) defaultGroupAsCollaborator(git *domain.Git) (*domain.Git, error) {
|
||||||
|
|
||||||
payload := permissionRequest{
|
payload := gitSetPermissionRequest{
|
||||||
Perm: "push",
|
Perm: "push",
|
||||||
}
|
}
|
||||||
|
|
||||||
///orgs/manurlerino/teams/devs/repos/manurlerino/x
|
|
||||||
respURL := "/orgs/mobilerino/teams/devs/repos/mobilerino/" + git.Name
|
respURL := "/orgs/mobilerino/teams/devs/repos/mobilerino/" + git.Name
|
||||||
|
|
||||||
resp, _ := gb.R().
|
resp, _ := gb.R().
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.23.0
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBTX interface {
|
||||||
|
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||||
|
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||||
|
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db DBTX) *Queries {
|
||||||
|
return &Queries{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Queries struct {
|
||||||
|
db DBTX
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
||||||
|
return &Queries{
|
||||||
|
db: tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.23.0
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Appconfig struct {
|
||||||
|
TicketKey pgtype.Text
|
||||||
|
TicketID pgtype.Int4
|
||||||
|
}
|
||||||
|
|
||||||
|
type Task struct {
|
||||||
|
ID int32
|
||||||
|
Creator pgtype.Text
|
||||||
|
CreatorLink pgtype.Text
|
||||||
|
Messageid pgtype.Text
|
||||||
|
Description pgtype.Text
|
||||||
|
Assignee pgtype.Text
|
||||||
|
CreatedAt pgtype.Timestamptz
|
||||||
|
DeletedAt pgtype.Timestamptz
|
||||||
|
UpdatedAt pgtype.Timestamptz
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ticket struct {
|
||||||
|
ID int32
|
||||||
|
Key pgtype.Text
|
||||||
|
Channelid pgtype.Text
|
||||||
|
ProjectGit pgtype.Text
|
||||||
|
BuildGit pgtype.Text
|
||||||
|
Folder pgtype.Text
|
||||||
|
CreatedAt pgtype.Timestamptz
|
||||||
|
DeletedAt pgtype.Timestamptz
|
||||||
|
UpdatedAt pgtype.Timestamptz
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,491 @@
|
||||||
|
// 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 closeTask = `-- name: CloseTask :one
|
||||||
|
UPDATE tasks
|
||||||
|
SET deleted_at = $1, assignee = $2
|
||||||
|
WHERE messageID = $3
|
||||||
|
RETURNING id, creator, creator_link, messageid, description, assignee, created_at, deleted_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CloseTaskParams struct {
|
||||||
|
DeletedAt pgtype.Timestamptz
|
||||||
|
Assignee pgtype.Text
|
||||||
|
Messageid pgtype.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CloseTask(ctx context.Context, arg CloseTaskParams) (Task, error) {
|
||||||
|
row := q.db.QueryRow(ctx, closeTask, arg.DeletedAt, arg.Assignee, arg.Messageid)
|
||||||
|
var i Task
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Creator,
|
||||||
|
&i.CreatorLink,
|
||||||
|
&i.Messageid,
|
||||||
|
&i.Description,
|
||||||
|
&i.Assignee,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.DeletedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTicket = `-- name: CreateTicket :one
|
||||||
|
INSERT INTO tickets (
|
||||||
|
key, channelID
|
||||||
|
) 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 getTaskByID = `-- name: GetTaskByID :one
|
||||||
|
SELECT id, creator, creator_link, messageid, description, assignee, created_at, deleted_at, updated_at FROM tasks WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetTaskByID(ctx context.Context, id int32) (Task, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getTaskByID, id)
|
||||||
|
var i Task
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Creator,
|
||||||
|
&i.CreatorLink,
|
||||||
|
&i.Messageid,
|
||||||
|
&i.Description,
|
||||||
|
&i.Assignee,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.DeletedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTaskByMessage = `-- name: GetTaskByMessage :one
|
||||||
|
SELECT id, creator, creator_link, messageid, description, assignee, created_at, deleted_at, updated_at FROM tasks WHERE messageID = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetTaskByMessage(ctx context.Context, messageid pgtype.Text) (Task, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getTaskByMessage, messageid)
|
||||||
|
var i Task
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Creator,
|
||||||
|
&i.CreatorLink,
|
||||||
|
&i.Messageid,
|
||||||
|
&i.Description,
|
||||||
|
&i.Assignee,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.DeletedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTicketByChannelID = `-- name: GetTicketByChannelID :one
|
||||||
|
SELECT id, key, channelid, project_git, build_git, folder, created_at, deleted_at, updated_at FROM tickets WHERE channelID = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
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 insertTask = `-- name: InsertTask :one
|
||||||
|
INSERT INTO tasks (
|
||||||
|
creator, creator_link, description
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3
|
||||||
|
)
|
||||||
|
RETURNING id, creator, creator_link, messageid, description, assignee, created_at, deleted_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type InsertTaskParams struct {
|
||||||
|
Creator pgtype.Text
|
||||||
|
CreatorLink pgtype.Text
|
||||||
|
Description pgtype.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) InsertTask(ctx context.Context, arg InsertTaskParams) (Task, error) {
|
||||||
|
row := q.db.QueryRow(ctx, insertTask, arg.Creator, arg.CreatorLink, arg.Description)
|
||||||
|
var i Task
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Creator,
|
||||||
|
&i.CreatorLink,
|
||||||
|
&i.Messageid,
|
||||||
|
&i.Description,
|
||||||
|
&i.Assignee,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.DeletedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const listTasksByCreator = `-- name: ListTasksByCreator :many
|
||||||
|
SELECT id, creator, creator_link, messageid, description, assignee, created_at, deleted_at, updated_at FROM tasks WHERE creator_link = $1 AND deleted_at is NULL
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListTasksByCreator(ctx context.Context, creatorLink pgtype.Text) ([]Task, error) {
|
||||||
|
rows, err := q.db.Query(ctx, listTasksByCreator, creatorLink)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Task
|
||||||
|
for rows.Next() {
|
||||||
|
var i Task
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Creator,
|
||||||
|
&i.CreatorLink,
|
||||||
|
&i.Messageid,
|
||||||
|
&i.Description,
|
||||||
|
&i.Assignee,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.DeletedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const listTickets = `-- name: ListTickets :many
|
||||||
|
SELECT id, key, channelid, project_git, build_git, folder, created_at, deleted_at, updated_at FROM tickets WHERE deleted_at IS NULL
|
||||||
|
`
|
||||||
|
|
||||||
|
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 startTask = `-- name: StartTask :one
|
||||||
|
UPDATE tasks
|
||||||
|
SET updated_at = $1, assignee = $2
|
||||||
|
WHERE messageID = $3
|
||||||
|
RETURNING id, creator, creator_link, messageid, description, assignee, created_at, deleted_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type StartTaskParams struct {
|
||||||
|
UpdatedAt pgtype.Timestamptz
|
||||||
|
Assignee pgtype.Text
|
||||||
|
Messageid pgtype.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) StartTask(ctx context.Context, arg StartTaskParams) (Task, error) {
|
||||||
|
row := q.db.QueryRow(ctx, startTask, arg.UpdatedAt, arg.Assignee, arg.Messageid)
|
||||||
|
var i Task
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Creator,
|
||||||
|
&i.CreatorLink,
|
||||||
|
&i.Messageid,
|
||||||
|
&i.Description,
|
||||||
|
&i.Assignee,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.DeletedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTaskWithMessageID = `-- name: UpdateTaskWithMessageID :exec
|
||||||
|
UPDATE tasks
|
||||||
|
SET messageID = $1
|
||||||
|
WHERE id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateTaskWithMessageIDParams struct {
|
||||||
|
Messageid pgtype.Text
|
||||||
|
ID int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateTaskWithMessageID(ctx context.Context, arg UpdateTaskWithMessageIDParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, updateTaskWithMessageID, arg.Messageid, arg.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTicketBuildGit = `-- name: UpdateTicketBuildGit :one
|
||||||
|
UPDATE tickets
|
||||||
|
SET build_git = $1, updated_at = $2
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
development:
|
||||||
|
dialect: postgres
|
||||||
|
datasource: host=postgres dbname=tickets user=postgres password=postgres sslmode=disable
|
||||||
|
dir: migrate
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
-- +migrate Up
|
||||||
|
CREATE TABLE appconfig (
|
||||||
|
ticket_key VARCHAR(5),
|
||||||
|
ticket_id INT
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO appconfig (ticket_key, ticket_id) VALUES ('xpp', 1);
|
||||||
|
|
||||||
|
CREATE TABLE tickets (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
key VARCHAR(10),
|
||||||
|
channelID VARCHAR(255),
|
||||||
|
project_git VARCHAR(255),
|
||||||
|
build_git VARCHAR(255),
|
||||||
|
folder VARCHAR(255),
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ DEFAULT current_timestamp,
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
updated_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE tasks (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
creator VARCHAR(255),
|
||||||
|
creator_link VARCHAR(255),
|
||||||
|
messageID VARCHAR(255),
|
||||||
|
|
||||||
|
description TEXT,
|
||||||
|
assignee VARCHAR(255),
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ DEFAULT current_timestamp,
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
updated_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
DROP TABLE tickets;
|
||||||
|
DROP TABLE appconfig;
|
||||||
|
DROP TABLE tasks;
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
version: "2"
|
||||||
|
sql:
|
||||||
|
- engine: "postgresql"
|
||||||
|
queries: "sqlc/queries.sql"
|
||||||
|
schema: "migrate"
|
||||||
|
gen:
|
||||||
|
go:
|
||||||
|
package: "db"
|
||||||
|
sql_package: "pgx/v5"
|
||||||
|
out: "db"
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
-- name: GetConfig :one
|
||||||
|
SELECT ticket_key, ticket_id
|
||||||
|
FROM appconfig;
|
||||||
|
|
||||||
|
-- name: SetNewConfig :one
|
||||||
|
UPDATE appconfig
|
||||||
|
SET ticket_id = ticket_id + 1
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: CreateTicket :one
|
||||||
|
INSERT INTO tickets (
|
||||||
|
key, channelID
|
||||||
|
) VALUES (
|
||||||
|
$1, $2
|
||||||
|
)
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- name: ListTicketsWithDeleted :many
|
||||||
|
SELECT * FROM tickets;
|
||||||
|
|
||||||
|
-- name: GetTicketByID :one
|
||||||
|
SELECT * FROM tickets WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: GetTicketByChannelID :one
|
||||||
|
SELECT * FROM tickets WHERE channelID = $1;
|
||||||
|
|
||||||
|
-- name: UpdateTicketByID :exec
|
||||||
|
UPDATE tickets SET project_git = $1, build_git = $2, folder = $3 WHERE id = $4;
|
||||||
|
|
||||||
|
-- name: DeleteTicketByID :exec
|
||||||
|
UPDATE tickets SET deleted_at = current_timestamp WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: DeleteTicketByKey :exec
|
||||||
|
UPDATE tickets SET deleted_at = current_timestamp WHERE key = $1;
|
||||||
|
|
||||||
|
-- name: InsertTask :one
|
||||||
|
INSERT INTO tasks (
|
||||||
|
creator, creator_link, description
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3
|
||||||
|
)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: UpdateTaskWithMessageID :exec
|
||||||
|
UPDATE tasks
|
||||||
|
SET messageID = $1
|
||||||
|
WHERE id = $2;
|
||||||
|
|
||||||
|
-- name: StartTask :one
|
||||||
|
UPDATE tasks
|
||||||
|
SET updated_at = $1, assignee = $2
|
||||||
|
WHERE messageID = $3
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: CloseTask :one
|
||||||
|
UPDATE tasks
|
||||||
|
SET deleted_at = $1, assignee = $2
|
||||||
|
WHERE messageID = $3
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetTaskByMessage :one
|
||||||
|
SELECT * FROM tasks WHERE messageID = $1;
|
||||||
|
|
||||||
|
-- name: ListTasksByCreator :many
|
||||||
|
SELECT * FROM tasks WHERE creator_link = $1 AND deleted_at is NULL;
|
||||||
|
|
||||||
|
-- name: GetTaskByID :one
|
||||||
|
SELECT * FROM tasks WHERE id = $1;
|
||||||
17
readme.md
17
readme.md
|
|
@ -1,12 +1,23 @@
|
||||||
# Сборка и запуск:
|
# Сборка и запуск:
|
||||||
## Первые шаги делаю на локальной машине:
|
Первые шаги делаю на локальной машине:
|
||||||
1. Поменять в коде файл окружения на '.env'
|
1. Поменять в коде файл окружения на '.env'
|
||||||
2. Собрать контейнер: `docker build -t naudachu/ticket-pimp:latest --pull .`
|
2. Собрать контейнер: `docker build -t naudachu/ticket-pimp:latest --pull .`
|
||||||
3. Затолкать контейнер в docker hub: `docker push naudachu/ticket-pimp:latest`
|
3. Затолкать контейнер в docker hub: `docker push naudachu/ticket-pimp:latest`
|
||||||
|
|
||||||
## Далее с сервера:
|
Далее с сервера:
|
||||||
1. Вытягиваем новый образ: `docker pull naudachu/ticket-pimp`
|
1. Вытягиваем новый образ: `docker pull naudachu/ticket-pimp`
|
||||||
2. Запускаем в фоне: `docker run -d naudachu/ticket-pimp`
|
2. Запускаем в фоне: `docker run -d naudachu/ticket-pimp`
|
||||||
|
|
||||||
## Инициализация бота:
|
Инициализация бота:
|
||||||
1. Написать в спам-чат команду: `init *{app key}* *{next ID}*`
|
1. Написать в спам-чат команду: `init *{app key}* *{next ID}*`
|
||||||
|
|
||||||
|
|
||||||
|
# Migrations / sqlc:
|
||||||
|
```
|
||||||
|
cd ${PROJECT_FOLDER}/internal/storage
|
||||||
|
sql-migrate up
|
||||||
|
```
|
||||||
|
|
||||||
|
# Repository code-gen
|
||||||
|
Запулить докер sqlc: `docker pull sqlc/sqlc`
|
||||||
|
Запустить команду из корня проекта: `cd ./internal/storage && docker run --rm -v "$(pwd):/src" -w /src sqlc/sqlc generate`
|
||||||
Loading…
Reference in New Issue