init commit
This commit is contained in:
commit
03ac788f32
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Основное:
|
||||||
|
- [ ] Сохранять правильную ссылку на Git;
|
||||||
|
- [ ] Сохранять правильную ссылку на GitBuild;
|
||||||
|
- [ ] Делать запросы в Git, ownCloud параллельно;
|
||||||
|
- [x] Сделать бота в Telegram;
|
||||||
|
|
||||||
|
|
||||||
|
# Под звёздочкой:
|
||||||
|
- [ ] Сохранять короткую ссылку на графику;
|
||||||
|
- [ ] Сохранять внешнюю ссылку на графику;
|
||||||
|
- [ ] Сделать бота в Discord;
|
||||||
|
- [ ] Подумать над нормальной обработкой ошибок, сейчас достаточно всрато;
|
||||||
|
- [ ] Складывать в описание репозитория ссылку на тикет;
|
||||||
|
- [ ] Сделать базулю с достойными пользователями;
|
||||||
Loading…
Reference in New Issue