init commit

This commit is contained in:
naudachu 2023-06-01 19:25:00 +05:00
commit 03ac788f32
5 changed files with 388 additions and 0 deletions

104
cmd/main.go Normal file
View File

@ -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"
}

56
domain/cloud.go Normal file
View File

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

67
domain/git.go Normal file
View File

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

147
domain/yt.go Normal file
View File

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

14
todo.md Normal file
View File

@ -0,0 +1,14 @@
# Основное:
- [ ] Сохранять правильную ссылку на Git;
- [ ] Сохранять правильную ссылку на GitBuild;
- [ ] Делать запросы в Git, ownCloud параллельно;
- [x] Сделать бота в Telegram;
# Под звёздочкой:
- [ ] Сохранять короткую ссылку на графику;
- [ ] Сохранять внешнюю ссылку на графику;
- [ ] Сделать бота в Discord;
- [ ] Подумать над нормальной обработкой ошибок, сейчас достаточно всрато;
- [ ] Складывать в описание репозитория ссылку на тикет;
- [ ] Сделать базулю с достойными пользователями;