diff --git a/.gitignore b/.gitignore index a091954..73341e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .vscode/ -.env \ No newline at end of file +.env +.dev.env \ No newline at end of file diff --git a/controller/controller.go b/controller/controller.go new file mode 100644 index 0000000..ca638df --- /dev/null +++ b/controller/controller.go @@ -0,0 +1,83 @@ +package controller + +import ( + "fmt" + "os" + "sync" + "ticket-creator/ext" +) + +func Workflow(name string) (string, error) { + yt := ext.NewYT(os.Getenv("YT_URL"), os.Getenv("YT_TOKEN")) + + projects, err := yt.GetProjects() + + if err != nil { + return "", err + } + + issue, err := yt.CreateIssue(projects[0].ID, name) + + if err != nil { + return "", err + } + + if issue != nil { + var ( + git, gitBuild, folder string + ) + + var wg sync.WaitGroup + wg.Add(3) + + go func() { + defer wg.Done() + git, _ = CreateRepo(issue.Key, 0) + }() + + go func() { + defer wg.Done() + gitBuild, _ = CreateRepo(issue.Key+"-build", 1) + }() + + go func() { + defer wg.Done() + folder = CreateFolder(issue.Key + " - " + issue.Summary) + }() + + wg.Wait() + + yt.UpdateIssue(issue, folder, git, gitBuild) + } + return issue.Key, nil +} + +func CreateRepo(name string, param uint) (string, error) { + gb := ext.NewGit(os.Getenv("GIT_BASE_URL"), os.Getenv("GIT_TOKEN")) + repo, err := gb.NewRepo(name) + gb.AppsAsCollaboratorTo(repo) + + // Result string formatting: + if repo != nil { + switch param { + case 0: + return repo.HtmlUrl, err + case 1: + return fmt.Sprintf("ssh://%s/%s.git", repo.SshUrl, repo.FullName), err + default: + return repo.CloneUrl, err + } + } + + return "", err +} + +func CreateFolder(name string) string { + oc := ext.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/git.go b/domain/git.go new file mode 100644 index 0000000..31882a3 --- /dev/null +++ b/domain/git.go @@ -0,0 +1,11 @@ +package domain + +type Git 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"` +} diff --git a/ext/cloud.go b/ext/cloud.go index 3ee1612..54d4add 100644 --- a/ext/cloud.go +++ b/ext/cloud.go @@ -1,23 +1,18 @@ package ext import ( + "os" "time" - - "github.com/imroc/req/v3" ) -type cloud struct { - *req.Client -} - -func NewCloud(base, user, pass string) *cloud { +func NewCloud(base, user, pass string) *Client { client := NewClient(). SetTimeout(5*time.Second). SetCommonBasicAuth(user, pass). SetBaseURL(base) - return &cloud{ + return &Client{ client, } } @@ -27,24 +22,20 @@ type Cloud struct { FolderPath string } -func (c *cloud) CreateFolder(name string) (*Cloud, error) { - const ( - HOMEPATH = "/remote.php/dav/files/naudachu/%23mobiledev/" - PATH = "/apps/files/?dir=/%23mobiledev/" - ) +func (c *Client) CreateFolder(name string) (*Cloud, error) { cloud := Cloud{ FolderName: name, FolderPath: "", } - pathName := HOMEPATH + name + pathName := os.Getenv("HOMEPATH") + name resp, err := c.R(). Send("MKCOL", pathName) if resp.IsSuccessState() { - cloud.FolderPath = c.BaseURL + PATH + name + cloud.FolderPath = c.BaseURL + os.Getenv("FOLDER_PATH") + name } return &cloud, err diff --git a/ext/git.go b/ext/git.go index e476c13..df2281e 100644 --- a/ext/git.go +++ b/ext/git.go @@ -2,18 +2,18 @@ package ext import ( "log" - "regexp" - "strings" + "os" + "ticket-creator/domain" + "ticket-creator/helpers" "time" - - "github.com/imroc/req/v3" ) -type gitbucket struct { - *req.Client +type Git struct { + *Client + *domain.Git } -func NewGitBucket(base, token string) *gitbucket { +func NewGit(base, token string) *Git { headers := map[string]string{ "Accept": "application/vnd.github+json", "Authorization": "Token " + token, @@ -25,80 +25,65 @@ func NewGitBucket(base, token string) *gitbucket { SetTimeout(5 * time.Second). SetCommonHeaders(headers). SetBaseURL(base) - return &gitbucket{ - client, + + return &Git{ + Client: &Client{client}, + Git: &domain.Git{ + Name: "", + FullName: "", + Private: true, + Url: "", + CloneUrl: "", + HtmlUrl: "", + SshUrl: "", + }, } } -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"` +type request struct { + Name string `json:"name"` + Private bool `json:"private"` } -func gitHubLikeNaming(input string) string { - // Remove leading and trailing whitespace - input = strings.TrimSpace(input) - - // Replace non-Latin letters with spaces - reg := regexp.MustCompile("[^a-zA-Z]+") - input = reg.ReplaceAllString(input, " ") - - // Split into words and capitalize first letter of each - words := strings.Fields(input) - for i, word := range words { - words[i] = strings.ToLower(word) - } - - // Join words and return - return strings.Join(words, "-") +type permissionRequest struct { + Perm string `json:"permission"` } -func (gb *gitbucket) NewRepo(name string) (*Repo, error) { - name = gitHubLikeNaming(name) - - type request struct { - Name string `json:"name"` - Private bool `json:"private"` - } +func (gb *Git) NewRepo(name string) (*domain.Git, error) { + name = helpers.GitNaming(name) payload := request{ Name: name, - Private: false, + Private: true, } - var git Repo + var git domain.Git resp, err := gb.R(). SetBody(&payload). SetSuccessResult(&git). Post("/user/repos") + //Post("/orgs/apps/repos") if err != nil { log.Print(resp) - return nil, err - } - - type permissionRequest struct { - Perm string `json:"permission"` - } - - payloadPermission := permissionRequest{ - Perm: "admin", - } - - resp, err = gb.R(). - SetBody(&payloadPermission). - Put("/repos/naudachu/" + name + "/collaborators/apps") - - if err != nil { - log.Print(resp) - return nil, err } return &git, err } + +func (gb *Client) AppsAsCollaboratorTo(git *domain.Git) (*domain.Git, error) { + payloadPermission := permissionRequest{ + Perm: "admin", + } + + resp, err := gb.R(). + SetBody(&payloadPermission). + Put("/repos/" + os.Getenv("GIT_USER") + "/" + git.Name + "/collaborators/apps") + + if err != nil { + log.Print(resp) + } + + return git, err +} diff --git a/ext/yt.go b/ext/yt.go index 7339282..92e89c3 100644 --- a/ext/yt.go +++ b/ext/yt.go @@ -8,12 +8,6 @@ import ( "github.com/imroc/req/v3" ) -type IYouTrack interface { - GetProjects() ([]Project, error) - CreateIssue(projectID, name string) (*IssueCreateRequest, error) - UpdateIssue(issue *IssueCreateRequest, folder, git, gitBuild string) (*IssueUpdateRequest, error) -} - type youtrack struct { *req.Client } diff --git a/handler/handler.go b/handler/handler.go new file mode 100644 index 0000000..ecc7b17 --- /dev/null +++ b/handler/handler.go @@ -0,0 +1,78 @@ +package handler + +import ( + "context" + "errors" + "fmt" + "strings" + "ticket-creator/controller" + + "github.com/mr-linch/go-tg" + "github.com/mr-linch/go-tg/tgb" +) + +func errorAnswer(errorMsg string) string { + return tg.HTML.Text( + tg.HTML.Line( + tg.HTML.Italic(errorMsg), + ), + ) +} + +func NewTicketHandler(ctx context.Context, mu *tgb.MessageUpdate) error { + + str := strings.Replace(mu.Text, "/new", "", 1) + + if str == "" { + return errors.New("empty command provided") + } + + issueKeyStr, err := controller.Workflow(str) + + if err != nil { + return mu.Answer(errorAnswer(err.Error())).ParseMode(tg.HTML).DoVoid(ctx) + } + + return mu.Answer(newTicketAnswer(issueKeyStr)).ParseMode(tg.HTML).DoVoid(ctx) +} + +func newTicketAnswer(name string) string { + return tg.HTML.Text( + tg.HTML.Line( + "🤘 Ticket ", + tg.HTML.Link(name, fmt.Sprintf("https://marlerino.youtrack.cloud/issue/%s", name)), + "has been created!", + ), + ) +} + +func NewRepoHandler(ctx context.Context, mu *tgb.MessageUpdate) error { + + str := strings.Replace(mu.Text, "/repo", "", 1) + + if str == "" { + return errors.New("empty command provided") + } + + repoStr, err := controller.CreateRepo(str, 0) + + if err != nil { + return mu.Answer(errorAnswer(err.Error())).ParseMode(tg.HTML).DoVoid(ctx) + } + + return mu.Answer(newRepoAnswer(repoStr)).ParseMode(tg.HTML).DoVoid(ctx) +} + +func newRepoAnswer(name string) string { + return tg.HTML.Text( + tg.HTML.Line( + "Repo ", + name, + "has been created!", + ), + ) +} + +func PingHandler(ctx context.Context, mu *tgb.MessageUpdate) error { + return mu.Answer("pong").DoVoid(ctx) +} diff --git a/helpers/helpers.go b/helpers/helpers.go new file mode 100644 index 0000000..93c17ba --- /dev/null +++ b/helpers/helpers.go @@ -0,0 +1,24 @@ +package helpers + +import ( + "regexp" + "strings" +) + +func GitNaming(input string) string { + // Remove leading and trailing whitespace + input = strings.TrimSpace(input) + + // Replace non-Latin letters with spaces + reg := regexp.MustCompile("[^a-zA-Z0-9]+") + input = strings.TrimSpace(reg.ReplaceAllString(input, " ")) + + // Split into words + words := strings.Fields(input) + for i, word := range words { + words[i] = strings.ToLower(word) + } + + // Join words and return + return strings.Join(words, "-") +} diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go new file mode 100644 index 0000000..bf22d06 --- /dev/null +++ b/helpers/helpers_test.go @@ -0,0 +1,23 @@ +package helpers + +import "testing" + +type test struct { + arg, expected string +} + +var tests = []test{ + {" App-21", "app-21"}, + {"App-21-build", "app-21-build"}, + {" hello - biatch", "hello-biatch"}, + {" `~!@#$%^&*()=+ abc `~!@#$%^&*()=+ 2-22 ", "abc-2-22"}, +} + +func TestGitNaming(t *testing.T) { + + for _, test := range tests { + if output := GitNaming(test.arg); output != test.expected { + t.Errorf("Output %q not equal to expected %q", output, test.expected) + } + } +}