Skip to content

Commit b34996a

Browse files
aungertechknowlogick
authored andcommitted
Implement Default Webhooks (#4299)
Partially implement #770. Add "Default Webhooks" page in site admin UI. Persist to the existing webhooks table, but store with RepoID=0 and OrgID=0. Upon repo creation, copy the set of default webhooks into the new repo.
1 parent cac9e6e commit b34996a

File tree

18 files changed

+252
-39
lines changed

18 files changed

+252
-39
lines changed

models/repo.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,6 +1366,10 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
13661366
return fmt.Errorf("newRepoAction: %v", err)
13671367
}
13681368

1369+
if err = copyDefaultWebhooksToRepo(e, repo.ID); err != nil {
1370+
return fmt.Errorf("copyDefaultWebhooksToRepo: %v", err)
1371+
}
1372+
13691373
return nil
13701374
}
13711375

models/webhook.go

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,11 @@ func (w *Webhook) EventsArray() []string {
241241

242242
// CreateWebhook creates a new web hook.
243243
func CreateWebhook(w *Webhook) error {
244-
_, err := x.Insert(w)
244+
return createWebhook(x, w)
245+
}
246+
247+
func createWebhook(e Engine, w *Webhook) error {
248+
_, err := e.Insert(w)
245249
return err
246250
}
247251

@@ -316,6 +320,32 @@ func GetWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) {
316320
return ws, err
317321
}
318322

323+
// GetDefaultWebhook returns admin-default webhook by given ID.
324+
func GetDefaultWebhook(id int64) (*Webhook, error) {
325+
webhook := &Webhook{ID: id}
326+
has, err := x.
327+
Where("repo_id=? AND org_id=?", 0, 0).
328+
Get(webhook)
329+
if err != nil {
330+
return nil, err
331+
} else if !has {
332+
return nil, ErrWebhookNotExist{id}
333+
}
334+
return webhook, nil
335+
}
336+
337+
// GetDefaultWebhooks returns all admin-default webhooks.
338+
func GetDefaultWebhooks() ([]*Webhook, error) {
339+
return getDefaultWebhooks(x)
340+
}
341+
342+
func getDefaultWebhooks(e Engine) ([]*Webhook, error) {
343+
webhooks := make([]*Webhook, 0, 5)
344+
return webhooks, e.
345+
Where("repo_id=? AND org_id=?", 0, 0).
346+
Find(&webhooks)
347+
}
348+
319349
// UpdateWebhook updates information of webhook.
320350
func UpdateWebhook(w *Webhook) error {
321351
_, err := x.ID(w.ID).AllCols().Update(w)
@@ -364,6 +394,47 @@ func DeleteWebhookByOrgID(orgID, id int64) error {
364394
})
365395
}
366396

397+
// DeleteDefaultWebhook deletes an admin-default webhook by given ID.
398+
func DeleteDefaultWebhook(id int64) error {
399+
sess := x.NewSession()
400+
defer sess.Close()
401+
if err := sess.Begin(); err != nil {
402+
return err
403+
}
404+
405+
count, err := sess.
406+
Where("repo_id=? AND org_id=?", 0, 0).
407+
Delete(&Webhook{ID: id})
408+
if err != nil {
409+
return err
410+
} else if count == 0 {
411+
return ErrWebhookNotExist{ID: id}
412+
}
413+
414+
if _, err := sess.Delete(&HookTask{HookID: id}); err != nil {
415+
return err
416+
}
417+
418+
return sess.Commit()
419+
}
420+
421+
// copyDefaultWebhooksToRepo creates copies of the default webhooks in a new repo
422+
func copyDefaultWebhooksToRepo(e Engine, repoID int64) error {
423+
ws, err := getDefaultWebhooks(e)
424+
if err != nil {
425+
return fmt.Errorf("GetDefaultWebhooks: %v", err)
426+
}
427+
428+
for _, w := range ws {
429+
w.ID = 0
430+
w.RepoID = repoID
431+
if err := createWebhook(e, w); err != nil {
432+
return fmt.Errorf("CreateWebhook: %v", err)
433+
}
434+
}
435+
return nil
436+
}
437+
367438
// ___ ___ __ ___________ __
368439
// / | \ ____ ____ | | _\__ ___/____ _____| | __
369440
// / ~ \/ _ \ / _ \| |/ / | | \__ \ / ___/ |/ /

options/locale/locale_en-US.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1433,6 +1433,7 @@ dashboard = Dashboard
14331433
users = User Accounts
14341434
organizations = Organizations
14351435
repositories = Repositories
1436+
hooks = Default Webhooks
14361437
authentication = Authentication Sources
14371438
config = Configuration
14381439
notices = System Notices
@@ -1546,6 +1547,10 @@ repos.forks = Forks
15461547
repos.issues = Issues
15471548
repos.size = Size
15481549
1550+
hooks.desc = Webhooks automatically make HTTP POST requests to a server when certain Gitea events trigger. Webhooks defined here are defaults and will be copied into all new repositories. Read more in the <a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/webhooks/">webhooks guide</a>.
1551+
hooks.add_webhook = Add Default Webhook
1552+
hooks.update_webhook = Update Default Webhook
1553+
15491554
auths.auth_manage_panel = Authentication Source Management
15501555
auths.new = Add Authentication Source
15511556
auths.name = Name

routers/admin/hooks.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2018 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package admin
6+
7+
import (
8+
"code.gitea.io/gitea/models"
9+
"code.gitea.io/gitea/modules/base"
10+
"code.gitea.io/gitea/modules/context"
11+
"code.gitea.io/gitea/modules/setting"
12+
)
13+
14+
const (
15+
// tplAdminHooks template path for render hook settings
16+
tplAdminHooks base.TplName = "admin/hooks"
17+
)
18+
19+
// DefaultWebhooks render admin-default webhook list page
20+
func DefaultWebhooks(ctx *context.Context) {
21+
ctx.Data["Title"] = ctx.Tr("admin.hooks")
22+
ctx.Data["PageIsAdminHooks"] = true
23+
ctx.Data["BaseLink"] = setting.AppSubURL + "/admin/hooks"
24+
ctx.Data["Description"] = ctx.Tr("admin.hooks.desc")
25+
26+
ws, err := models.GetDefaultWebhooks()
27+
if err != nil {
28+
ctx.ServerError("GetWebhooksDefaults", err)
29+
return
30+
}
31+
32+
ctx.Data["Webhooks"] = ws
33+
ctx.HTML(200, tplAdminHooks)
34+
}
35+
36+
// DeleteDefaultWebhook response for delete admin-default webhook
37+
func DeleteDefaultWebhook(ctx *context.Context) {
38+
if err := models.DeleteDefaultWebhook(ctx.QueryInt64("id")); err != nil {
39+
ctx.Flash.Error("DeleteDefaultWebhook: " + err.Error())
40+
} else {
41+
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
42+
}
43+
44+
ctx.JSON(200, map[string]interface{}{
45+
"redirect": setting.AppSubURL + "/admin/hooks",
46+
})
47+
}

