* add more webhook support * move hooks templates to standalone dir and add more webhooks ui * fix tests * update vendor checksum * add more webhook support * move hooks templates to standalone dir and add more webhooks ui * fix tests * update vendor checksum * update vendor Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com> * load attributes when created release * update comparsion doctags/v1.21.12.1
| @@ -537,7 +537,7 @@ _Symbols used in table:_ | |||||
| </tr> | </tr> | ||||
| <tr> | <tr> | ||||
| <td>Webhook support</td> | <td>Webhook support</td> | ||||
| <td>⁄</td> | |||||
| <td>✓</td> | |||||
| <td>✓</td> | <td>✓</td> | ||||
| <td>✓</td> | <td>✓</td> | ||||
| <td>✓</td> | <td>✓</td> | ||||
| @@ -618,6 +618,16 @@ func CommitRepoAction(opts CommitRepoActionOptions) error { | |||||
| case ActionDeleteBranch: // Delete Branch | case ActionDeleteBranch: // Delete Branch | ||||
| isHookEventPush = true | isHookEventPush = true | ||||
| if err = PrepareWebhooks(repo, HookEventDelete, &api.DeletePayload{ | |||||
| Ref: refName, | |||||
| RefType: "branch", | |||||
| PusherType: api.PusherTypeUser, | |||||
| Repo: apiRepo, | |||||
| Sender: apiPusher, | |||||
| }); err != nil { | |||||
| return fmt.Errorf("PrepareWebhooks.(delete branch): %v", err) | |||||
| } | |||||
| case ActionPushTag: // Create | case ActionPushTag: // Create | ||||
| isHookEventPush = true | isHookEventPush = true | ||||
| @@ -640,6 +650,16 @@ func CommitRepoAction(opts CommitRepoActionOptions) error { | |||||
| } | } | ||||
| case ActionDeleteTag: // Delete Tag | case ActionDeleteTag: // Delete Tag | ||||
| isHookEventPush = true | isHookEventPush = true | ||||
| if err = PrepareWebhooks(repo, HookEventDelete, &api.DeletePayload{ | |||||
| Ref: refName, | |||||
| RefType: "tag", | |||||
| PusherType: api.PusherTypeUser, | |||||
| Repo: apiRepo, | |||||
| Sender: apiPusher, | |||||
| }); err != nil { | |||||
| return fmt.Errorf("PrepareWebhooks.(delete tag): %v", err) | |||||
| } | |||||
| } | } | ||||
| if isHookEventPush { | if isHookEventPush { | ||||
| @@ -83,9 +83,10 @@ const ( | |||||
| type Comment struct { | type Comment struct { | ||||
| ID int64 `xorm:"pk autoincr"` | ID int64 `xorm:"pk autoincr"` | ||||
| Type CommentType | Type CommentType | ||||
| PosterID int64 `xorm:"INDEX"` | |||||
| Poster *User `xorm:"-"` | |||||
| IssueID int64 `xorm:"INDEX"` | |||||
| PosterID int64 `xorm:"INDEX"` | |||||
| Poster *User `xorm:"-"` | |||||
| IssueID int64 `xorm:"INDEX"` | |||||
| Issue *Issue `xorm:"-"` | |||||
| LabelID int64 | LabelID int64 | ||||
| Label *Label `xorm:"-"` | Label *Label `xorm:"-"` | ||||
| OldMilestoneID int64 | OldMilestoneID int64 | ||||
| @@ -116,6 +117,15 @@ type Comment struct { | |||||
| ShowTag CommentTag `xorm:"-"` | ShowTag CommentTag `xorm:"-"` | ||||
| } | } | ||||
| // LoadIssue loads issue from database | |||||
| func (c *Comment) LoadIssue() (err error) { | |||||
| if c.Issue != nil { | |||||
| return nil | |||||
| } | |||||
| c.Issue, err = GetIssueByID(c.IssueID) | |||||
| return | |||||
| } | |||||
| // AfterLoad is invoked from XORM after setting the values of all fields of this object. | // AfterLoad is invoked from XORM after setting the values of all fields of this object. | ||||
| func (c *Comment) AfterLoad(session *xorm.Session) { | func (c *Comment) AfterLoad(session *xorm.Session) { | ||||
| var err error | var err error | ||||
| @@ -146,40 +156,40 @@ func (c *Comment) AfterDelete() { | |||||
| // HTMLURL formats a URL-string to the issue-comment | // HTMLURL formats a URL-string to the issue-comment | ||||
| func (c *Comment) HTMLURL() string { | func (c *Comment) HTMLURL() string { | ||||
| issue, err := GetIssueByID(c.IssueID) | |||||
| err := c.LoadIssue() | |||||
| if err != nil { // Silently dropping errors :unamused: | if err != nil { // Silently dropping errors :unamused: | ||||
| log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err) | |||||
| log.Error(4, "LoadIssue(%d): %v", c.IssueID, err) | |||||
| return "" | return "" | ||||
| } | } | ||||
| return fmt.Sprintf("%s#%s", issue.HTMLURL(), c.HashTag()) | |||||
| return fmt.Sprintf("%s#%s", c.Issue.HTMLURL(), c.HashTag()) | |||||
| } | } | ||||
| // IssueURL formats a URL-string to the issue | // IssueURL formats a URL-string to the issue | ||||
| func (c *Comment) IssueURL() string { | func (c *Comment) IssueURL() string { | ||||
| issue, err := GetIssueByID(c.IssueID) | |||||
| err := c.LoadIssue() | |||||
| if err != nil { // Silently dropping errors :unamused: | if err != nil { // Silently dropping errors :unamused: | ||||
| log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err) | |||||
| log.Error(4, "LoadIssue(%d): %v", c.IssueID, err) | |||||
| return "" | return "" | ||||
| } | } | ||||
| if issue.IsPull { | |||||
| if c.Issue.IsPull { | |||||
| return "" | return "" | ||||
| } | } | ||||
| return issue.HTMLURL() | |||||
| return c.Issue.HTMLURL() | |||||
| } | } | ||||
| // PRURL formats a URL-string to the pull-request | // PRURL formats a URL-string to the pull-request | ||||
| func (c *Comment) PRURL() string { | func (c *Comment) PRURL() string { | ||||
| issue, err := GetIssueByID(c.IssueID) | |||||
| err := c.LoadIssue() | |||||
| if err != nil { // Silently dropping errors :unamused: | if err != nil { // Silently dropping errors :unamused: | ||||
| log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err) | |||||
| log.Error(4, "LoadIssue(%d): %v", c.IssueID, err) | |||||
| return "" | return "" | ||||
| } | } | ||||
| if !issue.IsPull { | |||||
| if !c.Issue.IsPull { | |||||
| return "" | return "" | ||||
| } | } | ||||
| return issue.HTMLURL() | |||||
| return c.Issue.HTMLURL() | |||||
| } | } | ||||
| // APIFormat converts a Comment to the api.Comment format | // APIFormat converts a Comment to the api.Comment format | ||||
| @@ -196,9 +206,14 @@ func (c *Comment) APIFormat() *api.Comment { | |||||
| } | } | ||||
| } | } | ||||
| // CommentHashTag returns unique hash tag for comment id. | |||||
| func CommentHashTag(id int64) string { | |||||
| return fmt.Sprintf("issuecomment-%d", id) | |||||
| } | |||||
| // HashTag returns unique hash tag for comment. | // HashTag returns unique hash tag for comment. | ||||
| func (c *Comment) HashTag() string { | func (c *Comment) HashTag() string { | ||||
| return "issuecomment-" + com.ToStr(c.ID) | |||||
| return CommentHashTag(c.ID) | |||||
| } | } | ||||
| // EventTag returns unique event hash tag for comment. | // EventTag returns unique event hash tag for comment. | ||||
| @@ -576,7 +591,7 @@ func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) { | |||||
| // CreateIssueComment creates a plain issue comment. | // CreateIssueComment creates a plain issue comment. | ||||
| func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) { | func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) { | ||||
| return CreateComment(&CreateCommentOptions{ | |||||
| comment, err := CreateComment(&CreateCommentOptions{ | |||||
| Type: CommentTypeComment, | Type: CommentTypeComment, | ||||
| Doer: doer, | Doer: doer, | ||||
| Repo: repo, | Repo: repo, | ||||
| @@ -584,6 +599,21 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri | |||||
| Content: content, | Content: content, | ||||
| Attachments: attachments, | Attachments: attachments, | ||||
| }) | }) | ||||
| if err != nil { | |||||
| return nil, fmt.Errorf("CreateComment: %v", err) | |||||
| } | |||||
| mode, _ := AccessLevel(doer.ID, repo) | |||||
| if err = PrepareWebhooks(repo, HookEventIssueComment, &api.IssueCommentPayload{ | |||||
| Action: api.HookIssueCommentCreated, | |||||
| Issue: issue.APIFormat(), | |||||
| Comment: comment.APIFormat(), | |||||
| Repository: repo.APIFormat(mode), | |||||
| Sender: doer.APIFormat(), | |||||
| }); err != nil { | |||||
| log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err) | |||||
| } | |||||
| return comment, nil | |||||
| } | } | ||||
| // CreateRefComment creates a commit reference comment to issue. | // CreateRefComment creates a commit reference comment to issue. | ||||
| @@ -696,17 +726,41 @@ func GetCommentsByRepoIDSince(repoID, since int64) ([]*Comment, error) { | |||||
| } | } | ||||
| // UpdateComment updates information of comment. | // UpdateComment updates information of comment. | ||||
| func UpdateComment(c *Comment) error { | |||||
| func UpdateComment(doer *User, c *Comment, oldContent string) error { | |||||
| if _, err := x.ID(c.ID).AllCols().Update(c); err != nil { | if _, err := x.ID(c.ID).AllCols().Update(c); err != nil { | ||||
| return err | return err | ||||
| } else if c.Type == CommentTypeComment { | } else if c.Type == CommentTypeComment { | ||||
| UpdateIssueIndexer(c.IssueID) | UpdateIssueIndexer(c.IssueID) | ||||
| } | } | ||||
| if err := c.LoadIssue(); err != nil { | |||||
| return err | |||||
| } | |||||
| if err := c.Issue.LoadAttributes(); err != nil { | |||||
| return err | |||||
| } | |||||
| mode, _ := AccessLevel(doer.ID, c.Issue.Repo) | |||||
| if err := PrepareWebhooks(c.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{ | |||||
| Action: api.HookIssueCommentEdited, | |||||
| Issue: c.Issue.APIFormat(), | |||||
| Comment: c.APIFormat(), | |||||
| Changes: &api.ChangesPayload{ | |||||
| Body: &api.ChangesFromPayload{ | |||||
| From: oldContent, | |||||
| }, | |||||
| }, | |||||
| Repository: c.Issue.Repo.APIFormat(mode), | |||||
| Sender: doer.APIFormat(), | |||||
| }); err != nil { | |||||
| log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", c.ID, err) | |||||
| } | |||||
| return nil | return nil | ||||
| } | } | ||||
| // DeleteComment deletes the comment | // DeleteComment deletes the comment | ||||
| func DeleteComment(comment *Comment) error { | |||||
| func DeleteComment(doer *User, comment *Comment) error { | |||||
| sess := x.NewSession() | sess := x.NewSession() | ||||
| defer sess.Close() | defer sess.Close() | ||||
| if err := sess.Begin(); err != nil { | if err := sess.Begin(); err != nil { | ||||
| @@ -733,5 +787,25 @@ func DeleteComment(comment *Comment) error { | |||||
| } else if comment.Type == CommentTypeComment { | } else if comment.Type == CommentTypeComment { | ||||
| UpdateIssueIndexer(comment.IssueID) | UpdateIssueIndexer(comment.IssueID) | ||||
| } | } | ||||
| if err := comment.LoadIssue(); err != nil { | |||||
| return err | |||||
| } | |||||
| if err := comment.Issue.LoadAttributes(); err != nil { | |||||
| return err | |||||
| } | |||||
| mode, _ := AccessLevel(doer.ID, comment.Issue.Repo) | |||||
| if err := PrepareWebhooks(comment.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{ | |||||
| Action: api.HookIssueCommentDeleted, | |||||
| Issue: comment.Issue.APIFormat(), | |||||
| Comment: comment.APIFormat(), | |||||
| Repository: comment.Issue.Repo.APIFormat(mode), | |||||
| Sender: doer.APIFormat(), | |||||
| }); err != nil { | |||||
| log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err) | |||||
| } | |||||
| return nil | return nil | ||||
| } | } | ||||
| @@ -5,6 +5,9 @@ | |||||
| package models | package models | ||||
| import ( | import ( | ||||
| "fmt" | |||||
| "code.gitea.io/gitea/modules/log" | |||||
| "code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
| "code.gitea.io/gitea/modules/util" | "code.gitea.io/gitea/modules/util" | ||||
| api "code.gitea.io/sdk/gitea" | api "code.gitea.io/sdk/gitea" | ||||
| @@ -358,7 +361,49 @@ func ChangeMilestoneAssign(issue *Issue, doer *User, oldMilestoneID int64) (err | |||||
| if err = changeMilestoneAssign(sess, doer, issue, oldMilestoneID); err != nil { | if err = changeMilestoneAssign(sess, doer, issue, oldMilestoneID); err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| return sess.Commit() | |||||
| if err = sess.Commit(); err != nil { | |||||
| return fmt.Errorf("Commit: %v", err) | |||||
| } | |||||
| var hookAction api.HookIssueAction | |||||
| if issue.MilestoneID > 0 { | |||||
| hookAction = api.HookIssueMilestoned | |||||
| } else { | |||||
| hookAction = api.HookIssueDemilestoned | |||||
| } | |||||
| if err = issue.LoadAttributes(); err != nil { | |||||
| return err | |||||
| } | |||||
| mode, _ := AccessLevel(doer.ID, issue.Repo) | |||||
| if issue.IsPull { | |||||
| err = issue.PullRequest.LoadIssue() | |||||
| if err != nil { | |||||
| log.Error(2, "LoadIssue: %v", err) | |||||
| return | |||||
| } | |||||
| err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{ | |||||
| Action: hookAction, | |||||
| Index: issue.Index, | |||||
| PullRequest: issue.PullRequest.APIFormat(), | |||||
| Repository: issue.Repo.APIFormat(mode), | |||||
| Sender: doer.APIFormat(), | |||||
| }) | |||||
| } else { | |||||
| err = PrepareWebhooks(issue.Repo, HookEventIssues, &api.IssuePayload{ | |||||
| Action: hookAction, | |||||
| Index: issue.Index, | |||||
| Issue: issue.APIFormat(), | |||||
| Repository: issue.Repo.APIFormat(mode), | |||||
| Sender: doer.APIFormat(), | |||||
| }) | |||||
| } | |||||
| if err != nil { | |||||
| log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) | |||||
| } | |||||
| return nil | |||||
| } | } | ||||
| // DeleteMilestoneByRepoID deletes a milestone from a repository. | // DeleteMilestoneByRepoID deletes a milestone from a repository. | ||||
| @@ -232,6 +232,8 @@ func TestChangeMilestoneAssign(t *testing.T) { | |||||
| assert.NoError(t, PrepareTestDatabase()) | assert.NoError(t, PrepareTestDatabase()) | ||||
| issue := AssertExistsAndLoadBean(t, &Issue{RepoID: 1}).(*Issue) | issue := AssertExistsAndLoadBean(t, &Issue{RepoID: 1}).(*Issue) | ||||
| doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | ||||
| assert.NotNil(t, issue) | |||||
| assert.NotNil(t, doer) | |||||
| oldMilestoneID := issue.MilestoneID | oldMilestoneID := issue.MilestoneID | ||||
| issue.MilestoneID = 2 | issue.MilestoneID = 2 | ||||
| @@ -10,6 +10,7 @@ import ( | |||||
| "strings" | "strings" | ||||
| "code.gitea.io/git" | "code.gitea.io/git" | ||||
| "code.gitea.io/gitea/modules/log" | |||||
| "code.gitea.io/gitea/modules/process" | "code.gitea.io/gitea/modules/process" | ||||
| "code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
| "code.gitea.io/gitea/modules/util" | "code.gitea.io/gitea/modules/util" | ||||
| @@ -190,8 +191,27 @@ func CreateRelease(gitRepo *git.Repository, rel *Release, attachmentUUIDs []stri | |||||
| } | } | ||||
| err = addReleaseAttachments(rel.ID, attachmentUUIDs) | err = addReleaseAttachments(rel.ID, attachmentUUIDs) | ||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| return err | |||||
| if !rel.IsDraft { | |||||
| if err := rel.LoadAttributes(); err != nil { | |||||
| log.Error(2, "LoadAttributes: %v", err) | |||||
| } else { | |||||
| mode, _ := AccessLevel(rel.PublisherID, rel.Repo) | |||||
| if err := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{ | |||||
| Action: api.HookReleasePublished, | |||||
| Release: rel.APIFormat(), | |||||
| Repository: rel.Repo.APIFormat(mode), | |||||
| Sender: rel.Publisher.APIFormat(), | |||||
| }); err != nil { | |||||
| log.Error(2, "PrepareWebhooks: %v", err) | |||||
| } | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | } | ||||
| // GetRelease returns release by given ID. | // GetRelease returns release by given ID. | ||||
| @@ -2456,6 +2456,17 @@ func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *R | |||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| oldMode, _ := AccessLevel(doer.ID, oldRepo) | |||||
| mode, _ := AccessLevel(doer.ID, repo) | |||||
| if err = PrepareWebhooks(oldRepo, HookEventFork, &api.ForkPayload{ | |||||
| Forkee: repo.APIFormat(mode), | |||||
| Repo: oldRepo.APIFormat(oldMode), | |||||
| Sender: doer.APIFormat(), | |||||
| }); err != nil { | |||||
| log.Error(2, "PrepareWebhooks [repo_id: %d]: %v", oldRepo.ID, err) | |||||
| } | |||||
| if err = repo.UpdateSize(); err != nil { | if err = repo.UpdateSize(); err != nil { | ||||
| log.Error(4, "Failed to update size for repository: %v", err) | log.Error(4, "Failed to update size for repository: %v", err) | ||||
| } | } | ||||
| @@ -66,10 +66,15 @@ func IsValidHookContentType(name string) bool { | |||||
| // HookEvents is a set of web hook events | // HookEvents is a set of web hook events | ||||
| type HookEvents struct { | type HookEvents struct { | ||||
| Create bool `json:"create"` | |||||
| Push bool `json:"push"` | |||||
| PullRequest bool `json:"pull_request"` | |||||
| Repository bool `json:"repository"` | |||||
| Create bool `json:"create"` | |||||
| Delete bool `json:"delete"` | |||||
| Fork bool `json:"fork"` | |||||
| Issues bool `json:"issues"` | |||||
| IssueComment bool `json:"issue_comment"` | |||||
| Push bool `json:"push"` | |||||
| PullRequest bool `json:"pull_request"` | |||||
| Repository bool `json:"repository"` | |||||
| Release bool `json:"release"` | |||||
| } | } | ||||
| // HookEvent represents events that will delivery hook. | // HookEvent represents events that will delivery hook. | ||||
| @@ -155,6 +160,30 @@ func (w *Webhook) HasCreateEvent() bool { | |||||
| (w.ChooseEvents && w.HookEvents.Create) | (w.ChooseEvents && w.HookEvents.Create) | ||||
| } | } | ||||
| // HasDeleteEvent returns true if hook enabled delete event. | |||||
| func (w *Webhook) HasDeleteEvent() bool { | |||||
| return w.SendEverything || | |||||
| (w.ChooseEvents && w.HookEvents.Delete) | |||||
| } | |||||
| // HasForkEvent returns true if hook enabled fork event. | |||||
| func (w *Webhook) HasForkEvent() bool { | |||||
| return w.SendEverything || | |||||
| (w.ChooseEvents && w.HookEvents.Fork) | |||||
| } | |||||
| // HasIssuesEvent returns true if hook enabled issues event. | |||||
| func (w *Webhook) HasIssuesEvent() bool { | |||||
| return w.SendEverything || | |||||
| (w.ChooseEvents && w.HookEvents.Issues) | |||||
| } | |||||
| // HasIssueCommentEvent returns true if hook enabled issue_comment event. | |||||
| func (w *Webhook) HasIssueCommentEvent() bool { | |||||
| return w.SendEverything || | |||||
| (w.ChooseEvents && w.HookEvents.IssueComment) | |||||
| } | |||||
| // HasPushEvent returns true if hook enabled push event. | // HasPushEvent returns true if hook enabled push event. | ||||
| func (w *Webhook) HasPushEvent() bool { | func (w *Webhook) HasPushEvent() bool { | ||||
| return w.PushOnly || w.SendEverything || | return w.PushOnly || w.SendEverything || | ||||
| @@ -167,23 +196,46 @@ func (w *Webhook) HasPullRequestEvent() bool { | |||||
| (w.ChooseEvents && w.HookEvents.PullRequest) | (w.ChooseEvents && w.HookEvents.PullRequest) | ||||
| } | } | ||||
| // HasReleaseEvent returns if hook enabled release event. | |||||
| func (w *Webhook) HasReleaseEvent() bool { | |||||
| return w.SendEverything || | |||||
| (w.ChooseEvents && w.HookEvents.Release) | |||||
| } | |||||
| // HasRepositoryEvent returns if hook enabled repository event. | // HasRepositoryEvent returns if hook enabled repository event. | ||||
| func (w *Webhook) HasRepositoryEvent() bool { | func (w *Webhook) HasRepositoryEvent() bool { | ||||
| return w.SendEverything || | return w.SendEverything || | ||||
| (w.ChooseEvents && w.HookEvents.Repository) | (w.ChooseEvents && w.HookEvents.Repository) | ||||
| } | } | ||||
| func (w *Webhook) eventCheckers() []struct { | |||||
| has func() bool | |||||
| typ HookEventType | |||||
| } { | |||||
| return []struct { | |||||
| has func() bool | |||||
| typ HookEventType | |||||
| }{ | |||||
| {w.HasCreateEvent, HookEventCreate}, | |||||
| {w.HasDeleteEvent, HookEventDelete}, | |||||
| {w.HasForkEvent, HookEventFork}, | |||||
| {w.HasPushEvent, HookEventPush}, | |||||
| {w.HasIssuesEvent, HookEventIssues}, | |||||
| {w.HasIssueCommentEvent, HookEventIssueComment}, | |||||
| {w.HasPullRequestEvent, HookEventPullRequest}, | |||||
| {w.HasRepositoryEvent, HookEventRepository}, | |||||
| {w.HasReleaseEvent, HookEventRelease}, | |||||
| } | |||||
| } | |||||
| // EventsArray returns an array of hook events | // EventsArray returns an array of hook events | ||||
| func (w *Webhook) EventsArray() []string { | func (w *Webhook) EventsArray() []string { | ||||
| events := make([]string, 0, 3) | |||||
| if w.HasCreateEvent() { | |||||
| events = append(events, "create") | |||||
| } | |||||
| if w.HasPushEvent() { | |||||
| events = append(events, "push") | |||||
| } | |||||
| if w.HasPullRequestEvent() { | |||||
| events = append(events, "pull_request") | |||||
| events := make([]string, 0, 7) | |||||
| for _, c := range w.eventCheckers() { | |||||
| if c.has() { | |||||
| events = append(events, string(c.typ)) | |||||
| } | |||||
| } | } | ||||
| return events | return events | ||||
| } | } | ||||
| @@ -373,10 +425,15 @@ type HookEventType string | |||||
| // Types of hook events | // Types of hook events | ||||
| const ( | const ( | ||||
| HookEventCreate HookEventType = "create" | |||||
| HookEventPush HookEventType = "push" | |||||
| HookEventPullRequest HookEventType = "pull_request" | |||||
| HookEventRepository HookEventType = "repository" | |||||
| HookEventCreate HookEventType = "create" | |||||
| HookEventDelete HookEventType = "delete" | |||||
| HookEventFork HookEventType = "fork" | |||||
| HookEventPush HookEventType = "push" | |||||
| HookEventIssues HookEventType = "issues" | |||||
| HookEventIssueComment HookEventType = "issue_comment" | |||||
| HookEventPullRequest HookEventType = "pull_request" | |||||
| HookEventRepository HookEventType = "repository" | |||||
| HookEventRelease HookEventType = "release" | |||||
| ) | ) | ||||
| // HookRequest represents hook task request information. | // HookRequest represents hook task request information. | ||||
| @@ -488,22 +545,11 @@ func PrepareWebhook(w *Webhook, repo *Repository, event HookEventType, p api.Pay | |||||
| } | } | ||||
| func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType, p api.Payloader) error { | func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType, p api.Payloader) error { | ||||
| switch event { | |||||
| case HookEventCreate: | |||||
| if !w.HasCreateEvent() { | |||||
| return nil | |||||
| } | |||||
| case HookEventPush: | |||||
| if !w.HasPushEvent() { | |||||
| return nil | |||||
| } | |||||
| case HookEventPullRequest: | |||||
| if !w.HasPullRequestEvent() { | |||||
| return nil | |||||
| } | |||||
| case HookEventRepository: | |||||
| if !w.HasRepositoryEvent() { | |||||
| return nil | |||||
| for _, e := range w.eventCheckers() { | |||||
| if event == e.typ { | |||||
| if !e.has() { | |||||
| return nil | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -49,6 +49,38 @@ func getDingtalkCreatePayload(p *api.CreatePayload) (*DingtalkPayload, error) { | |||||
| }, nil | }, nil | ||||
| } | } | ||||
| func getDingtalkDeletePayload(p *api.DeletePayload) (*DingtalkPayload, error) { | |||||
| // created tag/branch | |||||
| refName := git.RefEndName(p.Ref) | |||||
| title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) | |||||
| return &DingtalkPayload{ | |||||
| MsgType: "actionCard", | |||||
| ActionCard: dingtalk.ActionCard{ | |||||
| Text: title, | |||||
| Title: title, | |||||
| HideAvatar: "0", | |||||
| SingleTitle: fmt.Sprintf("view branch %s", refName), | |||||
| SingleURL: p.Repo.HTMLURL + "/src/" + refName, | |||||
| }, | |||||
| }, nil | |||||
| } | |||||
| func getDingtalkForkPayload(p *api.ForkPayload) (*DingtalkPayload, error) { | |||||
| title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) | |||||
| return &DingtalkPayload{ | |||||
| MsgType: "actionCard", | |||||
| ActionCard: dingtalk.ActionCard{ | |||||
| Text: title, | |||||
| Title: title, | |||||
| HideAvatar: "0", | |||||
| SingleTitle: fmt.Sprintf("view forked repo %s", p.Repo.FullName), | |||||
| SingleURL: p.Repo.HTMLURL, | |||||
| }, | |||||
| }, nil | |||||
| } | |||||
| func getDingtalkPushPayload(p *api.PushPayload) (*DingtalkPayload, error) { | func getDingtalkPushPayload(p *api.PushPayload) (*DingtalkPayload, error) { | ||||
| var ( | var ( | ||||
| branchName = git.RefEndName(p.Ref) | branchName = git.RefEndName(p.Ref) | ||||
| @@ -98,6 +130,80 @@ func getDingtalkPushPayload(p *api.PushPayload) (*DingtalkPayload, error) { | |||||
| }, nil | }, nil | ||||
| } | } | ||||
| func getDingtalkIssuesPayload(p *api.IssuePayload) (*DingtalkPayload, error) { | |||||
| var text, title string | |||||
| switch p.Action { | |||||
| case api.HookIssueOpened: | |||||
| title = fmt.Sprintf("[%s] Issue opened: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| case api.HookIssueClosed: | |||||
| title = fmt.Sprintf("[%s] Issue closed: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| case api.HookIssueReOpened: | |||||
| title = fmt.Sprintf("[%s] Issue re-opened: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| case api.HookIssueEdited: | |||||
| title = fmt.Sprintf("[%s] Issue edited: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| case api.HookIssueAssigned: | |||||
| title = fmt.Sprintf("[%s] Issue assigned to %s: #%d %s", p.Repository.FullName, | |||||
| p.Issue.Assignee.UserName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| case api.HookIssueUnassigned: | |||||
| title = fmt.Sprintf("[%s] Issue unassigned: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| case api.HookIssueLabelUpdated: | |||||
| title = fmt.Sprintf("[%s] Pull request labels updated: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| case api.HookIssueLabelCleared: | |||||
| title = fmt.Sprintf("[%s] Pull request labels cleared: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| case api.HookIssueSynchronized: | |||||
| title = fmt.Sprintf("[%s] Pull request synchronized: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| } | |||||
| return &DingtalkPayload{ | |||||
| MsgType: "actionCard", | |||||
| ActionCard: dingtalk.ActionCard{ | |||||
| Text: text, | |||||
| Title: title, | |||||
| HideAvatar: "0", | |||||
| SingleTitle: "view pull request", | |||||
| SingleURL: p.Issue.URL, | |||||
| }, | |||||
| }, nil | |||||
| } | |||||
| func getDingtalkIssueCommentPayload(p *api.IssueCommentPayload) (*DingtalkPayload, error) { | |||||
| title := fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title) | |||||
| url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)) | |||||
| var content string | |||||
| switch p.Action { | |||||
| case api.HookIssueCommentCreated: | |||||
| title = "New comment: " + title | |||||
| content = p.Comment.Body | |||||
| case api.HookIssueCommentEdited: | |||||
| title = "Comment edited: " + title | |||||
| content = p.Comment.Body | |||||
| case api.HookIssueCommentDeleted: | |||||
| title = "Comment deleted: " + title | |||||
| url = fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index) | |||||
| content = p.Comment.Body | |||||
| } | |||||
| return &DingtalkPayload{ | |||||
| MsgType: "actionCard", | |||||
| ActionCard: dingtalk.ActionCard{ | |||||
| Text: content, | |||||
| Title: title, | |||||
| HideAvatar: "0", | |||||
| SingleTitle: "view pull request", | |||||
| SingleURL: url, | |||||
| }, | |||||
| }, nil | |||||
| } | |||||
| func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload, error) { | func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload, error) { | ||||
| var text, title string | var text, title string | ||||
| switch p.Action { | switch p.Action { | ||||
| @@ -182,6 +288,27 @@ func getDingtalkRepositoryPayload(p *api.RepositoryPayload) (*DingtalkPayload, e | |||||
| return nil, nil | return nil, nil | ||||
| } | } | ||||
| func getDingtalkReleasePayload(p *api.ReleasePayload) (*DingtalkPayload, error) { | |||||
| var title, url string | |||||
| switch p.Action { | |||||
| case api.HookReleasePublished: | |||||
| title = fmt.Sprintf("[%s] Release created", p.Release.TagName) | |||||
| url = p.Release.URL | |||||
| return &DingtalkPayload{ | |||||
| MsgType: "actionCard", | |||||
| ActionCard: dingtalk.ActionCard{ | |||||
| Text: title, | |||||
| Title: title, | |||||
| HideAvatar: "0", | |||||
| SingleTitle: "view repository", | |||||
| SingleURL: url, | |||||
| }, | |||||
| }, nil | |||||
| } | |||||
| return nil, nil | |||||
| } | |||||
| // GetDingtalkPayload converts a ding talk webhook into a DingtalkPayload | // GetDingtalkPayload converts a ding talk webhook into a DingtalkPayload | ||||
| func GetDingtalkPayload(p api.Payloader, event HookEventType, meta string) (*DingtalkPayload, error) { | func GetDingtalkPayload(p api.Payloader, event HookEventType, meta string) (*DingtalkPayload, error) { | ||||
| s := new(DingtalkPayload) | s := new(DingtalkPayload) | ||||
| @@ -189,12 +316,22 @@ func GetDingtalkPayload(p api.Payloader, event HookEventType, meta string) (*Din | |||||
| switch event { | switch event { | ||||
| case HookEventCreate: | case HookEventCreate: | ||||
| return getDingtalkCreatePayload(p.(*api.CreatePayload)) | return getDingtalkCreatePayload(p.(*api.CreatePayload)) | ||||
| case HookEventDelete: | |||||
| return getDingtalkDeletePayload(p.(*api.DeletePayload)) | |||||
| case HookEventFork: | |||||
| return getDingtalkForkPayload(p.(*api.ForkPayload)) | |||||
| case HookEventIssues: | |||||
| return getDingtalkIssuesPayload(p.(*api.IssuePayload)) | |||||
| case HookEventIssueComment: | |||||
| return getDingtalkIssueCommentPayload(p.(*api.IssueCommentPayload)) | |||||
| case HookEventPush: | case HookEventPush: | ||||
| return getDingtalkPushPayload(p.(*api.PushPayload)) | return getDingtalkPushPayload(p.(*api.PushPayload)) | ||||
| case HookEventPullRequest: | case HookEventPullRequest: | ||||
| return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload)) | return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload)) | ||||
| case HookEventRepository: | case HookEventRepository: | ||||
| return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload)) | return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload)) | ||||
| case HookEventRelease: | |||||
| return getDingtalkReleasePayload(p.(*api.ReleasePayload)) | |||||
| } | } | ||||
| return s, nil | return s, nil | ||||
| @@ -115,6 +115,51 @@ func getDiscordCreatePayload(p *api.CreatePayload, meta *DiscordMeta) (*DiscordP | |||||
| }, nil | }, nil | ||||
| } | } | ||||
| func getDiscordDeletePayload(p *api.DeletePayload, meta *DiscordMeta) (*DiscordPayload, error) { | |||||
| // deleted tag/branch | |||||
| refName := git.RefEndName(p.Ref) | |||||
| title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) | |||||
| return &DiscordPayload{ | |||||
| Username: meta.Username, | |||||
| AvatarURL: meta.IconURL, | |||||
| Embeds: []DiscordEmbed{ | |||||
| { | |||||
| Title: title, | |||||
| URL: p.Repo.HTMLURL + "/src/" + refName, | |||||
| Color: warnColor, | |||||
| Author: DiscordEmbedAuthor{ | |||||
| Name: p.Sender.UserName, | |||||
| URL: setting.AppURL + p.Sender.UserName, | |||||
| IconURL: p.Sender.AvatarURL, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| }, nil | |||||
| } | |||||
| func getDiscordForkPayload(p *api.ForkPayload, meta *DiscordMeta) (*DiscordPayload, error) { | |||||
| // fork | |||||
| title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) | |||||
| return &DiscordPayload{ | |||||
| Username: meta.Username, | |||||
| AvatarURL: meta.IconURL, | |||||
| Embeds: []DiscordEmbed{ | |||||
| { | |||||
| Title: title, | |||||
| URL: p.Repo.HTMLURL, | |||||
| Color: successColor, | |||||
| Author: DiscordEmbedAuthor{ | |||||
| Name: p.Sender.UserName, | |||||
| URL: setting.AppURL + p.Sender.UserName, | |||||
| IconURL: p.Sender.AvatarURL, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| }, nil | |||||
| } | |||||
| func getDiscordPushPayload(p *api.PushPayload, meta *DiscordMeta) (*DiscordPayload, error) { | func getDiscordPushPayload(p *api.PushPayload, meta *DiscordMeta) (*DiscordPayload, error) { | ||||
| var ( | var ( | ||||
| branchName = git.RefEndName(p.Ref) | branchName = git.RefEndName(p.Ref) | ||||
| @@ -165,6 +210,108 @@ func getDiscordPushPayload(p *api.PushPayload, meta *DiscordMeta) (*DiscordPaylo | |||||
| }, nil | }, nil | ||||
| } | } | ||||
| func getDiscordIssuesPayload(p *api.IssuePayload, meta *DiscordMeta) (*DiscordPayload, error) { | |||||
| var text, title string | |||||
| var color int | |||||
| switch p.Action { | |||||
| case api.HookIssueOpened: | |||||
| title = fmt.Sprintf("[%s] Issue opened: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| color = warnColor | |||||
| case api.HookIssueClosed: | |||||
| title = fmt.Sprintf("[%s] Issue closed: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| color = failedColor | |||||
| text = p.Issue.Body | |||||
| case api.HookIssueReOpened: | |||||
| title = fmt.Sprintf("[%s] Issue re-opened: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| color = warnColor | |||||
| case api.HookIssueEdited: | |||||
| title = fmt.Sprintf("[%s] Issue edited: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| color = warnColor | |||||
| case api.HookIssueAssigned: | |||||
| title = fmt.Sprintf("[%s] Issue assigned to %s: #%d %s", p.Repository.FullName, | |||||
| p.Issue.Assignee.UserName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| color = successColor | |||||
| case api.HookIssueUnassigned: | |||||
| title = fmt.Sprintf("[%s] Issue unassigned: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| color = warnColor | |||||
| case api.HookIssueLabelUpdated: | |||||
| title = fmt.Sprintf("[%s] Issue labels updated: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| color = warnColor | |||||
| case api.HookIssueLabelCleared: | |||||
| title = fmt.Sprintf("[%s] Issue labels cleared: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| color = warnColor | |||||
| case api.HookIssueSynchronized: | |||||
| title = fmt.Sprintf("[%s] Issue synchronized: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title) | |||||
| text = p.Issue.Body | |||||
| color = warnColor | |||||
| } | |||||
| return &DiscordPayload{ | |||||
| Username: meta.Username, | |||||
| AvatarURL: meta.IconURL, | |||||
| Embeds: []DiscordEmbed{ | |||||
| { | |||||
| Title: title, | |||||
| Description: text, | |||||
| URL: p.Issue.URL, | |||||
| Color: color, | |||||
| Author: DiscordEmbedAuthor{ | |||||
| Name: p.Sender.UserName, | |||||
| URL: setting.AppURL + p.Sender.UserName, | |||||
| IconURL: p.Sender.AvatarURL, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| }, nil | |||||
| } | |||||
| func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, discord *DiscordMeta) (*DiscordPayload, error) { | |||||
| title := fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title) | |||||
| url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)) | |||||
| content := "" | |||||
| var color int | |||||
| switch p.Action { | |||||
| case api.HookIssueCommentCreated: | |||||
| title = "New comment: " + title | |||||
| content = p.Comment.Body | |||||
| color = successColor | |||||
| case api.HookIssueCommentEdited: | |||||
| title = "Comment edited: " + title | |||||
| content = p.Comment.Body | |||||
| color = warnColor | |||||
| case api.HookIssueCommentDeleted: | |||||
| title = "Comment deleted: " + title | |||||
| url = fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index) | |||||
| content = p.Comment.Body | |||||
| color = warnColor | |||||
| } | |||||
| return &DiscordPayload{ | |||||
| Username: discord.Username, | |||||
| AvatarURL: discord.IconURL, | |||||
| Embeds: []DiscordEmbed{ | |||||
| { | |||||
| Title: title, | |||||
| Description: content, | |||||
| URL: url, | |||||
| Color: color, | |||||
| Author: DiscordEmbedAuthor{ | |||||
| Name: p.Sender.UserName, | |||||
| URL: setting.AppURL + p.Sender.UserName, | |||||
| IconURL: p.Sender.AvatarURL, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| }, nil | |||||
| } | |||||
| func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta) (*DiscordPayload, error) { | func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta) (*DiscordPayload, error) { | ||||
| var text, title string | var text, title string | ||||
| var color int | var color int | ||||
| @@ -267,6 +414,35 @@ func getDiscordRepositoryPayload(p *api.RepositoryPayload, meta *DiscordMeta) (* | |||||
| }, nil | }, nil | ||||
| } | } | ||||
| func getDiscordReleasePayload(p *api.ReleasePayload, meta *DiscordMeta) (*DiscordPayload, error) { | |||||
| var title, url string | |||||
| var color int | |||||
| switch p.Action { | |||||
| case api.HookReleasePublished: | |||||
| title = fmt.Sprintf("[%s] Release created", p.Release.TagName) | |||||
| url = p.Release.URL | |||||
| color = successColor | |||||
| } | |||||
| return &DiscordPayload{ | |||||
| Username: meta.Username, | |||||
| AvatarURL: meta.IconURL, | |||||
| Embeds: []DiscordEmbed{ | |||||
| { | |||||
| Title: title, | |||||
| Description: fmt.Sprintf("%s", p.Release.Note), | |||||
| URL: url, | |||||
| Color: color, | |||||
| Author: DiscordEmbedAuthor{ | |||||
| Name: p.Sender.UserName, | |||||
| URL: setting.AppURL + p.Sender.UserName, | |||||
| IconURL: p.Sender.AvatarURL, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| }, nil | |||||
| } | |||||
| // GetDiscordPayload converts a discord webhook into a DiscordPayload | // GetDiscordPayload converts a discord webhook into a DiscordPayload | ||||
| func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*DiscordPayload, error) { | func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*DiscordPayload, error) { | ||||
| s := new(DiscordPayload) | s := new(DiscordPayload) | ||||
| @@ -279,12 +455,22 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*Disc | |||||
| switch event { | switch event { | ||||
| case HookEventCreate: | case HookEventCreate: | ||||
| return getDiscordCreatePayload(p.(*api.CreatePayload), discord) | return getDiscordCreatePayload(p.(*api.CreatePayload), discord) | ||||
| case HookEventDelete: | |||||
| return getDiscordDeletePayload(p.(*api.DeletePayload), discord) | |||||
| case HookEventFork: | |||||
| return getDiscordForkPayload(p.(*api.ForkPayload), discord) | |||||
| case HookEventIssues: | |||||
| return getDiscordIssuesPayload(p.(*api.IssuePayload), discord) | |||||
| case HookEventIssueComment: | |||||
| return getDiscordIssueCommentPayload(p.(*api.IssueCommentPayload), discord) | |||||
| case HookEventPush: | case HookEventPush: | ||||
| return getDiscordPushPayload(p.(*api.PushPayload), discord) | return getDiscordPushPayload(p.(*api.PushPayload), discord) | ||||
| case HookEventPullRequest: | case HookEventPullRequest: | ||||
| return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord) | return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord) | ||||
| case HookEventRepository: | case HookEventRepository: | ||||
| return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord) | return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord) | ||||
| case HookEventRelease: | |||||
| return getDiscordReleasePayload(p.(*api.ReleasePayload), discord) | |||||
| } | } | ||||
| return s, nil | return s, nil | ||||
| @@ -106,6 +106,122 @@ func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayloa | |||||
| }, nil | }, nil | ||||
| } | } | ||||
| // getSlackDeletePayload composes Slack payload for delete a branch or tag. | |||||
| func getSlackDeletePayload(p *api.DeletePayload, slack *SlackMeta) (*SlackPayload, error) { | |||||
| refName := git.RefEndName(p.Ref) | |||||
| repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) | |||||
| text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName) | |||||
| return &SlackPayload{ | |||||
| Channel: slack.Channel, | |||||
| Text: text, | |||||
| Username: slack.Username, | |||||
| IconURL: slack.IconURL, | |||||
| }, nil | |||||
| } | |||||
| // getSlackForkPayload composes Slack payload for forked by a repository. | |||||
| func getSlackForkPayload(p *api.ForkPayload, slack *SlackMeta) (*SlackPayload, error) { | |||||
| baseLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) | |||||
| forkLink := SlackLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName) | |||||
| text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink) | |||||
| return &SlackPayload{ | |||||
| Channel: slack.Channel, | |||||
| Text: text, | |||||
| Username: slack.Username, | |||||
| IconURL: slack.IconURL, | |||||
| }, nil | |||||
| } | |||||
| func getSlackIssuesPayload(p *api.IssuePayload, slack *SlackMeta) (*SlackPayload, error) { | |||||
| senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) | |||||
| titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index), | |||||
| fmt.Sprintf("#%d %s", p.Index, p.Issue.Title)) | |||||
| var text, title, attachmentText string | |||||
| switch p.Action { | |||||
| case api.HookIssueOpened: | |||||
| text = fmt.Sprintf("[%s] Issue submitted by %s", p.Repository.FullName, senderLink) | |||||
| title = titleLink | |||||
| attachmentText = SlackTextFormatter(p.Issue.Body) | |||||
| case api.HookIssueClosed: | |||||
| text = fmt.Sprintf("[%s] Issue closed: %s by %s", p.Repository.FullName, titleLink, senderLink) | |||||
| case api.HookIssueReOpened: | |||||
| text = fmt.Sprintf("[%s] Issue re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink) | |||||
| case api.HookIssueEdited: | |||||
| text = fmt.Sprintf("[%s] Issue edited: %s by %s", p.Repository.FullName, titleLink, senderLink) | |||||
| attachmentText = SlackTextFormatter(p.Issue.Body) | |||||
| case api.HookIssueAssigned: | |||||
| text = fmt.Sprintf("[%s] Issue assigned to %s: %s by %s", p.Repository.FullName, | |||||
| SlackLinkFormatter(setting.AppURL+p.Issue.Assignee.UserName, p.Issue.Assignee.UserName), | |||||
| titleLink, senderLink) | |||||
| case api.HookIssueUnassigned: | |||||
| text = fmt.Sprintf("[%s] Issue unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink) | |||||
| case api.HookIssueLabelUpdated: | |||||
| text = fmt.Sprintf("[%s] Issue labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink) | |||||
| case api.HookIssueLabelCleared: | |||||
| text = fmt.Sprintf("[%s] Issue labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink) | |||||
| case api.HookIssueSynchronized: | |||||
| text = fmt.Sprintf("[%s] Issue synchronized: %s by %s", p.Repository.FullName, titleLink, senderLink) | |||||
| } | |||||
| return &SlackPayload{ | |||||
| Channel: slack.Channel, | |||||
| Text: text, | |||||
| Username: slack.Username, | |||||
| IconURL: slack.IconURL, | |||||
| Attachments: []SlackAttachment{{ | |||||
| Color: slack.Color, | |||||
| Title: title, | |||||
| Text: attachmentText, | |||||
| }}, | |||||
| }, nil | |||||
| } | |||||
| func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*SlackPayload, error) { | |||||
| senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) | |||||
| titleLink := SlackLinkFormatter(fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)), | |||||
| fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title)) | |||||
| var text, title, attachmentText string | |||||
| switch p.Action { | |||||
| case api.HookIssueCommentCreated: | |||||
| text = fmt.Sprintf("[%s] New comment created by %s", p.Repository.FullName, senderLink) | |||||
| title = titleLink | |||||
| attachmentText = SlackTextFormatter(p.Comment.Body) | |||||
| case api.HookIssueCommentEdited: | |||||
| text = fmt.Sprintf("[%s] Comment edited by %s", p.Repository.FullName, senderLink) | |||||
| title = titleLink | |||||
| attachmentText = SlackTextFormatter(p.Comment.Body) | |||||
| case api.HookIssueCommentDeleted: | |||||
| text = fmt.Sprintf("[%s] Comment deleted by %s", p.Repository.FullName, senderLink) | |||||
| title = SlackLinkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index), | |||||
| fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title)) | |||||
| attachmentText = SlackTextFormatter(p.Comment.Body) | |||||
| } | |||||
| return &SlackPayload{ | |||||
| Channel: slack.Channel, | |||||
| Text: text, | |||||
| Username: slack.Username, | |||||
| IconURL: slack.IconURL, | |||||
| Attachments: []SlackAttachment{{ | |||||
| Color: slack.Color, | |||||
| Title: title, | |||||
| Text: attachmentText, | |||||
| }}, | |||||
| }, nil | |||||
| } | |||||
| func getSlackReleasePayload(p *api.ReleasePayload, slack *SlackMeta) (*SlackPayload, error) { | |||||
| repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.Name) | |||||
| refLink := SlackLinkFormatter(p.Repository.HTMLURL+"/src/"+p.Release.TagName, p.Release.TagName) | |||||
| text := fmt.Sprintf("[%s] new release %s published by %s", repoLink, refLink, p.Sender.UserName) | |||||
| return &SlackPayload{ | |||||
| Channel: slack.Channel, | |||||
| Text: text, | |||||
| Username: slack.Username, | |||||
| IconURL: slack.IconURL, | |||||
| }, nil | |||||
| } | |||||
| func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, error) { | func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, error) { | ||||
| // n new commits | // n new commits | ||||
| var ( | var ( | ||||
| @@ -238,12 +354,22 @@ func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackP | |||||
| switch event { | switch event { | ||||
| case HookEventCreate: | case HookEventCreate: | ||||
| return getSlackCreatePayload(p.(*api.CreatePayload), slack) | return getSlackCreatePayload(p.(*api.CreatePayload), slack) | ||||
| case HookEventDelete: | |||||
| return getSlackDeletePayload(p.(*api.DeletePayload), slack) | |||||
| case HookEventFork: | |||||
| return getSlackForkPayload(p.(*api.ForkPayload), slack) | |||||
| case HookEventIssues: | |||||
| return getSlackIssuesPayload(p.(*api.IssuePayload), slack) | |||||
| case HookEventIssueComment: | |||||
| return getSlackIssueCommentPayload(p.(*api.IssueCommentPayload), slack) | |||||
| case HookEventPush: | case HookEventPush: | ||||
| return getSlackPushPayload(p.(*api.PushPayload), slack) | return getSlackPushPayload(p.(*api.PushPayload), slack) | ||||
| case HookEventPullRequest: | case HookEventPullRequest: | ||||
| return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack) | return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack) | ||||
| case HookEventRepository: | case HookEventRepository: | ||||
| return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack) | return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack) | ||||
| case HookEventRelease: | |||||
| return getSlackReleasePayload(p.(*api.ReleasePayload), slack) | |||||
| } | } | ||||
| return s, nil | return s, nil | ||||
| @@ -73,7 +73,7 @@ func TestWebhook_UpdateEvent(t *testing.T) { | |||||
| } | } | ||||
| func TestWebhook_EventsArray(t *testing.T) { | func TestWebhook_EventsArray(t *testing.T) { | ||||
| assert.Equal(t, []string{"create", "push", "pull_request"}, | |||||
| assert.Equal(t, []string{"create", "delete", "fork", "push", "issues", "issue_comment", "pull_request", "repository", "release"}, | |||||
| (&Webhook{ | (&Webhook{ | ||||
| HookEvent: &HookEvent{SendEverything: true}, | HookEvent: &HookEvent{SendEverything: true}, | ||||
| }).EventsArray(), | }).EventsArray(), | ||||
| @@ -155,12 +155,17 @@ func (f *ProtectBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) | |||||
| // WebhookForm form for changing web hook | // WebhookForm form for changing web hook | ||||
| type WebhookForm struct { | type WebhookForm struct { | ||||
| Events string | |||||
| Create bool | |||||
| Push bool | |||||
| PullRequest bool | |||||
| Repository bool | |||||
| Active bool | |||||
| Events string | |||||
| Create bool | |||||
| Delete bool | |||||
| Fork bool | |||||
| Issues bool | |||||
| IssueComment bool | |||||
| Release bool | |||||
| Push bool | |||||
| PullRequest bool | |||||
| Repository bool | |||||
| Active bool | |||||
| } | } | ||||
| // PushOnly if the hook will be triggered when push | // PushOnly if the hook will be triggered when push | ||||
| @@ -1000,6 +1000,16 @@ settings.event_send_everything = All Events | |||||
| settings.event_choose = Custom Events… | settings.event_choose = Custom Events… | ||||
| settings.event_create = Create | settings.event_create = Create | ||||
| settings.event_create_desc = Branch or tag created. | settings.event_create_desc = Branch or tag created. | ||||
| settings.event_delete = Delete | |||||
| settings.event_delete_desc = Branch or tag deleted | |||||
| settings.event_fork = Fork | |||||
| settings.event_fork_desc = Repository forked | |||||
| settings.event_issues = Issues | |||||
| settings.event_issues_desc = Issue opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared, milestoned, or demilestoned. | |||||
| settings.event_issue_comment = Issue Comment | |||||
| settings.event_issue_comment_desc = Issue comment created, edited, or deleted. | |||||
| settings.event_release = Release | |||||
| settings.event_release_desc = Release published in a repository. | |||||
| settings.event_pull_request = Pull Request | settings.event_pull_request = Pull Request | ||||
| settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared or synchronized. | settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared or synchronized. | ||||
| settings.event_push = Push | settings.event_push = Push | ||||
| @@ -261,8 +261,9 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) | |||||
| return | return | ||||
| } | } | ||||
| oldContent := comment.Content | |||||
| comment.Content = form.Body | comment.Content = form.Body | ||||
| if err := models.UpdateComment(comment); err != nil { | |||||
| if err := models.UpdateComment(ctx.User, comment, oldContent); err != nil { | |||||
| ctx.Error(500, "UpdateComment", err) | ctx.Error(500, "UpdateComment", err) | ||||
| return | return | ||||
| } | } | ||||
| @@ -348,7 +349,7 @@ func deleteIssueComment(ctx *context.APIContext) { | |||||
| return | return | ||||
| } | } | ||||
| if err = models.DeleteComment(comment); err != nil { | |||||
| if err = models.DeleteComment(ctx.User, comment); err != nil { | |||||
| ctx.Error(500, "DeleteCommentByID", err) | ctx.Error(500, "DeleteCommentByID", err) | ||||
| return | return | ||||
| } | } | ||||
| @@ -7,12 +7,13 @@ package utils | |||||
| import ( | import ( | ||||
| api "code.gitea.io/sdk/gitea" | api "code.gitea.io/sdk/gitea" | ||||
| "encoding/json" | |||||
| "net/http" | |||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| "code.gitea.io/gitea/modules/context" | "code.gitea.io/gitea/modules/context" | ||||
| "code.gitea.io/gitea/routers/api/v1/convert" | "code.gitea.io/gitea/routers/api/v1/convert" | ||||
| "encoding/json" | |||||
| "github.com/Unknwon/com" | "github.com/Unknwon/com" | ||||
| "net/http" | |||||
| ) | ) | ||||
| // GetOrgHook get an organization's webhook. If there is an error, write to | // GetOrgHook get an organization's webhook. If there is an error, write to | ||||
| @@ -98,9 +99,15 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID | |||||
| HookEvent: &models.HookEvent{ | HookEvent: &models.HookEvent{ | ||||
| ChooseEvents: true, | ChooseEvents: true, | ||||
| HookEvents: models.HookEvents{ | HookEvents: models.HookEvents{ | ||||
| Create: com.IsSliceContainsStr(form.Events, string(models.HookEventCreate)), | |||||
| Push: com.IsSliceContainsStr(form.Events, string(models.HookEventPush)), | |||||
| PullRequest: com.IsSliceContainsStr(form.Events, string(models.HookEventPullRequest)), | |||||
| Create: com.IsSliceContainsStr(form.Events, string(models.HookEventCreate)), | |||||
| Delete: com.IsSliceContainsStr(form.Events, string(models.HookEventDelete)), | |||||
| Fork: com.IsSliceContainsStr(form.Events, string(models.HookEventFork)), | |||||
| Issues: com.IsSliceContainsStr(form.Events, string(models.HookEventIssues)), | |||||
| IssueComment: com.IsSliceContainsStr(form.Events, string(models.HookEventIssueComment)), | |||||
| Push: com.IsSliceContainsStr(form.Events, string(models.HookEventPush)), | |||||
| PullRequest: com.IsSliceContainsStr(form.Events, string(models.HookEventPullRequest)), | |||||
| Repository: com.IsSliceContainsStr(form.Events, string(models.HookEventRepository)), | |||||
| Release: com.IsSliceContainsStr(form.Events, string(models.HookEventRelease)), | |||||
| }, | }, | ||||
| }, | }, | ||||
| IsActive: form.Active, | IsActive: form.Active, | ||||
| @@ -211,6 +218,16 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *models.Webho | |||||
| w.Create = com.IsSliceContainsStr(form.Events, string(models.HookEventCreate)) | w.Create = com.IsSliceContainsStr(form.Events, string(models.HookEventCreate)) | ||||
| w.Push = com.IsSliceContainsStr(form.Events, string(models.HookEventPush)) | w.Push = com.IsSliceContainsStr(form.Events, string(models.HookEventPush)) | ||||
| w.PullRequest = com.IsSliceContainsStr(form.Events, string(models.HookEventPullRequest)) | w.PullRequest = com.IsSliceContainsStr(form.Events, string(models.HookEventPullRequest)) | ||||
| w.Create = com.IsSliceContainsStr(form.Events, string(models.HookEventCreate)) | |||||
| w.Delete = com.IsSliceContainsStr(form.Events, string(models.HookEventDelete)) | |||||
| w.Fork = com.IsSliceContainsStr(form.Events, string(models.HookEventFork)) | |||||
| w.Issues = com.IsSliceContainsStr(form.Events, string(models.HookEventIssues)) | |||||
| w.IssueComment = com.IsSliceContainsStr(form.Events, string(models.HookEventIssueComment)) | |||||
| w.Push = com.IsSliceContainsStr(form.Events, string(models.HookEventPush)) | |||||
| w.PullRequest = com.IsSliceContainsStr(form.Events, string(models.HookEventPullRequest)) | |||||
| w.Repository = com.IsSliceContainsStr(form.Events, string(models.HookEventRepository)) | |||||
| w.Release = com.IsSliceContainsStr(form.Events, string(models.HookEventRelease)) | |||||
| if err := w.UpdateEvent(); err != nil { | if err := w.UpdateEvent(); err != nil { | ||||
| ctx.Error(500, "UpdateEvent", err) | ctx.Error(500, "UpdateEvent", err) | ||||
| return false | return false | ||||
| @@ -1086,6 +1086,7 @@ func UpdateCommentContent(ctx *context.Context) { | |||||
| return | return | ||||
| } | } | ||||
| oldContent := comment.Content | |||||
| comment.Content = ctx.Query("content") | comment.Content = ctx.Query("content") | ||||
| if len(comment.Content) == 0 { | if len(comment.Content) == 0 { | ||||
| ctx.JSON(200, map[string]interface{}{ | ctx.JSON(200, map[string]interface{}{ | ||||
| @@ -1093,7 +1094,7 @@ func UpdateCommentContent(ctx *context.Context) { | |||||
| }) | }) | ||||
| return | return | ||||
| } | } | ||||
| if err = models.UpdateComment(comment); err != nil { | |||||
| if err = models.UpdateComment(ctx.User, comment, oldContent); err != nil { | |||||
| ctx.ServerError("UpdateComment", err) | ctx.ServerError("UpdateComment", err) | ||||
| return | return | ||||
| } | } | ||||
| @@ -1119,7 +1120,7 @@ func DeleteComment(ctx *context.Context) { | |||||
| return | return | ||||
| } | } | ||||
| if err = models.DeleteComment(comment); err != nil { | |||||
| if err = models.DeleteComment(ctx.User, comment); err != nil { | |||||
| ctx.ServerError("DeleteCommentByID", err) | ctx.ServerError("DeleteCommentByID", err) | ||||
| return | return | ||||
| } | } | ||||
| @@ -23,9 +23,9 @@ import ( | |||||
| ) | ) | ||||
| const ( | const ( | ||||
| tplHooks base.TplName = "repo/settings/hooks" | |||||
| tplHookNew base.TplName = "repo/settings/hook_new" | |||||
| tplOrgHookNew base.TplName = "org/settings/hook_new" | |||||
| tplHooks base.TplName = "repo/settings/webhook/base" | |||||
| tplHookNew base.TplName = "repo/settings/webhook/new" | |||||
| tplOrgHookNew base.TplName = "org/settings/webhook/new" | |||||
| ) | ) | ||||
| // Webhooks render web hooks list page | // Webhooks render web hooks list page | ||||
| @@ -118,10 +118,15 @@ func ParseHookEvent(form auth.WebhookForm) *models.HookEvent { | |||||
| SendEverything: form.SendEverything(), | SendEverything: form.SendEverything(), | ||||
| ChooseEvents: form.ChooseEvents(), | ChooseEvents: form.ChooseEvents(), | ||||
| HookEvents: models.HookEvents{ | HookEvents: models.HookEvents{ | ||||
| Create: form.Create, | |||||
| Push: form.Push, | |||||
| PullRequest: form.PullRequest, | |||||
| Repository: form.Repository, | |||||
| Create: form.Create, | |||||
| Delete: form.Delete, | |||||
| Fork: form.Fork, | |||||
| Issues: form.Issues, | |||||
| IssueComment: form.IssueComment, | |||||
| Release: form.Release, | |||||
| Push: form.Push, | |||||
| PullRequest: form.PullRequest, | |||||
| Repository: form.Repository, | |||||
| }, | }, | ||||
| } | } | ||||
| } | } | ||||
| @@ -3,7 +3,7 @@ | |||||
| {{template "repo/header" .}} | {{template "repo/header" .}} | ||||
| {{template "repo/settings/navbar" .}} | {{template "repo/settings/navbar" .}} | ||||
| <div class="ui container"> | <div class="ui container"> | ||||
| {{template "repo/settings/hook_list" .}} | |||||
| {{template "repo/settings/webhook/list" .}} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{template "base/footer" .}} | {{template "base/footer" .}} | ||||
| @@ -6,6 +6,6 @@ | |||||
| <label for="payload_url">{{.i18n.Tr "repo.settings.payload_url"}}</label> | <label for="payload_url">{{.i18n.Tr "repo.settings.payload_url"}}</label> | ||||
| <input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required> | <input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required> | ||||
| </div> | </div> | ||||
| {{template "repo/settings/hook_settings" .}} | |||||
| {{template "repo/settings/webhook/settings" .}} | |||||
| </form> | </form> | ||||
| {{end}} | {{end}} | ||||
| @@ -14,6 +14,6 @@ | |||||
| <label for="icon_url">{{.i18n.Tr "repo.settings.discord_icon_url"}}</label> | <label for="icon_url">{{.i18n.Tr "repo.settings.discord_icon_url"}}</label> | ||||
| <input id="icon_url" name="icon_url" value="{{.DiscordHook.IconURL}}" placeholder="e.g. https://example.com/img/favicon.png"> | <input id="icon_url" name="icon_url" value="{{.DiscordHook.IconURL}}" placeholder="e.g. https://example.com/img/favicon.png"> | ||||
| </div> | </div> | ||||
| {{template "repo/settings/hook_settings" .}} | |||||
| {{template "repo/settings/webhook/settings" .}} | |||||
| </form> | </form> | ||||
| {{end}} | {{end}} | ||||
| @@ -23,6 +23,6 @@ | |||||
| <label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label> | <label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label> | ||||
| <input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off"> | <input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off"> | ||||
| </div> | </div> | ||||
| {{template "repo/settings/hook_settings" .}} | |||||
| {{template "repo/settings/webhook/settings" .}} | |||||
| </form> | </form> | ||||
| {{end}} | {{end}} | ||||
| @@ -23,6 +23,6 @@ | |||||
| <label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label> | <label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label> | ||||
| <input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off"> | <input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off"> | ||||
| </div> | </div> | ||||
| {{template "repo/settings/hook_settings" .}} | |||||
| {{template "repo/settings/webhook/settings" .}} | |||||
| </form> | </form> | ||||
| {{end}} | {{end}} | ||||
| @@ -48,4 +48,4 @@ | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{template "repo/settings/hook_delete_modal" .}} | |||||
| {{template "repo/settings/webhook/delete_modal" .}} | |||||
| @@ -21,14 +21,14 @@ | |||||
| </div> | </div> | ||||
| </h4> | </h4> | ||||
| <div class="ui attached segment"> | <div class="ui attached segment"> | ||||
| {{template "repo/settings/hook_gitea" .}} | |||||
| {{template "repo/settings/hook_gogs" .}} | |||||
| {{template "repo/settings/hook_slack" .}} | |||||
| {{template "repo/settings/hook_discord" .}} | |||||
| {{template "repo/settings/hook_dingtalk" .}} | |||||
| {{template "repo/settings/webhook/gitea" .}} | |||||
| {{template "repo/settings/webhook/gogs" .}} | |||||
| {{template "repo/settings/webhook/slack" .}} | |||||
| {{template "repo/settings/webhook/discord" .}} | |||||
| {{template "repo/settings/webhook/dingtalk" .}} | |||||
| </div> | </div> | ||||
| {{template "repo/settings/hook_history" .}} | |||||
| {{template "repo/settings/webhook/history" .}} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{template "base/footer" .}} | {{template "base/footer" .}} | ||||
| @@ -32,6 +32,26 @@ | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <!-- Delete --> | |||||
| <div class="seven wide column"> | |||||
| <div class="field"> | |||||
| <div class="ui checkbox"> | |||||
| <input class="hidden" name="delete" type="checkbox" tabindex="0" {{if .Webhook.Delete}}checked{{end}}> | |||||
| <label>{{.i18n.Tr "repo.settings.event_delete"}}</label> | |||||
| <span class="help">{{.i18n.Tr "repo.settings.event_delete_desc"}}</span> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <!-- Fork --> | |||||
| <div class="seven wide column"> | |||||
| <div class="field"> | |||||
| <div class="ui checkbox"> | |||||
| <input class="hidden" name="fork" type="checkbox" tabindex="0" {{if .Webhook.Fork}}checked{{end}}> | |||||
| <label>{{.i18n.Tr "repo.settings.event_fork"}}</label> | |||||
| <span class="help">{{.i18n.Tr "repo.settings.event_fork_desc"}}</span> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <!-- Push --> | <!-- Push --> | ||||
| <div class="seven wide column"> | <div class="seven wide column"> | ||||
| <div class="field"> | <div class="field"> | ||||
| @@ -42,6 +62,26 @@ | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <!-- Issues --> | |||||
| <div class="seven wide column"> | |||||
| <div class="field"> | |||||
| <div class="ui checkbox"> | |||||
| <input class="hidden" name="issues" type="checkbox" tabindex="0" {{if .Webhook.Issues}}checked{{end}}> | |||||
| <label>{{.i18n.Tr "repo.settings.event_issues"}}</label> | |||||
| <span class="help">{{.i18n.Tr "repo.settings.event_issues_desc"}}</span> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <!-- Issue Comment --> | |||||
| <div class="seven wide column"> | |||||
| <div class="field"> | |||||
| <div class="ui checkbox"> | |||||
| <input class="hidden" name="issue_comment" type="checkbox" tabindex="0" {{if .Webhook.IssueComment}}checked{{end}}> | |||||
| <label>{{.i18n.Tr "repo.settings.event_issue_comment"}}</label> | |||||
| <span class="help">{{.i18n.Tr "repo.settings.event_issue_comment_desc"}}</span> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <!-- Pull Request --> | <!-- Pull Request --> | ||||
| <div class="seven wide column"> | <div class="seven wide column"> | ||||
| <div class="field"> | <div class="field"> | ||||
| @@ -62,6 +102,16 @@ | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <!-- Release --> | |||||
| <div class="seven wide column"> | |||||
| <div class="field"> | |||||
| <div class="ui checkbox"> | |||||
| <input class="hidden" name="release" type="checkbox" tabindex="0" {{if .Webhook.Release}}checked{{end}}> | |||||
| <label>{{.i18n.Tr "repo.settings.event_release"}}</label> | |||||
| <span class="help">{{.i18n.Tr "repo.settings.event_release_desc"}}</span> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -83,4 +133,4 @@ | |||||
| {{end}} | {{end}} | ||||
| </div> | </div> | ||||
| {{template "repo/settings/hook_delete_modal" .}} | |||||
| {{template "repo/settings/webhook/delete_modal" .}} | |||||
| @@ -23,6 +23,6 @@ | |||||
| <label for="color">{{.i18n.Tr "repo.settings.slack_color"}}</label> | <label for="color">{{.i18n.Tr "repo.settings.slack_color"}}</label> | ||||
| <input id="color" name="color" value="{{.SlackHook.Color}}" placeholder="e.g. #dd4b39, good, warning, danger"> | <input id="color" name="color" value="{{.SlackHook.Color}}" placeholder="e.g. #dd4b39, good, warning, danger"> | ||||
| </div> | </div> | ||||
| {{template "repo/settings/hook_settings" .}} | |||||
| {{template "repo/settings/webhook/settings" .}} | |||||
| </form> | </form> | ||||
| {{end}} | {{end}} | ||||
| @@ -172,9 +172,14 @@ type PayloadCommitVerification struct { | |||||
| var ( | var ( | ||||
| _ Payloader = &CreatePayload{} | _ Payloader = &CreatePayload{} | ||||
| _ Payloader = &DeletePayload{} | |||||
| _ Payloader = &ForkPayload{} | |||||
| _ Payloader = &PushPayload{} | _ Payloader = &PushPayload{} | ||||
| _ Payloader = &IssuePayload{} | _ Payloader = &IssuePayload{} | ||||
| _ Payloader = &IssueCommentPayload{} | |||||
| _ Payloader = &PullRequestPayload{} | _ Payloader = &PullRequestPayload{} | ||||
| _ Payloader = &RepositoryPayload{} | |||||
| _ Payloader = &ReleasePayload{} | |||||
| ) | ) | ||||
| // _________ __ | // _________ __ | ||||
| @@ -224,6 +229,123 @@ func ParseCreateHook(raw []byte) (*CreatePayload, error) { | |||||
| return hook, nil | return hook, nil | ||||
| } | } | ||||
| // ________ .__ __ | |||||
| // \______ \ ____ | | _____/ |_ ____ | |||||
| // | | \_/ __ \| | _/ __ \ __\/ __ \ | |||||
| // | ` \ ___/| |_\ ___/| | \ ___/ | |||||
| // /_______ /\___ >____/\___ >__| \___ > | |||||
| // \/ \/ \/ \/ | |||||
| // PusherType define the type to push | |||||
| type PusherType string | |||||
| // describe all the PusherTypes | |||||
| const ( | |||||
| PusherTypeUser PusherType = "user" | |||||
| ) | |||||
| // DeletePayload represents delete payload | |||||
| type DeletePayload struct { | |||||
| Ref string `json:"ref"` | |||||
| RefType string `json:"ref_type"` | |||||
| PusherType PusherType `json:"pusher_type"` | |||||
| Repo *Repository `json:"repository"` | |||||
| Sender *User `json:"sender"` | |||||
| } | |||||
| // SetSecret implements Payload | |||||
| func (p *DeletePayload) SetSecret(secret string) { | |||||
| } | |||||
| // JSONPayload implements Payload | |||||
| func (p *DeletePayload) JSONPayload() ([]byte, error) { | |||||
| return json.MarshalIndent(p, "", " ") | |||||
| } | |||||
| // ___________ __ | |||||
| // \_ _____/__________| | __ | |||||
| // | __)/ _ \_ __ \ |/ / | |||||
| // | \( <_> ) | \/ < | |||||
| // \___ / \____/|__| |__|_ \ | |||||
| // \/ \/ | |||||
| // ForkPayload represents fork payload | |||||
| type ForkPayload struct { | |||||
| Forkee *Repository `json:"forkee"` | |||||
| Repo *Repository `json:"repository"` | |||||
| Sender *User `json:"sender"` | |||||
| } | |||||
| // SetSecret implements Payload | |||||
| func (p *ForkPayload) SetSecret(secret string) { | |||||
| } | |||||
| // JSONPayload implements Payload | |||||
| func (p *ForkPayload) JSONPayload() ([]byte, error) { | |||||
| return json.MarshalIndent(p, "", " ") | |||||
| } | |||||
| // HookIssueCommentAction defines hook issue comment action | |||||
| type HookIssueCommentAction string | |||||
| // all issue comment actions | |||||
| const ( | |||||
| HookIssueCommentCreated HookIssueCommentAction = "created" | |||||
| HookIssueCommentEdited HookIssueCommentAction = "edited" | |||||
| HookIssueCommentDeleted HookIssueCommentAction = "deleted" | |||||
| ) | |||||
| // IssueCommentPayload represents a payload information of issue comment event. | |||||
| type IssueCommentPayload struct { | |||||
| Action HookIssueCommentAction `json:"action"` | |||||
| Issue *Issue `json:"issue"` | |||||
| Comment *Comment `json:"comment"` | |||||
| Changes *ChangesPayload `json:"changes,omitempty"` | |||||
| Repository *Repository `json:"repository"` | |||||
| Sender *User `json:"sender"` | |||||
| } | |||||
| // SetSecret implements Payload | |||||
| func (p *IssueCommentPayload) SetSecret(secret string) { | |||||
| } | |||||
| // JSONPayload implements Payload | |||||
| func (p *IssueCommentPayload) JSONPayload() ([]byte, error) { | |||||
| return json.MarshalIndent(p, "", " ") | |||||
| } | |||||
| // __________ .__ | |||||
| // \______ \ ____ | | ____ _____ ______ ____ | |||||
| // | _// __ \| | _/ __ \\__ \ / ___// __ \ | |||||
| // | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/ | |||||
| // |____|_ /\___ >____/\___ >____ /____ >\___ > | |||||
| // \/ \/ \/ \/ \/ \/ | |||||
| // HookReleaseAction defines hook release action type | |||||
| type HookReleaseAction string | |||||
| // all release actions | |||||
| const ( | |||||
| HookReleasePublished HookReleaseAction = "published" | |||||
| ) | |||||
| // ReleasePayload represents a payload information of release event. | |||||
| type ReleasePayload struct { | |||||
| Action HookReleaseAction `json:"action"` | |||||
| Release *Release `json:"release"` | |||||
| Repository *Repository `json:"repository"` | |||||
| Sender *User `json:"sender"` | |||||
| } | |||||
| // SetSecret implements Payload | |||||
| func (p *ReleasePayload) SetSecret(secret string) { | |||||
| } | |||||
| // JSONPayload implements Payload | |||||
| func (p *ReleasePayload) JSONPayload() ([]byte, error) { | |||||
| return json.MarshalIndent(p, "", " ") | |||||
| } | |||||
| // __________ .__ | // __________ .__ | ||||
| // \______ \__ __ _____| |__ | // \______ \__ __ _____| |__ | ||||
| // | ___/ | \/ ___/ | \ | // | ___/ | \/ ___/ | \ | ||||
| @@ -118,14 +118,14 @@ func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue, | |||||
| // EditIssueOption options for editing an issue | // EditIssueOption options for editing an issue | ||||
| type EditIssueOption struct { | type EditIssueOption struct { | ||||
| Title string `json:"title"` | |||||
| Body *string `json:"body"` | |||||
| Assignee *string `json:"assignee"` | |||||
| Assignees []string `json:"assignees"` | |||||
| Milestone *int64 `json:"milestone"` | |||||
| State *string `json:"state"` | |||||
| Title string `json:"title"` | |||||
| Body *string `json:"body"` | |||||
| Assignee *string `json:"assignee"` | |||||
| Assignees []string `json:"assignees"` | |||||
| Milestone *int64 `json:"milestone"` | |||||
| State *string `json:"state"` | |||||
| // swagger:strfmt date-time | // swagger:strfmt date-time | ||||
| Deadline *time.Time `json:"due_date"` | |||||
| Deadline *time.Time `json:"due_date"` | |||||
| } | } | ||||
| // EditIssue modify an existing issue for a given repository | // EditIssue modify an existing issue for a given repository | ||||
| @@ -138,3 +138,17 @@ func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption) | |||||
| return issue, c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), | return issue, c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), | ||||
| jsonHeader, bytes.NewReader(body), issue) | jsonHeader, bytes.NewReader(body), issue) | ||||
| } | } | ||||
| // EditDeadlineOption options for creating a deadline | |||||
| type EditDeadlineOption struct { | |||||
| // required:true | |||||
| // swagger:strfmt date-time | |||||
| Deadline *time.Time `json:"due_date"` | |||||
| } | |||||
| // IssueDeadline represents an issue deadline | |||||
| // swagger:model | |||||
| type IssueDeadline struct { | |||||
| // swagger:strfmt date-time | |||||
| Deadline *time.Time `json:"due_date"` | |||||
| } | |||||
| @@ -85,16 +85,16 @@ func (c *Client) GetPullRequest(owner, repo string, index int64) (*PullRequest, | |||||
| // CreatePullRequestOption options when creating a pull request | // CreatePullRequestOption options when creating a pull request | ||||
| type CreatePullRequestOption struct { | type CreatePullRequestOption struct { | ||||
| Head string `json:"head" binding:"Required"` | |||||
| Base string `json:"base" binding:"Required"` | |||||
| Title string `json:"title" binding:"Required"` | |||||
| Body string `json:"body"` | |||||
| Assignee string `json:"assignee"` | |||||
| Assignees []string `json:"assignees"` | |||||
| Milestone int64 `json:"milestone"` | |||||
| Labels []int64 `json:"labels"` | |||||
| Head string `json:"head" binding:"Required"` | |||||
| Base string `json:"base" binding:"Required"` | |||||
| Title string `json:"title" binding:"Required"` | |||||
| Body string `json:"body"` | |||||
| Assignee string `json:"assignee"` | |||||
| Assignees []string `json:"assignees"` | |||||
| Milestone int64 `json:"milestone"` | |||||
| Labels []int64 `json:"labels"` | |||||
| // swagger:strfmt date-time | // swagger:strfmt date-time | ||||
| Deadline *time.Time `json:"due_date"` | |||||
| Deadline *time.Time `json:"due_date"` | |||||
| } | } | ||||
| // CreatePullRequest create pull request with options | // CreatePullRequest create pull request with options | ||||
| @@ -110,15 +110,15 @@ func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOpti | |||||
| // EditPullRequestOption options when modify pull request | // EditPullRequestOption options when modify pull request | ||||
| type EditPullRequestOption struct { | type EditPullRequestOption struct { | ||||
| Title string `json:"title"` | |||||
| Body string `json:"body"` | |||||
| Assignee string `json:"assignee"` | |||||
| Assignees []string `json:"assignees"` | |||||
| Milestone int64 `json:"milestone"` | |||||
| Labels []int64 `json:"labels"` | |||||
| State *string `json:"state"` | |||||
| Title string `json:"title"` | |||||
| Body string `json:"body"` | |||||
| Assignee string `json:"assignee"` | |||||
| Assignees []string `json:"assignees"` | |||||
| Milestone int64 `json:"milestone"` | |||||
| Labels []int64 `json:"labels"` | |||||
| State *string `json:"state"` | |||||
| // swagger:strfmt date-time | // swagger:strfmt date-time | ||||
| Deadline *time.Time `json:"due_date"` | |||||
| Deadline *time.Time `json:"due_date"` | |||||
| } | } | ||||
| // EditPullRequest modify pull request with PR id and options | // EditPullRequest modify pull request with PR id and options | ||||
| @@ -9,10 +9,10 @@ | |||||
| "revisionTime": "2018-04-21T01:08:19Z" | "revisionTime": "2018-04-21T01:08:19Z" | ||||
| }, | }, | ||||
| { | { | ||||
| "checksumSHA1": "WMD6+Qh2+5hd9uiq910pF/Ihylw=", | |||||
| "checksumSHA1": "LnxY/6xD4h9dCCJ5nxKEfZZs1Vk=", | |||||
| "path": "code.gitea.io/sdk/gitea", | "path": "code.gitea.io/sdk/gitea", | ||||
| "revision": "1c8d12f79a51605ed91587aa6b86cf38fc0f987f", | |||||
| "revisionTime": "2018-05-01T11:15:19Z" | |||||
| "revision": "7fa627fa5d67d18c39d6dd3c6c4db836916bf234", | |||||
| "revisionTime": "2018-05-10T12:54:05Z" | |||||
| }, | }, | ||||
| { | { | ||||
| "checksumSHA1": "bOODD4Gbw3GfcuQPU2dI40crxxk=", | "checksumSHA1": "bOODD4Gbw3GfcuQPU2dI40crxxk=", | ||||