From fa5ba4742a74c168046f6302dcc4b7057e928eb4 Mon Sep 17 00:00:00 2001 From: naudachu Date: Sun, 4 Jun 2023 21:01:37 +0500 Subject: [PATCH] - setup answer messageas a separate function; - async git/cloud requests; - error handling middleware; --- cmd/.env.dev | 11 +++++++ cmd/main.go | 80 ++++++++++++++++++++++++++++++++++++++---------- domain/client.go | 30 ++++++++++++++++++ domain/cloud.go | 21 +++++-------- domain/git.go | 28 ++++++++--------- domain/yt.go | 51 +++++++++++++++++------------- 6 files changed, 152 insertions(+), 69 deletions(-) create mode 100644 cmd/.env.dev create mode 100644 domain/client.go diff --git a/cmd/.env.dev b/cmd/.env.dev new file mode 100644 index 0000000..edbb086 --- /dev/null +++ b/cmd/.env.dev @@ -0,0 +1,11 @@ +CLOUD_BASE_URL = 'http://82.151.222.22:7000' +CLOUD_USER = 'naudachu' +CLOUD_PASS = '123456' + +GIT_BASE_URL = 'http://82.151.222.22:7001/api/v3' +GIT_TOKEN = '7bd9d60cf7b9e78a4f2f1aea4734fbfa1052e419' + +YT_URL = 'https://marlerino.youtrack.cloud/api' +YT_TOKEN = 'perm:bmF1ZGFjaHU=.NTYtMQ==.4TVHQx65u4EKnCGjadeMB1NMAmSHSL' + +TG_API = '6002875059:AAFp1ZR9Y68oaqSL6vTNQdhrVgcM_yHouCY' \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index b3af948..1bc3d08 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -8,6 +8,7 @@ import ( "os" "os/signal" "strings" + "sync" "syscall" "ticket-creator/domain" @@ -18,7 +19,7 @@ import ( ) func main() { - env(".env") + env(".env.dev") ctx := context.Background() ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, os.Kill, syscall.SIGTERM) @@ -37,6 +38,24 @@ func env(envFilePath string) { } } +func answer(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 errorAnswer(errorMsg string) string { + return tg.HTML.Text( + tg.HTML.Line( + tg.HTML.Italic(errorMsg), + ), + ) +} + func run(ctx context.Context) error { client := tg.New(os.Getenv("TG_API")) @@ -47,16 +66,16 @@ func run(ctx context.Context) error { if str == "" { return errors.New("empty command provided") } - issueKeyStr := workflow(str) + issueKeyStr, err := workflow(str) + if err != nil { + return mu.Answer(errorAnswer(err.Error())).ParseMode(tg.HTML).DoVoid(ctx) + } - 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 mu.Answer(answer(issueKeyStr)).ParseMode(tg.HTML).DoVoid(ctx) + }, tgb.TextHasPrefix("/new")). + Message(func(ctx context.Context, mu *tgb.MessageUpdate) error { + return mu.Answer("pong").DoVoid(ctx) + }, tgb.Command("ping")) return tgb.NewPoller( router, @@ -64,17 +83,44 @@ func run(ctx context.Context) error { ).Run(ctx) } -func workflow(name string) string { +func workflow(name string) (string, error) { yt := domain.NewYT(os.Getenv("YT_URL"), os.Getenv("YT_TOKEN")) - projects := yt.GetProjects() - issue := yt.CreateIssue(projects[0].ID, name) + + projects, err := yt.GetProjects() + if err != nil { + return "", err + } + + issue, err := yt.CreateIssue(projects[1].ID, name) + if err != nil { + return "", err + } if issue != nil { - git := createRepo(issue.Key, 0) - gitBuild := createRepo(issue.Key+"-build", 1) - folder := createFolder(issue.Key + " - " + issue.Summary) + var ( + wg sync.WaitGroup + git, gitBuild, folder string + ) + + 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 + return issue.Key, nil } func createRepo(name string, param uint) string { diff --git a/domain/client.go b/domain/client.go new file mode 100644 index 0000000..217c2ae --- /dev/null +++ b/domain/client.go @@ -0,0 +1,30 @@ +package domain + +import ( + "fmt" + + "github.com/imroc/req/v3" +) + +type Client struct { + *req.Client +} + +func NewClient() *Client { + return &Client{req.C(). + OnAfterResponse(func(client *req.Client, resp *req.Response) error { + if resp.Err != nil { + if dump := resp.Dump(); dump != "" { + resp.Err = fmt.Errorf("%s\nraw content:\n%s", resp.Err.Error(), resp.Dump()) + } + return nil // Skip the following logic if there is an underlying error. + } + + if !resp.IsSuccessState() { + resp.Err = fmt.Errorf("bad response, raw content:\n%s", resp.Dump()) + return nil + } + return nil + }), + } +} diff --git a/domain/cloud.go b/domain/cloud.go index 3531050..328da56 100644 --- a/domain/cloud.go +++ b/domain/cloud.go @@ -1,26 +1,24 @@ package domain import ( - "log" "time" "github.com/imroc/req/v3" ) type cloud struct { - baseUrl string - client *req.Client + *req.Client } func NewCloud(base, user, pass string) *cloud { - client := req.C(). + client := NewClient(). SetTimeout(5*time.Second). SetCommonBasicAuth(user, pass). SetBaseURL(base) + return &cloud{ - baseUrl: base, - client: client, + client, } } @@ -42,17 +40,12 @@ func (c *cloud) CreateFolder(name string) (*Cloud, error) { pathName := HOMEPATH + name - resp, err := c.client.R(). + resp, err := c.R(). Send("MKCOL", pathName) - // 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.IsSuccessState() { + cloud.FolderPath = c.BaseURL + PATH + name } - if resp.StatusCode == 201 { - cloud.FolderPath = c.baseUrl + PATH + name - } return &cloud, err } diff --git a/domain/git.go b/domain/git.go index 0fa75e0..b1232c0 100644 --- a/domain/git.go +++ b/domain/git.go @@ -1,14 +1,13 @@ package domain import ( - "log" "time" "github.com/imroc/req/v3" ) type gitbucket struct { - client *req.Client + *req.Client } func NewGitBucket(base, token string) *gitbucket { @@ -19,12 +18,12 @@ func NewGitBucket(base, token string) *gitbucket { "Content-Type": "application/json", } - client := req.C(). + client := NewClient(). SetTimeout(5 * time.Second). SetCommonHeaders(headers). SetBaseURL(base) return &gitbucket{ - client: client, + client, } } @@ -52,32 +51,29 @@ func (gb *gitbucket) NewRepo(name string) (*Repo, error) { var git Repo - resp, err := gb.client.R(). + _, err := gb.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()) + if err != nil { + return nil, err } type permissionRequest struct { - perm string `json:"permission"` + Perm string `json:"permission"` } payloadPermission := permissionRequest{ - perm: "admin", + Perm: "admin", } - resp, err = gb.client.R(). + _, err = gb.R(). SetBody(&payloadPermission). - Post("/repos/naudachu/" + name + "/collaborators/apps") + Put("/repos/naudachu/" + name + "/collaborators/apps") - if !resp.IsSuccessState() || err != nil { - log.Print("bad status:", resp.Status) - log.Print(resp.Dump()) + if err != nil { + return nil, err } return &git, err diff --git a/domain/yt.go b/domain/yt.go index ad5f6a5..4c34f72 100644 --- a/domain/yt.go +++ b/domain/yt.go @@ -1,6 +1,7 @@ package domain import ( + "fmt" "log" "time" @@ -8,7 +9,7 @@ import ( ) type youtrack struct { - client *req.Client + *req.Client } func NewYT(base, token string) *youtrack { @@ -17,14 +18,14 @@ func NewYT(base, token string) *youtrack { "Content-Type": "application/json", } - client := req.C(). + client := NewClient(). SetTimeout(15 * time.Second). SetCommonHeaders(headers). SetBaseURL(base). SetCommonBearerAuthToken(token) return &youtrack{ - client: client, + client, } } @@ -36,21 +37,22 @@ type Project struct { // GetProjects // provides an array of existing projects; -func (yt *youtrack) GetProjects() []Project { +func (yt *youtrack) GetProjects() ([]Project, error) { var projects []Project - resp, err := yt.client.R(). + _, err := yt.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()) + // Check if the request failed; + if err != nil { + return nil, fmt.Errorf("some problem with YT request. error message: %v", err) } - return projects + + return projects, nil } type ProjectID struct { @@ -67,7 +69,7 @@ type IssueCreateRequest struct { // CreateIssue // example: newIssue := yt.CreateIssue("0-2", "Summary", "Description"); -func (yt *youtrack) CreateIssue(projectID, name string) *IssueCreateRequest { +func (yt *youtrack) CreateIssue(projectID, name string) (*IssueCreateRequest, error) { // Create an issue with the provided:, Project ID, Name, Description; issue := IssueCreateRequest{ @@ -79,19 +81,18 @@ func (yt *youtrack) CreateIssue(projectID, name string) *IssueCreateRequest { } // Push issue to the YT; - resp, err := yt.client.R(). + _, err := yt.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()) + // Check if the request failed; + if err != nil { + return nil, fmt.Errorf("some problem with YT request. error message: %v", err) } - return &issue + return &issue, nil } type IssueUpdateRequest struct { @@ -109,7 +110,7 @@ type CustomField struct { Value string `json:"value"` } -func (yt *youtrack) UpdateIssue(issue *IssueCreateRequest, folder, git, gitBuild string) *IssueUpdateRequest { +func (yt *youtrack) UpdateIssue(issue *IssueCreateRequest, folder, git, gitBuild string) (*IssueUpdateRequest, error) { // Set Folder, Git, GitBuild to the Issue: update := IssueUpdateRequest{ IssueCreateRequest: *issue, @@ -133,15 +134,21 @@ func (yt *youtrack) UpdateIssue(issue *IssueCreateRequest, folder, git, gitBuild } // Push issue update to YT - resp, err := yt.client.R(). + resp, err := yt.R(). SetBody(&update). SetSuccessResult(&issue). Post("/issues/" + issue.Key) - if !resp.IsSuccessState() || err != nil { - log.Print("bad status:", resp.Status) - log.Print(resp.Dump()) + // Check if the request failed; + if err != nil { + return nil, fmt.Errorf("some problem with YT request. error message: %v", err) } - return &update + if !resp.IsSuccessState() { + log.Print("bad status:", resp.Status) + log.Print(resp.Dump()) + return nil, fmt.Errorf("YouTrack responded with %d", resp.StatusCode) + } + + return &update, nil }