From 03ac788f3265b183822d92832571e42f75b1b687 Mon Sep 17 00:00:00 2001 From: naudachu Date: Thu, 1 Jun 2023 19:25:00 +0500 Subject: [PATCH] init commit --- cmd/main.go | 104 ++++++++++++++++++++++++++++++++++ domain/cloud.go | 56 ++++++++++++++++++ domain/git.go | 67 ++++++++++++++++++++++ domain/yt.go | 147 ++++++++++++++++++++++++++++++++++++++++++++++++ todo.md | 14 +++++ 5 files changed, 388 insertions(+) create mode 100644 cmd/main.go create mode 100644 domain/cloud.go create mode 100644 domain/git.go create mode 100644 domain/yt.go create mode 100644 todo.md diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..d8ce155 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,104 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "os/signal" + "strings" + "syscall" + + "ticket-creator/domain" + + "github.com/joho/godotenv" + "github.com/mr-linch/go-tg" + "github.com/mr-linch/go-tg/tgb" +) + +func main() { + env("../.env") + ctx := context.Background() + + ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, os.Kill, syscall.SIGTERM) + defer cancel() + + if err := run(ctx); err != nil { + fmt.Println(err) + defer os.Exit(1) + } +} + +func env(envFilePath string) { + err := godotenv.Load(envFilePath) + if err != nil { + log.Fatal("Error loading .env file") + } +} + +func run(ctx context.Context) error { + client := tg.New(os.Getenv("TG_API")) + + router := tgb.NewRouter(). + Message(func(ctx context.Context, mu *tgb.MessageUpdate) error { + + str := strings.Replace(mu.Text, "/new", "", 1) + if str == "" { + return errors.New("empty command provided") + } + issueKeyStr := workflow(str) + + return mu.Answer(tg.HTML.Text( + tg.HTML.Line( + "🤘 Ticket ", + tg.HTML.Link(issueKeyStr, fmt.Sprintf("https://marlerino.youtrack.cloud/issue/%s", issueKeyStr)), + "has been created!", + ), + )).ParseMode(tg.HTML).DoVoid(ctx) + }, tgb.TextHasPrefix("/new")) + + return tgb.NewPoller( + router, + client, + ).Run(ctx) +} + +func workflow(name string) string { + yt := domain.NewYT(os.Getenv("YT_URL"), os.Getenv("YT_TOKEN")) + projects := yt.GetProjects() + issue := yt.CreateIssue(projects[0].ID, name) + if issue != nil { + git := createRepo(issue.Key, 0) + gitBuild := createRepo(issue.Key+"-build", 1) + folder := createFolder(issue.Key + " - " + issue.Summary) + updated := yt.UpdateIssue(issue, folder, git, gitBuild) + log.Print(updated) + } + return issue.Key +} + +func createRepo(name string, param uint) string { + gb := domain.NewGitBucket(os.Getenv("GIT_BASE_URL"), os.Getenv("GIT_TOKEN")) + repo, _ := gb.NewRepo(name) + if repo != nil { + switch param { + case 0: + return repo.HtmlUrl + case 1: + return fmt.Sprintf("ssh://%s/%s.git", repo.SshUrl, repo.FullName) + default: + return repo.CloneUrl + } + } + return "no-repo" +} + +func createFolder(name string) string { + oc := domain.NewCloud(os.Getenv("CLOUD_BASE_URL"), os.Getenv("CLOUD_USER"), os.Getenv("CLOUD_PASS")) + cloud, _ := oc.CreateFolder(name) + if cloud != nil { + return cloud.FolderPath + } + return "no-folder" +} diff --git a/domain/cloud.go b/domain/cloud.go new file mode 100644 index 0000000..d3ef3e0 --- /dev/null +++ b/domain/cloud.go @@ -0,0 +1,56 @@ +package domain + +import ( + "log" + "time" + + "github.com/imroc/req/v3" +) + +type cloud struct { + baseUrl string + client *req.Client +} + +func NewCloud(base, user, pass string) *cloud { + + client := req.C(). + SetTimeout(5*time.Second). + SetCommonBasicAuth(user, pass). + SetBaseURL(base) + return &cloud{ + baseUrl: base, + client: client, + } +} + +type Cloud struct { + FolderName string + FolderPath string +} + +func (c *cloud) CreateFolder(name string) (*Cloud, error) { + const ( + HOMEPATH = "/remote.php/dav/files/naudachu/%23mobiledev/" + PATH = "/apps/files/?dir=/%23mobiledev/" + ) + + cloud := Cloud{ + FolderName: name, + FolderPath: "", + } + + resp, err := c.client.R(). + Send("MKCOL", HOMEPATH+name) + + // Check if request failed or response status is not Ok; + if !resp.IsSuccessState() || err != nil { + log.Print("bad status:", resp.Status) + log.Print(resp.Dump()) + } + + if resp.StatusCode == 201 { + cloud.FolderPath = c.baseUrl + PATH + name + } + return &cloud, err +} diff --git a/domain/git.go b/domain/git.go new file mode 100644 index 0000000..85e71a7 --- /dev/null +++ b/domain/git.go @@ -0,0 +1,67 @@ +package domain + +import ( + "log" + "time" + + "github.com/imroc/req/v3" +) + +type gitbucket struct { + client *req.Client +} + +func NewGitBucket(base, token string) *gitbucket { + headers := map[string]string{ + "Accept": "application/vnd.github+json", + "Authorization": "Token " + token, + "X-GitHub-Api-Version": "2022-11-28", + "Content-Type": "application/json", + } + + client := req.C(). + SetTimeout(5 * time.Second). + SetCommonHeaders(headers). + SetBaseURL(base) + return &gitbucket{ + client: client, + } +} + +type Repo struct { + Name string `json:"name"` + FullName string `json:"full_name"` + Private bool `json:"private"` + Url string `json:"url"` + CloneUrl string `json:"clone_url"` + HtmlUrl string `json:"Html_url"` + SshUrl string `json:"ssh_url"` +} + +func (gb *gitbucket) NewRepo(name string) (*Repo, error) { + + type request struct { + Name string `json:"name"` + Private bool `json:"private"` + } + + payload := request{ + Name: name, + Private: false, + } + + var git Repo + + resp, err := gb.client.R(). + SetBody(&payload). + SetSuccessResult(&git). + Post("/user/repos") + + // Check if request failed or response status is not Ok; + if !resp.IsSuccessState() || err != nil { + log.Print("bad status:", resp.Status) + log.Print(resp.Dump()) + } + + return &git, err +} diff --git a/domain/yt.go b/domain/yt.go new file mode 100644 index 0000000..ad5f6a5 --- /dev/null +++ b/domain/yt.go @@ -0,0 +1,147 @@ +package domain + +import ( + "log" + "time" + + "github.com/imroc/req/v3" +) + +type youtrack struct { + client *req.Client +} + +func NewYT(base, token string) *youtrack { + headers := map[string]string{ + "Accept": "application/json", + "Content-Type": "application/json", + } + + client := req.C(). + SetTimeout(15 * time.Second). + SetCommonHeaders(headers). + SetBaseURL(base). + SetCommonBearerAuthToken(token) + + return &youtrack{ + client: client, + } +} + +type Project struct { + ID string `json:"id"` + ShortName string `json:"shortName"` + Name string `json:"name"` +} + +// GetProjects +// provides an array of existing projects; +func (yt *youtrack) GetProjects() []Project { + + var projects []Project + + resp, err := yt.client.R(). + EnableDump(). + SetQueryParam("fields", "id,name,shortName"). + SetSuccessResult(&projects). + Get("/admin/projects") + + if !resp.IsSuccessState() || err != nil { + log.Print("bad status:", resp.Status) + log.Print(resp.Dump()) + } + return projects +} + +type ProjectID struct { + ID string `json:"id"` +} + +type IssueCreateRequest struct { + ProjectID ProjectID `json:"project"` + Key string `json:"idReadable"` + ID string `json:"id"` + Summary string `json:"summary"` + Description string `json:"description"` +} + +// CreateIssue +// example: newIssue := yt.CreateIssue("0-2", "Summary", "Description"); +func (yt *youtrack) CreateIssue(projectID, name string) *IssueCreateRequest { + + // Create an issue with the provided:, Project ID, Name, Description; + issue := IssueCreateRequest{ + ProjectID: ProjectID{ + ID: projectID, //"id":"0-2" + }, + Summary: name, + //Description: description, + } + + // Push issue to the YT; + resp, err := yt.client.R(). + SetQueryParam("fields", "idReadable,id"). + SetBody(&issue). + SetSuccessResult(&issue). + Post("/issues") + + // Check if request failed or response status is not Ok; + if !resp.IsSuccessState() || err != nil { + log.Print("bad status:", resp.Status) + log.Print(resp.Dump()) + } + + return &issue +} + +type IssueUpdateRequest struct { + IssueCreateRequest + CustomFields []CustomField `json:"customFields"` +} + +type CustomFields struct { + List []CustomField `json:"customFields"` +} + +type CustomField struct { + Name string `json:"name"` + Type string `json:"$type"` + Value string `json:"value"` +} + +func (yt *youtrack) UpdateIssue(issue *IssueCreateRequest, folder, git, gitBuild string) *IssueUpdateRequest { + // Set Folder, Git, GitBuild to the Issue: + update := IssueUpdateRequest{ + IssueCreateRequest: *issue, + CustomFields: []CustomField{ + { + Name: "Директория графики", + Type: "SimpleIssueCustomField", + Value: folder, + }, + { + Name: "Репо проекта", + Type: "SimpleIssueCustomField", + Value: git, + }, + { + Name: "Репо iOS сборки", + Type: "SimpleIssueCustomField", + Value: gitBuild, + }, + }, + } + + // Push issue update to YT + resp, err := yt.client.R(). + SetBody(&update). + SetSuccessResult(&issue). + Post("/issues/" + issue.Key) + + if !resp.IsSuccessState() || err != nil { + log.Print("bad status:", resp.Status) + log.Print(resp.Dump()) + } + + return &update +} diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..ac26d70 --- /dev/null +++ b/todo.md @@ -0,0 +1,14 @@ +# Основное: +- [ ] Сохранять правильную ссылку на Git; +- [ ] Сохранять правильную ссылку на GitBuild; +- [ ] Делать запросы в Git, ownCloud параллельно; +- [x] Сделать бота в Telegram; + + +# Под звёздочкой: +- [ ] Сохранять короткую ссылку на графику; +- [ ] Сохранять внешнюю ссылку на графику; +- [ ] Сделать бота в Discord; +- [ ] Подумать над нормальной обработкой ошибок, сейчас достаточно всрато; +- [ ] Складывать в описание репозитория ссылку на тикет; +- [ ] Сделать базулю с достойными пользователями; \ No newline at end of file