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