loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request '[REFACTOR] webhook settings: merge Create/Update endpoints' (#2843) from oliverpool/forgejo:webhook_3_endpoint_merge into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2843
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>

+342 -486
+2 -2
modules/web/middleware/binding.go
··· 79 79 return getRuleBody(field, "Include(") 80 80 } 81 81 82 - // Validate validate TODO: 83 - func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Locale) binding.Errors { 82 + // Validate populates the data with validation error (if any). 83 + func Validate(errs binding.Errors, data map[string]any, f any, l translation.Locale) binding.Errors { 84 84 if errs.Len() == 0 { 85 85 return errs 86 86 }
+49 -299
routers/web/repo/setting/webhook.go
··· 23 23 "code.gitea.io/gitea/modules/setting" 24 24 api "code.gitea.io/gitea/modules/structs" 25 25 "code.gitea.io/gitea/modules/util" 26 - "code.gitea.io/gitea/modules/web" 26 + "code.gitea.io/gitea/modules/web/middleware" 27 27 webhook_module "code.gitea.io/gitea/modules/webhook" 28 28 "code.gitea.io/gitea/services/context" 29 29 "code.gitea.io/gitea/services/convert" 30 30 "code.gitea.io/gitea/services/forms" 31 31 webhook_service "code.gitea.io/gitea/services/webhook" 32 + 33 + "gitea.com/go-chi/binding" 32 34 ) 33 35 34 36 const ( ··· 201 203 Meta any 202 204 } 203 205 206 + func WebhookCreate(ctx *context.Context) { 207 + typ := ctx.Params(":type") 208 + handler := webhook_service.GetWebhookHandler(typ) 209 + if handler == nil { 210 + ctx.NotFound("GetWebhookHandler", nil) 211 + return 212 + } 213 + 214 + fields := handler.FormFields(func(form any) { 215 + errs := binding.Bind(ctx.Req, form) 216 + middleware.Validate(errs, ctx.Data, form, ctx.Locale) // error will be checked later in ctx.HasError 217 + }) 218 + createWebhook(ctx, webhookParams{ 219 + Type: typ, 220 + URL: fields.URL, 221 + ContentType: fields.ContentType, 222 + Secret: fields.Secret, 223 + HTTPMethod: fields.HTTPMethod, 224 + WebhookForm: fields.WebhookForm, 225 + Meta: fields.Metadata, 226 + }) 227 + } 228 + 204 229 func createWebhook(ctx *context.Context, params webhookParams) { 205 230 ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") 206 231 ctx.Data["PageIsSettingsHooks"] = true ··· 260 285 ctx.Redirect(orCtx.Link) 261 286 } 262 287 288 + func WebhookUpdate(ctx *context.Context) { 289 + typ := ctx.Params(":type") 290 + handler := webhook_service.GetWebhookHandler(typ) 291 + if handler == nil { 292 + ctx.NotFound("GetWebhookHandler", nil) 293 + return 294 + } 295 + 296 + fields := handler.FormFields(func(form any) { 297 + errs := binding.Bind(ctx.Req, form) 298 + middleware.Validate(errs, ctx.Data, form, ctx.Locale) // error will be checked later in ctx.HasError 299 + }) 300 + editWebhook(ctx, webhookParams{ 301 + Type: typ, 302 + URL: fields.URL, 303 + ContentType: fields.ContentType, 304 + Secret: fields.Secret, 305 + HTTPMethod: fields.HTTPMethod, 306 + WebhookForm: fields.WebhookForm, 307 + Meta: fields.Metadata, 308 + }) 309 + } 310 + 263 311 func editWebhook(ctx *context.Context, params webhookParams) { 264 312 ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook") 265 313 ctx.Data["PageIsSettingsHooks"] = true ··· 310 358 311 359 ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) 312 360 ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) 313 - } 314 - 315 - // ForgejoHooksNewPost response for creating Forgejo webhook 316 - func ForgejoHooksNewPost(ctx *context.Context) { 317 - createWebhook(ctx, forgejoHookParams(ctx)) 318 - } 319 - 320 - // ForgejoHooksEditPost response for editing Forgejo webhook 321 - func ForgejoHooksEditPost(ctx *context.Context) { 322 - editWebhook(ctx, forgejoHookParams(ctx)) 323 - } 324 - 325 - func forgejoHookParams(ctx *context.Context) webhookParams { 326 - form := web.GetForm(ctx).(*forms.NewWebhookForm) 327 - 328 - contentType := webhook.ContentTypeJSON 329 - if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { 330 - contentType = webhook.ContentTypeForm 331 - } 332 - 333 - return webhookParams{ 334 - Type: webhook_module.FORGEJO, 335 - URL: form.PayloadURL, 336 - ContentType: contentType, 337 - Secret: form.Secret, 338 - HTTPMethod: form.HTTPMethod, 339 - WebhookForm: form.WebhookForm, 340 - } 341 - } 342 - 343 - // GiteaHooksNewPost response for creating Gitea webhook 344 - func GiteaHooksNewPost(ctx *context.Context) { 345 - createWebhook(ctx, giteaHookParams(ctx)) 346 - } 347 - 348 - // GiteaHooksEditPost response for editing Gitea webhook 349 - func GiteaHooksEditPost(ctx *context.Context) { 350 - editWebhook(ctx, giteaHookParams(ctx)) 351 - } 352 - 353 - func giteaHookParams(ctx *context.Context) webhookParams { 354 - form := web.GetForm(ctx).(*forms.NewWebhookForm) 355 - 356 - contentType := webhook.ContentTypeJSON 357 - if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { 358 - contentType = webhook.ContentTypeForm 359 - } 360 - 361 - return webhookParams{ 362 - Type: webhook_module.GITEA, 363 - URL: form.PayloadURL, 364 - ContentType: contentType, 365 - Secret: form.Secret, 366 - HTTPMethod: form.HTTPMethod, 367 - WebhookForm: form.WebhookForm, 368 - } 369 - } 370 - 371 - // GogsHooksNewPost response for creating Gogs webhook 372 - func GogsHooksNewPost(ctx *context.Context) { 373 - createWebhook(ctx, gogsHookParams(ctx)) 374 - } 375 - 376 - // GogsHooksEditPost response for editing Gogs webhook 377 - func GogsHooksEditPost(ctx *context.Context) { 378 - editWebhook(ctx, gogsHookParams(ctx)) 379 - } 380 - 381 - func gogsHookParams(ctx *context.Context) webhookParams { 382 - form := web.GetForm(ctx).(*forms.NewGogshookForm) 383 - 384 - contentType := webhook.ContentTypeJSON 385 - if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { 386 - contentType = webhook.ContentTypeForm 387 - } 388 - 389 - return webhookParams{ 390 - Type: webhook_module.GOGS, 391 - URL: form.PayloadURL, 392 - ContentType: contentType, 393 - Secret: form.Secret, 394 - WebhookForm: form.WebhookForm, 395 - } 396 - } 397 - 398 - // DiscordHooksNewPost response for creating Discord webhook 399 - func DiscordHooksNewPost(ctx *context.Context) { 400 - createWebhook(ctx, discordHookParams(ctx)) 401 - } 402 - 403 - // DiscordHooksEditPost response for editing Discord webhook 404 - func DiscordHooksEditPost(ctx *context.Context) { 405 - editWebhook(ctx, discordHookParams(ctx)) 406 - } 407 - 408 - func discordHookParams(ctx *context.Context) webhookParams { 409 - form := web.GetForm(ctx).(*forms.NewDiscordHookForm) 410 - 411 - return webhookParams{ 412 - Type: webhook_module.DISCORD, 413 - URL: form.PayloadURL, 414 - ContentType: webhook.ContentTypeJSON, 415 - WebhookForm: form.WebhookForm, 416 - Meta: &webhook_service.DiscordMeta{ 417 - Username: form.Username, 418 - IconURL: form.IconURL, 419 - }, 420 - } 421 - } 422 - 423 - // DingtalkHooksNewPost response for creating Dingtalk webhook 424 - func DingtalkHooksNewPost(ctx *context.Context) { 425 - createWebhook(ctx, dingtalkHookParams(ctx)) 426 - } 427 - 428 - // DingtalkHooksEditPost response for editing Dingtalk webhook 429 - func DingtalkHooksEditPost(ctx *context.Context) { 430 - editWebhook(ctx, dingtalkHookParams(ctx)) 431 - } 432 - 433 - func dingtalkHookParams(ctx *context.Context) webhookParams { 434 - form := web.GetForm(ctx).(*forms.NewDingtalkHookForm) 435 - 436 - return webhookParams{ 437 - Type: webhook_module.DINGTALK, 438 - URL: form.PayloadURL, 439 - ContentType: webhook.ContentTypeJSON, 440 - WebhookForm: form.WebhookForm, 441 - } 442 - } 443 - 444 - // TelegramHooksNewPost response for creating Telegram webhook 445 - func TelegramHooksNewPost(ctx *context.Context) { 446 - createWebhook(ctx, telegramHookParams(ctx)) 447 - } 448 - 449 - // TelegramHooksEditPost response for editing Telegram webhook 450 - func TelegramHooksEditPost(ctx *context.Context) { 451 - editWebhook(ctx, telegramHookParams(ctx)) 452 - } 453 - 454 - func telegramHookParams(ctx *context.Context) webhookParams { 455 - form := web.GetForm(ctx).(*forms.NewTelegramHookForm) 456 - 457 - return webhookParams{ 458 - Type: webhook_module.TELEGRAM, 459 - URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&message_thread_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID), url.QueryEscape(form.ThreadID)), 460 - ContentType: webhook.ContentTypeJSON, 461 - WebhookForm: form.WebhookForm, 462 - Meta: &webhook_service.TelegramMeta{ 463 - BotToken: form.BotToken, 464 - ChatID: form.ChatID, 465 - ThreadID: form.ThreadID, 466 - }, 467 - } 468 - } 469 - 470 - // MatrixHooksNewPost response for creating Matrix webhook 471 - func MatrixHooksNewPost(ctx *context.Context) { 472 - createWebhook(ctx, matrixHookParams(ctx)) 473 - } 474 - 475 - // MatrixHooksEditPost response for editing Matrix webhook 476 - func MatrixHooksEditPost(ctx *context.Context) { 477 - editWebhook(ctx, matrixHookParams(ctx)) 478 - } 479 - 480 - func matrixHookParams(ctx *context.Context) webhookParams { 481 - form := web.GetForm(ctx).(*forms.NewMatrixHookForm) 482 - 483 - return webhookParams{ 484 - Type: webhook_module.MATRIX, 485 - URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), 486 - ContentType: webhook.ContentTypeJSON, 487 - HTTPMethod: http.MethodPut, 488 - WebhookForm: form.WebhookForm, 489 - Meta: &webhook_service.MatrixMeta{ 490 - HomeserverURL: form.HomeserverURL, 491 - Room: form.RoomID, 492 - MessageType: form.MessageType, 493 - }, 494 - } 495 - } 496 - 497 - // MSTeamsHooksNewPost response for creating MSTeams webhook 498 - func MSTeamsHooksNewPost(ctx *context.Context) { 499 - createWebhook(ctx, mSTeamsHookParams(ctx)) 500 - } 501 - 502 - // MSTeamsHooksEditPost response for editing MSTeams webhook 503 - func MSTeamsHooksEditPost(ctx *context.Context) { 504 - editWebhook(ctx, mSTeamsHookParams(ctx)) 505 - } 506 - 507 - func mSTeamsHookParams(ctx *context.Context) webhookParams { 508 - form := web.GetForm(ctx).(*forms.NewMSTeamsHookForm) 509 - 510 - return webhookParams{ 511 - Type: webhook_module.MSTEAMS, 512 - URL: form.PayloadURL, 513 - ContentType: webhook.ContentTypeJSON, 514 - WebhookForm: form.WebhookForm, 515 - } 516 - } 517 - 518 - // SlackHooksNewPost response for creating Slack webhook 519 - func SlackHooksNewPost(ctx *context.Context) { 520 - createWebhook(ctx, slackHookParams(ctx)) 521 - } 522 - 523 - // SlackHooksEditPost response for editing Slack webhook 524 - func SlackHooksEditPost(ctx *context.Context) { 525 - editWebhook(ctx, slackHookParams(ctx)) 526 - } 527 - 528 - func slackHookParams(ctx *context.Context) webhookParams { 529 - form := web.GetForm(ctx).(*forms.NewSlackHookForm) 530 - 531 - return webhookParams{ 532 - Type: webhook_module.SLACK, 533 - URL: form.PayloadURL, 534 - ContentType: webhook.ContentTypeJSON, 535 - WebhookForm: form.WebhookForm, 536 - Meta: &webhook_service.SlackMeta{ 537 - Channel: strings.TrimSpace(form.Channel), 538 - Username: form.Username, 539 - IconURL: form.IconURL, 540 - Color: form.Color, 541 - }, 542 - } 543 - } 544 - 545 - // FeishuHooksNewPost response for creating Feishu webhook 546 - func FeishuHooksNewPost(ctx *context.Context) { 547 - createWebhook(ctx, feishuHookParams(ctx)) 548 - } 549 - 550 - // FeishuHooksEditPost response for editing Feishu webhook 551 - func FeishuHooksEditPost(ctx *context.Context) { 552 - editWebhook(ctx, feishuHookParams(ctx)) 553 - } 554 - 555 - func feishuHookParams(ctx *context.Context) webhookParams { 556 - form := web.GetForm(ctx).(*forms.NewFeishuHookForm) 557 - 558 - return webhookParams{ 559 - Type: webhook_module.FEISHU, 560 - URL: form.PayloadURL, 561 - ContentType: webhook.ContentTypeJSON, 562 - WebhookForm: form.WebhookForm, 563 - } 564 - } 565 - 566 - // WechatworkHooksNewPost response for creating Wechatwork webhook 567 - func WechatworkHooksNewPost(ctx *context.Context) { 568 - createWebhook(ctx, wechatworkHookParams(ctx)) 569 - } 570 - 571 - // WechatworkHooksEditPost response for editing Wechatwork webhook 572 - func WechatworkHooksEditPost(ctx *context.Context) { 573 - editWebhook(ctx, wechatworkHookParams(ctx)) 574 - } 575 - 576 - func wechatworkHookParams(ctx *context.Context) webhookParams { 577 - form := web.GetForm(ctx).(*forms.NewWechatWorkHookForm) 578 - 579 - return webhookParams{ 580 - Type: webhook_module.WECHATWORK, 581 - URL: form.PayloadURL, 582 - ContentType: webhook.ContentTypeJSON, 583 - WebhookForm: form.WebhookForm, 584 - } 585 - } 586 - 587 - // PackagistHooksNewPost response for creating Packagist webhook 588 - func PackagistHooksNewPost(ctx *context.Context) { 589 - createWebhook(ctx, packagistHookParams(ctx)) 590 - } 591 - 592 - // PackagistHooksEditPost response for editing Packagist webhook 593 - func PackagistHooksEditPost(ctx *context.Context) { 594 - editWebhook(ctx, packagistHookParams(ctx)) 595 - } 596 - 597 - func packagistHookParams(ctx *context.Context) webhookParams { 598 - form := web.GetForm(ctx).(*forms.NewPackagistHookForm) 599 - 600 - return webhookParams{ 601 - Type: webhook_module.PACKAGIST, 602 - URL: fmt.Sprintf("https://packagist.org/api/update-package?username=%s&apiToken=%s", url.QueryEscape(form.Username), url.QueryEscape(form.APIToken)), 603 - ContentType: webhook.ContentTypeJSON, 604 - WebhookForm: form.WebhookForm, 605 - Meta: &webhook_service.PackagistMeta{ 606 - Username: form.Username, 607 - APIToken: form.APIToken, 608 - PackageURL: form.PackageURL, 609 - }, 610 - } 611 361 } 612 362 613 363 func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
+2 -24
routers/web/web.go
··· 402 402 403 403 addWebhookAddRoutes := func() { 404 404 m.Get("/{type}/new", repo_setting.WebhooksNew) 405 - m.Post("/forgejo/new", web.Bind(forms.NewWebhookForm{}), repo_setting.ForgejoHooksNewPost) 406 - m.Post("/gitea/new", web.Bind(forms.NewWebhookForm{}), repo_setting.GiteaHooksNewPost) 407 - m.Post("/gogs/new", web.Bind(forms.NewGogshookForm{}), repo_setting.GogsHooksNewPost) 408 - m.Post("/slack/new", web.Bind(forms.NewSlackHookForm{}), repo_setting.SlackHooksNewPost) 409 - m.Post("/discord/new", web.Bind(forms.NewDiscordHookForm{}), repo_setting.DiscordHooksNewPost) 410 - m.Post("/dingtalk/new", web.Bind(forms.NewDingtalkHookForm{}), repo_setting.DingtalkHooksNewPost) 411 - m.Post("/telegram/new", web.Bind(forms.NewTelegramHookForm{}), repo_setting.TelegramHooksNewPost) 412 - m.Post("/matrix/new", web.Bind(forms.NewMatrixHookForm{}), repo_setting.MatrixHooksNewPost) 413 - m.Post("/msteams/new", web.Bind(forms.NewMSTeamsHookForm{}), repo_setting.MSTeamsHooksNewPost) 414 - m.Post("/feishu/new", web.Bind(forms.NewFeishuHookForm{}), repo_setting.FeishuHooksNewPost) 415 - m.Post("/wechatwork/new", web.Bind(forms.NewWechatWorkHookForm{}), repo_setting.WechatworkHooksNewPost) 416 - m.Post("/packagist/new", web.Bind(forms.NewPackagistHookForm{}), repo_setting.PackagistHooksNewPost) 405 + m.Post("/{type}/new", repo_setting.WebhookCreate) 417 406 } 418 407 419 408 addWebhookEditRoutes := func() { 420 - m.Post("/forgejo/{id}", web.Bind(forms.NewWebhookForm{}), repo_setting.ForgejoHooksEditPost) 421 - m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo_setting.GiteaHooksEditPost) 422 - m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo_setting.GogsHooksEditPost) 423 - m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo_setting.SlackHooksEditPost) 424 - m.Post("/discord/{id}", web.Bind(forms.NewDiscordHookForm{}), repo_setting.DiscordHooksEditPost) 425 - m.Post("/dingtalk/{id}", web.Bind(forms.NewDingtalkHookForm{}), repo_setting.DingtalkHooksEditPost) 426 - m.Post("/telegram/{id}", web.Bind(forms.NewTelegramHookForm{}), repo_setting.TelegramHooksEditPost) 427 - m.Post("/matrix/{id}", web.Bind(forms.NewMatrixHookForm{}), repo_setting.MatrixHooksEditPost) 428 - m.Post("/msteams/{id}", web.Bind(forms.NewMSTeamsHookForm{}), repo_setting.MSTeamsHooksEditPost) 429 - m.Post("/feishu/{id}", web.Bind(forms.NewFeishuHookForm{}), repo_setting.FeishuHooksEditPost) 430 - m.Post("/wechatwork/{id}", web.Bind(forms.NewWechatWorkHookForm{}), repo_setting.WechatworkHooksEditPost) 431 - m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo_setting.PackagistHooksEditPost) 409 + m.Post("/{type}/{id:[0-9]+}", repo_setting.WebhookUpdate) 432 410 } 433 411 434 412 addSettingsVariablesRoutes := func() {
-157
services/forms/repo_form.go
··· 16 16 "code.gitea.io/gitea/modules/structs" 17 17 "code.gitea.io/gitea/modules/web/middleware" 18 18 "code.gitea.io/gitea/services/context" 19 - "code.gitea.io/gitea/services/webhook" 20 19 21 20 "gitea.com/go-chi/binding" 22 21 ) ··· 277 276 // ChooseEvents if the hook will be triggered choose events 278 277 func (f WebhookForm) ChooseEvents() bool { 279 278 return f.Events == "choose_events" 280 - } 281 - 282 - // NewWebhookForm form for creating web hook 283 - type NewWebhookForm struct { 284 - PayloadURL string `binding:"Required;ValidUrl"` 285 - HTTPMethod string `binding:"Required;In(POST,GET)"` 286 - ContentType int `binding:"Required"` 287 - Secret string 288 - WebhookForm 289 - } 290 - 291 - // Validate validates the fields 292 - func (f *NewWebhookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 293 - ctx := context.GetValidateContext(req) 294 - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 295 - } 296 - 297 - // NewGogshookForm form for creating gogs hook 298 - type NewGogshookForm struct { 299 - PayloadURL string `binding:"Required;ValidUrl"` 300 - ContentType int `binding:"Required"` 301 - Secret string 302 - WebhookForm 303 - } 304 - 305 - // Validate validates the fields 306 - func (f *NewGogshookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 307 - ctx := context.GetValidateContext(req) 308 - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 309 - } 310 - 311 - // NewSlackHookForm form for creating slack hook 312 - type NewSlackHookForm struct { 313 - PayloadURL string `binding:"Required;ValidUrl"` 314 - Channel string `binding:"Required"` 315 - Username string 316 - IconURL string 317 - Color string 318 - WebhookForm 319 - } 320 - 321 - // Validate validates the fields 322 - func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 323 - ctx := context.GetValidateContext(req) 324 - if !webhook.IsValidSlackChannel(strings.TrimSpace(f.Channel)) { 325 - errs = append(errs, binding.Error{ 326 - FieldNames: []string{"Channel"}, 327 - Classification: "", 328 - Message: ctx.Locale.TrString("repo.settings.add_webhook.invalid_channel_name"), 329 - }) 330 - } 331 - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 332 - } 333 - 334 - // NewDiscordHookForm form for creating discord hook 335 - type NewDiscordHookForm struct { 336 - PayloadURL string `binding:"Required;ValidUrl"` 337 - Username string 338 - IconURL string 339 - WebhookForm 340 - } 341 - 342 - // Validate validates the fields 343 - func (f *NewDiscordHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 344 - ctx := context.GetValidateContext(req) 345 - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 346 - } 347 - 348 - // NewDingtalkHookForm form for creating dingtalk hook 349 - type NewDingtalkHookForm struct { 350 - PayloadURL string `binding:"Required;ValidUrl"` 351 - WebhookForm 352 - } 353 - 354 - // Validate validates the fields 355 - func (f *NewDingtalkHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 356 - ctx := context.GetValidateContext(req) 357 - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 358 - } 359 - 360 - // NewTelegramHookForm form for creating telegram hook 361 - type NewTelegramHookForm struct { 362 - BotToken string `binding:"Required"` 363 - ChatID string `binding:"Required"` 364 - ThreadID string 365 - WebhookForm 366 - } 367 - 368 - // Validate validates the fields 369 - func (f *NewTelegramHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 370 - ctx := context.GetValidateContext(req) 371 - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 372 - } 373 - 374 - // NewMatrixHookForm form for creating Matrix hook 375 - type NewMatrixHookForm struct { 376 - HomeserverURL string `binding:"Required;ValidUrl"` 377 - RoomID string `binding:"Required"` 378 - MessageType int 379 - WebhookForm 380 - } 381 - 382 - // Validate validates the fields 383 - func (f *NewMatrixHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 384 - ctx := context.GetValidateContext(req) 385 - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 386 - } 387 - 388 - // NewMSTeamsHookForm form for creating MS Teams hook 389 - type NewMSTeamsHookForm struct { 390 - PayloadURL string `binding:"Required;ValidUrl"` 391 - WebhookForm 392 - } 393 - 394 - // Validate validates the fields 395 - func (f *NewMSTeamsHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 396 - ctx := context.GetValidateContext(req) 397 - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 398 - } 399 - 400 - // NewFeishuHookForm form for creating feishu hook 401 - type NewFeishuHookForm struct { 402 - PayloadURL string `binding:"Required;ValidUrl"` 403 - WebhookForm 404 - } 405 - 406 - // Validate validates the fields 407 - func (f *NewFeishuHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 408 - ctx := context.GetValidateContext(req) 409 - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 410 - } 411 - 412 - // NewWechatWorkHookForm form for creating wechatwork hook 413 - type NewWechatWorkHookForm struct { 414 - PayloadURL string `binding:"Required;ValidUrl"` 415 - WebhookForm 416 - } 417 - 418 - // Validate validates the fields 419 - func (f *NewWechatWorkHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 420 - ctx := context.GetValidateContext(req) 421 - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 422 - } 423 - 424 - // NewPackagistHookForm form for creating packagist hook 425 - type NewPackagistHookForm struct { 426 - Username string `binding:"Required"` 427 - APIToken string `binding:"Required"` 428 - PackageURL string `binding:"Required;ValidUrl"` 429 - WebhookForm 430 - } 431 - 432 - // Validate validates the fields 433 - func (f *NewPackagistHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 434 - ctx := context.GetValidateContext(req) 435 - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 436 279 } 437 280 438 281 // .___
+25
services/webhook/default.go
··· 18 18 webhook_model "code.gitea.io/gitea/models/webhook" 19 19 "code.gitea.io/gitea/modules/log" 20 20 webhook_module "code.gitea.io/gitea/modules/webhook" 21 + "code.gitea.io/gitea/services/forms" 21 22 ) 22 23 23 24 var _ Handler = defaultHandler{} ··· 34 35 } 35 36 36 37 func (defaultHandler) Metadata(*webhook_model.Webhook) any { return nil } 38 + 39 + func (defaultHandler) FormFields(bind func(any)) FormFields { 40 + var form struct { 41 + forms.WebhookForm 42 + PayloadURL string `binding:"Required;ValidUrl"` 43 + HTTPMethod string `binding:"Required;In(POST,GET)"` 44 + ContentType int `binding:"Required"` 45 + Secret string 46 + } 47 + bind(&form) 48 + 49 + contentType := webhook_model.ContentTypeJSON 50 + if webhook_model.HookContentType(form.ContentType) == webhook_model.ContentTypeForm { 51 + contentType = webhook_model.ContentTypeForm 52 + } 53 + return FormFields{ 54 + WebhookForm: form.WebhookForm, 55 + URL: form.PayloadURL, 56 + ContentType: contentType, 57 + Secret: form.Secret, 58 + HTTPMethod: form.HTTPMethod, 59 + Metadata: nil, 60 + } 61 + } 37 62 38 63 func (defaultHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (req *http.Request, body []byte, err error) { 39 64 switch w.HTTPMethod {
+17
services/webhook/dingtalk.go
··· 15 15 api "code.gitea.io/gitea/modules/structs" 16 16 "code.gitea.io/gitea/modules/util" 17 17 webhook_module "code.gitea.io/gitea/modules/webhook" 18 + "code.gitea.io/gitea/services/forms" 18 19 19 20 dingtalk "gitea.com/lunny/dingtalk_webhook" 20 21 ) ··· 23 24 24 25 func (dingtalkHandler) Type() webhook_module.HookType { return webhook_module.DINGTALK } 25 26 func (dingtalkHandler) Metadata(*webhook_model.Webhook) any { return nil } 27 + func (dingtalkHandler) FormFields(bind func(any)) FormFields { 28 + var form struct { 29 + forms.WebhookForm 30 + PayloadURL string `binding:"Required;ValidUrl"` 31 + } 32 + bind(&form) 33 + 34 + return FormFields{ 35 + WebhookForm: form.WebhookForm, 36 + URL: form.PayloadURL, 37 + ContentType: webhook_model.ContentTypeJSON, 38 + Secret: "", 39 + HTTPMethod: http.MethodPost, 40 + Metadata: nil, 41 + } 42 + } 26 43 27 44 type ( 28 45 // DingtalkPayload represents
+23
services/webhook/discord.go
··· 20 20 api "code.gitea.io/gitea/modules/structs" 21 21 "code.gitea.io/gitea/modules/util" 22 22 webhook_module "code.gitea.io/gitea/modules/webhook" 23 + "code.gitea.io/gitea/services/forms" 23 24 ) 24 25 25 26 type discordHandler struct{} 26 27 27 28 func (discordHandler) Type() webhook_module.HookType { return webhook_module.DISCORD } 29 + 30 + func (discordHandler) FormFields(bind func(any)) FormFields { 31 + var form struct { 32 + forms.WebhookForm 33 + PayloadURL string `binding:"Required;ValidUrl"` 34 + Username string 35 + IconURL string 36 + } 37 + bind(&form) 38 + 39 + return FormFields{ 40 + WebhookForm: form.WebhookForm, 41 + URL: form.PayloadURL, 42 + ContentType: webhook_model.ContentTypeJSON, 43 + Secret: "", 44 + HTTPMethod: http.MethodPost, 45 + Metadata: &DiscordMeta{ 46 + Username: form.Username, 47 + IconURL: form.IconURL, 48 + }, 49 + } 50 + } 28 51 29 52 type ( 30 53 // DiscordEmbedFooter for Embed Footer Structure.
+20 -1
services/webhook/feishu.go
··· 13 13 "code.gitea.io/gitea/modules/git" 14 14 api "code.gitea.io/gitea/modules/structs" 15 15 webhook_module "code.gitea.io/gitea/modules/webhook" 16 + "code.gitea.io/gitea/services/forms" 16 17 ) 17 18 18 19 type feishuHandler struct{} 19 20 20 - func (feishuHandler) Type() webhook_module.HookType { return webhook_module.FEISHU } 21 + func (feishuHandler) Type() webhook_module.HookType { return webhook_module.FEISHU } 22 + 23 + func (feishuHandler) FormFields(bind func(any)) FormFields { 24 + var form struct { 25 + forms.WebhookForm 26 + PayloadURL string `binding:"Required;ValidUrl"` 27 + } 28 + bind(&form) 29 + 30 + return FormFields{ 31 + WebhookForm: form.WebhookForm, 32 + URL: form.PayloadURL, 33 + ContentType: webhook_model.ContentTypeJSON, 34 + Secret: "", 35 + HTTPMethod: http.MethodPost, 36 + Metadata: nil, 37 + } 38 + } 39 + 21 40 func (feishuHandler) Metadata(*webhook_model.Webhook) any { return nil } 22 41 23 42 type (
+27
services/webhook/gogs.go
··· 4 4 package webhook 5 5 6 6 import ( 7 + "net/http" 8 + 9 + webhook_model "code.gitea.io/gitea/models/webhook" 7 10 webhook_module "code.gitea.io/gitea/modules/webhook" 11 + "code.gitea.io/gitea/services/forms" 8 12 ) 9 13 10 14 type gogsHandler struct{ defaultHandler } 11 15 12 16 func (gogsHandler) Type() webhook_module.HookType { return webhook_module.GOGS } 17 + 18 + func (gogsHandler) FormFields(bind func(any)) FormFields { 19 + var form struct { 20 + forms.WebhookForm 21 + PayloadURL string `binding:"Required;ValidUrl"` 22 + ContentType int `binding:"Required"` 23 + Secret string 24 + } 25 + bind(&form) 26 + 27 + contentType := webhook_model.ContentTypeJSON 28 + if webhook_model.HookContentType(form.ContentType) == webhook_model.ContentTypeForm { 29 + contentType = webhook_model.ContentTypeForm 30 + } 31 + return FormFields{ 32 + WebhookForm: form.WebhookForm, 33 + URL: form.PayloadURL, 34 + ContentType: contentType, 35 + Secret: form.Secret, 36 + HTTPMethod: http.MethodPost, 37 + Metadata: nil, 38 + } 39 + }
+28
services/webhook/matrix.go
··· 22 22 api "code.gitea.io/gitea/modules/structs" 23 23 "code.gitea.io/gitea/modules/util" 24 24 webhook_module "code.gitea.io/gitea/modules/webhook" 25 + "code.gitea.io/gitea/services/forms" 25 26 ) 26 27 27 28 type matrixHandler struct{} 28 29 29 30 func (matrixHandler) Type() webhook_module.HookType { return webhook_module.MATRIX } 31 + 32 + func (matrixHandler) FormFields(bind func(any)) FormFields { 33 + var form struct { 34 + forms.WebhookForm 35 + HomeserverURL string `binding:"Required;ValidUrl"` 36 + RoomID string `binding:"Required"` 37 + MessageType int 38 + 39 + // enforce requirement of authorization_header 40 + // (value will still be set in the embedded WebhookForm) 41 + AuthorizationHeader string `binding:"Required"` 42 + } 43 + bind(&form) 44 + 45 + return FormFields{ 46 + WebhookForm: form.WebhookForm, 47 + URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), 48 + ContentType: webhook_model.ContentTypeJSON, 49 + Secret: "", 50 + HTTPMethod: http.MethodPut, 51 + Metadata: &MatrixMeta{ 52 + HomeserverURL: form.HomeserverURL, 53 + Room: form.RoomID, 54 + MessageType: form.MessageType, 55 + }, 56 + } 57 + } 30 58 31 59 func (matrixHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { 32 60 meta := &MatrixMeta{}
+18
services/webhook/msteams.go
··· 15 15 api "code.gitea.io/gitea/modules/structs" 16 16 "code.gitea.io/gitea/modules/util" 17 17 webhook_module "code.gitea.io/gitea/modules/webhook" 18 + "code.gitea.io/gitea/services/forms" 18 19 ) 19 20 20 21 type msteamsHandler struct{} 21 22 22 23 func (msteamsHandler) Type() webhook_module.HookType { return webhook_module.MSTEAMS } 23 24 func (msteamsHandler) Metadata(*webhook_model.Webhook) any { return nil } 25 + 26 + func (msteamsHandler) FormFields(bind func(any)) FormFields { 27 + var form struct { 28 + forms.WebhookForm 29 + PayloadURL string `binding:"Required;ValidUrl"` 30 + } 31 + bind(&form) 32 + 33 + return FormFields{ 34 + WebhookForm: form.WebhookForm, 35 + URL: form.PayloadURL, 36 + ContentType: webhook_model.ContentTypeJSON, 37 + Secret: "", 38 + HTTPMethod: http.MethodPost, 39 + Metadata: nil, 40 + } 41 + } 24 42 25 43 type ( 26 44 // MSTeamsFact for Fact Structure
+25
services/webhook/packagist.go
··· 7 7 "context" 8 8 "fmt" 9 9 "net/http" 10 + "net/url" 10 11 11 12 webhook_model "code.gitea.io/gitea/models/webhook" 12 13 "code.gitea.io/gitea/modules/json" 13 14 "code.gitea.io/gitea/modules/log" 14 15 webhook_module "code.gitea.io/gitea/modules/webhook" 16 + "code.gitea.io/gitea/services/forms" 15 17 ) 16 18 17 19 type packagistHandler struct{} 18 20 19 21 func (packagistHandler) Type() webhook_module.HookType { return webhook_module.PACKAGIST } 22 + 23 + func (packagistHandler) FormFields(bind func(any)) FormFields { 24 + var form struct { 25 + forms.WebhookForm 26 + Username string `binding:"Required"` 27 + APIToken string `binding:"Required"` 28 + PackageURL string `binding:"Required;ValidUrl"` 29 + } 30 + bind(&form) 31 + 32 + return FormFields{ 33 + WebhookForm: form.WebhookForm, 34 + URL: fmt.Sprintf("https://packagist.org/api/update-package?username=%s&apiToken=%s", url.QueryEscape(form.Username), url.QueryEscape(form.APIToken)), 35 + ContentType: webhook_model.ContentTypeJSON, 36 + Secret: "", 37 + HTTPMethod: http.MethodPost, 38 + Metadata: &PackagistMeta{ 39 + Username: form.Username, 40 + APIToken: form.APIToken, 41 + PackageURL: form.PackageURL, 42 + }, 43 + } 44 + } 20 45 21 46 type ( 22 47 // PackagistPayload represents a packagist payload
+47
services/webhook/slack.go
··· 17 17 "code.gitea.io/gitea/modules/setting" 18 18 api "code.gitea.io/gitea/modules/structs" 19 19 webhook_module "code.gitea.io/gitea/modules/webhook" 20 + gitea_context "code.gitea.io/gitea/services/context" 21 + "code.gitea.io/gitea/services/forms" 22 + 23 + "gitea.com/go-chi/binding" 20 24 ) 21 25 22 26 type slackHandler struct{} 23 27 24 28 func (slackHandler) Type() webhook_module.HookType { return webhook_module.SLACK } 29 + 30 + type slackForm struct { 31 + forms.WebhookForm 32 + PayloadURL string `binding:"Required;ValidUrl"` 33 + Channel string `binding:"Required"` 34 + Username string 35 + IconURL string 36 + Color string 37 + } 38 + 39 + var _ binding.Validator = &slackForm{} 40 + 41 + // Validate implements binding.Validator. 42 + func (s *slackForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 43 + ctx := gitea_context.GetWebContext(req) 44 + if !IsValidSlackChannel(strings.TrimSpace(s.Channel)) { 45 + errs = append(errs, binding.Error{ 46 + FieldNames: []string{"Channel"}, 47 + Classification: "", 48 + Message: ctx.Locale.TrString("repo.settings.add_webhook.invalid_channel_name"), 49 + }) 50 + } 51 + return errs 52 + } 53 + 54 + func (slackHandler) FormFields(bind func(any)) FormFields { 55 + var form slackForm 56 + bind(&form) 57 + 58 + return FormFields{ 59 + WebhookForm: form.WebhookForm, 60 + URL: form.PayloadURL, 61 + ContentType: webhook_model.ContentTypeJSON, 62 + Secret: "", 63 + HTTPMethod: http.MethodPost, 64 + Metadata: &SlackMeta{ 65 + Channel: strings.TrimSpace(form.Channel), 66 + Username: form.Username, 67 + IconURL: form.IconURL, 68 + Color: form.Color, 69 + }, 70 + } 71 + } 25 72 26 73 // SlackMeta contains the slack metadata 27 74 type SlackMeta struct {
+25
services/webhook/telegram.go
··· 7 7 "context" 8 8 "fmt" 9 9 "net/http" 10 + "net/url" 10 11 "strings" 11 12 12 13 webhook_model "code.gitea.io/gitea/models/webhook" ··· 15 16 "code.gitea.io/gitea/modules/log" 16 17 api "code.gitea.io/gitea/modules/structs" 17 18 webhook_module "code.gitea.io/gitea/modules/webhook" 19 + "code.gitea.io/gitea/services/forms" 18 20 ) 19 21 20 22 type telegramHandler struct{} 21 23 22 24 func (telegramHandler) Type() webhook_module.HookType { return webhook_module.TELEGRAM } 25 + 26 + func (telegramHandler) FormFields(bind func(any)) FormFields { 27 + var form struct { 28 + forms.WebhookForm 29 + BotToken string `binding:"Required"` 30 + ChatID string `binding:"Required"` 31 + ThreadID string 32 + } 33 + bind(&form) 34 + 35 + return FormFields{ 36 + WebhookForm: form.WebhookForm, 37 + URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&message_thread_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID), url.QueryEscape(form.ThreadID)), 38 + ContentType: webhook_model.ContentTypeJSON, 39 + Secret: "", 40 + HTTPMethod: http.MethodPost, 41 + Metadata: &TelegramMeta{ 42 + BotToken: form.BotToken, 43 + ChatID: form.ChatID, 44 + ThreadID: form.ThreadID, 45 + }, 46 + } 47 + } 23 48 24 49 type ( 25 50 // TelegramPayload represents
+14 -1
services/webhook/webhook.go
··· 23 23 api "code.gitea.io/gitea/modules/structs" 24 24 "code.gitea.io/gitea/modules/util" 25 25 webhook_module "code.gitea.io/gitea/modules/webhook" 26 + "code.gitea.io/gitea/services/forms" 26 27 27 28 "github.com/gobwas/glob" 28 29 ) 29 30 30 31 type Handler interface { 31 32 Type() webhook_module.HookType 33 + Metadata(*webhook_model.Webhook) any 34 + // FormFields provides a function to bind the request to the form. 35 + // If form implements the [binding.Validator] interface, the Validate method will be called 36 + FormFields(bind func(form any)) FormFields 32 37 NewRequest(context.Context, *webhook_model.Webhook, *webhook_model.HookTask) (req *http.Request, body []byte, err error) 33 - Metadata(*webhook_model.Webhook) any 38 + } 39 + 40 + type FormFields struct { 41 + forms.WebhookForm 42 + URL string 43 + ContentType webhook_model.HookContentType 44 + Secret string 45 + HTTPMethod string 46 + Metadata any 34 47 } 35 48 36 49 var webhookHandlers = []Handler{
+18
services/webhook/wechatwork.go
··· 13 13 "code.gitea.io/gitea/modules/git" 14 14 api "code.gitea.io/gitea/modules/structs" 15 15 webhook_module "code.gitea.io/gitea/modules/webhook" 16 + "code.gitea.io/gitea/services/forms" 16 17 ) 17 18 18 19 type wechatworkHandler struct{} 19 20 20 21 func (wechatworkHandler) Type() webhook_module.HookType { return webhook_module.WECHATWORK } 21 22 func (wechatworkHandler) Metadata(*webhook_model.Webhook) any { return nil } 23 + 24 + func (wechatworkHandler) FormFields(bind func(any)) FormFields { 25 + var form struct { 26 + forms.WebhookForm 27 + PayloadURL string `binding:"Required;ValidUrl"` 28 + } 29 + bind(&form) 30 + 31 + return FormFields{ 32 + WebhookForm: form.WebhookForm, 33 + URL: form.PayloadURL, 34 + ContentType: webhook_model.ContentTypeJSON, 35 + Secret: "", 36 + HTTPMethod: http.MethodPost, 37 + Metadata: nil, 38 + } 39 + } 22 40 23 41 type ( 24 42 // WechatworkPayload represents
+2 -2
tests/integration/repo_webhook_test.go
··· 203 203 "homeserver_url": "https://matrix.example.com", 204 204 "room_id": "123", 205 205 "authorization_header": "Bearer 123456", 206 - // }, map[string]string{ // authorization_header is actually required, but not enforced (yet) 207 - // "authorization_header": "", 206 + }, map[string]string{ 207 + "authorization_header": "", 208 208 })) 209 209 t.Run("matrix/optional", testWebhookForms("matrix", session, map[string]string{ 210 210 "homeserver_url": "https://matrix.example.com",