- setup answer messageas a separate function;

- async git/cloud requests;
- error handling middleware;
This commit is contained in:
naudachu 2023-06-04 21:01:37 +05:00
parent b8e3dd5392
commit fa5ba4742a
6 changed files with 152 additions and 69 deletions

11
cmd/.env.dev Normal file
View File

@ -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'

View File

@ -8,6 +8,7 @@ import (
"os" "os"
"os/signal" "os/signal"
"strings" "strings"
"sync"
"syscall" "syscall"
"ticket-creator/domain" "ticket-creator/domain"
@ -18,7 +19,7 @@ import (
) )
func main() { func main() {
env(".env") env(".env.dev")
ctx := context.Background() ctx := context.Background()
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, os.Kill, syscall.SIGTERM) 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 { func run(ctx context.Context) error {
client := tg.New(os.Getenv("TG_API")) client := tg.New(os.Getenv("TG_API"))
@ -47,16 +66,16 @@ func run(ctx context.Context) error {
if str == "" { if str == "" {
return errors.New("empty command provided") 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( return mu.Answer(answer(issueKeyStr)).ParseMode(tg.HTML).DoVoid(ctx)
tg.HTML.Line( }, tgb.TextHasPrefix("/new")).
"🤘 Ticket ", Message(func(ctx context.Context, mu *tgb.MessageUpdate) error {
tg.HTML.Link(issueKeyStr, fmt.Sprintf("https://marlerino.youtrack.cloud/issue/%s", issueKeyStr)), return mu.Answer("pong").DoVoid(ctx)
"has been created!", }, tgb.Command("ping"))
),
)).ParseMode(tg.HTML).DoVoid(ctx)
}, tgb.TextHasPrefix("/new"))
return tgb.NewPoller( return tgb.NewPoller(
router, router,
@ -64,17 +83,44 @@ func run(ctx context.Context) error {
).Run(ctx) ).Run(ctx)
} }
func workflow(name string) string { func workflow(name string) (string, error) {
yt := domain.NewYT(os.Getenv("YT_URL"), os.Getenv("YT_TOKEN")) 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 { if issue != nil {
git := createRepo(issue.Key, 0) var (
gitBuild := createRepo(issue.Key+"-build", 1) wg sync.WaitGroup
folder := createFolder(issue.Key + " - " + issue.Summary) 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) yt.UpdateIssue(issue, folder, git, gitBuild)
} }
return issue.Key return issue.Key, nil
} }
func createRepo(name string, param uint) string { func createRepo(name string, param uint) string {

30
domain/client.go Normal file
View File

@ -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
}),
}
}

View File

@ -1,26 +1,24 @@
package domain package domain
import ( import (
"log"
"time" "time"
"github.com/imroc/req/v3" "github.com/imroc/req/v3"
) )
type cloud struct { type cloud struct {
baseUrl string *req.Client
client *req.Client
} }
func NewCloud(base, user, pass string) *cloud { func NewCloud(base, user, pass string) *cloud {
client := req.C(). client := NewClient().
SetTimeout(5*time.Second). SetTimeout(5*time.Second).
SetCommonBasicAuth(user, pass). SetCommonBasicAuth(user, pass).
SetBaseURL(base) SetBaseURL(base)
return &cloud{ return &cloud{
baseUrl: base, client,
client: client,
} }
} }
@ -42,17 +40,12 @@ func (c *cloud) CreateFolder(name string) (*Cloud, error) {
pathName := HOMEPATH + name pathName := HOMEPATH + name
resp, err := c.client.R(). resp, err := c.R().
Send("MKCOL", pathName) Send("MKCOL", pathName)
// Check if request failed or response status is not Ok; if resp.IsSuccessState() {
if !resp.IsSuccessState() || err != nil { cloud.FolderPath = c.BaseURL + PATH + name
log.Print("bad status:", resp.Status)
log.Print(resp.Dump())
} }
if resp.StatusCode == 201 {
cloud.FolderPath = c.baseUrl + PATH + name
}
return &cloud, err return &cloud, err
} }

View File

