mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-10-31 22:38:58 +00:00
[REFACTOR] webhook.Handler interface
This commit is contained in:
parent
142459bbe0
commit
702152bfde
35 changed files with 378 additions and 210 deletions
|
@ -342,4 +342,5 @@ package "code.gitea.io/gitea/services/repository/files"
|
|||
|
||||
package "code.gitea.io/gitea/services/webhook"
|
||||
func NewNotifier
|
||||
func List
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
repo_id: 1
|
||||
url: http://www.example.com/url1
|
||||
http_method: POST
|
||||
type: forgejo
|
||||
content_type: 1 # json
|
||||
events: '{"push_only":true,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":false}}'
|
||||
is_active: false # disable to prevent sending hook task during unrelated tests
|
||||
|
|
|
@ -20,10 +20,14 @@ type Hook struct {
|
|||
ID int64 `json:"id"`
|
||||
Type string `json:"type"`
|
||||
BranchFilter string `json:"branch_filter"`
|
||||
URL string `json:"-"`
|
||||
URL string `json:"url"`
|
||||
|
||||
// Deprecated: use Metadata instead
|
||||
Config map[string]string `json:"config"`
|
||||
Events []string `json:"events"`
|
||||
AuthorizationHeader string `json:"authorization_header"`
|
||||
ContentType string `json:"content_type"`
|
||||
Metadata any `json:"metadata"`
|
||||
Active bool `json:"active"`
|
||||
// swagger:strfmt date-time
|
||||
Updated time.Time `json:"updated_at"`
|
||||
|
|
|
@ -637,17 +637,9 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
|
|||
}
|
||||
|
||||
ctx.Data["HookType"] = w.Type
|
||||
switch w.Type {
|
||||
case webhook_module.SLACK:
|
||||
ctx.Data["SlackHook"] = webhook_service.GetSlackHook(w)
|
||||
case webhook_module.DISCORD:
|
||||
ctx.Data["DiscordHook"] = webhook_service.GetDiscordHook(w)
|
||||
case webhook_module.TELEGRAM:
|
||||
ctx.Data["TelegramHook"] = webhook_service.GetTelegramHook(w)
|
||||
case webhook_module.MATRIX:
|
||||
ctx.Data["MatrixHook"] = webhook_service.GetMatrixHook(w)
|
||||
case webhook_module.PACKAGIST:
|
||||
ctx.Data["PackagistHook"] = webhook_service.GetPackagistHook(w)
|
||||
|
||||
if handler := webhook_service.GetWebhookHandler(w.Type); handler != nil {
|
||||
ctx.Data["HookMetadata"] = handler.Metadata(w)
|
||||
}
|
||||
|
||||
ctx.Data["History"], err = w.History(ctx, 1)
|
||||
|
|
136
services/webhook/default.go
Normal file
136
services/webhook/default.go
Normal file
|
@ -0,0 +1,136 @@
|
|||
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
webhook_model "code.gitea.io/gitea/models/webhook"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
)
|
||||
|
||||
var _ Handler = defaultHandler{}
|
||||
|
||||
type defaultHandler struct {
|
||||
forgejo bool
|
||||
}
|
||||
|
||||
func (dh defaultHandler) Type() webhook_module.HookType {
|
||||
if dh.forgejo {
|
||||
return webhook_module.FORGEJO
|
||||
}
|
||||
return webhook_module.GITEA
|
||||
}
|
||||
|
||||
func (defaultHandler) Metadata(*webhook_model.Webhook) any { return nil }
|
||||
|
||||
func (defaultHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (req *http.Request, body []byte, err error) {
|
||||
switch w.HTTPMethod {
|
||||
case "":
|
||||
log.Info("HTTP Method for %s webhook %s [ID: %d] is not set, defaulting to POST", w.Type, w.URL, w.ID)
|
||||
fallthrough
|
||||
case http.MethodPost:
|
||||
switch w.ContentType {
|
||||
case webhook_model.ContentTypeJSON:
|
||||
req, err = http.NewRequest("POST", w.URL, strings.NewReader(t.PayloadContent))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
case webhook_model.ContentTypeForm:
|
||||
forms := url.Values{
|
||||
"payload": []string{t.PayloadContent},
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("POST", w.URL, strings.NewReader(forms.Encode()))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("invalid content type: %v", w.ContentType)
|
||||
}
|
||||
case http.MethodGet:
|
||||
u, err := url.Parse(w.URL)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid URL: %w", err)
|
||||
}
|
||||
vals := u.Query()
|
||||
vals["payload"] = []string{t.PayloadContent}
|
||||
u.RawQuery = vals.Encode()
|
||||
req, err = http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case http.MethodPut:
|
||||
switch w.Type {
|
||||
case webhook_module.MATRIX: // used when t.Version == 1
|
||||
txnID, err := getMatrixTxnID([]byte(t.PayloadContent))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
url := fmt.Sprintf("%s/%s", w.URL, url.PathEscape(txnID))
|
||||
req, err = http.NewRequest("PUT", url, strings.NewReader(t.PayloadContent))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("invalid http method: %v", w.HTTPMethod)
|
||||
}
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("invalid http method: %v", w.HTTPMethod)
|
||||
}
|
||||
|
||||
body = []byte(t.PayloadContent)
|
||||
return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body)
|
||||
}
|
||||
|
||||
func addDefaultHeaders(req *http.Request, secret []byte, t *webhook_model.HookTask, payloadContent []byte) error {
|
||||
var signatureSHA1 string
|
||||
var signatureSHA256 string
|
||||
if len(secret) > 0 {
|
||||
sig1 := hmac.New(sha1.New, secret)
|
||||
sig256 := hmac.New(sha256.New, secret)
|
||||
_, err := io.MultiWriter(sig1, sig256).Write(payloadContent)
|
||||
if err != nil {
|
||||
// this error should never happen, since the hashes are writing to []byte and always return a nil error.
|
||||
return fmt.Errorf("prepareWebhooks.sigWrite: %w", err)
|
||||
}
|
||||
signatureSHA1 = hex.EncodeToString(sig1.Sum(nil))
|
||||
signatureSHA256 = hex.EncodeToString(sig256.Sum(nil))
|
||||
}
|
||||
|
||||
event := t.EventType.Event()
|
||||
eventType := string(t.EventType)
|
||||
req.Header.Add("X-Forgejo-Delivery", t.UUID)
|
||||
req.Header.Add("X-Forgejo-Event", event)
|
||||
req.Header.Add("X-Forgejo-Event-Type", eventType)
|
||||
req.Header.Add("X-Forgejo-Signature", signatureSHA256)
|
||||
req.Header.Add("X-Gitea-Delivery", t.UUID)
|
||||
req.Header.Add("X-Gitea-Event", event)
|
||||
req.Header.Add("X-Gitea-Event-Type", eventType)
|
||||
req.Header.Add("X-Gitea-Signature", signatureSHA256)
|
||||
req.Header.Add("X-Gogs-Delivery", t.UUID)
|
||||
req.Header.Add("X-Gogs-Event", event)
|
||||
req.Header.Add("X-Gogs-Event-Type", eventType)
|
||||
req.Header.Add("X-Gogs-Signature", signatureSHA256)
|
||||
req.Header.Add("X-Hub-Signature", "sha1="+signatureSHA1)
|
||||
req.Header.Add("X-Hub-Signature-256", "sha256="+signatureSHA256)
|
||||
req.Header["X-GitHub-Delivery"] = []string{t.UUID}
|
||||
req.Header["X-GitHub-Event"] = []string{event}
|
||||
req.Header["X-GitHub-Event-Type"] = []string{eventType}
|
||||
return nil
|
||||
}
|
|
@ -5,11 +5,7 @@ package webhook
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -32,106 +28,6 @@ import (
|
|||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (req *http.Request, body []byte, err error) {
|
||||
switch w.HTTPMethod {
|
||||
case "":
|
||||
log.Info("HTTP Method for %s webhook %s [ID: %d] is not set, defaulting to POST", w.Type, w.URL, w.ID)
|
||||
fallthrough
|
||||
case http.MethodPost:
|
||||
switch w.ContentType {
|
||||
case webhook_model.ContentTypeJSON:
|
||||
req, err = http.NewRequest("POST", w.URL, strings.NewReader(t.PayloadContent))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
case webhook_model.ContentTypeForm:
|
||||
forms := url.Values{
|
||||
"payload": []string{t.PayloadContent},
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("POST", w.URL, strings.NewReader(forms.Encode()))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("invalid content type: %v", w.ContentType)
|
||||
}
|
||||
case http.MethodGet:
|
||||
u, err := url.Parse(w.URL)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid URL: %w", err)
|
||||
}
|
||||
vals := u.Query()
|
||||
vals["payload"] = []string{t.PayloadContent}
|
||||
u.RawQuery = vals.Encode()
|
||||
req, err = http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case http.MethodPut:
|
||||
switch w.Type {
|
||||
case webhook_module.MATRIX: // used when t.Version == 1
|
||||
txnID, err := getMatrixTxnID([]byte(t.PayloadContent))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
url := fmt.Sprintf("%s/%s", w.URL, url.PathEscape(txnID))
|
||||
req, err = http.NewRequest("PUT", url, strings.NewReader(t.PayloadContent))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("invalid http method: %v", w.HTTPMethod)
|
||||
}
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("invalid http method: %v", w.HTTPMethod)
|
||||
}
|
||||
|
||||
body = []byte(t.PayloadContent)
|
||||
return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body)
|
||||
}
|
||||
|
||||
func addDefaultHeaders(req *http.Request, secret []byte, t *webhook_model.HookTask, payloadContent []byte) error {
|
||||
var signatureSHA1 string
|
||||
var signatureSHA256 string
|
||||
if len(secret) > 0 {
|
||||
sig1 := hmac.New(sha1.New, secret)
|
||||
sig256 := hmac.New(sha256.New, secret)
|
||||
_, err := io.MultiWriter(sig1, sig256).Write(payloadContent)
|
||||
if err != nil {
|
||||
// this error should never happen, since the hashes are writing to []byte and always return a nil error.
|
||||
return fmt.Errorf("prepareWebhooks.sigWrite: %w", err)
|
||||
}
|
||||
signatureSHA1 = hex.EncodeToString(sig1.Sum(nil))
|
||||
signatureSHA256 = hex.EncodeToString(sig256.Sum(nil))
|
||||
}
|
||||
|
||||
event := t.EventType.Event()
|
||||
eventType := string(t.EventType)
|
||||
req.Header.Add("X-Forgejo-Delivery", t.UUID)
|
||||
req.Header.Add("X-Forgejo-Event", event)
|
||||
req.Header.Add("X-Forgejo-Event-Type", eventType)
|
||||
req.Header.Add("X-Forgejo-Signature", signatureSHA256)
|
||||
req.Header.Add("X-Gitea-Delivery", t.UUID)
|
||||
req.Header.Add("X-Gitea-Event", event)
|
||||
req.Header.Add("X-Gitea-Event-Type", eventType)
|
||||
req.Header.Add("X-Gitea-Signature", signatureSHA256)
|
||||
req.Header.Add("X-Gogs-Delivery", t.UUID)
|
||||
req.Header.Add("X-Gogs-Event", event)
|
||||
req.Header.Add("X-Gogs-Event-Type", eventType)
|
||||
req.Header.Add("X-Gogs-Signature", signatureSHA256)
|
||||
req.Header.Add("X-Hub-Signature", "sha1="+signatureSHA1)
|
||||
req.Header.Add("X-Hub-Signature-256", "sha256="+signatureSHA256)
|
||||
req.Header["X-GitHub-Delivery"] = []string{t.UUID}
|
||||
req.Header["X-GitHub-Event"] = []string{event}
|
||||
req.Header["X-GitHub-Event-Type"] = []string{eventType}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deliver creates the [http.Request] (depending on the webhook type), sends it
|
||||
// and records the status and response.
|
||||
func Deliver(ctx context.Context, t *webhook_model.HookTask) error {
|
||||
|
@ -151,12 +47,15 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error {
|
|||
|
||||
t.IsDelivered = true
|
||||
|
||||
newRequest := webhookRequesters[w.Type]
|
||||
if t.PayloadVersion == 1 || newRequest == nil {
|
||||
newRequest = newDefaultRequest
|
||||
handler := GetWebhookHandler(w.Type)
|
||||
if handler == nil {
|
||||
return fmt.Errorf("GetWebhookHandler %q", w.Type)
|
||||
}
|
||||
if t.PayloadVersion == 1 {
|
||||
handler = defaultHandler{true}
|
||||
}
|
||||
|
||||
req, body, err := newRequest(ctx, w, t)
|
||||
req, body, err := handler.NewRequest(ctx, w, t)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create http request for webhook %s[%d %s]: %w", w.Type, w.ID, w.URL, err)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,11 @@ import (
|
|||
dingtalk "gitea.com/lunny/dingtalk_webhook"
|
||||
)
|
||||
|
||||
type dingtalkHandler struct{}
|
||||
|
||||
func (dingtalkHandler) Type() webhook_module.HookType { return webhook_module.DINGTALK }
|
||||
func (dingtalkHandler) Metadata(*webhook_model.Webhook) any { return nil }
|
||||
|
||||
type (
|
||||
// DingtalkPayload represents
|
||||
DingtalkPayload dingtalk.Payload
|
||||
|
@ -190,6 +195,6 @@ type dingtalkConvertor struct{}
|
|||
|
||||
var _ payloadConvertor[DingtalkPayload] = dingtalkConvertor{}
|
||||
|
||||
func newDingtalkRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
func (dingtalkHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
return newJSONRequest(dingtalkConvertor{}, w, t, true)
|
||||
}
|
||||
|
|
|
@ -236,7 +236,7 @@ func TestDingTalkJSONPayload(t *testing.T) {
|
|||
PayloadVersion: 2,
|
||||
}
|
||||
|
||||
req, reqBody, err := newDingtalkRequest(context.Background(), hook, task)
|
||||
req, reqBody, err := dingtalkHandler{}.NewRequest(context.Background(), hook, task)
|
||||
require.NotNil(t, req)
|
||||
require.NotNil(t, reqBody)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -22,6 +22,10 @@ import (
|
|||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
)
|
||||
|
||||
type discordHandler struct{}
|
||||
|
||||
func (discordHandler) Type() webhook_module.HookType { return webhook_module.DISCORD }
|
||||
|
||||
type (
|
||||
// DiscordEmbedFooter for Embed Footer Structure.
|
||||
DiscordEmbedFooter struct {
|
||||
|
@ -69,11 +73,11 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
// GetDiscordHook returns discord metadata
|
||||
func GetDiscordHook(w *webhook_model.Webhook) *DiscordMeta {
|
||||
// Metadata returns discord metadata
|
||||
func (discordHandler) Metadata(w *webhook_model.Webhook) any {
|
||||
s := &DiscordMeta{}
|
||||
if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
|
||||
log.Error("webhook.GetDiscordHook(%d): %v", w.ID, err)
|
||||
log.Error("discordHandler.Metadata(%d): %v", w.ID, err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
@ -260,10 +264,10 @@ type discordConvertor struct {
|
|||
|
||||
var _ payloadConvertor[DiscordPayload] = discordConvertor{}
|
||||
|
||||
func newDiscordRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
func (discordHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
meta := &DiscordMeta{}
|
||||
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
|
||||
return nil, nil, fmt.Errorf("newDiscordRequest meta json: %w", err)
|
||||
return nil, nil, fmt.Errorf("discordHandler.NewRequest meta json: %w", err)
|
||||
}
|
||||
sc := discordConvertor{
|
||||
Username: meta.Username,
|
||||
|
|
|
@ -275,7 +275,7 @@ func TestDiscordJSONPayload(t *testing.T) {
|
|||
PayloadVersion: 2,
|
||||
}
|
||||
|
||||
req, reqBody, err := newDiscordRequest(context.Background(), hook, task)
|
||||
req, reqBody, err := discordHandler{}.NewRequest(context.Background(), hook, task)
|
||||
require.NotNil(t, req)
|
||||
require.NotNil(t, reqBody)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -15,6 +15,11 @@ import (
|
|||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
)
|
||||
|
||||
type feishuHandler struct{}
|
||||
|
||||
func (feishuHandler) Type() webhook_module.HookType { return webhook_module.FEISHU }
|
||||
func (feishuHandler) Metadata(*webhook_model.Webhook) any { return nil }
|
||||
|
||||
type (
|
||||
// FeishuPayload represents
|
||||
FeishuPayload struct {
|
||||
|
@ -168,6 +173,6 @@ type feishuConvertor struct{}
|
|||
|
||||
var _ payloadConvertor[FeishuPayload] = feishuConvertor{}
|
||||
|
||||
func newFeishuRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
func (feishuHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
return newJSONRequest(feishuConvertor{}, w, t, true)
|
||||
}
|
||||
|
|
|
@ -177,7 +177,7 @@ func TestFeishuJSONPayload(t *testing.T) {
|
|||
PayloadVersion: 2,
|
||||
}
|
||||
|
||||
req, reqBody, err := newFeishuRequest(context.Background(), hook, task)
|
||||
req, reqBody, err := feishuHandler{}.NewRequest(context.Background(), hook, task)
|
||||
require.NotNil(t, req)
|
||||
require.NotNil(t, reqBody)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -314,33 +314,41 @@ func getPackagePayloadInfo(p *api.PackagePayload, linkFormatter linkFormatter, w
|
|||
// ToHook convert models.Webhook to api.Hook
|
||||
// This function is not part of the convert package to prevent an import cycle
|
||||
func ToHook(repoLink string, w *webhook_model.Webhook) (*api.Hook, error) {
|
||||
// config is deprecated, but kept for compatibility
|
||||
config := map[string]string{
|
||||
"url": w.URL,
|
||||
"content_type": w.ContentType.Name(),
|
||||
}
|
||||
if w.Type == webhook_module.SLACK {
|
||||
s := GetSlackHook(w)
|
||||
if s, ok := (slackHandler{}.Metadata(w)).(*SlackMeta); ok {
|
||||
config["channel"] = s.Channel
|
||||
config["username"] = s.Username
|
||||
config["icon_url"] = s.IconURL
|
||||
config["color"] = s.Color
|
||||
}
|
||||
}
|
||||
|
||||
authorizationHeader, err := w.HeaderAuthorization()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var metadata any
|
||||
if handler := GetWebhookHandler(w.Type); handler != nil {
|
||||
metadata = handler.Metadata(w)
|
||||
}
|
||||
|
||||
return &api.Hook{
|
||||
ID: w.ID,
|
||||
Type: w.Type,
|
||||
URL: fmt.Sprintf("%s/settings/hooks/%d", repoLink, w.ID),
|
||||
Active: w.IsActive,
|
||||
BranchFilter: w.BranchFilter,
|
||||
URL: w.URL,
|
||||
Config: config,
|
||||
Events: w.EventsArray(),
|
||||
AuthorizationHeader: authorizationHeader,
|
||||
ContentType: w.ContentType.Name(),
|
||||
Metadata: metadata,
|
||||
Active: w.IsActive,
|
||||
Updated: w.UpdatedUnix.AsTime(),
|
||||
Created: w.CreatedUnix.AsTime(),
|
||||
BranchFilter: w.BranchFilter,
|
||||
}, nil
|
||||
}
|
||||
|
|
12
services/webhook/gogs.go
Normal file
12
services/webhook/gogs.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
)
|
||||
|
||||
type gogsHandler struct{ defaultHandler }
|
||||
|
||||
func (gogsHandler) Type() webhook_module.HookType { return webhook_module.GOGS }
|
|
@ -24,10 +24,14 @@ import (
|
|||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
)
|
||||
|
||||
func newMatrixRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
type matrixHandler struct{}
|
||||
|
||||
func (matrixHandler) Type() webhook_module.HookType { return webhook_module.MATRIX }
|
||||
|
||||
func (matrixHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
meta := &MatrixMeta{}
|
||||
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
|
||||
return nil, nil, fmt.Errorf("GetMatrixPayload meta json: %w", err)
|
||||
return nil, nil, fmt.Errorf("matrixHandler.NewRequest meta json: %w", err)
|
||||
}
|
||||
mc := matrixConvertor{
|
||||
MsgType: messageTypeText[meta.MessageType],
|
||||
|
@ -69,11 +73,11 @@ var messageTypeText = map[int]string{
|
|||
2: "m.text",
|
||||
}
|
||||
|
||||
// GetMatrixHook returns Matrix metadata
|
||||
func GetMatrixHook(w *webhook_model.Webhook) *MatrixMeta {
|
||||
// Metadata returns Matrix metadata
|
||||
func (matrixHandler) Metadata(w *webhook_model.Webhook) any {
|
||||
s := &MatrixMeta{}
|
||||
if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
|
||||
log.Error("webhook.GetMatrixHook(%d): %v", w.ID, err)
|
||||
log.Error("matrixHandler.Metadata(%d): %v", w.ID, err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
|
|
@ -211,7 +211,7 @@ func TestMatrixJSONPayload(t *testing.T) {
|
|||
PayloadVersion: 2,
|
||||
}
|
||||
|
||||
req, reqBody, err := newMatrixRequest(context.Background(), hook, task)
|
||||
req, reqBody, err := matrixHandler{}.NewRequest(context.Background(), hook, task)
|
||||
require.NotNil(t, req)
|
||||
require.NotNil(t, reqBody)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -17,6 +17,11 @@ import (
|
|||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
)
|
||||
|
||||
type msteamsHandler struct{}
|
||||
|
||||
func (msteamsHandler) Type() webhook_module.HookType { return webhook_module.MSTEAMS }
|
||||
func (msteamsHandler) Metadata(*webhook_model.Webhook) any { return nil }
|
||||
|
||||
type (
|
||||
// MSTeamsFact for Fact Structure
|
||||
MSTeamsFact struct {
|
||||
|
@ -347,6 +352,6 @@ type msteamsConvertor struct{}
|
|||
|
||||
var _ payloadConvertor[MSTeamsPayload] = msteamsConvertor{}
|
||||
|
||||
func newMSTeamsRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
func (msteamsHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
return newJSONRequest(msteamsConvertor{}, w, t, true)
|
||||
}
|
||||
|
|
|
@ -439,7 +439,7 @@ func TestMSTeamsJSONPayload(t *testing.T) {
|
|||
PayloadVersion: 2,
|
||||
}
|
||||
|
||||
req, reqBody, err := newMSTeamsRequest(context.Background(), hook, task)
|
||||
req, reqBody, err := msteamsHandler{}.NewRequest(context.Background(), hook, task)
|
||||
require.NotNil(t, req)
|
||||
require.NotNil(t, reqBody)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -11,8 +11,13 @@ import (
|
|||
webhook_model "code.gitea.io/gitea/models/webhook"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
)
|
||||
|
||||
type packagistHandler struct{}
|
||||
|
||||
func (packagistHandler) Type() webhook_module.HookType { return webhook_module.PACKAGIST }
|
||||
|
||||
type (
|
||||
// PackagistPayload represents a packagist payload
|
||||
// as expected by https://packagist.org/about
|
||||
|
@ -30,20 +35,20 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
// GetPackagistHook returns packagist metadata
|
||||
func GetPackagistHook(w *webhook_model.Webhook) *PackagistMeta {
|
||||
// Metadata returns packagist metadata
|
||||
func (packagistHandler) Metadata(w *webhook_model.Webhook) any {
|
||||
s := &PackagistMeta{}
|
||||
if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
|
||||
log.Error("webhook.GetPackagistHook(%d): %v", w.ID, err)
|
||||
log.Error("packagistHandler.Metadata(%d): %v", w.ID, err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// newPackagistRequest creates a request with the [PackagistPayload] for packagist (same payload for all events).
|
||||
func newPackagistRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
func (packagistHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
meta := &PackagistMeta{}
|
||||
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
|
||||
return nil, nil, fmt.Errorf("newpackagistRequest meta json: %w", err)
|
||||
return nil, nil, fmt.Errorf("packagistHandler.NewRequest meta json: %w", err)
|
||||
}
|
||||
|
||||
payload := PackagistPayload{
|
||||
|
|
|
@ -53,7 +53,7 @@ func TestPackagistPayload(t *testing.T) {
|
|||
PayloadVersion: 2,
|
||||
}
|
||||
|
||||
req, reqBody, err := newPackagistRequest(context.Background(), hook, task)
|
||||
req, reqBody, err := packagistHandler{}.NewRequest(context.Background(), hook, task)
|
||||
require.NotNil(t, req)
|
||||
require.NotNil(t, reqBody)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -19,6 +19,10 @@ import (
|
|||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
)
|
||||
|
||||
type slackHandler struct{}
|
||||
|
||||
func (slackHandler) Type() webhook_module.HookType { return webhook_module.SLACK }
|
||||
|
||||
// SlackMeta contains the slack metadata
|
||||
type SlackMeta struct {
|
||||
Channel string `json:"channel"`
|
||||
|
@ -27,11 +31,11 @@ type SlackMeta struct {
|
|||
Color string `json:"color"`
|
||||
}
|
||||
|
||||
// GetSlackHook returns slack metadata
|
||||
func GetSlackHook(w *webhook_model.Webhook) *SlackMeta {
|
||||
// Metadata returns slack metadata
|
||||
func (slackHandler) Metadata(w *webhook_model.Webhook) any {
|
||||
s := &SlackMeta{}
|
||||
if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
|
||||
log.Error("webhook.GetSlackHook(%d): %v", w.ID, err)
|
||||
log.Error("slackHandler.Metadata(%d): %v", w.ID, err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
@ -283,10 +287,10 @@ type slackConvertor struct {
|
|||
|
||||
var _ payloadConvertor[SlackPayload] = slackConvertor{}
|
||||
|
||||
func newSlackRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
func (slackHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
meta := &SlackMeta{}
|
||||
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
|
||||
return nil, nil, fmt.Errorf("newSlackRequest meta json: %w", err)
|
||||
return nil, nil, fmt.Errorf("slackHandler.NewRequest meta json: %w", err)
|
||||
}
|
||||
sc := slackConvertor{
|
||||
Channel: meta.Channel,
|
||||
|
|
|
@ -178,7 +178,7 @@ func TestSlackJSONPayload(t *testing.T) {
|
|||
PayloadVersion: 2,
|
||||
}
|
||||
|
||||
req, reqBody, err := newSlackRequest(context.Background(), hook, task)
|
||||
req, reqBody, err := slackHandler{}.NewRequest(context.Background(), hook, task)
|
||||
require.NotNil(t, req)
|
||||
require.NotNil(t, reqBody)
|
||||
require.NoError(t, err)
|
||||
|
@ -211,3 +211,54 @@ func TestIsValidSlackChannel(t *testing.T) {
|
|||
assert.Equal(t, v.expected, IsValidSlackChannel(v.channelName))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlackMetadata(t *testing.T) {
|
||||
w := &webhook_model.Webhook{
|
||||
Meta: `{"channel": "foo", "username": "username", "color": "blue"}`,
|
||||
}
|
||||
slackHook := slackHandler{}.Metadata(w)
|
||||
assert.Equal(t, *slackHook.(*SlackMeta), SlackMeta{
|
||||
Channel: "foo",
|
||||
Username: "username",
|
||||
Color: "blue",
|
||||
})
|
||||
}
|
||||
|
||||
func TestSlackToHook(t *testing.T) {
|
||||
w := &webhook_model.Webhook{
|
||||
Type: webhook_module.SLACK,
|
||||
ContentType: webhook_model.ContentTypeJSON,
|
||||
URL: "https://slack.example.com",
|
||||
Meta: `{"channel": "foo", "username": "username", "color": "blue"}`,
|
||||
HookEvent: &webhook_module.HookEvent{
|
||||
PushOnly: true,
|
||||
SendEverything: false,
|
||||
ChooseEvents: false,
|
||||
HookEvents: webhook_module.HookEvents{
|
||||
Create: false,
|
||||
Push: true,
|
||||
PullRequest: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
h, err := ToHook("repoLink", w)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, h.Config, map[string]string{
|
||||
"url": "https://slack.example.com",
|
||||
"content_type": "json",
|
||||
|
||||
"channel": "foo",
|
||||
"color": "blue",
|
||||
"icon_url": "",
|
||||
"username": "username",
|
||||
})
|
||||
assert.Equal(t, h.URL, "https://slack.example.com")
|
||||
assert.Equal(t, h.ContentType, "json")
|
||||
assert.Equal(t, h.Metadata, &SlackMeta{
|
||||
Channel: "foo",
|
||||
Username: "username",
|
||||
IconURL: "",
|
||||
Color: "blue",
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,6 +17,10 @@ import (
|
|||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
)
|
||||
|
||||
type telegramHandler struct{}
|
||||
|
||||
func (telegramHandler) Type() webhook_module.HookType { return webhook_module.TELEGRAM }
|
||||
|
||||
type (
|
||||
// TelegramPayload represents
|
||||
TelegramPayload struct {
|
||||
|
@ -33,11 +37,11 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
// GetTelegramHook returns telegram metadata
|
||||
func GetTelegramHook(w *webhook_model.Webhook) *TelegramMeta {
|
||||
// Metadata returns telegram metadata
|
||||
func (telegramHandler) Metadata(w *webhook_model.Webhook) any {
|
||||
s := &TelegramMeta{}
|
||||
if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
|
||||
log.Error("webhook.GetTelegramHook(%d): %v", w.ID, err)
|
||||
log.Error("telegramHandler.Metadata(%d): %v", w.ID, err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
@ -189,6 +193,6 @@ type telegramConvertor struct{}
|
|||
|
||||
var _ payloadConvertor[TelegramPayload] = telegramConvertor{}
|
||||
|
||||
func newTelegramRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
func (telegramHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
return newJSONRequest(telegramConvertor{}, w, t, true)
|
||||
}
|
||||
|
|
|
@ -177,7 +177,7 @@ func TestTelegramJSONPayload(t *testing.T) {
|
|||
PayloadVersion: 2,
|
||||
}
|
||||
|
||||
req, reqBody, err := newTelegramRequest(context.Background(), hook, task)
|
||||
req, reqBody, err := telegramHandler{}.NewRequest(context.Background(), hook, task)
|
||||
require.NotNil(t, req)
|
||||
require.NotNil(t, reqBody)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -27,25 +27,46 @@ import (
|
|||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
var webhookRequesters = map[webhook_module.HookType]func(context.Context, *webhook_model.Webhook, *webhook_model.HookTask) (req *http.Request, body []byte, err error){
|
||||
webhook_module.SLACK: newSlackRequest,
|
||||
webhook_module.DISCORD: newDiscordRequest,
|
||||
webhook_module.DINGTALK: newDingtalkRequest,
|
||||
webhook_module.TELEGRAM: newTelegramRequest,
|
||||
webhook_module.MSTEAMS: newMSTeamsRequest,
|
||||
webhook_module.FEISHU: newFeishuRequest,
|
||||
webhook_module.MATRIX: newMatrixRequest,
|
||||
webhook_module.WECHATWORK: newWechatworkRequest,
|
||||
webhook_module.PACKAGIST: newPackagistRequest,
|
||||
type Handler interface {
|
||||
Type() webhook_module.HookType
|
||||
NewRequest(context.Context, *webhook_model.Webhook, *webhook_model.HookTask) (req *http.Request, body []byte, err error)
|
||||
Metadata(*webhook_model.Webhook) any
|
||||
}
|
||||
|
||||
var webhookHandlers = []Handler{
|
||||
defaultHandler{true},
|
||||
defaultHandler{false},
|
||||
gogsHandler{},
|
||||
|
||||
slackHandler{},
|
||||
discordHandler{},
|
||||
dingtalkHandler{},
|
||||
telegramHandler{},
|
||||
msteamsHandler{},
|
||||
feishuHandler{},
|
||||
matrixHandler{},
|
||||
wechatworkHandler{},
|
||||
packagistHandler{},
|
||||
}
|
||||
|
||||
// GetWebhookHandler return the handler for a given webhook type (nil if not found)
|
||||
func GetWebhookHandler(name webhook_module.HookType) Handler {
|
||||
for _, h := range webhookHandlers {
|
||||
if h.Type() == name {
|
||||
return h
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List provides a list of the supported webhooks
|
||||
func List() []Handler {
|
||||
return webhookHandlers
|
||||
}
|
||||
|
||||
// IsValidHookTaskType returns true if a webhook registered
|
||||
func IsValidHookTaskType(name string) bool {
|
||||
if name == webhook_module.FORGEJO || name == webhook_module.GITEA || name == webhook_module.GOGS {
|
||||
return true
|
||||
}
|
||||
_, ok := webhookRequesters[name]
|
||||
return ok
|
||||
return GetWebhookHandler(name) != nil
|
||||
}
|
||||
|
||||
// hookQueue is a global queue of web hooks
|
||||
|
|
|
@ -16,18 +16,6 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWebhook_GetSlackHook(t *testing.T) {
|
||||
w := &webhook_model.Webhook{
|
||||
Meta: `{"channel": "foo", "username": "username", "color": "blue"}`,
|
||||
}
|
||||
slackHook := GetSlackHook(w)
|
||||
assert.Equal(t, *slackHook, SlackMeta{
|
||||
Channel: "foo",
|
||||
Username: "username",
|
||||
Color: "blue",
|
||||
})
|
||||
}
|
||||
|
||||
func activateWebhook(t *testing.T, hookID int64) {
|
||||
t.Helper()
|
||||
updated, err := db.GetEngine(db.DefaultContext).ID(hookID).Cols("is_active").Update(webhook_model.Webhook{IsActive: true})
|
||||
|
|
|
@ -15,6 +15,11 @@ import (
|
|||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
)
|
||||
|
||||
type wechatworkHandler struct{}
|
||||
|
||||
func (wechatworkHandler) Type() webhook_module.HookType { return webhook_module.WECHATWORK }
|
||||
func (wechatworkHandler) Metadata(*webhook_model.Webhook) any { return nil }
|
||||
|
||||
type (
|
||||
// WechatworkPayload represents
|
||||
WechatworkPayload struct {
|
||||
|
@ -177,6 +182,6 @@ type wechatworkConvertor struct{}
|
|||
|
||||
var _ payloadConvertor[WechatworkPayload] = wechatworkConvertor{}
|
||||
|
||||
func newWechatworkRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
func (wechatworkHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
return newJSONRequest(wechatworkConvertor{}, w, t, true)
|
||||
}
|
||||
|
|
|
@ -8,11 +8,11 @@
|
|||
</div>
|
||||
<div class="field">
|
||||
<label for="username">{{ctx.Locale.Tr "repo.settings.discord_username"}}</label>
|
||||
<input id="username" name="username" value="{{.DiscordHook.Username}}" placeholder="Forgejo">
|
||||
<input id="username" name="username" value="{{.HookMetadata.Username}}" placeholder="Forgejo">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="icon_url">{{ctx.Locale.Tr "repo.settings.discord_icon_url"}}</label>
|
||||
<input id="icon_url" name="icon_url" value="{{.DiscordHook.IconURL}}" placeholder="https://example.com/assets/img/logo.svg">
|
||||
<input id="icon_url" name="icon_url" value="{{.HookMetadata.IconURL}}" placeholder="https://example.com/assets/img/logo.svg">
|
||||
</div>
|
||||
{{template "repo/settings/webhook/settings" .}}
|
||||
</form>
|
||||
|
|
|
@ -4,16 +4,16 @@
|
|||
{{.CsrfTokenHtml}}
|
||||
<div class="required field {{if .Err_HomeserverURL}}error{{end}}">
|
||||
<label for="homeserver_url">{{ctx.Locale.Tr "repo.settings.matrix.homeserver_url"}}</label>
|
||||
<input id="homeserver_url" name="homeserver_url" type="url" value="{{.MatrixHook.HomeserverURL}}" autofocus required>
|
||||
<input id="homeserver_url" name="homeserver_url" type="url" value="{{.HookMetadata.HomeserverURL}}" autofocus required>
|
||||
</div>
|
||||
<div class="required field {{if .Err_Room}}error{{end}}">
|
||||
<label for="room_id">{{ctx.Locale.Tr "repo.settings.matrix.room_id"}}</label>
|
||||
<input id="room_id" name="room_id" type="text" value="{{.MatrixHook.Room}}" required>
|
||||
<input id="room_id" name="room_id" type="text" value="{{.HookMetadata.Room}}" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.matrix.message_type"}}</label>
|
||||
<div class="ui selection dropdown">
|
||||
<input type="hidden" id="message_type" name="message_type" value="{{if .MatrixHook.MessageType}}{{.MatrixHook.MessageType}}{{else}}1{{end}}">
|
||||
<input type="hidden" id="message_type" name="message_type" value="{{if .HookMetadata.MessageType}}{{.HookMetadata.MessageType}}{{else}}1{{end}}">
|
||||
<div class="default text"></div>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="menu">
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
{{.CsrfTokenHtml}}
|
||||
<div class="required field {{if .Err_Username}}error{{end}}">
|
||||
<label for="username">{{ctx.Locale.Tr "repo.settings.packagist_username"}}</label>
|
||||
<input id="username" name="username" value="{{.PackagistHook.Username}}" placeholder="Forgejo" autofocus required>
|
||||
<input id="username" name="username" value="{{.HookMetadata.Username}}" placeholder="Forgejo" autofocus required>
|
||||
</div>
|
||||
<div class="required field {{if .Err_APIToken}}error{{end}}">
|
||||
<label for="api_token">{{ctx.Locale.Tr "repo.settings.packagist_api_token"}}</label>
|
||||
<input id="api_token" name="api_token" value="{{.PackagistHook.APIToken}}" placeholder="X5F_tZ-Wj3c1vqaU2Rky" required>
|
||||
<input id="api_token" name="api_token" value="{{.HookMetadata.APIToken}}" placeholder="X5F_tZ-Wj3c1vqaU2Rky" required>
|
||||
</div>
|
||||
<div class="required field {{if .Err_PackageURL}}error{{end}}">
|
||||
<label for="package_url">{{ctx.Locale.Tr "repo.settings.packagist_package_url"}}</label>
|
||||
<input id="package_url" name="package_url" value="{{.PackagistHook.PackageURL}}" placeholder="https://packagist.org/packages/laravel/framework" required>
|
||||
<input id="package_url" name="package_url" value="{{.HookMetadata.PackageURL}}" placeholder="https://packagist.org/packages/laravel/framework" required>
|
||||
</div>
|
||||
{{template "repo/settings/webhook/settings" .}}
|
||||
</form>
|
||||
|
|
|
@ -8,20 +8,20 @@
|
|||
</div>
|
||||
<div class="required field {{if .Err_Channel}}error{{end}}">
|
||||
<label for="channel">{{ctx.Locale.Tr "repo.settings.slack_channel"}}</label>
|
||||
<input id="channel" name="channel" value="{{.SlackHook.Channel}}" placeholder="#general" required>
|
||||
<input id="channel" name="channel" value="{{.HookMetadata.Channel}}" placeholder="#general" required>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="username">{{ctx.Locale.Tr "repo.settings.slack_username"}}</label>
|
||||
<input id="username" name="username" value="{{.SlackHook.Username}}" placeholder="Forgejo">
|
||||
<input id="username" name="username" value="{{.HookMetadata.Username}}" placeholder="Forgejo">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="icon_url">{{ctx.Locale.Tr "repo.settings.slack_icon_url"}}</label>
|
||||
<input id="icon_url" name="icon_url" value="{{.SlackHook.IconURL}}" placeholder="https://example.com/img/favicon.png">
|
||||
<input id="icon_url" name="icon_url" value="{{.HookMetadata.IconURL}}" placeholder="https://example.com/img/favicon.png">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="color">{{ctx.Locale.Tr "repo.settings.slack_color"}}</label>
|
||||
<input id="color" name="color" value="{{.SlackHook.Color}}" placeholder="#dd4b39, good, warning, danger">
|
||||
<input id="color" name="color" value="{{.HookMetadata.Color}}" placeholder="#dd4b39, good, warning, danger">
|
||||
</div>
|
||||
{{template "repo/settings/webhook/settings" .}}
|
||||
</form>
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
{{.CsrfTokenHtml}}
|
||||
<div class="required field {{if .Err_BotToken}}error{{end}}">
|
||||
<label for="bot_token">{{ctx.Locale.Tr "repo.settings.bot_token"}}</label>
|
||||
<input id="bot_token" name="bot_token" type="text" value="{{.TelegramHook.BotToken}}" autofocus required>
|
||||
<input id="bot_token" name="bot_token" type="text" value="{{.HookMetadata.BotToken}}" autofocus required>
|
||||
</div>
|
||||
<div class="required field {{if .Err_ChatID}}error{{end}}">
|
||||
<label for="chat_id">{{ctx.Locale.Tr "repo.settings.chat_id"}}</label>
|
||||
<input id="chat_id" name="chat_id" type="text" value="{{.TelegramHook.ChatID}}" required>
|
||||
<input id="chat_id" name="chat_id" type="text" value="{{.HookMetadata.ChatID}}" required>
|
||||
</div>
|
||||
<div class="field {{if .Err_ThreadID}}error{{end}}">
|
||||
<label for="thread_id">{{ctx.Locale.Tr "repo.settings.thread_id"}}</label>
|
||||
<input id="thread_id" name="thread_id" type="text" value="{{.TelegramHook.ThreadID}}">
|
||||
<input id="thread_id" name="thread_id" type="text" value="{{.HookMetadata.ThreadID}}">
|
||||
</div>
|
||||
{{template "repo/settings/webhook/settings" .}}
|
||||
</form>
|
||||
|
|
12
templates/swagger/v1_json.tmpl
generated
12
templates/swagger/v1_json.tmpl
generated
|
@ -20952,12 +20952,17 @@
|
|||
"x-go-name": "BranchFilter"
|
||||
},
|
||||
"config": {
|
||||
"description": "Deprecated: use Metadata instead",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"x-go-name": "Config"
|
||||
},
|
||||
"content_type": {
|
||||
"type": "string",
|
||||
"x-go-name": "ContentType"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
|
@ -20975,6 +20980,9 @@
|
|||
"format": "int64",
|
||||
"x-go-name": "ID"
|
||||
},
|
||||
"metadata": {
|
||||
"x-go-name": "Metadata"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"x-go-name": "Type"
|
||||
|
@ -20983,6 +20991,10 @@
|
|||
"type": "string",
|
||||
"format": "date-time",
|
||||
"x-go-name": "Updated"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"x-go-name": "URL"
|
||||
}
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
|
|
|
@ -40,5 +40,6 @@ func TestAPICreateHook(t *testing.T) {
|
|||
var apiHook *api.Hook
|
||||
DecodeJSON(t, resp, &apiHook)
|
||||
assert.Equal(t, "http://example.com/", apiHook.Config["url"])
|
||||
assert.Equal(t, "http://example.com/", apiHook.URL)
|
||||
assert.Equal(t, "Bearer s3cr3t", apiHook.AuthorizationHeader)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"testing"
|
||||
|
||||
gitea_context "code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/webhook"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
|
@ -21,7 +22,7 @@ func TestNewWebHookLink(t *testing.T) {
|
|||
defer tests.PrepareTestEnv(t)()
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
webhooksLen := 12
|
||||
webhooksLen := len(webhook.List())
|
||||
baseurl := "/user2/repo1/settings/hooks"
|
||||
tests := []string{
|
||||
// webhook list page
|
||||
|
|
Loading…
Reference in a new issue