From 6876c9b973ae31903f9cb5afcb99e99dd8d1c622 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 1 Feb 2021 12:27:12 +0800 Subject: [PATCH 1/6] Support systeml hook API --- models/webhook.go | 80 ----------------- models/webhook_system.go | 86 ++++++++++++++++++ routers/api/v1/admin/hooks.go | 163 ++++++++++++++++++++++++++++++++++ routers/api/v1/api.go | 7 ++ routers/api/v1/utils/hook.go | 25 ++++++ 5 files changed, 281 insertions(+), 80 deletions(-) create mode 100644 models/webhook_system.go create mode 100644 routers/api/v1/admin/hooks.go diff --git a/models/webhook.go b/models/webhook.go index c7fcfba49e78..285402cd16be 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -8,7 +8,6 @@ package models import ( "context" "encoding/json" - "fmt" "strings" "time" @@ -421,44 +420,6 @@ func GetWebhooksByOrgID(orgID int64, listOptions ListOptions) ([]*Webhook, error return ws, sess.Find(&ws, &Webhook{OrgID: orgID}) } -// GetDefaultWebhooks returns all admin-default webhooks. -func GetDefaultWebhooks() ([]*Webhook, error) { - return getDefaultWebhooks(x) -} - -func getDefaultWebhooks(e Engine) ([]*Webhook, error) { - webhooks := make([]*Webhook, 0, 5) - return webhooks, e. - Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false). - Find(&webhooks) -} - -// GetSystemOrDefaultWebhook returns admin system or default webhook by given ID. -func GetSystemOrDefaultWebhook(id int64) (*Webhook, error) { - webhook := &Webhook{ID: id} - has, err := x. - Where("repo_id=? AND org_id=?", 0, 0). - Get(webhook) - if err != nil { - return nil, err - } else if !has { - return nil, ErrWebhookNotExist{id} - } - return webhook, nil -} - -// GetSystemWebhooks returns all admin system webhooks. -func GetSystemWebhooks() ([]*Webhook, error) { - return getSystemWebhooks(x) -} - -func getSystemWebhooks(e Engine) ([]*Webhook, error) { - webhooks := make([]*Webhook, 0, 5) - return webhooks, e. - Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true). - Find(&webhooks) -} - // UpdateWebhook updates information of webhook. func UpdateWebhook(w *Webhook) error { _, err := x.ID(w.ID).AllCols().Update(w) @@ -507,47 +468,6 @@ func DeleteWebhookByOrgID(orgID, id int64) error { }) } -// DeleteDefaultSystemWebhook deletes an admin-configured default or system webhook (where Org and Repo ID both 0) -func DeleteDefaultSystemWebhook(id int64) error { - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - - count, err := sess. - Where("repo_id=? AND org_id=?", 0, 0). - Delete(&Webhook{ID: id}) - if err != nil { - return err - } else if count == 0 { - return ErrWebhookNotExist{ID: id} - } - - if _, err := sess.Delete(&HookTask{HookID: id}); err != nil { - return err - } - - return sess.Commit() -} - -// copyDefaultWebhooksToRepo creates copies of the default webhooks in a new repo -func copyDefaultWebhooksToRepo(e Engine, repoID int64) error { - ws, err := getDefaultWebhooks(e) - if err != nil { - return fmt.Errorf("GetDefaultWebhooks: %v", err) - } - - for _, w := range ws { - w.ID = 0 - w.RepoID = repoID - if err := createWebhook(e, w); err != nil { - return fmt.Errorf("CreateWebhook: %v", err) - } - } - return nil -} - // ___ ___ __ ___________ __ // / | \ ____ ____ | | _\__ ___/____ _____| | __ // / ~ \/ _ \ / _ \| |/ / | | \__ \ / ___/ |/ / diff --git a/models/webhook_system.go b/models/webhook_system.go new file mode 100644 index 000000000000..585a41dc7d11 --- /dev/null +++ b/models/webhook_system.go @@ -0,0 +1,86 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +import "fmt" + +// GetDefaultWebhooks returns all admin-default webhooks. +func GetDefaultWebhooks() ([]*Webhook, error) { + return getDefaultWebhooks(x) +} + +func getDefaultWebhooks(e Engine) ([]*Webhook, error) { + webhooks := make([]*Webhook, 0, 5) + return webhooks, e. + Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false). + Find(&webhooks) +} + +// GetSystemOrDefaultWebhook returns admin system or default webhook by given ID. +func GetSystemOrDefaultWebhook(id int64) (*Webhook, error) { + webhook := &Webhook{ID: id} + has, err := x. + Where("repo_id=? AND org_id=?", 0, 0). + Get(webhook) + if err != nil { + return nil, err + } else if !has { + return nil, ErrWebhookNotExist{id} + } + return webhook, nil +} + +// GetSystemWebhooks returns all admin system webhooks. +func GetSystemWebhooks() ([]*Webhook, error) { + return getSystemWebhooks(x) +} + +func getSystemWebhooks(e Engine) ([]*Webhook, error) { + webhooks := make([]*Webhook, 0, 5) + return webhooks, e. + Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true). + Find(&webhooks) +} + +// DeleteDefaultSystemWebhook deletes an admin-configured default or system webhook (where Org and Repo ID both 0) +func DeleteDefaultSystemWebhook(id int64) error { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + count, err := sess. + Where("repo_id=? AND org_id=?", 0, 0). + Delete(&Webhook{ID: id}) + if err != nil { + return err + } else if count == 0 { + return ErrWebhookNotExist{ID: id} + } + + if _, err := sess.Delete(&HookTask{HookID: id}); err != nil { + return err + } + + return sess.Commit() +} + +// copyDefaultWebhooksToRepo creates copies of the default webhooks in a new repo +func copyDefaultWebhooksToRepo(e Engine, repoID int64) error { + ws, err := getDefaultWebhooks(e) + if err != nil { + return fmt.Errorf("GetDefaultWebhooks: %v", err) + } + + for _, w := range ws { + w.ID = 0 + w.RepoID = repoID + if err := createWebhook(e, w); err != nil { + return fmt.Errorf("CreateWebhook: %v", err) + } + } + return nil +} diff --git a/routers/api/v1/admin/hooks.go b/routers/api/v1/admin/hooks.go new file mode 100644 index 000000000000..7efff912be5f --- /dev/null +++ b/routers/api/v1/admin/hooks.go @@ -0,0 +1,163 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package org + +import ( + "net/http" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers/api/v1/utils" +) + +// ListHooks list system's webhooks +func ListHooks(ctx *context.APIContext) { + // swagger:operation GET /admin/hooks admin adminListHooks + // --- + // summary: List system'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" + + sysHooks, err := models.GetSystemWebhooks(utils.GetListOptions(ctx)) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetSystemWebhooks", err) + return + } + hooks := make([]*api.Hook, len(sysHooks)) + for i, hook := range sysHooks { + hooks[i] = convert.ToHook(setting.AppURL+"/admin", hook) + } + ctx.JSON(http.StatusOK, hooks) +} + +// GetHook get an organization's hook by id +func GetHook(ctx *context.APIContext) { + // swagger:operation GET /hooks/{id} admin adminGetHook + // --- + // 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" + + hookID := ctx.ParamsInt64(":id") + hook, err := models.GetSystemOrDefaultWebhook(hookID) + if err != nil { + return + } + ctx.JSON(http.StatusOK, convert.ToHook("/admin/", hook)) +} + +// CreateHook create a hook for an organization +func CreateHook(ctx *context.APIContext) { + // swagger:operation POST /admin/hooks/ admin adminCreateHook + // --- + // 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" + + form := web.GetForm(ctx).(*api.CreateHookOption) + //TODO in body params + if !utils.CheckCreateHookOption(ctx, form) { + return + } + utils.AddSystemHook(ctx, form) +} + +// EditHook modify a hook of a repository +func EditHook(ctx *context.APIContext) { + // swagger:operation PATCH /admin/hooks/{id} admin adminEditHook + // --- + // 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" + + form := web.GetForm(ctx).(*api.EditHookOption) + + //TODO in body params + hookID := ctx.ParamsInt64(":id") + utils.EditSystemHook(ctx, form, hookID) +} + +// DeleteHook delete a system hook +func DeleteHook(ctx *context.APIContext) { + // swagger:operation DELETE /amdin/hooks/{id} admin adminDeleteHook + // --- + // 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" + + hookID := ctx.ParamsInt64(":id") + if err := models.DeleteDefaultSystemWebhook(hookID); err != nil { + if models.IsErrWebhookNotExist(err) { + ctx.NotFound() + } else { + ctx.Error(http.StatusInternalServerError, "DeleteDefaultSystemWebhook", err) + } + return + } + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 42b52db93657..1076325a91d4 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1028,6 +1028,13 @@ func Routes() *web.Route { m.Post("/{username}/{reponame}", admin.AdoptRepository) m.Delete("/{username}/{reponame}", admin.DeleteUnadoptedRepository) }) + m.Group("/hooks", func() { + m.Combo("").Get(admin.ListHooks). + Post(bind(api.CreateHookOption{}), admin.CreateHook) + m.Combo("/{id}").Get(admin.GetHook). + Patch(bind(admin.EditHookOption{}), admin.EditHook). + Delete(admin.DeleteHook) + }) }, reqToken(), reqSiteAdmin()) m.Group("/topics", func() { diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index b0ce40b9fb73..70b1891ea5ab 100644 --- a/routers/api/v1/utils/hook.go +++ b/routers/api/v1/utils/hook.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/utils" @@ -69,6 +70,14 @@ func CheckCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption) return true } +// AddSystemHook add a system hook +func AddSystemHook(ctx *context.APIContext, form *api.CreateHookOption) { + hook, ok := addHook(ctx, form, 0, 0) + if ok { + ctx.JSON(http.StatusCreated, convert.ToHook(setting.AppSubURL+"/admin", hook)) + } +} + // AddOrgHook add a hook to an organization. Writes to `ctx` accordingly func AddOrgHook(ctx *context.APIContext, form *api.CreateHookOption) { org := ctx.Org.Organization @@ -170,6 +179,22 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID return w, true } +// EditSystemHook edit system webhook `w` according to `form`. Writes to `ctx` accordingly +func EditSystemHook(ctx *context.APIContext, form *api.EditHookOption, hookID int64) { + hook, err := models.GetSystemOrDefaultWebhook(hookID) + if err != nil { + return + } + if !editHook(ctx, form, hook) { + return + } + updated, err := models.GetSystemOrDefaultWebhook(hookID) + if err != nil { + return + } + ctx.JSON(http.StatusOK, convert.ToHook(setting.AppURL+"/admin", updated)) +} + // EditOrgHook edit webhook `w` according to `form`. Writes to `ctx` accordingly func EditOrgHook(ctx *context.APIContext, form *api.EditHookOption, hookID int64) { org := ctx.Org.Organization From 4c7c4947212c46f55a5ae68b6a5ee94f28768ef2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 1 Feb 2021 21:12:51 +0800 Subject: [PATCH 2/6] Fix test --- routers/api/v1/admin/hooks.go | 4 ++-- routers/api/v1/api.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/routers/api/v1/admin/hooks.go b/routers/api/v1/admin/hooks.go index 7efff912be5f..e50c1bc596ed 100644 --- a/routers/api/v1/admin/hooks.go +++ b/routers/api/v1/admin/hooks.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package org +package admin import ( "net/http" @@ -36,7 +36,7 @@ func ListHooks(ctx *context.APIContext) { // "200": // "$ref": "#/responses/HookList" - sysHooks, err := models.GetSystemWebhooks(utils.GetListOptions(ctx)) + sysHooks, err := models.GetSystemWebhooks() if err != nil { ctx.Error(http.StatusInternalServerError, "GetSystemWebhooks", err) return diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 1076325a91d4..e107d0e552e6 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1032,7 +1032,7 @@ func Routes() *web.Route { m.Combo("").Get(admin.ListHooks). Post(bind(api.CreateHookOption{}), admin.CreateHook) m.Combo("/{id}").Get(admin.GetHook). - Patch(bind(admin.EditHookOption{}), admin.EditHook). + Patch(bind(api.EditHookOption{}), admin.EditHook). Delete(admin.DeleteHook) }) }, reqToken(), reqSiteAdmin()) From 4d77c59cdaad66c8252fa9ac70d85bbda707f1b7 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 12 Dec 2022 14:59:14 +0800 Subject: [PATCH 3/6] Fix swagger --- templates/swagger/v1_json.tmpl | 154 ++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 1 deletion(-) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index c86c6744deec..4e20d7079c97 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -138,6 +138,104 @@ } } }, + "/admin/hooks": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "List system's webhooks", + "operationId": "adminListHooks", + "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" + } + } + } + }, + "/admin/hooks/": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Create a hook", + "operationId": "adminCreateHook", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/CreateHookOption" + } + } + ], + "responses": { + "201": { + "$ref": "#/responses/Hook" + } + } + } + }, + "/admin/hooks/{id}": { + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Update a hook", + "operationId": "adminEditHook", + "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" + } + } + } + }, "/admin/orgs": { "get": { "produces": [ @@ -601,6 +699,60 @@ } } }, + "/amdin/hooks/{id}": { + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Delete a hook", + "operationId": "adminDeleteHook", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "id of the hook to delete", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "$ref": "#/responses/empty" + } + } + } + }, + "/hooks/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get a hook", + "operationId": "adminGetHook", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "id of the hook to get", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/Hook" + } + } + } + }, "/markdown": { "post": { "consumes": [ @@ -20780,4 +20932,4 @@ "TOTPHeader": [] } ] -} +} \ No newline at end of file From 62c964d7a454aeec0592619f52e2e2f18cc029b2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 12 Dec 2022 15:17:15 +0800 Subject: [PATCH 4/6] Fix lint --- routers/api/v1/admin/hooks.go | 4 +-- templates/swagger/v1_json.tmpl | 56 ++++++++++++++++------------------ 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/routers/api/v1/admin/hooks.go b/routers/api/v1/admin/hooks.go index 39a0cf320551..57e0df4ae842 100644 --- a/routers/api/v1/admin/hooks.go +++ b/routers/api/v1/admin/hooks.go @@ -55,7 +55,7 @@ func ListHooks(ctx *context.APIContext) { // GetHook get an organization's hook by id func GetHook(ctx *context.APIContext) { - // swagger:operation GET /hooks/{id} admin adminGetHook + // swagger:operation GET /admin/hooks/{id} admin adminGetHook // --- // summary: Get a hook // produces: @@ -87,7 +87,7 @@ func GetHook(ctx *context.APIContext) { // CreateHook create a hook for an organization func CreateHook(ctx *context.APIContext) { - // swagger:operation POST /admin/hooks/ admin adminCreateHook + // swagger:operation POST /admin/hooks admin adminCreateHook // --- // summary: Create a hook // consumes: diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 4e20d7079c97..14b9fc8c4ba8 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -167,9 +167,7 @@ "$ref": "#/responses/HookList" } } - } - }, - "/admin/hooks/": { + }, "post": { "consumes": [ "application/json" @@ -200,6 +198,31 @@ } }, "/admin/hooks/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get a hook", + "operationId": "adminGetHook", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "id of the hook to get", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/Hook" + } + } + }, "patch": { "consumes": [ "application/json" @@ -726,33 +749,6 @@ } } }, - "/hooks/{id}": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "admin" - ], - "summary": "Get a hook", - "operationId": "adminGetHook", - "parameters": [ - { - "type": "integer", - "format": "int64", - "description": "id of the hook to get", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "$ref": "#/responses/Hook" - } - } - } - }, "/markdown": { "post": { "consumes": [ From 6ce6d7a90e00ae9eee95f0be2b0c0fbfea13d0d0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 14 Dec 2022 18:15:52 +0800 Subject: [PATCH 5/6] Fix lint --- templates/swagger/v1_json.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 14b9fc8c4ba8..31dfc5031dcf 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -20928,4 +20928,4 @@ "TOTPHeader": [] } ] -} \ No newline at end of file +} From a61d04e5fcc24c3f4cb92b53414527f95e99c68a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 19 Jan 2023 11:34:01 +0800 Subject: [PATCH 6/6] Update models/webhook/webhook_system.go Co-authored-by: Jason Song --- models/webhook/webhook_system.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/webhook/webhook_system.go b/models/webhook/webhook_system.go index ebe9700b909b..21dc0406a0d1 100644 --- a/models/webhook/webhook_system.go +++ b/models/webhook/webhook_system.go @@ -28,7 +28,7 @@ func GetSystemOrDefaultWebhook(ctx context.Context, id int64) (*Webhook, error) if err != nil { return nil, err } else if !has { - return nil, ErrWebhookNotExist{id} + return nil, ErrWebhookNotExist{ID: id} } return webhook, nil }