@ -1,14 +1,13 @@
package domain package domain
import ( import (
"log"
"time" "time"
"github.com/imroc/req/v3" "github.com/imroc/req/v3"
) )
type gitbucket struct { type gitbucket struct {
client *req.Client *req.Client
} }
func NewGitBucket(base, token string) *gitbucket { func NewGitBucket(base, token string) *gitbucket {
@ -19,12 +18,12 @@ func NewGitBucket(base, token string) *gitbucket {
"Content-Type": "application/json", "Content-Type": "application/json",
} }
client := req.C(). client := NewClient().
SetTimeout(5 * time.Second). SetTimeout(5 * time.Second).
SetCommonHeaders(headers). SetCommonHeaders(headers).
SetBaseURL(base) SetBaseURL(base)
return &gitbucket{ return &gitbucket{
client: client, client,
} }
} }
@ -52,32 +51,29 @@ func (gb *gitbucket) NewRepo(name string) (*Repo, error) {
var git Repo var git Repo
resp, err := gb.client.R(). _, err := gb.R().
SetBody(&payload). SetBody(&payload).
SetSuccessResult(&git). SetSuccessResult(&git).
Post("/user/repos") Post("/user/repos")
// Check if request failed or response status is not Ok; if err != nil {
if !resp.IsSuccessState() || err != nil { return nil, err
log.Print("bad status:", resp.Status)
log.Print(resp.Dump())
} }
type permissionRequest struct { type permissionRequest struct {
perm string `json:"permission"` Perm string `json:"permission"`
} }
payloadPermission := permissionRequest{ payloadPermission := permissionRequest{
perm: "admin", Perm: "admin",
} }
resp, err = gb.client.R(). _, err = gb.R().
SetBody(&payloadPermission). SetBody(&payloadPermission).
Post("/repos/naudachu/" + name + "/collaborators/apps") Put("/repos/naudachu/" + name + "/collaborators/apps")
if !resp.IsSuccessState() || err != nil { if err != nil {
log.Print("bad status:", resp.Status) return nil, err
log.Print(resp.Dump())
} }
return &git, err return &git, err

View File

@ -1,6 +1,7 @@
package domain package domain
import ( import (
"fmt"
"log" "log"
"time" "time"
@ -8,7 +9,7 @@ import (
) )
type youtrack struct { type youtrack struct {
client *req.Client *req.Client
} }
func NewYT(base, token string) *youtrack { func NewYT(base, token string) *youtrack {
@ -17,14 +18,14 @@ func NewYT(base, token string) *youtrack {
"Content-Type": "application/json", "Content-Type": "application/json",
} }
client := req.C(). client := NewClient().
SetTimeout(15 * time.Second). SetTimeout(15 * time.Second).
SetCommonHeaders(headers). SetCommonHeaders(headers).
SetBaseURL(base). SetBaseURL(base).
SetCommonBearerAuthToken(token) SetCommonBearerAuthToken(token)
return &youtrack{ return &youtrack{
client: client, client,
} }
} }
@ -36,21 +37,22 @@ type Project struct {
// GetProjects // GetProjects
// provides an array of existing projects; // provides an array of existing projects;
func (yt *youtrack) GetProjects() []Project { func (yt *youtrack) GetProjects() ([]Project, error) {
var projects []Project var projects []Project
resp, err := yt.client.R(). _, err := yt.R().
EnableDump(). EnableDump().
SetQueryParam("fields", "id,name,shortName"). SetQueryParam("fields", "id,name,shortName").
SetSuccessResult(&projects). SetSuccessResult(&projects).
Get("/admin/projects") Get("/admin/projects")
if !resp.IsSuccessState() || err != nil { // Check if the request failed;
log.Print("bad status:", resp.Status) if err != nil {
log.Print(resp.Dump()) return nil, fmt.Errorf("some problem with YT request. error message: %v", err)
} }
return projects
return projects, nil
} }
type ProjectID struct { type ProjectID struct {
@ -67,7 +69,7 @@ type IssueCreateRequest struct {
// CreateIssue // CreateIssue
// example: newIssue := yt.CreateIssue("0-2", "Summary", "Description"); // 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; // Create an issue with the provided:, Project ID, Name, Description;
issue := IssueCreateRequest{ issue := IssueCreateRequest{
@ -79,19 +81,18 @@ func (yt *youtrack) CreateIssue(projectID, name string) *IssueCreateRequest {
} }
// Push issue to the YT; // Push issue to the YT;
resp, err := yt.client.R(). _, err := yt.R().
SetQueryParam("fields", "idReadable,id"). SetQueryParam("fields", "idReadable,id").
SetBody(&issue). SetBody(&issue).
SetSuccessResult(&issue). SetSuccessResult(&issue).
Post("/issues") Post("/issues")
// Check if request failed or response status is not Ok; // Check if the request failed;
if !resp.IsSuccessState() || err != nil { if err != nil {
log.Print("bad status:", resp.Status) return nil, fmt.Errorf("some problem with YT request. error message: %v", err)
log.Print(resp.Dump())
} }
return &issue return &issue, nil
} }
type IssueUpdateRequest struct { type IssueUpdateRequest struct {
@ -109,7 +110,7 @@ type CustomField struct {
Value string `json:"value"` 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: // Set Folder, Git, GitBuild to the Issue:
update := IssueUpdateRequest{ update := IssueUpdateRequest{
IssueCreateRequest: *issue, IssueCreateRequest: *issue,
@ -133,15 +134,21 @@ func (yt *youtrack) UpdateIssue(issue *IssueCreateRequest, folder, git, gitBuild
} }
// Push issue update to YT // Push issue update to YT
resp, err := yt.client.R(). resp, err := yt.R().
SetBody(&update). SetBody(&update).
SetSuccessResult(&issue). SetSuccessResult(&issue).
Post("/issues/" + issue.Key) Post("/issues/" + issue.Key)
if !resp.IsSuccessState() || err != nil { // Check if the request failed;
log.Print("bad status:", resp.Status) if err != nil {
log.Print(resp.Dump()) 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
} }