commit
45beec029e
20
cmd/main.go
20
cmd/main.go
|
|
@ -16,7 +16,7 @@ import (
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Print("started")
|
log.Print("started")
|
||||||
env(".env")
|
env(".dev.env")
|
||||||
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)
|
||||||
|
|
@ -36,12 +36,24 @@ func env(envFilePath string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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"))
|
||||||
|
|
||||||
|
h := handler.NewHandler(
|
||||||
|
os.Getenv("GIT_BASE_URL"),
|
||||||
|
os.Getenv("GIT_TOKEN"),
|
||||||
|
os.Getenv("CLOUD_BASE_URL"),
|
||||||
|
os.Getenv("CLOUD_USER"),
|
||||||
|
os.Getenv("CLOUD_PASS"),
|
||||||
|
os.Getenv("YT_URL"),
|
||||||
|
os.Getenv("YT_TOKEN"),
|
||||||
|
)
|
||||||
|
|
||||||
router := tgb.NewRouter().
|
router := tgb.NewRouter().
|
||||||
Message(handler.NewTicketHandler, tgb.TextHasPrefix("/new")).
|
Message(h.NewTicketHandler, tgb.TextHasPrefix("/new")).
|
||||||
Message(handler.PingHandler, tgb.Command("ping")).
|
Message(h.PingHandler, tgb.Command("ping")).
|
||||||
Message(handler.NewRepoHandler, tgb.TextHasPrefix("/repo"))
|
Message(h.NewRepoHandler, tgb.TextHasPrefix("/repo")).
|
||||||
|
Message(h.NewFolderHandler, tgb.TextHasPrefix("/folder"))
|
||||||
|
|
||||||
return tgb.NewPoller(
|
return tgb.NewPoller(
|
||||||
router,
|
router,
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,41 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
d "ticket-pimp/domain"
|
||||||
"ticket-pimp/ext"
|
"ticket-pimp/ext"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Workflow(name string) (string, error) {
|
type WorkflowController struct {
|
||||||
yt := ext.NewYT(os.Getenv("YT_URL"), os.Getenv("YT_TOKEN"))
|
iGit ext.IGit
|
||||||
|
iCloud ext.ICloud
|
||||||
|
iYouTrack ext.IYouTrack
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWorkflowController(
|
||||||
|
gitBaseURL,
|
||||||
|
gitToken,
|
||||||
|
cloudBaseURL,
|
||||||
|
cloudAuthUser,
|
||||||
|
cloudAuthPass,
|
||||||
|
ytBaseURL,
|
||||||
|
ytToken string,
|
||||||
|
) *WorkflowController {
|
||||||
|
return &WorkflowController{
|
||||||
|
iGit: ext.NewGit(gitBaseURL, gitToken),
|
||||||
|
iCloud: ext.NewCloud(cloudBaseURL, cloudAuthUser, cloudAuthPass),
|
||||||
|
iYouTrack: ext.NewYT(ytBaseURL, ytToken),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type IWorkflowController interface {
|
||||||
|
Workflow(name string) (string, error)
|
||||||
|
CreateRepo(name string) (*d.Git, error)
|
||||||
|
CreateFolder(name string) (*d.Folder, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *WorkflowController) Workflow(name string) (string, error) {
|
||||||
|
yt := wc.iYouTrack
|
||||||
|
|
||||||
projects, err := yt.GetProjects()
|
projects, err := yt.GetProjects()
|
||||||
|
|
||||||
|
|
@ -16,7 +44,7 @@ func Workflow(name string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
issue, err := yt.CreateIssue(projects[0].ID, name)
|
issue, err := yt.CreateIssue(projects[1].ID, name)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -24,60 +52,66 @@ func Workflow(name string) (string, error) {
|
||||||
|
|
||||||
if issue != nil {
|
if issue != nil {
|
||||||
var (
|
var (
|
||||||
git, gitBuild, folder string
|
git, gitBuild *d.Git
|
||||||
|
cloud *d.Folder
|
||||||
)
|
)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(3)
|
wg.Add(3)
|
||||||
|
|
||||||
go func() {
|
go func(ref **d.Git) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
git, _ = CreateRepo(issue.Key, 0)
|
*ref, _ = wc.CreateRepo(issue.Key)
|
||||||
}()
|
}(&git)
|
||||||
|
|
||||||
go func() {
|
go func(ref **d.Git) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
gitBuild, _ = CreateRepo(issue.Key+"-build", 1)
|
*ref, _ = wc.CreateRepo(issue.Key + "-build")
|
||||||
}()
|
}(&gitBuild)
|
||||||
|
|
||||||
go func() {
|
go func(ref **d.Folder) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
folder = CreateFolder(issue.Key + " - " + issue.Summary)
|
*ref, _ = wc.CreateFolder(issue.Key + " - " + issue.Summary)
|
||||||
}()
|
}(&cloud)
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
yt.UpdateIssue(issue, folder, git, gitBuild)
|
yt.UpdateIssue(
|
||||||
|
issue,
|
||||||
|
cloud.PrivateURL,
|
||||||
|
git.HtmlUrl,
|
||||||
|
fmt.Sprintf("ssh://%s/%s.git", gitBuild.SshUrl, gitBuild.FullName))
|
||||||
}
|
}
|
||||||
return issue.Key, nil
|
return issue.Key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateRepo(name string, param uint) (string, error) {
|
func (wc *WorkflowController) CreateRepo(name string) (*d.Git, error) {
|
||||||
gb := ext.NewGit(os.Getenv("GIT_BASE_URL"), os.Getenv("GIT_TOKEN"))
|
//Create git repository with iGit interface;
|
||||||
repo, err := gb.NewRepo(name)
|
repo, err := wc.iGit.NewRepo(name)
|
||||||
gb.AppsAsCollaboratorTo(repo)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
// 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
|
//Set 'apps' as collaborator to created repository;
|
||||||
|
_, err = wc.iGit.AppsAsCollaboratorTo(repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateFolder(name string) string {
|
return repo, nil
|
||||||
oc := ext.NewCloud(os.Getenv("CLOUD_BASE_URL"), os.Getenv("CLOUD_USER"), os.Getenv("CLOUD_PASS"))
|
}
|
||||||
|
|
||||||
cloud, _ := oc.CreateFolder(name)
|
func (wc *WorkflowController) CreateFolder(name string) (*d.Folder, error) {
|
||||||
if cloud != nil {
|
|
||||||
return cloud.FolderPath
|
//Create ownCloud folder w/ iCloud interface;
|
||||||
|
cloud, err := wc.iCloud.CreateFolder(name)
|
||||||
|
if cloud == nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return "no-folder"
|
|
||||||
|
/* [ ] Experimental call:
|
||||||
|
wc.iCloud.ShareToExternals(cloud)
|
||||||
|
*/
|
||||||
|
|
||||||
|
return cloud, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
type Folder struct {
|
||||||
|
Title string // k
|
||||||
|
PathTo string // /temp/k
|
||||||
|
PrivateURL string // http://domain/apps/files/?dir=/temp/k OR http://domain/f/3333
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
type Git struct {
|
type Git struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"` // "poop"
|
||||||
FullName string `json:"full_name"`
|
FullName string `json:"full_name"` // "developer/poop"
|
||||||
Private bool `json:"private"`
|
Private bool `json:"private"`
|
||||||
Url string `json:"url"`
|
Url string `json:"url"` // "http://localhost:8081/api/v3/repos/developer/poop"
|
||||||
CloneUrl string `json:"clone_url"`
|
CloneUrl string `json:"clone_url"` // "http://localhost:8081/git/developer/poop.git"
|
||||||
HtmlUrl string `json:"Html_url"`
|
HtmlUrl string `json:"Html_url"` // "http://localhost:8081/developer/poop"
|
||||||
SshUrl string `json:"ssh_url"`
|
SshUrl string `json:"ssh_url"` // ?!
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
type Project struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
ShortName string `json:"shortName"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// [ ] try `,omitempty` to remove extra struct;
|
||||||
|
|
||||||
|
type IssueUpdateRequest struct {
|
||||||
|
IssueCreateRequest
|
||||||
|
CustomFields []CustomField `json:"customFields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomField struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"$type"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
93
ext/cloud.go
93
ext/cloud.go
|
|
@ -1,42 +1,103 @@
|
||||||
package ext
|
package ext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
d "ticket-pimp/domain"
|
||||||
|
"ticket-pimp/helpers"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCloud(base, user, pass string) *Client {
|
type Cloud struct {
|
||||||
|
*Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type ICloud interface {
|
||||||
|
CreateFolder(name string) (*d.Folder, error)
|
||||||
|
ShareToExternals(cloud *d.Folder) (*d.Folder, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCloud(base, user, pass string) *Cloud {
|
||||||
|
|
||||||
client := NewClient().
|
client := NewClient().
|
||||||
SetTimeout(5*time.Second).
|
SetTimeout(5*time.Second).
|
||||||
SetCommonBasicAuth(user, pass).
|
SetCommonBasicAuth(user, pass).
|
||||||
SetBaseURL(base)
|
SetBaseURL(base)
|
||||||
|
|
||||||
return &Client{
|
return &Cloud{
|
||||||
|
Client: &Client{
|
||||||
client,
|
client,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Cloud struct {
|
func (c *Cloud) CreateFolder(name string) (*d.Folder, error) {
|
||||||
FolderName string
|
rootDir := os.Getenv("ROOTDIR")
|
||||||
FolderPath string
|
user := os.Getenv("CLOUD_USER")
|
||||||
|
|
||||||
|
davPath := "/remote.php/dav/files/"
|
||||||
|
parentPath := "/apps/files/?dir="
|
||||||
|
|
||||||
|
name = helpers.GitNaming(name)
|
||||||
|
|
||||||
|
cloud := d.Folder{
|
||||||
|
Title: name,
|
||||||
|
PrivateURL: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) CreateFolder(name string) (*Cloud, error) {
|
requestPath := davPath + user + rootDir + name
|
||||||
|
|
||||||
cloud := Cloud{
|
cloud.PathTo = parentPath + rootDir + name
|
||||||
FolderName: name,
|
|
||||||
FolderPath: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
pathName := os.Getenv("HOMEPATH") + name
|
resp, _ := c.R().
|
||||||
|
Send("MKCOL", requestPath)
|
||||||
resp, err := c.R().
|
|
||||||
Send("MKCOL", pathName)
|
|
||||||
|
|
||||||
if resp.IsSuccessState() {
|
if resp.IsSuccessState() {
|
||||||
cloud.FolderPath = c.BaseURL + os.Getenv("FOLDER_PATH") + name
|
// Set stupid URL to the d entity
|
||||||
}
|
cloud.PrivateURL = c.BaseURL + cloud.PathTo
|
||||||
|
|
||||||
|
// Try to set short URL to the d entity
|
||||||
|
if err := c.setPrivateURL(requestPath, &cloud); err != nil {
|
||||||
return &cloud, err
|
return &cloud, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cloud, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cloud) setPrivateURL(requestPath string, cloud *d.Folder) error {
|
||||||
|
|
||||||
|
payload := []byte(`<?xml version="1.0"?><a:propfind xmlns:a="DAV:" xmlns:oc="http://owncloud.org/ns"><a:prop><oc:fileid/></a:prop></a:propfind>`)
|
||||||
|
|
||||||
|
// Deprecated: Read XML file
|
||||||
|
/*
|
||||||
|
xmlFile, err := ioutil.ReadFile("./fileid.xml") // moved into this method as a string..
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("request xml file error: %v", err)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
resp, _ := c.R().
|
||||||
|
SetBody(payload).
|
||||||
|
Send("PROPFIND", requestPath)
|
||||||
|
|
||||||
|
if resp.Err != nil {
|
||||||
|
return resp.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
id := helpers.GetFileIDFromRespBody(resp.Bytes())
|
||||||
|
|
||||||
|
if id == 0 {
|
||||||
|
return fmt.Errorf("unable to get fileid")
|
||||||
|
}
|
||||||
|
|
||||||
|
cloud.PrivateURL = c.BaseURL + "/f/" + strconv.Itoa(id)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cloud) ShareToExternals(cloud *d.Folder) (*d.Folder, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
46
ext/git.go
46
ext/git.go
|
|
@ -10,7 +10,11 @@ import (
|
||||||
|
|
||||||
type Git struct {
|
type Git struct {
|
||||||
*Client
|
*Client
|
||||||
*domain.Git
|
}
|
||||||
|
|
||||||
|
type IGit interface {
|
||||||
|
NewRepo(string) (*domain.Git, error)
|
||||||
|
AppsAsCollaboratorTo(*domain.Git) (*domain.Git, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGit(base, token string) *Git {
|
func NewGit(base, token string) *Git {
|
||||||
|
|
@ -28,15 +32,6 @@ func NewGit(base, token string) *Git {
|
||||||
|
|
||||||
return &Git{
|
return &Git{
|
||||||
Client: &Client{client},
|
Client: &Client{client},
|
||||||
Git: &domain.Git{
|
|
||||||
Name: "",
|
|
||||||
FullName: "",
|
|
||||||
Private: true,
|
|
||||||
Url: "",
|
|
||||||
CloneUrl: "",
|
|
||||||
HtmlUrl: "",
|
|
||||||
SshUrl: "",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,32 +53,37 @@ func (gb *Git) NewRepo(name string) (*domain.Git, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var git domain.Git
|
var git domain.Git
|
||||||
|
git.Private = true
|
||||||
|
|
||||||
resp, err := gb.R().
|
resp, _ := gb.R().
|
||||||
SetBody(&payload).
|
SetBody(&payload).
|
||||||
SetSuccessResult(&git).
|
SetSuccessResult(&git).
|
||||||
Post("/user/repos")
|
Post("/user/repos")
|
||||||
//Post("/orgs/apps/repos")
|
|
||||||
|
|
||||||
if err != nil {
|
if resp.Err != nil {
|
||||||
log.Print(resp)
|
log.Print(resp.Err)
|
||||||
|
return nil, resp.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &git, err
|
return &git, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gb *Client) AppsAsCollaboratorTo(git *domain.Git) (*domain.Git, error) {
|
func (gb *Git) AppsAsCollaboratorTo(git *domain.Git) (*domain.Git, error) {
|
||||||
payloadPermission := permissionRequest{
|
|
||||||
|
payload := permissionRequest{
|
||||||
Perm: "admin",
|
Perm: "admin",
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := gb.R().
|
respURL := "/repos/" + os.Getenv("GIT_USER") + "/" + git.Name + "/collaborators/apps"
|
||||||
SetBody(&payloadPermission).
|
|
||||||
Put("/repos/" + os.Getenv("GIT_USER") + "/" + git.Name + "/collaborators/apps")
|
|
||||||
|
|
||||||
if err != nil {
|
resp, _ := gb.R().
|
||||||
log.Print(resp)
|
SetBody(&payload).
|
||||||
|
Put(respURL)
|
||||||
|
|
||||||
|
if resp.Err != nil {
|
||||||
|
log.Print(resp.Err)
|
||||||
|
return nil, resp.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
return git, err
|
return git, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
75
ext/yt.go
75
ext/yt.go
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
d "ticket-pimp/domain"
|
||||||
|
|
||||||
"github.com/imroc/req/v3"
|
"github.com/imroc/req/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -12,6 +14,12 @@ type youtrack struct {
|
||||||
*req.Client
|
*req.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IYouTrack interface {
|
||||||
|
GetProjects() ([]d.Project, error)
|
||||||
|
CreateIssue(projectID, name string) (*d.IssueCreateRequest, error)
|
||||||
|
UpdateIssue(issue *d.IssueCreateRequest, folder, git, gitBuild string) (*d.IssueUpdateRequest, error)
|
||||||
|
}
|
||||||
|
|
||||||
func NewYT(base, token string) *youtrack {
|
func NewYT(base, token string) *youtrack {
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
|
|
@ -29,51 +37,33 @@ func NewYT(base, token string) *youtrack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Project struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
ShortName string `json:"shortName"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProjects
|
// GetProjects
|
||||||
// provides an array of existing projects;
|
// provides an array of existing projects;
|
||||||
func (yt *youtrack) GetProjects() ([]Project, error) {
|
func (yt *youtrack) GetProjects() ([]d.Project, error) {
|
||||||
|
|
||||||
var projects []Project
|
var projects []d.Project
|
||||||
|
|
||||||
_, err := yt.R().
|
resp, _ := yt.R().
|
||||||
EnableDump().
|
EnableDump().
|
||||||
SetQueryParam("fields", "id,name,shortName").
|
SetQueryParam("fields", "id,name,shortName").
|
||||||
SetSuccessResult(&projects).
|
SetSuccessResult(&projects).
|
||||||
Get("/admin/projects")
|
Get("/admin/projects")
|
||||||
|
|
||||||
// Check if the request failed;
|
// Check if the request failed;
|
||||||
if err != nil {
|
if resp.Err != nil {
|
||||||
return nil, fmt.Errorf("some problem with YT request. error message: %v", err)
|
return nil, fmt.Errorf("some problem with YT request. error message: %v", resp.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return projects, nil
|
return projects, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// 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, error) {
|
func (yt *youtrack) CreateIssue(projectID, name string) (*d.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 := d.IssueCreateRequest{
|
||||||
ProjectID: ProjectID{
|
ProjectID: d.ProjectID{
|
||||||
ID: projectID, //"id":"0-2"
|
ID: projectID, //"id":"0-2"
|
||||||
},
|
},
|
||||||
Summary: name,
|
Summary: name,
|
||||||
|
|
@ -81,40 +71,25 @@ func (yt *youtrack) CreateIssue(projectID, name string) (*IssueCreateRequest, er
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push issue to the YT;
|
// Push issue to the YT;
|
||||||
_, err := yt.R().
|
resp, _ := yt.R().
|
||||||
SetQueryParam("fields", "idReadable,id").
|
SetQueryParam("fields", "idReadable,id").
|
||||||
SetBody(&issue).
|
SetBody(&issue).
|
||||||
SetSuccessResult(&issue).
|
SetSuccessResult(&issue).
|
||||||
Post("/issues")
|
Post("/issues")
|
||||||
|
|
||||||
// Check if the request failed;
|
// Check if the request failed;
|
||||||
if err != nil {
|
if resp.Err != nil {
|
||||||
return nil, fmt.Errorf("some problem with YT request. error message: %v", err)
|
return nil, fmt.Errorf("some problem with YT request. error message: %v", resp.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &issue, nil
|
return &issue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type IssueUpdateRequest struct {
|
func (yt *youtrack) UpdateIssue(issue *d.IssueCreateRequest, folder, git, gitBuild string) (*d.IssueUpdateRequest, error) {
|
||||||
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, error) {
|
|
||||||
// Set Folder, Git, GitBuild to the Issue:
|
// Set Folder, Git, GitBuild to the Issue:
|
||||||
update := IssueUpdateRequest{
|
update := d.IssueUpdateRequest{
|
||||||
IssueCreateRequest: *issue,
|
IssueCreateRequest: *issue,
|
||||||
CustomFields: []CustomField{
|
CustomFields: []d.CustomField{
|
||||||
{
|
{
|
||||||
Name: "Директория графики",
|
Name: "Директория графики",
|
||||||
Type: "SimpleIssueCustomField",
|
Type: "SimpleIssueCustomField",
|
||||||
|
|
@ -134,14 +109,14 @@ func (yt *youtrack) UpdateIssue(issue *IssueCreateRequest, folder, git, gitBuild
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push issue update to YT
|
// Push issue update to YT
|
||||||
resp, err := yt.R().
|
resp, _ := yt.R().
|
||||||
SetBody(&update).
|
SetBody(&update).
|
||||||
SetSuccessResult(&issue).
|
SetSuccessResult(&issue).
|
||||||
Post("/issues/" + issue.Key)
|
Post("/issues/" + issue.Key)
|
||||||
|
|
||||||
// Check if the request failed;
|
// Check if the request failed;
|
||||||
if err != nil {
|
if resp.Err != nil {
|
||||||
return nil, fmt.Errorf("some problem with YT request. error message: %v", err)
|
return nil, fmt.Errorf("some problem with YT request. error message: %v", resp.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !resp.IsSuccessState() {
|
if !resp.IsSuccessState() {
|
||||||
|
|
|
||||||
1
go.mod
1
go.mod
|
|
@ -19,6 +19,7 @@ require (
|
||||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||||
github.com/quic-go/quic-go v0.35.1 // indirect
|
github.com/quic-go/quic-go v0.35.1 // indirect
|
||||||
|
github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2 // indirect
|
||||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
|
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
|
||||||
golang.org/x/crypto v0.9.0 // indirect
|
golang.org/x/crypto v0.9.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -38,6 +38,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||||
|
github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2 h1:VsBj3UD2xyAOu7kJw6O/2jjG2UXLFoBzihqDU9Ofg9M=
|
||||||
|
github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
||||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
|
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
|
||||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,108 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"ticket-pimp/controller"
|
"ticket-pimp/controller"
|
||||||
|
d "ticket-pimp/domain"
|
||||||
|
|
||||||
"github.com/mr-linch/go-tg"
|
"github.com/mr-linch/go-tg"
|
||||||
"github.com/mr-linch/go-tg/tgb"
|
"github.com/mr-linch/go-tg/tgb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
workflow controller.IWorkflowController
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(gitBaseURL, gitToken, cloudBaseURL, cloudAuthUser, cloudAuthPass, ytBaseURL, ytToken string) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
workflow: controller.NewWorkflowController(
|
||||||
|
gitBaseURL,
|
||||||
|
gitToken,
|
||||||
|
cloudBaseURL,
|
||||||
|
cloudAuthUser,
|
||||||
|
cloudAuthPass,
|
||||||
|
ytBaseURL,
|
||||||
|
ytToken),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) PingHandler(ctx context.Context, mu *tgb.MessageUpdate) error {
|
||||||
|
return mu.Answer("pong").DoVoid(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type git struct {
|
||||||
|
name string
|
||||||
|
url string
|
||||||
|
|
||||||
|
git string
|
||||||
|
ssh string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGit(d *d.Git) *git {
|
||||||
|
return &git{
|
||||||
|
name: d.Name,
|
||||||
|
url: d.HtmlUrl,
|
||||||
|
git: d.CloneUrl,
|
||||||
|
ssh: fmt.Sprintf("ssh://%s/%s.git", d.SshUrl, d.FullName),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FYI: Telegram doesn't renders this hyperlink, if the url is localhost 🤷♂️
|
||||||
|
func (g *git) prepareAnswer() string {
|
||||||
|
return tg.HTML.Text(
|
||||||
|
tg.HTML.Line(
|
||||||
|
"Repo ",
|
||||||
|
tg.HTML.Link(g.name, g.url),
|
||||||
|
"has been created!",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) NewRepoHandler(ctx context.Context, mu *tgb.MessageUpdate) error {
|
||||||
|
|
||||||
|
str := strings.Replace(mu.Text, "/repo", "", 1)
|
||||||
|
|
||||||
|
if str == "" {
|
||||||
|
return errors.New("empty command provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
var g *d.Git
|
||||||
|
g, err := h.workflow.CreateRepo(str)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return mu.Answer(errorAnswer(err.Error())).ParseMode(tg.HTML).DoVoid(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := newGit(g).prepareAnswer()
|
||||||
|
|
||||||
|
return mu.Answer(resp).ParseMode(tg.HTML).DoVoid(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) NewFolderHandler(ctx context.Context, mu *tgb.MessageUpdate) error {
|
||||||
|
|
||||||
|
str := strings.Replace(mu.Text, "/folder", "", 1)
|
||||||
|
|
||||||
|
if str == "" {
|
||||||
|
return errors.New("empty command provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
cloud, err := h.workflow.CreateFolder(str)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return mu.Answer(errorAnswer(err.Error())).ParseMode(tg.HTML).DoVoid(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
answer := tg.HTML.Text(
|
||||||
|
tg.HTML.Line(
|
||||||
|
"✨ Shiny folder",
|
||||||
|
tg.HTML.Link(cloud.Title, cloud.PrivateURL),
|
||||||
|
"has been created!",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return mu.Answer(answer).
|
||||||
|
ParseMode(tg.HTML).
|
||||||
|
DoVoid(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func errorAnswer(errorMsg string) string {
|
func errorAnswer(errorMsg string) string {
|
||||||
return tg.HTML.Text(
|
return tg.HTML.Text(
|
||||||
tg.HTML.Line(
|
tg.HTML.Line(
|
||||||
|
|
@ -19,7 +116,7 @@ func errorAnswer(errorMsg string) string {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTicketHandler(ctx context.Context, mu *tgb.MessageUpdate) error {
|
func (h *Handler) NewTicketHandler(ctx context.Context, mu *tgb.MessageUpdate) error {
|
||||||
|
|
||||||
str := strings.Replace(mu.Text, "/new", "", 1)
|
str := strings.Replace(mu.Text, "/new", "", 1)
|
||||||
|
|
||||||
|
|
@ -27,7 +124,7 @@ func NewTicketHandler(ctx context.Context, mu *tgb.MessageUpdate) error {
|
||||||
return errors.New("empty command provided")
|
return errors.New("empty command provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
issueKeyStr, err := controller.Workflow(str)
|
issueKeyStr, err := h.workflow.Workflow(str)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mu.Answer(errorAnswer(err.Error())).ParseMode(tg.HTML).DoVoid(ctx)
|
return mu.Answer(errorAnswer(err.Error())).ParseMode(tg.HTML).DoVoid(ctx)
|
||||||
|
|
@ -45,34 +142,3 @@ func newTicketAnswer(name string) string {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package helpers
|
package helpers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/xml"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -22,3 +24,37 @@ func GitNaming(input string) string {
|
||||||
// Join words and return
|
// Join words and return
|
||||||
return strings.Join(words, "-")
|
return strings.Join(words, "-")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MultistatusObj struct {
|
||||||
|
XMLName xml.Name `xml:"multistatus"`
|
||||||
|
Multistatus struct {
|
||||||
|
XMLName xml.Name `xml:"response"`
|
||||||
|
Propstat struct {
|
||||||
|
XMLName xml.Name `xml:"propstat"`
|
||||||
|
Prop struct {
|
||||||
|
XMLName xml.Name `xml:"prop"`
|
||||||
|
FileID struct {
|
||||||
|
XMLName xml.Name `xml:"fileid"`
|
||||||
|
ID string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFileIDFromRespBody(str []byte) int {
|
||||||
|
|
||||||
|
var multi MultistatusObj
|
||||||
|
|
||||||
|
err := xml.Unmarshal(str, &multi)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(multi.Multistatus.Propstat.Prop.FileID.ID)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package helpers
|
package helpers
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
type test struct {
|
type test struct {
|
||||||
arg, expected string
|
arg, expected string
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EXAMPLE = "<?xml version=\"1.0\"?>\n<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\"><d:response><d:href>/remote.php/dav/files/naudachu/temp/id/</d:href><d:propstat><d:prop><oc:fileid>33225</oc:fileid></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response></d:multistatus>\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// [ ] todo normal test...
|
||||||
|
func TestGetFileID(t *testing.T) {
|
||||||
|
|
||||||
|
if output := GetFileIDFromRespBody([]byte(EXAMPLE)); output != 33225 {
|
||||||
|
t.Errorf("Output %q not equal to expected %q", output, 33225)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue