mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Add user webhooks (#21563)
Currently we can add webhooks for organizations but not for users. This PR adds the latter. You can access it from the current users settings. 
This commit is contained in:
		| @@ -60,6 +60,7 @@ Gitea supports the following scopes for tokens: | |||||||
| |     **write:public_key** | Grant read/write access to public keys | | |     **write:public_key** | Grant read/write access to public keys | | ||||||
| |     **read:public_key** | Grant read-only access to public keys | | |     **read:public_key** | Grant read-only access to public keys | | ||||||
| | **admin:org_hook** | Grants full access to organizational-level hooks | | | **admin:org_hook** | Grants full access to organizational-level hooks | | ||||||
|  | | **admin:user_hook** | Grants full access to user-level hooks | | ||||||
| | **notification** | Grants full access to notifications | | | **notification** | Grants full access to notifications | | ||||||
| | **user** | Grants full access to user profile info | | | **user** | Grants full access to user profile info | | ||||||
| |     **read:user** | Grants read access to user's profile | | |     **read:user** | Grants read access to user's profile | | ||||||
|   | |||||||
| @@ -32,6 +32,8 @@ const ( | |||||||
|  |  | ||||||
| 	AccessTokenScopeAdminOrgHook AccessTokenScope = "admin:org_hook" | 	AccessTokenScopeAdminOrgHook AccessTokenScope = "admin:org_hook" | ||||||
|  |  | ||||||
|  | 	AccessTokenScopeAdminUserHook AccessTokenScope = "admin:user_hook" | ||||||
|  |  | ||||||
| 	AccessTokenScopeNotification AccessTokenScope = "notification" | 	AccessTokenScopeNotification AccessTokenScope = "notification" | ||||||
|  |  | ||||||
| 	AccessTokenScopeUser       AccessTokenScope = "user" | 	AccessTokenScopeUser       AccessTokenScope = "user" | ||||||
| @@ -64,7 +66,7 @@ type AccessTokenScopeBitmap uint64 | |||||||
| const ( | const ( | ||||||
| 	// AccessTokenScopeAllBits is the bitmap of all access token scopes, except `sudo`. | 	// AccessTokenScopeAllBits is the bitmap of all access token scopes, except `sudo`. | ||||||
| 	AccessTokenScopeAllBits AccessTokenScopeBitmap = AccessTokenScopeRepoBits | | 	AccessTokenScopeAllBits AccessTokenScopeBitmap = AccessTokenScopeRepoBits | | ||||||
| 		AccessTokenScopeAdminOrgBits | AccessTokenScopeAdminPublicKeyBits | AccessTokenScopeAdminOrgHookBits | | 		AccessTokenScopeAdminOrgBits | AccessTokenScopeAdminPublicKeyBits | AccessTokenScopeAdminOrgHookBits | AccessTokenScopeAdminUserHookBits | | ||||||
| 		AccessTokenScopeNotificationBits | AccessTokenScopeUserBits | AccessTokenScopeDeleteRepoBits | | 		AccessTokenScopeNotificationBits | AccessTokenScopeUserBits | AccessTokenScopeDeleteRepoBits | | ||||||
| 		AccessTokenScopePackageBits | AccessTokenScopeAdminGPGKeyBits | AccessTokenScopeAdminApplicationBits | 		AccessTokenScopePackageBits | AccessTokenScopeAdminGPGKeyBits | AccessTokenScopeAdminApplicationBits | ||||||
|  |  | ||||||
| @@ -86,6 +88,8 @@ const ( | |||||||
|  |  | ||||||
| 	AccessTokenScopeAdminOrgHookBits AccessTokenScopeBitmap = 1 << iota | 	AccessTokenScopeAdminOrgHookBits AccessTokenScopeBitmap = 1 << iota | ||||||
|  |  | ||||||
|  | 	AccessTokenScopeAdminUserHookBits AccessTokenScopeBitmap = 1 << iota | ||||||
|  |  | ||||||
| 	AccessTokenScopeNotificationBits AccessTokenScopeBitmap = 1 << iota | 	AccessTokenScopeNotificationBits AccessTokenScopeBitmap = 1 << iota | ||||||
|  |  | ||||||
| 	AccessTokenScopeUserBits       AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeReadUserBits | AccessTokenScopeUserEmailBits | AccessTokenScopeUserFollowBits | 	AccessTokenScopeUserBits       AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeReadUserBits | AccessTokenScopeUserEmailBits | AccessTokenScopeUserFollowBits | ||||||
| @@ -123,6 +127,7 @@ var allAccessTokenScopes = []AccessTokenScope{ | |||||||
| 	AccessTokenScopeAdminPublicKey, AccessTokenScopeWritePublicKey, AccessTokenScopeReadPublicKey, | 	AccessTokenScopeAdminPublicKey, AccessTokenScopeWritePublicKey, AccessTokenScopeReadPublicKey, | ||||||
| 	AccessTokenScopeAdminRepoHook, AccessTokenScopeWriteRepoHook, AccessTokenScopeReadRepoHook, | 	AccessTokenScopeAdminRepoHook, AccessTokenScopeWriteRepoHook, AccessTokenScopeReadRepoHook, | ||||||
| 	AccessTokenScopeAdminOrgHook, | 	AccessTokenScopeAdminOrgHook, | ||||||
|  | 	AccessTokenScopeAdminUserHook, | ||||||
| 	AccessTokenScopeNotification, | 	AccessTokenScopeNotification, | ||||||
| 	AccessTokenScopeUser, AccessTokenScopeReadUser, AccessTokenScopeUserEmail, AccessTokenScopeUserFollow, | 	AccessTokenScopeUser, AccessTokenScopeReadUser, AccessTokenScopeUserEmail, AccessTokenScopeUserFollow, | ||||||
| 	AccessTokenScopeDeleteRepo, | 	AccessTokenScopeDeleteRepo, | ||||||
| @@ -147,6 +152,7 @@ var allAccessTokenScopeBits = map[AccessTokenScope]AccessTokenScopeBitmap{ | |||||||
| 	AccessTokenScopeWriteRepoHook:    AccessTokenScopeWriteRepoHookBits, | 	AccessTokenScopeWriteRepoHook:    AccessTokenScopeWriteRepoHookBits, | ||||||
| 	AccessTokenScopeReadRepoHook:     AccessTokenScopeReadRepoHookBits, | 	AccessTokenScopeReadRepoHook:     AccessTokenScopeReadRepoHookBits, | ||||||
| 	AccessTokenScopeAdminOrgHook:     AccessTokenScopeAdminOrgHookBits, | 	AccessTokenScopeAdminOrgHook:     AccessTokenScopeAdminOrgHookBits, | ||||||
|  | 	AccessTokenScopeAdminUserHook:    AccessTokenScopeAdminUserHookBits, | ||||||
| 	AccessTokenScopeNotification:     AccessTokenScopeNotificationBits, | 	AccessTokenScopeNotification:     AccessTokenScopeNotificationBits, | ||||||
| 	AccessTokenScopeUser:             AccessTokenScopeUserBits, | 	AccessTokenScopeUser:             AccessTokenScopeUserBits, | ||||||
| 	AccessTokenScopeReadUser:         AccessTokenScopeReadUserBits, | 	AccessTokenScopeReadUser:         AccessTokenScopeReadUserBits, | ||||||
| @@ -263,7 +269,7 @@ func (bitmap AccessTokenScopeBitmap) ToScope() AccessTokenScope { | |||||||
| 	scope := AccessTokenScope(strings.Join(scopes, ",")) | 	scope := AccessTokenScope(strings.Join(scopes, ",")) | ||||||
| 	scope = AccessTokenScope(strings.ReplaceAll( | 	scope = AccessTokenScope(strings.ReplaceAll( | ||||||
| 		string(scope), | 		string(scope), | ||||||
| 		"repo,admin:org,admin:public_key,admin:org_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application", | 		"repo,admin:org,admin:public_key,admin:org_hook,admin:user_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application", | ||||||
| 		"all", | 		"all", | ||||||
| 	)) | 	)) | ||||||
| 	return scope | 	return scope | ||||||
|   | |||||||
| @@ -40,8 +40,8 @@ func TestAccessTokenScope_Normalize(t *testing.T) { | |||||||
| 		{"admin:gpg_key,write:gpg_key,user", "user,admin:gpg_key", nil}, | 		{"admin:gpg_key,write:gpg_key,user", "user,admin:gpg_key", nil}, | ||||||
| 		{"admin:application,write:application,user", "user,admin:application", nil}, | 		{"admin:application,write:application,user", "user,admin:application", nil}, | ||||||
| 		{"all", "all", nil}, | 		{"all", "all", nil}, | ||||||
| 		{"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application", "all", nil}, | 		{"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,admin:user_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application", "all", nil}, | ||||||
| 		{"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application,sudo", "all,sudo", nil}, | 		{"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,admin:user_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application,sudo", "all,sudo", nil}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ | |||||||
|  |  | ||||||
| - | - | ||||||
|   id: 3 |   id: 3 | ||||||
|   org_id: 3 |   owner_id: 3 | ||||||
|   repo_id: 3 |   repo_id: 3 | ||||||
|   url: www.example.com/url3 |   url: www.example.com/url3 | ||||||
|   content_type: 1 # json |   content_type: 1 # json | ||||||
|   | |||||||
| @@ -467,6 +467,8 @@ var migrations = []Migration{ | |||||||
|  |  | ||||||
| 	// v244 -> v245 | 	// v244 -> v245 | ||||||
| 	NewMigration("Add NeedApproval to actions tables", v1_20.AddNeedApprovalToActionRun), | 	NewMigration("Add NeedApproval to actions tables", v1_20.AddNeedApprovalToActionRun), | ||||||
|  | 	// v245 -> v246 | ||||||
|  | 	NewMigration("Rename Webhook org_id to owner_id", v1_20.RenameWebhookOrgToOwner), | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetCurrentDBVersion returns the current db version | // GetCurrentDBVersion returns the current db version | ||||||
|   | |||||||
							
								
								
									
										74
									
								
								models/migrations/v1_20/v245.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								models/migrations/v1_20/v245.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | // Copyright 2023 The Gitea Authors. All rights reserved. | ||||||
|  | // SPDX-License-Identifier: MIT | ||||||
|  |  | ||||||
|  | package v1_20 //nolint | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/models/migrations/base" | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  |  | ||||||
|  | 	"xorm.io/xorm" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func RenameWebhookOrgToOwner(x *xorm.Engine) error { | ||||||
|  | 	type Webhook struct { | ||||||
|  | 		OrgID int64 `xorm:"INDEX"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// This migration maybe rerun so that we should check if it has been run | ||||||
|  | 	ownerExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webhook", "owner_id") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if ownerExist { | ||||||
|  | 		orgExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webhook", "org_id") | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if !orgExist { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	sess := x.NewSession() | ||||||
|  | 	defer sess.Close() | ||||||
|  | 	if err := sess.Begin(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := sess.Sync2(new(Webhook)); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if ownerExist { | ||||||
|  | 		if err := base.DropTableColumns(sess, "webhook", "owner_id"); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch { | ||||||
|  | 	case setting.Database.Type.IsMySQL(): | ||||||
|  | 		inferredTable, err := x.TableInfo(new(Webhook)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		sqlType := x.Dialect().SQLType(inferredTable.GetColumn("org_id")) | ||||||
|  | 		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `webhook` CHANGE org_id owner_id %s", sqlType)); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	case setting.Database.Type.IsMSSQL(): | ||||||
|  | 		if _, err := sess.Exec("sp_rename 'webhook.org_id', 'owner_id', 'COLUMN'"); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	default: | ||||||
|  | 		if _, err := sess.Exec("ALTER TABLE `webhook` RENAME COLUMN org_id TO owner_id"); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return sess.Commit() | ||||||
|  | } | ||||||
| @@ -122,7 +122,7 @@ func IsValidHookContentType(name string) bool { | |||||||
| type Webhook struct { | type Webhook struct { | ||||||
| 	ID                        int64 `xorm:"pk autoincr"` | 	ID                        int64 `xorm:"pk autoincr"` | ||||||
| 	RepoID                    int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook | 	RepoID                    int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook | ||||||
| 	OrgID                     int64 `xorm:"INDEX"` | 	OwnerID                   int64 `xorm:"INDEX"` | ||||||
| 	IsSystemWebhook           bool | 	IsSystemWebhook           bool | ||||||
| 	URL                       string `xorm:"url TEXT"` | 	URL                       string `xorm:"url TEXT"` | ||||||
| 	HTTPMethod                string `xorm:"http_method"` | 	HTTPMethod                string `xorm:"http_method"` | ||||||
| @@ -412,11 +412,11 @@ func GetWebhookByRepoID(repoID, id int64) (*Webhook, error) { | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetWebhookByOrgID returns webhook of organization by given ID. | // GetWebhookByOwnerID returns webhook of a user or organization by given ID. | ||||||
| func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) { | func GetWebhookByOwnerID(ownerID, id int64) (*Webhook, error) { | ||||||
| 	return getWebhook(&Webhook{ | 	return getWebhook(&Webhook{ | ||||||
| 		ID:    id, | 		ID:      id, | ||||||
| 		OrgID: orgID, | 		OwnerID: ownerID, | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -424,7 +424,7 @@ func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) { | |||||||
| type ListWebhookOptions struct { | type ListWebhookOptions struct { | ||||||
| 	db.ListOptions | 	db.ListOptions | ||||||
| 	RepoID   int64 | 	RepoID   int64 | ||||||
| 	OrgID    int64 | 	OwnerID  int64 | ||||||
| 	IsActive util.OptionalBool | 	IsActive util.OptionalBool | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -433,8 +433,8 @@ func (opts *ListWebhookOptions) toCond() builder.Cond { | |||||||
| 	if opts.RepoID != 0 { | 	if opts.RepoID != 0 { | ||||||
| 		cond = cond.And(builder.Eq{"webhook.repo_id": opts.RepoID}) | 		cond = cond.And(builder.Eq{"webhook.repo_id": opts.RepoID}) | ||||||
| 	} | 	} | ||||||
| 	if opts.OrgID != 0 { | 	if opts.OwnerID != 0 { | ||||||
| 		cond = cond.And(builder.Eq{"webhook.org_id": opts.OrgID}) | 		cond = cond.And(builder.Eq{"webhook.owner_id": opts.OwnerID}) | ||||||
| 	} | 	} | ||||||
| 	if !opts.IsActive.IsNone() { | 	if !opts.IsActive.IsNone() { | ||||||
| 		cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.IsTrue()}) | 		cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.IsTrue()}) | ||||||
| @@ -503,10 +503,10 @@ func DeleteWebhookByRepoID(repoID, id int64) error { | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| // DeleteWebhookByOrgID deletes webhook of organization by given ID. | // DeleteWebhookByOwnerID deletes webhook of a user or organization by given ID. | ||||||
| func DeleteWebhookByOrgID(orgID, id int64) error { | func DeleteWebhookByOwnerID(ownerID, id int64) error { | ||||||
| 	return deleteWebhook(&Webhook{ | 	return deleteWebhook(&Webhook{ | ||||||
| 		ID:    id, | 		ID:      id, | ||||||
| 		OrgID: orgID, | 		OwnerID: ownerID, | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ import ( | |||||||
| func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) { | func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) { | ||||||
| 	webhooks := make([]*Webhook, 0, 5) | 	webhooks := make([]*Webhook, 0, 5) | ||||||
| 	return webhooks, db.GetEngine(ctx). | 	return webhooks, db.GetEngine(ctx). | ||||||
| 		Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false). | 		Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, false). | ||||||
| 		Find(&webhooks) | 		Find(&webhooks) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -23,7 +23,7 @@ func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) { | |||||||
| func GetSystemOrDefaultWebhook(ctx context.Context, id int64) (*Webhook, error) { | func GetSystemOrDefaultWebhook(ctx context.Context, id int64) (*Webhook, error) { | ||||||
| 	webhook := &Webhook{ID: id} | 	webhook := &Webhook{ID: id} | ||||||
| 	has, err := db.GetEngine(ctx). | 	has, err := db.GetEngine(ctx). | ||||||
| 		Where("repo_id=? AND org_id=?", 0, 0). | 		Where("repo_id=? AND owner_id=?", 0, 0). | ||||||
| 		Get(webhook) | 		Get(webhook) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -38,11 +38,11 @@ func GetSystemWebhooks(ctx context.Context, isActive util.OptionalBool) ([]*Webh | |||||||
| 	webhooks := make([]*Webhook, 0, 5) | 	webhooks := make([]*Webhook, 0, 5) | ||||||
| 	if isActive.IsNone() { | 	if isActive.IsNone() { | ||||||
| 		return webhooks, db.GetEngine(ctx). | 		return webhooks, db.GetEngine(ctx). | ||||||
| 			Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true). | 			Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, true). | ||||||
| 			Find(&webhooks) | 			Find(&webhooks) | ||||||
| 	} | 	} | ||||||
| 	return webhooks, db.GetEngine(ctx). | 	return webhooks, db.GetEngine(ctx). | ||||||
| 		Where("repo_id=? AND org_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.IsTrue()). | 		Where("repo_id=? AND owner_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.IsTrue()). | ||||||
| 		Find(&webhooks) | 		Find(&webhooks) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -50,7 +50,7 @@ func GetSystemWebhooks(ctx context.Context, isActive util.OptionalBool) ([]*Webh | |||||||
| func DeleteDefaultSystemWebhook(ctx context.Context, id int64) error { | func DeleteDefaultSystemWebhook(ctx context.Context, id int64) error { | ||||||
| 	return db.WithTx(ctx, func(ctx context.Context) error { | 	return db.WithTx(ctx, func(ctx context.Context) error { | ||||||
| 		count, err := db.GetEngine(ctx). | 		count, err := db.GetEngine(ctx). | ||||||
| 			Where("repo_id=? AND org_id=?", 0, 0). | 			Where("repo_id=? AND owner_id=?", 0, 0). | ||||||
| 			Delete(&Webhook{ID: id}) | 			Delete(&Webhook{ID: id}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
|   | |||||||
| @@ -109,13 +109,13 @@ func TestGetWebhookByRepoID(t *testing.T) { | |||||||
| 	assert.True(t, IsErrWebhookNotExist(err)) | 	assert.True(t, IsErrWebhookNotExist(err)) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestGetWebhookByOrgID(t *testing.T) { | func TestGetWebhookByOwnerID(t *testing.T) { | ||||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||||
| 	hook, err := GetWebhookByOrgID(3, 3) | 	hook, err := GetWebhookByOwnerID(3, 3) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 	assert.Equal(t, int64(3), hook.ID) | 	assert.Equal(t, int64(3), hook.ID) | ||||||
|  |  | ||||||
| 	_, err = GetWebhookByOrgID(unittest.NonexistentID, unittest.NonexistentID) | 	_, err = GetWebhookByOwnerID(unittest.NonexistentID, unittest.NonexistentID) | ||||||
| 	assert.Error(t, err) | 	assert.Error(t, err) | ||||||
| 	assert.True(t, IsErrWebhookNotExist(err)) | 	assert.True(t, IsErrWebhookNotExist(err)) | ||||||
| } | } | ||||||
| @@ -140,9 +140,9 @@ func TestGetWebhooksByRepoID(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestGetActiveWebhooksByOrgID(t *testing.T) { | func TestGetActiveWebhooksByOwnerID(t *testing.T) { | ||||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||||
| 	hooks, err := ListWebhooksByOpts(db.DefaultContext, &ListWebhookOptions{OrgID: 3, IsActive: util.OptionalBoolTrue}) | 	hooks, err := ListWebhooksByOpts(db.DefaultContext, &ListWebhookOptions{OwnerID: 3, IsActive: util.OptionalBoolTrue}) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 	if assert.Len(t, hooks, 1) { | 	if assert.Len(t, hooks, 1) { | ||||||
| 		assert.Equal(t, int64(3), hooks[0].ID) | 		assert.Equal(t, int64(3), hooks[0].ID) | ||||||
| @@ -150,9 +150,9 @@ func TestGetActiveWebhooksByOrgID(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestGetWebhooksByOrgID(t *testing.T) { | func TestGetWebhooksByOwnerID(t *testing.T) { | ||||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||||
| 	hooks, err := ListWebhooksByOpts(db.DefaultContext, &ListWebhookOptions{OrgID: 3}) | 	hooks, err := ListWebhooksByOpts(db.DefaultContext, &ListWebhookOptions{OwnerID: 3}) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 	if assert.Len(t, hooks, 1) { | 	if assert.Len(t, hooks, 1) { | ||||||
| 		assert.Equal(t, int64(3), hooks[0].ID) | 		assert.Equal(t, int64(3), hooks[0].ID) | ||||||
| @@ -181,13 +181,13 @@ func TestDeleteWebhookByRepoID(t *testing.T) { | |||||||
| 	assert.True(t, IsErrWebhookNotExist(err)) | 	assert.True(t, IsErrWebhookNotExist(err)) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestDeleteWebhookByOrgID(t *testing.T) { | func TestDeleteWebhookByOwnerID(t *testing.T) { | ||||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||||
| 	unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 3, OrgID: 3}) | 	unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 3, OwnerID: 3}) | ||||||
| 	assert.NoError(t, DeleteWebhookByOrgID(3, 3)) | 	assert.NoError(t, DeleteWebhookByOwnerID(3, 3)) | ||||||
| 	unittest.AssertNotExistsBean(t, &Webhook{ID: 3, OrgID: 3}) | 	unittest.AssertNotExistsBean(t, &Webhook{ID: 3, OwnerID: 3}) | ||||||
|  |  | ||||||
| 	err := DeleteWebhookByOrgID(unittest.NonexistentID, unittest.NonexistentID) | 	err := DeleteWebhookByOwnerID(unittest.NonexistentID, unittest.NonexistentID) | ||||||
| 	assert.Error(t, err) | 	assert.Error(t, err) | ||||||
| 	assert.True(t, IsErrWebhookNotExist(err)) | 	assert.True(t, IsErrWebhookNotExist(err)) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -821,6 +821,8 @@ remove_account_link = Remove Linked Account | |||||||
| remove_account_link_desc = Removing a linked account will revoke its access to your Gitea account. Continue? | remove_account_link_desc = Removing a linked account will revoke its access to your Gitea account. Continue? | ||||||
| remove_account_link_success = The linked account has been removed. | remove_account_link_success = The linked account has been removed. | ||||||
|  |  | ||||||
|  | hooks.desc = Add webhooks which will be triggered for <strong>all repositories</strong> owned by this user. | ||||||
|  |  | ||||||
| orgs_none = You are not a member of any organizations. | orgs_none = You are not a member of any organizations. | ||||||
| repos_none = You do not own any repositories | repos_none = You do not own any repositories | ||||||
|  |  | ||||||
|   | |||||||
| @@ -105,10 +105,7 @@ func CreateHook(ctx *context.APIContext) { | |||||||
| 	//     "$ref": "#/responses/Hook" | 	//     "$ref": "#/responses/Hook" | ||||||
|  |  | ||||||
| 	form := web.GetForm(ctx).(*api.CreateHookOption) | 	form := web.GetForm(ctx).(*api.CreateHookOption) | ||||||
| 	// TODO in body params |  | ||||||
| 	if !utils.CheckCreateHookOption(ctx, form) { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	utils.AddSystemHook(ctx, form) | 	utils.AddSystemHook(ctx, form) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -835,6 +835,13 @@ func Routes(ctx gocontext.Context) *web.Route { | |||||||
| 			m.Get("/stopwatches", reqToken(auth_model.AccessTokenScopeRepo), repo.GetStopwatches) | 			m.Get("/stopwatches", reqToken(auth_model.AccessTokenScopeRepo), repo.GetStopwatches) | ||||||
| 			m.Get("/subscriptions", reqToken(auth_model.AccessTokenScopeRepo), user.GetMyWatchedRepos) | 			m.Get("/subscriptions", reqToken(auth_model.AccessTokenScopeRepo), user.GetMyWatchedRepos) | ||||||
| 			m.Get("/teams", reqToken(auth_model.AccessTokenScopeRepo), org.ListUserTeams) | 			m.Get("/teams", reqToken(auth_model.AccessTokenScopeRepo), org.ListUserTeams) | ||||||
|  | 			m.Group("/hooks", func() { | ||||||
|  | 				m.Combo("").Get(user.ListHooks). | ||||||
|  | 					Post(bind(api.CreateHookOption{}), user.CreateHook) | ||||||
|  | 				m.Combo("/{id}").Get(user.GetHook). | ||||||
|  | 					Patch(bind(api.EditHookOption{}), user.EditHook). | ||||||
|  | 					Delete(user.DeleteHook) | ||||||
|  | 			}, reqToken(auth_model.AccessTokenScopeAdminUserHook), reqWebhooksEnabled()) | ||||||
| 		}, reqToken("")) | 		}, reqToken("")) | ||||||
|  |  | ||||||
| 		// Repositories | 		// Repositories | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ package org | |||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  |  | ||||||
| 	webhook_model "code.gitea.io/gitea/models/webhook" |  | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/web" | 	"code.gitea.io/gitea/modules/web" | ||||||
| @@ -39,34 +38,10 @@ func ListHooks(ctx *context.APIContext) { | |||||||
| 	//   "200": | 	//   "200": | ||||||
| 	//     "$ref": "#/responses/HookList" | 	//     "$ref": "#/responses/HookList" | ||||||
|  |  | ||||||
| 	opts := &webhook_model.ListWebhookOptions{ | 	utils.ListOwnerHooks( | ||||||
| 		ListOptions: utils.GetListOptions(ctx), | 		ctx, | ||||||
| 		OrgID:       ctx.Org.Organization.ID, | 		ctx.ContextUser, | ||||||
| 	} | 	) | ||||||
|  |  | ||||||
| 	count, err := webhook_model.CountWebhooksByOpts(opts) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.InternalServerError(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	orgHooks, err := webhook_model.ListWebhooksByOpts(ctx, opts) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.InternalServerError(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	hooks := make([]*api.Hook, len(orgHooks)) |  | ||||||
| 	for i, hook := range orgHooks { |  | ||||||
| 		hooks[i], err = webhook_service.ToHook(ctx.Org.Organization.AsUser().HomeLink(), hook) |  | ||||||
| 		if err != nil { |  | ||||||
| 			ctx.InternalServerError(err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ctx.SetTotalCountHeader(count) |  | ||||||
| 	ctx.JSON(http.StatusOK, hooks) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetHook get an organization's hook by id | // GetHook get an organization's hook by id | ||||||
| @@ -92,14 +67,12 @@ func GetHook(ctx *context.APIContext) { | |||||||
| 	//   "200": | 	//   "200": | ||||||
| 	//     "$ref": "#/responses/Hook" | 	//     "$ref": "#/responses/Hook" | ||||||
|  |  | ||||||
| 	org := ctx.Org.Organization | 	hook, err := utils.GetOwnerHook(ctx, ctx.ContextUser.ID, ctx.ParamsInt64("id")) | ||||||
| 	hookID := ctx.ParamsInt64(":id") |  | ||||||
| 	hook, err := utils.GetOrgHook(ctx, org.ID, hookID) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	apiHook, err := webhook_service.ToHook(org.AsUser().HomeLink(), hook) | 	apiHook, err := webhook_service.ToHook(ctx.ContextUser.HomeLink(), hook) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.InternalServerError(err) | 		ctx.InternalServerError(err) | ||||||
| 		return | 		return | ||||||
| @@ -131,15 +104,14 @@ func CreateHook(ctx *context.APIContext) { | |||||||
| 	//   "201": | 	//   "201": | ||||||
| 	//     "$ref": "#/responses/Hook" | 	//     "$ref": "#/responses/Hook" | ||||||
|  |  | ||||||
| 	form := web.GetForm(ctx).(*api.CreateHookOption) | 	utils.AddOwnerHook( | ||||||
| 	// TODO in body params | 		ctx, | ||||||
| 	if !utils.CheckCreateHookOption(ctx, form) { | 		ctx.ContextUser, | ||||||
| 		return | 		web.GetForm(ctx).(*api.CreateHookOption), | ||||||
| 	} | 	) | ||||||
| 	utils.AddOrgHook(ctx, form) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // EditHook modify a hook of a repository | // EditHook modify a hook of an organization | ||||||
| func EditHook(ctx *context.APIContext) { | func EditHook(ctx *context.APIContext) { | ||||||
| 	// swagger:operation PATCH /orgs/{org}/hooks/{id} organization orgEditHook | 	// swagger:operation PATCH /orgs/{org}/hooks/{id} organization orgEditHook | ||||||
| 	// --- | 	// --- | ||||||
| @@ -168,11 +140,12 @@ func EditHook(ctx *context.APIContext) { | |||||||
| 	//   "200": | 	//   "200": | ||||||
| 	//     "$ref": "#/responses/Hook" | 	//     "$ref": "#/responses/Hook" | ||||||
|  |  | ||||||
| 	form := web.GetForm(ctx).(*api.EditHookOption) | 	utils.EditOwnerHook( | ||||||
|  | 		ctx, | ||||||
| 	// TODO in body params | 		ctx.ContextUser, | ||||||
| 	hookID := ctx.ParamsInt64(":id") | 		web.GetForm(ctx).(*api.EditHookOption), | ||||||
| 	utils.EditOrgHook(ctx, form, hookID) | 		ctx.ParamsInt64("id"), | ||||||
|  | 	) | ||||||
| } | } | ||||||
|  |  | ||||||
| // DeleteHook delete a hook of an organization | // DeleteHook delete a hook of an organization | ||||||
| @@ -198,15 +171,9 @@ func DeleteHook(ctx *context.APIContext) { | |||||||
| 	//   "204": | 	//   "204": | ||||||
| 	//     "$ref": "#/responses/empty" | 	//     "$ref": "#/responses/empty" | ||||||
|  |  | ||||||
| 	org := ctx.Org.Organization | 	utils.DeleteOwnerHook( | ||||||
| 	hookID := ctx.ParamsInt64(":id") | 		ctx, | ||||||
| 	if err := webhook_model.DeleteWebhookByOrgID(org.ID, hookID); err != nil { | 		ctx.ContextUser, | ||||||
| 		if webhook_model.IsErrWebhookNotExist(err) { | 		ctx.ParamsInt64("id"), | ||||||
| 			ctx.NotFound() | 	) | ||||||
| 		} else { |  | ||||||
| 			ctx.Error(http.StatusInternalServerError, "DeleteWebhookByOrgID", err) |  | ||||||
| 		} |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	ctx.Status(http.StatusNoContent) |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -223,12 +223,8 @@ func CreateHook(ctx *context.APIContext) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "201": | 	//   "201": | ||||||
| 	//     "$ref": "#/responses/Hook" | 	//     "$ref": "#/responses/Hook" | ||||||
| 	form := web.GetForm(ctx).(*api.CreateHookOption) |  | ||||||
|  |  | ||||||
| 	if !utils.CheckCreateHookOption(ctx, form) { | 	utils.AddRepoHook(ctx, web.GetForm(ctx).(*api.CreateHookOption)) | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	utils.AddRepoHook(ctx, form) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // EditHook modify a hook of a repository | // EditHook modify a hook of a repository | ||||||
|   | |||||||
							
								
								
									
										154
									
								
								routers/api/v1/user/hook.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								routers/api/v1/user/hook.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,154 @@ | |||||||
|  | // Copyright 2022 The Gitea Authors. All rights reserved. | ||||||
|  | // SPDX-License-Identifier: MIT | ||||||
|  |  | ||||||
|  | package user | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/context" | ||||||
|  | 	api "code.gitea.io/gitea/modules/structs" | ||||||
|  | 	"code.gitea.io/gitea/modules/web" | ||||||
|  | 	"code.gitea.io/gitea/routers/api/v1/utils" | ||||||
|  | 	webhook_service "code.gitea.io/gitea/services/webhook" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ListHooks list the authenticated user's webhooks | ||||||
|  | func ListHooks(ctx *context.APIContext) { | ||||||
|  | 	// swagger:operation GET /user/hooks user userListHooks | ||||||
|  | 	// --- | ||||||
|  | 	// summary: List the authenticated user's webhooks | ||||||
|  | 	// produces: | ||||||
|  | 	// - application/json | ||||||
|  | 	// parameters: | ||||||
|  | 	// - name: page | ||||||
|  | 	//   in: query | ||||||
|  | 	//   description: page number of results to return (1-based) | ||||||
|  | 	//   type: integer | ||||||
|  | 	// - name: limit | ||||||
|  | 	//   in: query | ||||||
|  | 	//   description: page size of results | ||||||
|  | 	//   type: integer | ||||||
|  | 	// responses: | ||||||
|  | 	//   "200": | ||||||
|  | 	//     "$ref": "#/responses/HookList" | ||||||
|  |  | ||||||
|  | 	utils.ListOwnerHooks( | ||||||
|  | 		ctx, | ||||||
|  | 		ctx.Doer, | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetHook get the authenticated user's hook by id | ||||||
|  | func GetHook(ctx *context.APIContext) { | ||||||
|  | 	// swagger:operation GET /user/hooks/{id} user userGetHook | ||||||
|  | 	// --- | ||||||
|  | 	// summary: Get a hook | ||||||
|  | 	// produces: | ||||||
|  | 	// - application/json | ||||||
|  | 	// parameters: | ||||||
|  | 	// - name: id | ||||||
|  | 	//   in: path | ||||||
|  | 	//   description: id of the hook to get | ||||||
|  | 	//   type: integer | ||||||
|  | 	//   format: int64 | ||||||
|  | 	//   required: true | ||||||
|  | 	// responses: | ||||||
|  | 	//   "200": | ||||||
|  | 	//     "$ref": "#/responses/Hook" | ||||||
|  |  | ||||||
|  | 	hook, err := utils.GetOwnerHook(ctx, ctx.Doer.ID, ctx.ParamsInt64("id")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	apiHook, err := webhook_service.ToHook(ctx.Doer.HomeLink(), hook) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.InternalServerError(err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	ctx.JSON(http.StatusOK, apiHook) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CreateHook create a hook for the authenticated user | ||||||
|  | func CreateHook(ctx *context.APIContext) { | ||||||
|  | 	// swagger:operation POST /user/hooks user userCreateHook | ||||||
|  | 	// --- | ||||||
|  | 	// summary: Create a hook | ||||||
|  | 	// consumes: | ||||||
|  | 	// - application/json | ||||||
|  | 	// produces: | ||||||
|  | 	// - application/json | ||||||
|  | 	// parameters: | ||||||
|  | 	// - name: body | ||||||
|  | 	//   in: body | ||||||
|  | 	//   required: true | ||||||
|  | 	//   schema: | ||||||
|  | 	//     "$ref": "#/definitions/CreateHookOption" | ||||||
|  | 	// responses: | ||||||
|  | 	//   "201": | ||||||
|  | 	//     "$ref": "#/responses/Hook" | ||||||
|  |  | ||||||
|  | 	utils.AddOwnerHook( | ||||||
|  | 		ctx, | ||||||
|  | 		ctx.Doer, | ||||||
|  | 		web.GetForm(ctx).(*api.CreateHookOption), | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // EditHook modify a hook of the authenticated user | ||||||
|  | func EditHook(ctx *context.APIContext) { | ||||||
|  | 	// swagger:operation PATCH /user/hooks/{id} user userEditHook | ||||||
|  | 	// --- | ||||||
|  | 	// summary: Update a hook | ||||||
|  | 	// consumes: | ||||||
|  | 	// - application/json | ||||||
|  | 	// produces: | ||||||
|  | 	// - application/json | ||||||
|  | 	// parameters: | ||||||
|  | 	// - name: id | ||||||
|  | 	//   in: path | ||||||
|  | 	//   description: id of the hook to update | ||||||
|  | 	//   type: integer | ||||||
|  | 	//   format: int64 | ||||||
|  | 	//   required: true | ||||||
|  | 	// - name: body | ||||||
|  | 	//   in: body | ||||||
|  | 	//   schema: | ||||||
|  | 	//     "$ref": "#/definitions/EditHookOption" | ||||||
|  | 	// responses: | ||||||
|  | 	//   "200": | ||||||
|  | 	//     "$ref": "#/responses/Hook" | ||||||
|  |  | ||||||
|  | 	utils.EditOwnerHook( | ||||||
|  | 		ctx, | ||||||
|  | 		ctx.Doer, | ||||||
|  | 		web.GetForm(ctx).(*api.EditHookOption), | ||||||
|  | 		ctx.ParamsInt64("id"), | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DeleteHook delete a hook of the authenticated user | ||||||
|  | func DeleteHook(ctx *context.APIContext) { | ||||||
|  | 	// swagger:operation DELETE /user/hooks/{id} user userDeleteHook | ||||||
|  | 	// --- | ||||||
|  | 	// summary: Delete a hook | ||||||
|  | 	// produces: | ||||||
|  | 	// - application/json | ||||||
|  | 	// parameters: | ||||||
|  | 	// - name: id | ||||||
|  | 	//   in: path | ||||||
|  | 	//   description: id of the hook to delete | ||||||
|  | 	//   type: integer | ||||||
|  | 	//   format: int64 | ||||||
|  | 	//   required: true | ||||||
|  | 	// responses: | ||||||
|  | 	//   "204": | ||||||
|  | 	//     "$ref": "#/responses/empty" | ||||||
|  |  | ||||||
|  | 	utils.DeleteOwnerHook( | ||||||
|  | 		ctx, | ||||||
|  | 		ctx.Doer, | ||||||
|  | 		ctx.ParamsInt64("id"), | ||||||
|  | 	) | ||||||
|  | } | ||||||
| @@ -8,6 +8,7 @@ import ( | |||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| 	"code.gitea.io/gitea/models/webhook" | 	"code.gitea.io/gitea/models/webhook" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/json" | 	"code.gitea.io/gitea/modules/json" | ||||||
| @@ -18,15 +19,46 @@ import ( | |||||||
| 	webhook_service "code.gitea.io/gitea/services/webhook" | 	webhook_service "code.gitea.io/gitea/services/webhook" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // GetOrgHook get an organization's webhook. If there is an error, write to | // ListOwnerHooks lists the webhooks of the provided owner | ||||||
| // `ctx` accordingly and return the error | func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) { | ||||||
| func GetOrgHook(ctx *context.APIContext, orgID, hookID int64) (*webhook.Webhook, error) { | 	opts := &webhook.ListWebhookOptions{ | ||||||
| 	w, err := webhook.GetWebhookByOrgID(orgID, hookID) | 		ListOptions: GetListOptions(ctx), | ||||||
|  | 		OwnerID:     owner.ID, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	count, err := webhook.CountWebhooksByOpts(opts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.InternalServerError(err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	hooks, err := webhook.ListWebhooksByOpts(ctx, opts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.InternalServerError(err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	apiHooks := make([]*api.Hook, len(hooks)) | ||||||
|  | 	for i, hook := range hooks { | ||||||
|  | 		apiHooks[i], err = webhook_service.ToHook(owner.HomeLink(), hook) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.InternalServerError(err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ctx.SetTotalCountHeader(count) | ||||||
|  | 	ctx.JSON(http.StatusOK, apiHooks) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetOwnerHook gets an user or organization webhook. Errors are written to ctx. | ||||||
|  | func GetOwnerHook(ctx *context.APIContext, ownerID, hookID int64) (*webhook.Webhook, error) { | ||||||
|  | 	w, err := webhook.GetWebhookByOwnerID(ownerID, hookID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if webhook.IsErrWebhookNotExist(err) { | 		if webhook.IsErrWebhookNotExist(err) { | ||||||
| 			ctx.NotFound() | 			ctx.NotFound() | ||||||
| 		} else { | 		} else { | ||||||
| 			ctx.Error(http.StatusInternalServerError, "GetWebhookByOrgID", err) | 			ctx.Error(http.StatusInternalServerError, "GetWebhookByOwnerID", err) | ||||||
| 		} | 		} | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -48,9 +80,9 @@ func GetRepoHook(ctx *context.APIContext, repoID, hookID int64) (*webhook.Webhoo | |||||||
| 	return w, nil | 	return w, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // CheckCreateHookOption check if a CreateHookOption form is valid. If invalid, | // checkCreateHookOption check if a CreateHookOption form is valid. If invalid, | ||||||
| // write the appropriate error to `ctx`. Return whether the form is valid | // write the appropriate error to `ctx`. Return whether the form is valid | ||||||
| func CheckCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption) bool { | func checkCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption) bool { | ||||||
| 	if !webhook_service.IsValidHookTaskType(form.Type) { | 	if !webhook_service.IsValidHookTaskType(form.Type) { | ||||||
| 		ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Invalid hook type: %s", form.Type)) | 		ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Invalid hook type: %s", form.Type)) | ||||||
| 		return false | 		return false | ||||||
| @@ -81,14 +113,13 @@ func AddSystemHook(ctx *context.APIContext, form *api.CreateHookOption) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // AddOrgHook add a hook to an organization. Writes to `ctx` accordingly | // AddOwnerHook adds a hook to an user or organization | ||||||
| func AddOrgHook(ctx *context.APIContext, form *api.CreateHookOption) { | func AddOwnerHook(ctx *context.APIContext, owner *user_model.User, form *api.CreateHookOption) { | ||||||
| 	org := ctx.Org.Organization | 	hook, ok := addHook(ctx, form, owner.ID, 0) | ||||||
| 	hook, ok := addHook(ctx, form, org.ID, 0) |  | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	apiHook, ok := toAPIHook(ctx, org.AsUser().HomeLink(), hook) | 	apiHook, ok := toAPIHook(ctx, owner.HomeLink(), hook) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -128,14 +159,18 @@ func pullHook(events []string, event string) bool { | |||||||
| 	return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventPullRequest), true) | 	return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventPullRequest), true) | ||||||
| } | } | ||||||
|  |  | ||||||
| // addHook add the hook specified by `form`, `orgID` and `repoID`. If there is | // addHook add the hook specified by `form`, `ownerID` and `repoID`. If there is | ||||||
| // an error, write to `ctx` accordingly. Return (webhook, ok) | // an error, write to `ctx` accordingly. Return (webhook, ok) | ||||||
| func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID int64) (*webhook.Webhook, bool) { | func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoID int64) (*webhook.Webhook, bool) { | ||||||
|  | 	if !checkCreateHookOption(ctx, form) { | ||||||
|  | 		return nil, false | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if len(form.Events) == 0 { | 	if len(form.Events) == 0 { | ||||||
| 		form.Events = []string{"push"} | 		form.Events = []string{"push"} | ||||||
| 	} | 	} | ||||||
| 	w := &webhook.Webhook{ | 	w := &webhook.Webhook{ | ||||||
| 		OrgID:       orgID, | 		OwnerID:     ownerID, | ||||||
| 		RepoID:      repoID, | 		RepoID:      repoID, | ||||||
| 		URL:         form.Config["url"], | 		URL:         form.Config["url"], | ||||||
| 		ContentType: webhook.ToHookContentType(form.Config["content_type"]), | 		ContentType: webhook.ToHookContentType(form.Config["content_type"]), | ||||||
| @@ -234,21 +269,20 @@ func EditSystemHook(ctx *context.APIContext, form *api.EditHookOption, hookID in | |||||||
| 	ctx.JSON(http.StatusOK, h) | 	ctx.JSON(http.StatusOK, h) | ||||||
| } | } | ||||||
|  |  | ||||||
| // EditOrgHook edit webhook `w` according to `form`. Writes to `ctx` accordingly | // EditOwnerHook updates a webhook of an user or organization | ||||||
| func EditOrgHook(ctx *context.APIContext, form *api.EditHookOption, hookID int64) { | func EditOwnerHook(ctx *context.APIContext, owner *user_model.User, form *api.EditHookOption, hookID int64) { | ||||||
| 	org := ctx.Org.Organization | 	hook, err := GetOwnerHook(ctx, owner.ID, hookID) | ||||||
| 	hook, err := GetOrgHook(ctx, org.ID, hookID) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if !editHook(ctx, form, hook) { | 	if !editHook(ctx, form, hook) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	updated, err := GetOrgHook(ctx, org.ID, hookID) | 	updated, err := GetOwnerHook(ctx, owner.ID, hookID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	apiHook, ok := toAPIHook(ctx, org.AsUser().HomeLink(), updated) | 	apiHook, ok := toAPIHook(ctx, owner.HomeLink(), updated) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -362,3 +396,16 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh | |||||||
| 	} | 	} | ||||||
| 	return true | 	return true | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // DeleteOwnerHook deletes the hook owned by the owner. | ||||||
|  | func DeleteOwnerHook(ctx *context.APIContext, owner *user_model.User, hookID int64) { | ||||||
|  | 	if err := webhook.DeleteWebhookByOwnerID(owner.ID, hookID); err != nil { | ||||||
|  | 		if webhook.IsErrWebhookNotExist(err) { | ||||||
|  | 			ctx.NotFound() | ||||||
|  | 		} else { | ||||||
|  | 			ctx.Error(http.StatusInternalServerError, "DeleteWebhookByOwnerID", err) | ||||||
|  | 		} | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	ctx.Status(http.StatusNoContent) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -218,9 +218,9 @@ func Webhooks(ctx *context.Context) { | |||||||
| 	ctx.Data["BaseLinkNew"] = ctx.Org.OrgLink + "/settings/hooks" | 	ctx.Data["BaseLinkNew"] = ctx.Org.OrgLink + "/settings/hooks" | ||||||
| 	ctx.Data["Description"] = ctx.Tr("org.settings.hooks_desc") | 	ctx.Data["Description"] = ctx.Tr("org.settings.hooks_desc") | ||||||
|  |  | ||||||
| 	ws, err := webhook.ListWebhooksByOpts(ctx, &webhook.ListWebhookOptions{OrgID: ctx.Org.Organization.ID}) | 	ws, err := webhook.ListWebhooksByOpts(ctx, &webhook.ListWebhookOptions{OwnerID: ctx.Org.Organization.ID}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.ServerError("GetWebhooksByOrgId", err) | 		ctx.ServerError("ListWebhooksByOpts", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -230,8 +230,8 @@ func Webhooks(ctx *context.Context) { | |||||||
|  |  | ||||||
| // DeleteWebhook response for delete webhook | // DeleteWebhook response for delete webhook | ||||||
| func DeleteWebhook(ctx *context.Context) { | func DeleteWebhook(ctx *context.Context) { | ||||||
| 	if err := webhook.DeleteWebhookByOrgID(ctx.Org.Organization.ID, ctx.FormInt64("id")); err != nil { | 	if err := webhook.DeleteWebhookByOwnerID(ctx.Org.Organization.ID, ctx.FormInt64("id")); err != nil { | ||||||
| 		ctx.Flash.Error("DeleteWebhookByOrgID: " + err.Error()) | 		ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error()) | ||||||
| 	} else { | 	} else { | ||||||
| 		ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) | 		ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -33,6 +33,7 @@ const ( | |||||||
| 	tplHooks        base.TplName = "repo/settings/webhook/base" | 	tplHooks        base.TplName = "repo/settings/webhook/base" | ||||||
| 	tplHookNew      base.TplName = "repo/settings/webhook/new" | 	tplHookNew      base.TplName = "repo/settings/webhook/new" | ||||||
| 	tplOrgHookNew   base.TplName = "org/settings/hook_new" | 	tplOrgHookNew   base.TplName = "org/settings/hook_new" | ||||||
|  | 	tplUserHookNew  base.TplName = "user/settings/hook_new" | ||||||
| 	tplAdminHookNew base.TplName = "admin/hook_new" | 	tplAdminHookNew base.TplName = "admin/hook_new" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -54,8 +55,8 @@ func Webhooks(ctx *context.Context) { | |||||||
| 	ctx.HTML(http.StatusOK, tplHooks) | 	ctx.HTML(http.StatusOK, tplHooks) | ||||||
| } | } | ||||||
|  |  | ||||||
| type orgRepoCtx struct { | type ownerRepoCtx struct { | ||||||
| 	OrgID           int64 | 	OwnerID         int64 | ||||||
| 	RepoID          int64 | 	RepoID          int64 | ||||||
| 	IsAdmin         bool | 	IsAdmin         bool | ||||||
| 	IsSystemWebhook bool | 	IsSystemWebhook bool | ||||||
| @@ -64,10 +65,10 @@ type orgRepoCtx struct { | |||||||
| 	NewTemplate     base.TplName | 	NewTemplate     base.TplName | ||||||
| } | } | ||||||
|  |  | ||||||
| // getOrgRepoCtx determines whether this is a repo, organization, or admin (both default and system) context. | // getOwnerRepoCtx determines whether this is a repo, owner, or admin (both default and system) context. | ||||||
| func getOrgRepoCtx(ctx *context.Context) (*orgRepoCtx, error) { | func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) { | ||||||
| 	if len(ctx.Repo.RepoLink) > 0 { | 	if is, ok := ctx.Data["IsRepositoryWebhook"]; ok && is.(bool) { | ||||||
| 		return &orgRepoCtx{ | 		return &ownerRepoCtx{ | ||||||
| 			RepoID:      ctx.Repo.Repository.ID, | 			RepoID:      ctx.Repo.Repository.ID, | ||||||
| 			Link:        path.Join(ctx.Repo.RepoLink, "settings/hooks"), | 			Link:        path.Join(ctx.Repo.RepoLink, "settings/hooks"), | ||||||
| 			LinkNew:     path.Join(ctx.Repo.RepoLink, "settings/hooks"), | 			LinkNew:     path.Join(ctx.Repo.RepoLink, "settings/hooks"), | ||||||
| @@ -75,37 +76,35 @@ func getOrgRepoCtx(ctx *context.Context) (*orgRepoCtx, error) { | |||||||
| 		}, nil | 		}, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(ctx.Org.OrgLink) > 0 { | 	if is, ok := ctx.Data["IsOrganizationWebhook"]; ok && is.(bool) { | ||||||
| 		return &orgRepoCtx{ | 		return &ownerRepoCtx{ | ||||||
| 			OrgID:       ctx.Org.Organization.ID, | 			OwnerID:     ctx.ContextUser.ID, | ||||||
| 			Link:        path.Join(ctx.Org.OrgLink, "settings/hooks"), | 			Link:        path.Join(ctx.Org.OrgLink, "settings/hooks"), | ||||||
| 			LinkNew:     path.Join(ctx.Org.OrgLink, "settings/hooks"), | 			LinkNew:     path.Join(ctx.Org.OrgLink, "settings/hooks"), | ||||||
| 			NewTemplate: tplOrgHookNew, | 			NewTemplate: tplOrgHookNew, | ||||||
| 		}, nil | 		}, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if ctx.Doer.IsAdmin { | 	if is, ok := ctx.Data["IsUserWebhook"]; ok && is.(bool) { | ||||||
| 		// Are we looking at default webhooks? | 		return &ownerRepoCtx{ | ||||||
| 		if ctx.Params(":configType") == "default-hooks" { | 			OwnerID:     ctx.Doer.ID, | ||||||
| 			return &orgRepoCtx{ | 			Link:        path.Join(setting.AppSubURL, "/user/settings/hooks"), | ||||||
| 				IsAdmin:     true, | 			LinkNew:     path.Join(setting.AppSubURL, "/user/settings/hooks"), | ||||||
| 				Link:        path.Join(setting.AppSubURL, "/admin/hooks"), | 			NewTemplate: tplUserHookNew, | ||||||
| 				LinkNew:     path.Join(setting.AppSubURL, "/admin/default-hooks"), | 		}, nil | ||||||
| 				NewTemplate: tplAdminHookNew, | 	} | ||||||
| 			}, nil |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Must be system webhooks instead | 	if ctx.Doer.IsAdmin { | ||||||
| 		return &orgRepoCtx{ | 		return &ownerRepoCtx{ | ||||||
| 			IsAdmin:         true, | 			IsAdmin:         true, | ||||||
| 			IsSystemWebhook: true, | 			IsSystemWebhook: ctx.Params(":configType") == "system-hooks", | ||||||
| 			Link:            path.Join(setting.AppSubURL, "/admin/hooks"), | 			Link:            path.Join(setting.AppSubURL, "/admin/hooks"), | ||||||
| 			LinkNew:         path.Join(setting.AppSubURL, "/admin/system-hooks"), | 			LinkNew:         path.Join(setting.AppSubURL, "/admin/system-hooks"), | ||||||
| 			NewTemplate:     tplAdminHookNew, | 			NewTemplate:     tplAdminHookNew, | ||||||
| 		}, nil | 		}, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil, errors.New("unable to set OrgRepo context") | 	return nil, errors.New("unable to set OwnerRepo context") | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkHookType(ctx *context.Context) string { | func checkHookType(ctx *context.Context) string { | ||||||
| @@ -122,9 +121,9 @@ func WebhooksNew(ctx *context.Context) { | |||||||
| 	ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") | 	ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") | ||||||
| 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}} | 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}} | ||||||
|  |  | ||||||
| 	orCtx, err := getOrgRepoCtx(ctx) | 	orCtx, err := getOwnerRepoCtx(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.ServerError("getOrgRepoCtx", err) | 		ctx.ServerError("getOwnerRepoCtx", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -205,9 +204,9 @@ func createWebhook(ctx *context.Context, params webhookParams) { | |||||||
| 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}} | 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}} | ||||||
| 	ctx.Data["HookType"] = params.Type | 	ctx.Data["HookType"] = params.Type | ||||||
|  |  | ||||||
| 	orCtx, err := getOrgRepoCtx(ctx) | 	orCtx, err := getOwnerRepoCtx(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.ServerError("getOrgRepoCtx", err) | 		ctx.ServerError("getOwnerRepoCtx", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	ctx.Data["BaseLink"] = orCtx.LinkNew | 	ctx.Data["BaseLink"] = orCtx.LinkNew | ||||||
| @@ -236,7 +235,7 @@ func createWebhook(ctx *context.Context, params webhookParams) { | |||||||
| 		IsActive:        params.WebhookForm.Active, | 		IsActive:        params.WebhookForm.Active, | ||||||
| 		Type:            params.Type, | 		Type:            params.Type, | ||||||
| 		Meta:            string(meta), | 		Meta:            string(meta), | ||||||
| 		OrgID:           orCtx.OrgID, | 		OwnerID:         orCtx.OwnerID, | ||||||
| 		IsSystemWebhook: orCtx.IsSystemWebhook, | 		IsSystemWebhook: orCtx.IsSystemWebhook, | ||||||
| 	} | 	} | ||||||
| 	err = w.SetHeaderAuthorization(params.WebhookForm.AuthorizationHeader) | 	err = w.SetHeaderAuthorization(params.WebhookForm.AuthorizationHeader) | ||||||
| @@ -577,19 +576,19 @@ func packagistHookParams(ctx *context.Context) webhookParams { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkWebhook(ctx *context.Context) (*orgRepoCtx, *webhook.Webhook) { | func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) { | ||||||
| 	orCtx, err := getOrgRepoCtx(ctx) | 	orCtx, err := getOwnerRepoCtx(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.ServerError("getOrgRepoCtx", err) | 		ctx.ServerError("getOwnerRepoCtx", err) | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
| 	ctx.Data["BaseLink"] = orCtx.Link | 	ctx.Data["BaseLink"] = orCtx.Link | ||||||
|  |  | ||||||
| 	var w *webhook.Webhook | 	var w *webhook.Webhook | ||||||
| 	if orCtx.RepoID > 0 { | 	if orCtx.RepoID > 0 { | ||||||
| 		w, err = webhook.GetWebhookByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) | 		w, err = webhook.GetWebhookByRepoID(orCtx.RepoID, ctx.ParamsInt64(":id")) | ||||||
| 	} else if orCtx.OrgID > 0 { | 	} else if orCtx.OwnerID > 0 { | ||||||
| 		w, err = webhook.GetWebhookByOrgID(ctx.Org.Organization.ID, ctx.ParamsInt64(":id")) | 		w, err = webhook.GetWebhookByOwnerID(orCtx.OwnerID, ctx.ParamsInt64(":id")) | ||||||
| 	} else if orCtx.IsAdmin { | 	} else if orCtx.IsAdmin { | ||||||
| 		w, err = webhook.GetSystemOrDefaultWebhook(ctx, ctx.ParamsInt64(":id")) | 		w, err = webhook.GetSystemOrDefaultWebhook(ctx, ctx.ParamsInt64(":id")) | ||||||
| 	} | 	} | ||||||
|   | |||||||
							
								
								
									
										48
									
								
								routers/web/user/setting/webhooks.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								routers/web/user/setting/webhooks.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | // Copyright 2023 The Gitea Authors. All rights reserved. | ||||||
|  | // SPDX-License-Identifier: MIT | ||||||
|  |  | ||||||
|  | package setting | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/models/webhook" | ||||||
|  | 	"code.gitea.io/gitea/modules/base" | ||||||
|  | 	"code.gitea.io/gitea/modules/context" | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	tplSettingsHooks base.TplName = "user/settings/hooks" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Webhooks render webhook list page | ||||||
|  | func Webhooks(ctx *context.Context) { | ||||||
|  | 	ctx.Data["Title"] = ctx.Tr("settings") | ||||||
|  | 	ctx.Data["PageIsSettingsHooks"] = true | ||||||
|  | 	ctx.Data["BaseLink"] = setting.AppSubURL + "/user/settings/hooks" | ||||||
|  | 	ctx.Data["BaseLinkNew"] = setting.AppSubURL + "/user/settings/hooks" | ||||||
|  | 	ctx.Data["Description"] = ctx.Tr("settings.hooks.desc") | ||||||
|  |  | ||||||
|  | 	ws, err := webhook.ListWebhooksByOpts(ctx, &webhook.ListWebhookOptions{OwnerID: ctx.Doer.ID}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.ServerError("ListWebhooksByOpts", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ctx.Data["Webhooks"] = ws | ||||||
|  | 	ctx.HTML(http.StatusOK, tplSettingsHooks) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DeleteWebhook response for delete webhook | ||||||
|  | func DeleteWebhook(ctx *context.Context) { | ||||||
|  | 	if err := webhook.DeleteWebhookByOwnerID(ctx.Doer.ID, ctx.FormInt64("id")); err != nil { | ||||||
|  | 		ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error()) | ||||||
|  | 	} else { | ||||||
|  | 		ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ctx.JSON(http.StatusOK, map[string]interface{}{ | ||||||
|  | 		"redirect": setting.AppSubURL + "/user/settings/hooks", | ||||||
|  | 	}) | ||||||
|  | } | ||||||
| @@ -315,6 +315,35 @@ func RegisterRoutes(m *web.Route) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	addWebhookAddRoutes := func() { | ||||||
|  | 		m.Get("/{type}/new", repo.WebhooksNew) | ||||||
|  | 		m.Post("/gitea/new", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksNewPost) | ||||||
|  | 		m.Post("/gogs/new", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksNewPost) | ||||||
|  | 		m.Post("/slack/new", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksNewPost) | ||||||
|  | 		m.Post("/discord/new", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksNewPost) | ||||||
|  | 		m.Post("/dingtalk/new", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost) | ||||||
|  | 		m.Post("/telegram/new", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksNewPost) | ||||||
|  | 		m.Post("/matrix/new", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksNewPost) | ||||||
|  | 		m.Post("/msteams/new", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost) | ||||||
|  | 		m.Post("/feishu/new", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost) | ||||||
|  | 		m.Post("/wechatwork/new", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost) | ||||||
|  | 		m.Post("/packagist/new", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksNewPost) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	addWebhookEditRoutes := func() { | ||||||
|  | 		m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksEditPost) | ||||||
|  | 		m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksEditPost) | ||||||
|  | 		m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksEditPost) | ||||||
|  | 		m.Post("/discord/{id}", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksEditPost) | ||||||
|  | 		m.Post("/dingtalk/{id}", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost) | ||||||
|  | 		m.Post("/telegram/{id}", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksEditPost) | ||||||
|  | 		m.Post("/matrix/{id}", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksEditPost) | ||||||
|  | 		m.Post("/msteams/{id}", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost) | ||||||
|  | 		m.Post("/feishu/{id}", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost) | ||||||
|  | 		m.Post("/wechatwork/{id}", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost) | ||||||
|  | 		m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// FIXME: not all routes need go through same middleware. | 	// FIXME: not all routes need go through same middleware. | ||||||
| 	// Especially some AJAX requests, we can reduce middleware number to improve performance. | 	// Especially some AJAX requests, we can reduce middleware number to improve performance. | ||||||
| 	// Routers. | 	// Routers. | ||||||
| @@ -482,6 +511,19 @@ func RegisterRoutes(m *web.Route) { | |||||||
| 		m.Get("/organization", user_setting.Organization) | 		m.Get("/organization", user_setting.Organization) | ||||||
| 		m.Get("/repos", user_setting.Repos) | 		m.Get("/repos", user_setting.Repos) | ||||||
| 		m.Post("/repos/unadopted", user_setting.AdoptOrDeleteRepository) | 		m.Post("/repos/unadopted", user_setting.AdoptOrDeleteRepository) | ||||||
|  |  | ||||||
|  | 		m.Group("/hooks", func() { | ||||||
|  | 			m.Get("", user_setting.Webhooks) | ||||||
|  | 			m.Post("/delete", user_setting.DeleteWebhook) | ||||||
|  | 			addWebhookAddRoutes() | ||||||
|  | 			m.Group("/{id}", func() { | ||||||
|  | 				m.Get("", repo.WebHooksEdit) | ||||||
|  | 				m.Post("/replay/{uuid}", repo.ReplayWebhook) | ||||||
|  | 			}) | ||||||
|  | 			addWebhookEditRoutes() | ||||||
|  | 		}, webhooksEnabled, func(ctx *context.Context) { | ||||||
|  | 			ctx.Data["IsUserWebhook"] = true | ||||||
|  | 		}) | ||||||
| 	}, reqSignIn, func(ctx *context.Context) { | 	}, reqSignIn, func(ctx *context.Context) { | ||||||
| 		ctx.Data["PageIsUserSettings"] = true | 		ctx.Data["PageIsUserSettings"] = true | ||||||
| 		ctx.Data["AllThemes"] = setting.UI.Themes | 		ctx.Data["AllThemes"] = setting.UI.Themes | ||||||
| @@ -575,32 +617,11 @@ func RegisterRoutes(m *web.Route) { | |||||||
| 				m.Get("", repo.WebHooksEdit) | 				m.Get("", repo.WebHooksEdit) | ||||||
| 				m.Post("/replay/{uuid}", repo.ReplayWebhook) | 				m.Post("/replay/{uuid}", repo.ReplayWebhook) | ||||||
| 			}) | 			}) | ||||||
| 			m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksEditPost) | 			addWebhookEditRoutes() | ||||||
| 			m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksEditPost) |  | ||||||
| 			m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksEditPost) |  | ||||||
| 			m.Post("/discord/{id}", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksEditPost) |  | ||||||
| 			m.Post("/dingtalk/{id}", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost) |  | ||||||
| 			m.Post("/telegram/{id}", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksEditPost) |  | ||||||
| 			m.Post("/matrix/{id}", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksEditPost) |  | ||||||
| 			m.Post("/msteams/{id}", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost) |  | ||||||
| 			m.Post("/feishu/{id}", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost) |  | ||||||
| 			m.Post("/wechatwork/{id}", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost) |  | ||||||
| 			m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost) |  | ||||||
| 		}, webhooksEnabled) | 		}, webhooksEnabled) | ||||||
|  |  | ||||||
| 		m.Group("/{configType:default-hooks|system-hooks}", func() { | 		m.Group("/{configType:default-hooks|system-hooks}", func() { | ||||||
| 			m.Get("/{type}/new", repo.WebhooksNew) | 			addWebhookAddRoutes() | ||||||
| 			m.Post("/gitea/new", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksNewPost) |  | ||||||
| 			m.Post("/gogs/new", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksNewPost) |  | ||||||
| 			m.Post("/slack/new", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksNewPost) |  | ||||||
| 			m.Post("/discord/new", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksNewPost) |  | ||||||
| 			m.Post("/dingtalk/new", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost) |  | ||||||
| 			m.Post("/telegram/new", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksNewPost) |  | ||||||
| 			m.Post("/matrix/new", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksNewPost) |  | ||||||
| 			m.Post("/msteams/new", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost) |  | ||||||
| 			m.Post("/feishu/new", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost) |  | ||||||
| 			m.Post("/wechatwork/new", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost) |  | ||||||
| 			m.Post("/packagist/new", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksNewPost) |  | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 		m.Group("/auths", func() { | 		m.Group("/auths", func() { | ||||||
| @@ -759,32 +780,15 @@ func RegisterRoutes(m *web.Route) { | |||||||
| 				m.Group("/hooks", func() { | 				m.Group("/hooks", func() { | ||||||
| 					m.Get("", org.Webhooks) | 					m.Get("", org.Webhooks) | ||||||
| 					m.Post("/delete", org.DeleteWebhook) | 					m.Post("/delete", org.DeleteWebhook) | ||||||
| 					m.Get("/{type}/new", repo.WebhooksNew) | 					addWebhookAddRoutes() | ||||||
| 					m.Post("/gitea/new", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksNewPost) |  | ||||||
| 					m.Post("/gogs/new", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksNewPost) |  | ||||||
| 					m.Post("/slack/new", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksNewPost) |  | ||||||
| 					m.Post("/discord/new", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksNewPost) |  | ||||||
| 					m.Post("/dingtalk/new", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost) |  | ||||||
| 					m.Post("/telegram/new", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksNewPost) |  | ||||||
| 					m.Post("/matrix/new", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksNewPost) |  | ||||||
| 					m.Post("/msteams/new", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost) |  | ||||||
| 					m.Post("/feishu/new", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost) |  | ||||||
| 					m.Post("/wechatwork/new", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost) |  | ||||||
| 					m.Group("/{id}", func() { | 					m.Group("/{id}", func() { | ||||||
| 						m.Get("", repo.WebHooksEdit) | 						m.Get("", repo.WebHooksEdit) | ||||||
| 						m.Post("/replay/{uuid}", repo.ReplayWebhook) | 						m.Post("/replay/{uuid}", repo.ReplayWebhook) | ||||||
| 					}) | 					}) | ||||||
| 					m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksEditPost) | 					addWebhookEditRoutes() | ||||||
| 					m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksEditPost) | 				}, webhooksEnabled, func(ctx *context.Context) { | ||||||
| 					m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksEditPost) | 					ctx.Data["IsOrganizationWebhook"] = true | ||||||
| 					m.Post("/discord/{id}", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksEditPost) | 				}) | ||||||
| 					m.Post("/dingtalk/{id}", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost) |  | ||||||
| 					m.Post("/telegram/{id}", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksEditPost) |  | ||||||
| 					m.Post("/matrix/{id}", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksEditPost) |  | ||||||
| 					m.Post("/msteams/{id}", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost) |  | ||||||
| 					m.Post("/feishu/{id}", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost) |  | ||||||
| 					m.Post("/wechatwork/{id}", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost) |  | ||||||
| 				}, webhooksEnabled) |  | ||||||
|  |  | ||||||
| 				m.Group("/labels", func() { | 				m.Group("/labels", func() { | ||||||
| 					m.Get("", org.RetrieveLabels, org.Labels) | 					m.Get("", org.RetrieveLabels, org.Labels) | ||||||
| @@ -962,35 +966,16 @@ func RegisterRoutes(m *web.Route) { | |||||||
| 			m.Group("/hooks", func() { | 			m.Group("/hooks", func() { | ||||||
| 				m.Get("", repo.Webhooks) | 				m.Get("", repo.Webhooks) | ||||||
| 				m.Post("/delete", repo.DeleteWebhook) | 				m.Post("/delete", repo.DeleteWebhook) | ||||||
| 				m.Get("/{type}/new", repo.WebhooksNew) | 				addWebhookAddRoutes() | ||||||
| 				m.Post("/gitea/new", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksNewPost) |  | ||||||
| 				m.Post("/gogs/new", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksNewPost) |  | ||||||
| 				m.Post("/slack/new", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksNewPost) |  | ||||||
| 				m.Post("/discord/new", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksNewPost) |  | ||||||
| 				m.Post("/dingtalk/new", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost) |  | ||||||
| 				m.Post("/telegram/new", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksNewPost) |  | ||||||
| 				m.Post("/matrix/new", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksNewPost) |  | ||||||
| 				m.Post("/msteams/new", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost) |  | ||||||
| 				m.Post("/feishu/new", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost) |  | ||||||
| 				m.Post("/wechatwork/new", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost) |  | ||||||
| 				m.Post("/packagist/new", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksNewPost) |  | ||||||
| 				m.Group("/{id}", func() { | 				m.Group("/{id}", func() { | ||||||
| 					m.Get("", repo.WebHooksEdit) | 					m.Get("", repo.WebHooksEdit) | ||||||
| 					m.Post("/test", repo.TestWebhook) | 					m.Post("/test", repo.TestWebhook) | ||||||
| 					m.Post("/replay/{uuid}", repo.ReplayWebhook) | 					m.Post("/replay/{uuid}", repo.ReplayWebhook) | ||||||
| 				}) | 				}) | ||||||
| 				m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksEditPost) | 				addWebhookEditRoutes() | ||||||
| 				m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksEditPost) | 			}, webhooksEnabled, func(ctx *context.Context) { | ||||||
| 				m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksEditPost) | 				ctx.Data["IsRepositoryWebhook"] = true | ||||||
| 				m.Post("/discord/{id}", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksEditPost) | 			}) | ||||||
| 				m.Post("/dingtalk/{id}", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost) |  | ||||||
| 				m.Post("/telegram/{id}", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksEditPost) |  | ||||||
| 				m.Post("/matrix/{id}", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksEditPost) |  | ||||||
| 				m.Post("/msteams/{id}", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost) |  | ||||||
| 				m.Post("/feishu/{id}", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost) |  | ||||||
| 				m.Post("/wechatwork/{id}", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost) |  | ||||||
| 				m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost) |  | ||||||
| 			}, webhooksEnabled) |  | ||||||
|  |  | ||||||
| 			m.Group("/keys", func() { | 			m.Group("/keys", func() { | ||||||
| 				m.Combo("").Get(repo.DeployKeys). | 				m.Combo("").Get(repo.DeployKeys). | ||||||
|   | |||||||
| @@ -101,7 +101,7 @@ func GenerateWebhooks(ctx context.Context, templateRepo, generateRepo *repo_mode | |||||||
| 			HookEvent:   templateWebhook.HookEvent, | 			HookEvent:   templateWebhook.HookEvent, | ||||||
| 			IsActive:    templateWebhook.IsActive, | 			IsActive:    templateWebhook.IsActive, | ||||||
| 			Type:        templateWebhook.Type, | 			Type:        templateWebhook.Type, | ||||||
| 			OrgID:       templateWebhook.OrgID, | 			OwnerID:     templateWebhook.OwnerID, | ||||||
| 			Events:      templateWebhook.Events, | 			Events:      templateWebhook.Events, | ||||||
| 			Meta:        templateWebhook.Meta, | 			Meta:        templateWebhook.Meta, | ||||||
| 		}) | 		}) | ||||||
|   | |||||||
| @@ -229,16 +229,16 @@ func PrepareWebhooks(ctx context.Context, source EventSource, event webhook_modu | |||||||
| 		owner = source.Repository.MustOwner(ctx) | 		owner = source.Repository.MustOwner(ctx) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// check if owner is an org and append additional webhooks | 	// append additional webhooks of a user or organization | ||||||
| 	if owner != nil && owner.IsOrganization() { | 	if owner != nil { | ||||||
| 		orgHooks, err := webhook_model.ListWebhooksByOpts(ctx, &webhook_model.ListWebhookOptions{ | 		ownerHooks, err := webhook_model.ListWebhooksByOpts(ctx, &webhook_model.ListWebhookOptions{ | ||||||
| 			OrgID:    owner.ID, | 			OwnerID:  owner.ID, | ||||||
| 			IsActive: util.OptionalBoolTrue, | 			IsActive: util.OptionalBoolTrue, | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return fmt.Errorf("ListWebhooksByOpts: %w", err) | 			return fmt.Errorf("ListWebhooksByOpts: %w", err) | ||||||
| 		} | 		} | ||||||
| 		ws = append(ws, orgHooks...) | 		ws = append(ws, ownerHooks...) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Add any admin-defined system webhooks | 	// Add any admin-defined system webhooks | ||||||
|   | |||||||
| @@ -40,7 +40,7 @@ | |||||||
| 									<span class="ui label">N/A</span> | 									<span class="ui label">N/A</span> | ||||||
| 								{{end}} | 								{{end}} | ||||||
| 							</a> | 							</a> | ||||||
| 							{{if or $.Permission.IsAdmin $.IsOrganizationOwner $.PageIsAdmin}} | 							{{if or $.Permission.IsAdmin $.IsOrganizationOwner $.PageIsAdmin $.PageIsUserSettings}} | ||||||
| 							<div class="right menu"> | 							<div class="right menu"> | ||||||
| 								<form class="item" action="{{$.Link}}/replay/{{.UUID}}" method="post"> | 								<form class="item" action="{{$.Link}}/replay/{{.UUID}}" method="post"> | ||||||
| 									{{$.CsrfTokenHtml}} | 									{{$.CsrfTokenHtml}} | ||||||
|   | |||||||
| @@ -13014,6 +13014,152 @@ | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "/user/hooks": { | ||||||
|  |       "get": { | ||||||
|  |         "produces": [ | ||||||
|  |           "application/json" | ||||||
|  |         ], | ||||||
|  |         "tags": [ | ||||||
|  |           "user" | ||||||
|  |         ], | ||||||
|  |         "summary": "List the authenticated user's webhooks", | ||||||
|  |         "operationId": "userListHooks", | ||||||
|  |         "parameters": [ | ||||||
|  |           { | ||||||
|  |             "type": "integer", | ||||||
|  |             "description": "page number of results to return (1-based)", | ||||||
|  |             "name": "page", | ||||||
|  |             "in": "query" | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             "type": "integer", | ||||||
|  |             "description": "page size of results", | ||||||
|  |             "name": "limit", | ||||||
|  |             "in": "query" | ||||||
|  |           } | ||||||
|  |         ], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "$ref": "#/responses/HookList" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       "post": { | ||||||
|  |         "consumes": [ | ||||||
|  |           "application/json" | ||||||
|  |         ], | ||||||
|  |         "produces": [ | ||||||
|  |           "application/json" | ||||||
|  |         ], | ||||||
|  |         "tags": [ | ||||||
|  |           "user" | ||||||
|  |         ], | ||||||
|  |         "summary": "Create a hook", | ||||||
|  |         "operationId": "userCreateHook", | ||||||
|  |         "parameters": [ | ||||||
|  |           { | ||||||
|  |             "name": "body", | ||||||
|  |             "in": "body", | ||||||
|  |             "required": true, | ||||||
|  |             "schema": { | ||||||
|  |               "$ref": "#/definitions/CreateHookOption" | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         ], | ||||||
|  |         "responses": { | ||||||
|  |           "201": { | ||||||
|  |             "$ref": "#/responses/Hook" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/user/hooks/{id}": { | ||||||
|  |       "get": { | ||||||
|  |         "produces": [ | ||||||
|  |           "application/json" | ||||||
|  |         ], | ||||||
|  |         "tags": [ | ||||||
|  |           "user" | ||||||
|  |         ], | ||||||
|  |         "summary": "Get a hook", | ||||||
|  |         "operationId": "userGetHook", | ||||||
|  |         "parameters": [ | ||||||
|  |           { | ||||||
|  |             "type": "integer", | ||||||
|  |             "format": "int64", | ||||||
|  |             "description": "id of the hook to get", | ||||||
|  |             "name": "id", | ||||||
|  |             "in": "path", | ||||||
|  |             "required": true | ||||||
|  |           } | ||||||
|  |         ], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "$ref": "#/responses/Hook" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       "delete": { | ||||||
|  |         "produces": [ | ||||||
|  |           "application/json" | ||||||
|  |         ], | ||||||
|  |         "tags": [ | ||||||
|  |           "user" | ||||||
|  |         ], | ||||||
|  |         "summary": "Delete a hook", | ||||||
|  |         "operationId": "userDeleteHook", | ||||||
|  |         "parameters": [ | ||||||
|  |           { | ||||||
|  |             "type": "integer", | ||||||
|  |             "format": "int64", | ||||||
|  |             "description": "id of the hook to delete", | ||||||
|  |             "name": "id", | ||||||
|  |             "in": "path", | ||||||
|  |             "required": true | ||||||
|  |           } | ||||||
|  |         ], | ||||||
|  |         "responses": { | ||||||
|  |           "204": { | ||||||
|  |             "$ref": "#/responses/empty" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       "patch": { | ||||||
|  |         "consumes": [ | ||||||
|  |           "application/json" | ||||||
|  |         ], | ||||||
|  |         "produces": [ | ||||||
|  |           "application/json" | ||||||
|  |         ], | ||||||
|  |         "tags": [ | ||||||
|  |           "user" | ||||||
|  |         ], | ||||||
|  |         "summary": "Update a hook", | ||||||
|  |         "operationId": "userEditHook", | ||||||
|  |         "parameters": [ | ||||||
|  |           { | ||||||
|  |             "type": "integer", | ||||||
|  |             "format": "int64", | ||||||
|  |             "description": "id of the hook to update", | ||||||
|  |             "name": "id", | ||||||
|  |             "in": "path", | ||||||
|  |             "required": true | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             "name": "body", | ||||||
|  |             "in": "body", | ||||||
|  |             "schema": { | ||||||
|  |               "$ref": "#/definitions/EditHookOption" | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         ], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "$ref": "#/responses/Hook" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "/user/keys": { |     "/user/keys": { | ||||||
|       "get": { |       "get": { | ||||||
|         "produces": [ |         "produces": [ | ||||||
|   | |||||||
| @@ -138,6 +138,12 @@ | |||||||
| 							<label>admin:org_hook</label> | 							<label>admin:org_hook</label> | ||||||
| 						</div> | 						</div> | ||||||
| 					</div> | 					</div> | ||||||
|  | 					<div class="field"> | ||||||
|  | 						<div class="ui checkbox"> | ||||||
|  | 							<input class="enable-system" type="checkbox" name="scope" value="admin:user_hook"> | ||||||
|  | 							<label>admin:user_hook</label> | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
| 					<div class="field"> | 					<div class="field"> | ||||||
| 						<div class="ui checkbox"> | 						<div class="ui checkbox"> | ||||||
| 							<input class="enable-system" type="checkbox" name="scope" value="notification"> | 							<input class="enable-system" type="checkbox" name="scope" value="notification"> | ||||||
|   | |||||||
							
								
								
									
										53
									
								
								templates/user/settings/hook_new.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								templates/user/settings/hook_new.tmpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | {{template "base/head" .}} | ||||||
|  | <div class="page-content user  settings new webhook"> | ||||||
|  | 	{{template "user/settings/navbar" .}} | ||||||
|  | 	<div class="ui container"> | ||||||
|  | 		<div class="twelve wide column content"> | ||||||
|  | 			{{template "base/alert" .}} | ||||||
|  | 			<h4 class="ui top attached header"> | ||||||
|  | 				{{if .PageIsSettingsHooksNew}}{{.locale.Tr "repo.settings.add_webhook"}}{{else}}{{.locale.Tr "repo.settings.update_webhook"}}{{end}} | ||||||
|  | 				<div class="ui right"> | ||||||
|  | 					{{if eq .HookType "gitea"}} | ||||||
|  | 						<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gitea.svg"> | ||||||
|  | 					{{else if eq .HookType "gogs"}} | ||||||
|  | 						<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gogs.ico"> | ||||||
|  | 					{{else if eq .HookType "slack"}} | ||||||
|  | 						<img width="26" height="26" src="{{AssetUrlPrefix}}/img/slack.png"> | ||||||
|  | 					{{else if eq .HookType "discord"}} | ||||||
|  | 						<img width="26" height="26" src="{{AssetUrlPrefix}}/img/discord.png"> | ||||||
|  | 					{{else if eq .HookType "dingtalk"}} | ||||||
|  | 						<img width="26" height="26" src="{{AssetUrlPrefix}}/img/dingtalk.ico"> | ||||||
|  | 					{{else if eq .HookType "telegram"}} | ||||||
|  | 						<img width="26" height="26" src="{{AssetUrlPrefix}}/img/telegram.png"> | ||||||
|  | 					{{else if eq .HookType "msteams"}} | ||||||
|  | 						<img width="26" height="26" src="{{AssetUrlPrefix}}/img/msteams.png"> | ||||||
|  | 					{{else if eq .HookType "feishu"}} | ||||||
|  | 						<img width="26" height="26" src="{{AssetUrlPrefix}}/img/feishu.png"> | ||||||
|  | 					{{else if eq .HookType "matrix"}} | ||||||
|  | 						<img width="26" height="26" src="{{AssetUrlPrefix}}/img/matrix.svg"> | ||||||
|  | 					{{else if eq .HookType "wechatwork"}} | ||||||
|  | 						<img width="26" height="26" src="{{AssetUrlPrefix}}/img/wechatwork.png"> | ||||||
|  | 					{{else if eq .HookType "packagist"}} | ||||||
|  | 						<img width="26" height="26" src="{{AssetUrlPrefix}}/img/packagist.png"> | ||||||
|  | 					{{end}} | ||||||
|  | 				</div> | ||||||
|  | 			</h4> | ||||||
|  | 			<div class="ui attached segment"> | ||||||
|  | 				{{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" .}} | ||||||
|  | 				{{template "repo/settings/webhook/telegram" .}} | ||||||
|  | 				{{template "repo/settings/webhook/msteams" .}} | ||||||
|  | 				{{template "repo/settings/webhook/feishu" .}} | ||||||
|  | 				{{template "repo/settings/webhook/matrix" .}} | ||||||
|  | 				{{template "repo/settings/webhook/wechatwork" .}} | ||||||
|  | 				{{template "repo/settings/webhook/packagist" .}} | ||||||
|  | 			</div> | ||||||
|  |  | ||||||
|  | 			{{template "repo/settings/webhook/history" .}} | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | {{template "base/footer" .}} | ||||||
							
								
								
									
										8
									
								
								templates/user/settings/hooks.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								templates/user/settings/hooks.tmpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | {{template "base/head" .}} | ||||||
|  | <div class="page-content user settings webhooks"> | ||||||
|  | 	{{template "user/settings/navbar" .}} | ||||||
|  | 	<div class="ui container"> | ||||||
|  | 		{{template "repo/settings/webhook/list" .}} | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | {{template "base/footer" .}} | ||||||
| @@ -26,6 +26,11 @@ | |||||||
| 			{{.locale.Tr "packages.title"}} | 			{{.locale.Tr "packages.title"}} | ||||||
| 		</a> | 		</a> | ||||||
| 		{{end}} | 		{{end}} | ||||||
|  | 		{{if not DisableWebhooks}} | ||||||
|  | 		<a class="{{if .PageIsSettingsHooks}}active {{end}}item" href="{{AppSubUrl}}/user/settings/hooks"> | ||||||
|  | 			{{.locale.Tr "repo.settings.hooks"}} | ||||||
|  | 		</a> | ||||||
|  | 		{{end}} | ||||||
| 		<a class="{{if .PageIsSettingsOrganization}}active {{end}}item" href="{{AppSubUrl}}/user/settings/organization"> | 		<a class="{{if .PageIsSettingsOrganization}}active {{end}}item" href="{{AppSubUrl}}/user/settings/organization"> | ||||||
| 			{{.locale.Tr "settings.organization"}} | 			{{.locale.Tr "settings.organization"}} | ||||||
| 		</a> | 		</a> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user