mirror of https://github.com/go-gitea/gitea
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. ![grafik](https://user-images.githubusercontent.com/1666336/197391408-15dfdc23-b476-4d0c-82f7-9bc9b065988f.png)pull/22705/head^2
parent
dad057b639
commit
2173f14708
@ -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() |
||||||
|
} |
@ -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"), |
||||||
|
) |
||||||
|
} |
@ -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", |
||||||
|
}) |
||||||
|
} |
@ -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" .}} |
@ -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" .}} |
Loading…
Reference in new issue