routers/org/setting.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func SettingsDelete(ctx *context.Context) {
150150
func Webhooks(ctx *context.Context) {
151151
ctx.Data["Title"] = ctx.Tr("org.settings")
152152
ctx.Data["PageIsSettingsHooks"] = true
153-
ctx.Data["BaseLink"] = ctx.Org.OrgLink
153+
ctx.Data["BaseLink"] = ctx.Org.OrgLink + "/settings/hooks"
154154
ctx.Data["Description"] = ctx.Tr("org.settings.hooks_desc")
155155

156156
ws, err := models.GetWebhooksByOrgID(ctx.Org.Organization.ID)

routers/repo/webhook.go

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"encoding/json"
1010
"errors"
1111
"fmt"
12+
"path"
1213
"strings"
1314

1415
"code.gitea.io/git"
@@ -23,16 +24,17 @@ import (
2324
)
2425

2526
const (
26-
tplHooks base.TplName = "repo/settings/webhook/base"
27-
tplHookNew base.TplName = "repo/settings/webhook/new"
28-
tplOrgHookNew base.TplName = "org/settings/hook_new"
27+
tplHooks base.TplName = "repo/settings/webhook/base"
28+
tplHookNew base.TplName = "repo/settings/webhook/new"
29+
tplOrgHookNew base.TplName = "org/settings/hook_new"
30+
tplAdminHookNew base.TplName = "admin/hook_new"
2931
)
3032

3133
// Webhooks render web hooks list page
3234
func Webhooks(ctx *context.Context) {
3335
ctx.Data["Title"] = ctx.Tr("repo.settings.hooks")
3436
ctx.Data["PageIsSettingsHooks"] = true
35-
ctx.Data["BaseLink"] = ctx.Repo.RepoLink
37+
ctx.Data["BaseLink"] = ctx.Repo.RepoLink + "/settings/hooks"
3638
ctx.Data["Description"] = ctx.Tr("repo.settings.hooks_desc", "https://docs.gitea.io/en-us/webhooks/")
3739

3840
ws, err := models.GetWebhooksByRepoID(ctx.Repo.Repository.ID)
@@ -48,28 +50,37 @@ func Webhooks(ctx *context.Context) {
4850
type orgRepoCtx struct {
4951
OrgID int64
5052
RepoID int64
53+
IsAdmin bool
5154
Link string
5255
NewTemplate base.TplName
5356
}
5457

55-
// getOrgRepoCtx determines whether this is a repo context or organization context.
58+
// getOrgRepoCtx determines whether this is a repo, organization, or admin context.
5659
func getOrgRepoCtx(ctx *context.Context) (*orgRepoCtx, error) {
5760
if len(ctx.Repo.RepoLink) > 0 {
5861
return &orgRepoCtx{
5962
RepoID: ctx.Repo.Repository.ID,
60-
Link: ctx.Repo.RepoLink,
63+
Link: path.Join(ctx.Repo.RepoLink, "settings/hooks"),
6164
NewTemplate: tplHookNew,
6265
}, nil
6366
}
6467

6568
if len(ctx.Org.OrgLink) > 0 {
6669
return &orgRepoCtx{
6770
OrgID: ctx.Org.Organization.ID,
68-
Link: ctx.Org.OrgLink,
71+
Link: path.Join(ctx.Org.OrgLink, "settings/hooks"),
6972
NewTemplate: tplOrgHookNew,
7073
}, nil
7174
}
7275

76+
if ctx.User.IsAdmin {
77+
return &orgRepoCtx{
78+
IsAdmin: true,
79+
Link: path.Join(setting.AppSubURL, "/admin/hooks"),
80+
NewTemplate: tplAdminHookNew,
81+
}, nil
82+
}
83+
7384
return nil, errors.New("Unable to set OrgRepo context")
7485
}
7586

@@ -85,8 +96,6 @@ func checkHookType(ctx *context.Context) string {
8596
// WebhooksNew render creating webhook page
8697
func WebhooksNew(ctx *context.Context) {
8798
ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
88-
ctx.Data["PageIsSettingsHooks"] = true
89-
ctx.Data["PageIsSettingsHooksNew"] = true
9099
ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
91100

92101
orCtx, err := getOrgRepoCtx(ctx)
@@ -95,6 +104,14 @@ func WebhooksNew(ctx *context.Context) {
95104
return
96105
}
97106

107+
if orCtx.IsAdmin {
108+
ctx.Data["PageIsAdminHooks"] = true
109+
ctx.Data["PageIsAdminHooksNew"] = true
110+
} else {
111+
ctx.Data["PageIsSettingsHooks"] = true
112+
ctx.Data["PageIsSettingsHooksNew"] = true
113+
}
114+
98115
hookType := checkHookType(ctx)
99116
ctx.Data["HookType"] = hookType
100117
if ctx.Written() {
@@ -175,7 +192,7 @@ func WebHooksNewPost(ctx *context.Context, form auth.NewWebhookForm) {
175192
}
176193

177194
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
178-
ctx.Redirect(orCtx.Link + "/settings/hooks")
195+
ctx.Redirect(orCtx.Link)
179196
}
180197

181198
// GogsHooksNewPost response for creating webhook
@@ -222,7 +239,7 @@ func GogsHooksNewPost(ctx *context.Context, form auth.NewGogshookForm) {
222239
}
223240

224241
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
225-
ctx.Redirect(orCtx.Link + "/settings/hooks")
242+
ctx.Redirect(orCtx.Link)
226243
}
227244

228245
// DiscordHooksNewPost response for creating discord hook
@@ -271,7 +288,7 @@ func DiscordHooksNewPost(ctx *context.Context, form auth.NewDiscordHookForm) {
271288
}
272289

273290
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
274-
ctx.Redirect(orCtx.Link + "/settings/hooks")
291+
ctx.Redirect(orCtx.Link)
275292
}
276293

277294
// DingtalkHooksNewPost response for creating dingtalk hook
@@ -311,7 +328,7 @@ func DingtalkHooksNewPost(ctx *context.Context, form auth.NewDingtalkHookForm) {
311328
}
312329

313330
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
314-
ctx.Redirect(orCtx.Link + "/settings/hooks")
331+
ctx.Redirect(orCtx.Link)
315332
}
316333

317334
// SlackHooksNewPost response for creating slack hook
@@ -368,7 +385,7 @@ func SlackHooksNewPost(ctx *context.Context, form auth.NewSlackHookForm) {
368385
}
369386

370387
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
371-
ctx.Redirect(orCtx.Link + "/settings/hooks")
388+
ctx.Redirect(orCtx.Link)
372389
}
373390

374391
func checkWebhook(ctx *context.Context) (*orgRepoCtx, *models.Webhook) {
@@ -384,8 +401,10 @@ func checkWebhook(ctx *context.Context) (*orgRepoCtx, *models.Webhook) {
384401
var w *models.Webhook
385402
if orCtx.RepoID > 0 {
386403
w, err = models.GetWebhookByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
387-
} else {
404+
} else if orCtx.OrgID > 0 {
388405
w, err = models.GetWebhookByOrgID(ctx.Org.Organization.ID, ctx.ParamsInt64(":id"))
406+
} else {
407+
w, err = models.GetDefaultWebhook(ctx.ParamsInt64(":id"))
389408
}
390409
if err != nil {
391410
if models.IsErrWebhookNotExist(err) {
@@ -462,7 +481,7 @@ func WebHooksEditPost(ctx *context.Context, form auth.NewWebhookForm) {
462481
}
463482

464483
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
465-
ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID))
484+
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
466485
}
467486

468487
// GogsHooksEditPost response for editing gogs hook
@@ -501,7 +520,7 @@ func GogsHooksEditPost(ctx *context.Context, form auth.NewGogshookForm) {
501520
}
502521

503522
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
504-
ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID))
523+
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
505524
}
506525

507526
// SlackHooksEditPost response for editing slack hook
@@ -551,7 +570,7 @@ func SlackHooksEditPost(ctx *context.Context, form auth.NewSlackHookForm) {
551570
}
552571

553572
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
554-
ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID))
573+
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
555574
}
556575

557576
// DiscordHooksEditPost response for editing discord hook
@@ -593,7 +612,7 @@ func DiscordHooksEditPost(ctx *context.Context, form auth.NewDiscordHookForm) {
593612
}
594613

595614
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
596-
ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID))
615+
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
597616
}
598617

599618
// DingtalkHooksEditPost response for editing discord hook
@@ -625,7 +644,7 @@ func DingtalkHooksEditPost(ctx *context.Context, form auth.NewDingtalkHookForm)
625644
}
626645

627646
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
628-
ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID))
647+
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
629648
}
630649

631650
// TestWebhook test if web hook is work fine

0 commit comments

Comments
 (0)