Skip to content

Commit e59bbd0

Browse files
committed
Merge branch 'feature/webhook-payload-optimization' of github.com:kerwin612/gitea into kerwin612-feature/webhook-payload-optimization
2 parents 731d803 + ef6aeda commit e59bbd0

File tree

17 files changed

+876
-3
lines changed

17 files changed

+876
-3
lines changed

models/migrations/migrations.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ func prepareMigrationTasks() []*migration {
395395
newMigration(321, "Use LONGTEXT for some columns and fix review_state.updated_files column", v1_25.UseLongTextInSomeColumnsAndFixBugs),
396396
newMigration(322, "Extend comment tree_path length limit", v1_25.ExtendCommentTreePathLength),
397397
newMigration(323, "Add support for actions concurrency", v1_25.AddActionsConcurrency),
398+
newMigration(324, "Add webhook payload optimization JSON field", v1_25.AddWebhookPayloadOptimizationColumns),
398399
}
399400
return preparedMigrations
400401
}

models/migrations/v1_25/v324.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_25
5+
6+
import (
7+
"xorm.io/xorm"
8+
)
9+
10+
func AddWebhookPayloadOptimizationColumns(x *xorm.Engine) error {
11+
type Webhook struct {
12+
MetaSettings string `xorm:"meta_settings TEXT"`
13+
}
14+
_, err := x.SyncWithOptions(
15+
xorm.SyncOptions{
16+
IgnoreConstrains: true,
17+
IgnoreIndices: true,
18+
},
19+
new(Webhook),
20+
)
21+
return err
22+
}

models/webhook/webhook.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,35 @@ import (
2222
"xorm.io/builder"
2323
)
2424

25+
// MetaSettings represents the metadata settings for webhook
26+
type MetaSettings struct {
27+
PayloadConfig PayloadConfig `json:"payload_config"` // Payload configuration
28+
}
29+
30+
// PayloadConfig represents the configuration for webhook payload
31+
type PayloadConfig struct {
32+
Files PayloadConfigItem `json:"files"` // Files configuration
33+
Commits PayloadConfigItem `json:"commits"` // Commits configuration
34+
}
35+
36+
// PayloadConfigItem represents a single payload configuration item
37+
type PayloadConfigItem struct {
38+
Enable bool `json:"enable"` // Whether to enable this configuration
39+
Limit int `json:"limit"` // 0: trim all (none kept), >0: keep N items (forward order), <0: keep N items (reverse order)
40+
}
41+
42+
// DefaultMetaSettings returns the default webhook meta settings
43+
func DefaultMetaSettings() MetaSettings {
44+
return MetaSettings{
45+
PayloadConfig: DefaultPayloadConfig(),
46+
}
47+
}
48+
49+
// DefaultPayloadConfig returns the default payload configuration
50+
func DefaultPayloadConfig() PayloadConfig {
51+
return PayloadConfig{}
52+
}
53+
2554
// ErrWebhookNotExist represents a "WebhookNotExist" kind of error.
2655
type ErrWebhookNotExist struct {
2756
ID int64
@@ -139,6 +168,9 @@ type Webhook struct {
139168
// HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization()
140169
HeaderAuthorizationEncrypted string `xorm:"TEXT"`
141170

171+
// Webhook metadata settings (JSON format)
172+
MetaSettings string `xorm:"meta_settings TEXT"` // JSON: webhook metadata configuration
173+
142174
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
143175
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
144176
}
@@ -346,3 +378,83 @@ func DeleteWebhookByOwnerID(ctx context.Context, ownerID, id int64) error {
346378
}
347379
return DeleteWebhookByID(ctx, id)
348380
}
381+
382+
// GetMetaSettings returns the webhook meta settings
383+
func (w *Webhook) GetMetaSettings() MetaSettings {
384+
if w.MetaSettings == "" {
385+
return DefaultMetaSettings()
386+
}
387+
388+
var settings MetaSettings
389+
if err := json.Unmarshal([]byte(w.MetaSettings), &settings); err != nil {
390+
log.Error("Failed to unmarshal webhook meta settings: %v", err)
391+
return DefaultMetaSettings()
392+
}
393+
394+
return settings
395+
}
396+
397+
// GetPayloadConfig returns the payload configuration
398+
func (w *Webhook) GetPayloadConfig() PayloadConfig {
399+
return w.GetMetaSettings().PayloadConfig
400+
}
401+
402+
// SetMetaSettings sets the webhook meta settings
403+
func (w *Webhook) SetMetaSettings(settings MetaSettings) error {
404+
data, err := json.Marshal(settings)
405+
if err != nil {
406+
return fmt.Errorf("failed to marshal webhook meta settings: %w", err)
407+
}
408+
409+
w.MetaSettings = string(data)
410+
return nil
411+
}
412+
413+
// SetPayloadConfig sets the payload configuration
414+
func (w *Webhook) SetPayloadConfig(config PayloadConfig) error {
415+
settings := w.GetMetaSettings()
416+
settings.PayloadConfig = config
417+
return w.SetMetaSettings(settings)
418+
}
419+
420+
// IsPayloadConfigEnabled returns whether payload configuration is enabled
421+
func (w *Webhook) IsPayloadConfigEnabled() bool {
422+
config := w.GetPayloadConfig()
423+
return config.Files.Enable || config.Commits.Enable
424+
}
425+
426+
// GetPayloadConfigLimit returns the payload configuration limit
427+
func (w *Webhook) GetPayloadConfigLimit() int {
428+
config := w.GetPayloadConfig()
429+
if config.Files.Enable {
430+
return config.Files.Limit
431+
}
432+
if config.Commits.Enable {
433+
return config.Commits.Limit
434+
}
435+
return 0
436+
}
437+
438+
// IsFilesConfigEnabled returns whether files configuration is enabled
439+
func (w *Webhook) IsFilesConfigEnabled() bool {
440+
config := w.GetPayloadConfig()
441+
return config.Files.Enable
442+
}
443+
444+
// GetFilesConfigLimit returns the files configuration limit
445+
func (w *Webhook) GetFilesConfigLimit() int {
446+
config := w.GetPayloadConfig()
447+
return config.Files.Limit
448+
}
449+
450+
// IsCommitsConfigEnabled returns whether commits configuration is enabled
451+
func (w *Webhook) IsCommitsConfigEnabled() bool {
452+
config := w.GetPayloadConfig()
453+
return config.Commits.Enable
454+
}
455+
456+
// GetCommitsConfigLimit returns the commits configuration limit
457+
func (w *Webhook) GetCommitsConfigLimit() int {
458+
config := w.GetPayloadConfig()
459+
return config.Commits.Limit
460+
}

models/webhook/webhook_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,63 @@ func TestCleanupHookTaskTable_OlderThan_LeavesTaskEarlierThanAgeToDelete(t *test
330330
assert.NoError(t, CleanupHookTaskTable(t.Context(), OlderThan, 168*time.Hour, 0))
331331
unittest.AssertExistsAndLoadBean(t, hookTask)
332332
}
333+
334+
func TestWebhookPayloadOptimization(t *testing.T) {
335+
webhook := &Webhook{}
336+
337+
// Test default configuration
338+
config := webhook.GetPayloadConfig()
339+
assert.False(t, config.Files.Enable)
340+
assert.Equal(t, 0, config.Files.Limit)
341+
assert.False(t, config.Commits.Enable)
342+
assert.Equal(t, 0, config.Commits.Limit)
343+
344+
// Test setting configuration via meta settings
345+
metaSettings := MetaSettings{
346+
PayloadConfig: PayloadConfig{
347+
Files: PayloadConfigItem{
348+
Enable: true,
349+
Limit: 5,
350+
},
351+
Commits: PayloadConfigItem{
352+
Enable: true,
353+
Limit: -3,
354+
},
355+
},
356+
}
357+
webhook.SetMetaSettings(metaSettings)
358+
359+
// Test getting configuration
360+
config = webhook.GetPayloadConfig()
361+
assert.True(t, config.Files.Enable)
362+
assert.Equal(t, 5, config.Files.Limit)
363+
assert.True(t, config.Commits.Enable)
364+
assert.Equal(t, -3, config.Commits.Limit)
365+
366+
// Test individual methods
367+
assert.True(t, webhook.IsFilesConfigEnabled())
368+
assert.Equal(t, 5, webhook.GetFilesConfigLimit())
369+
assert.True(t, webhook.IsCommitsConfigEnabled())
370+
assert.Equal(t, -3, webhook.GetCommitsConfigLimit())
371+
assert.True(t, webhook.IsPayloadConfigEnabled())
372+
373+
// Test backward compatibility with direct payload config setting
374+
newConfig := PayloadConfig{
375+
Files: PayloadConfigItem{
376+
Enable: false,
377+
Limit: 10,
378+
},
379+
Commits: PayloadConfigItem{
380+
Enable: false,
381+
Limit: 20,
382+
},
383+
}
384+
webhook.SetPayloadConfig(newConfig)
385+
386+
// Verify the config is properly set through meta settings
387+
config = webhook.GetPayloadConfig()
388+
assert.False(t, config.Files.Enable)
389+
assert.Equal(t, 10, config.Files.Limit)
390+
assert.False(t, config.Commits.Enable)
391+
assert.Equal(t, 20, config.Commits.Limit)
392+
}

modules/structs/hook.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ type Hook struct {
3333
AuthorizationHeader string `json:"authorization_header"`
3434
// Whether the webhook is active and will be triggered
3535
Active bool `json:"active"`
36+
// MetaSettings webhook metadata settings including payload optimization
37+
MetaSettings map[string]any `json:"meta_settings"`
3638
// swagger:strfmt date-time
3739
// The date and time when the webhook was last updated
3840
Updated time.Time `json:"updated_at"`
@@ -63,6 +65,8 @@ type CreateHookOption struct {
6365
BranchFilter string `json:"branch_filter" binding:"GlobPattern"`
6466
// Authorization header to include in webhook requests
6567
AuthorizationHeader string `json:"authorization_header"`
68+
// Webhook metadata settings including payload optimization
69+
MetaSettings map[string]any `json:"meta_settings"` // {"payload_config": {"files": {"enable": bool, "limit": int}, "commits": {"enable": bool, "limit": int}}}
6670
// default: false
6771
// Whether the webhook should be active upon creation
6872
Active bool `json:"active"`
@@ -78,6 +82,8 @@ type EditHookOption struct {
7882
BranchFilter string `json:"branch_filter" binding:"GlobPattern"`
7983
// Authorization header to include in webhook requests
8084
AuthorizationHeader string `json:"authorization_header"`
85+
// Webhook metadata settings including payload optimization
86+
MetaSettings *map[string]any `json:"meta_settings"` // {"payload_config": {"files": {"enable": bool, "limit": int}, "commits": {"enable": bool, "limit": int}}}
8187
// Whether the webhook is active and will be triggered
8288
Active *bool `json:"active"`
8389
}

options/locale/locale_en-US.ini

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2437,6 +2437,13 @@ settings.branch_filter = Branch filter
24372437
settings.branch_filter_desc_1 = Branch (and ref name) allowlist for push, branch creation and branch deletion events, specified as glob pattern. If empty or <code>*</code>, events for all branches and tags are reported.
24382438
settings.branch_filter_desc_2 = Use <code>refs/heads/</code> or <code>refs/tags/</code> prefix to match full ref names.
24392439
settings.branch_filter_desc_doc = See <a href="%[1]s">%[2]s</a> documentation for syntax.
2440+
settings.payload_optimization = Payload Size Optimization
2441+
settings.payload_optimization_files = Files
2442+
settings.payload_optimization_commits = Commits
2443+
settings.payload_optimization_enable = Enable optimization
2444+
settings.payload_optimization_enable_desc = Enable payload size optimization for this item
2445+
settings.payload_optimization_limit = Limit
2446+
settings.payload_optimization_limit_desc = 0: trim all (none kept), >0: keep N items (forward order), <0: keep N items (reverse order)
24402447
settings.authorization_header = Authorization Header
24412448
settings.authorization_header_desc = Will be included as authorization header for requests when present. Examples: %s.
24422449
settings.active = Active
@@ -3300,7 +3307,7 @@ auths.tip.github = Register a new OAuth application on %s
33003307
auths.tip.gitlab_new = Register a new application on %s
33013308
auths.tip.google_plus = Obtain OAuth2 client credentials from the Google API console at %s
33023309
auths.tip.openid_connect = Use the OpenID Connect Discovery URL "https://{server}/.well-known/openid-configuration" to specify the endpoints
3303-
auths.tip.twitter = Go to %s, create an application and ensure that the Allow this application to be used to Sign in with Twitter option is enabled
3310+
auths.tip.twitter = Go to %s, create an application and ensure that the "Allow this application to be used to Sign in with Twitter" option is enabled
33043311
auths.tip.discord = Register a new application on %s
33053312
auths.tip.gitea = Register a new OAuth2 application. Guide can be found at %s
33063313
auths.tip.yandex = Create a new application at %s. Select following permissions from the "Yandex.Passport API" section: "Access to email address", "Access to user avatar" and "Access to username, first name and surname, gender"

routers/api/v1/utils/hook.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,33 @@ import (
2121
webhook_service "code.gitea.io/gitea/services/webhook"
2222
)
2323

24+
// getPayloadConfigEnable extracts the "enable" boolean value from a payload config map
25+
func getPayloadConfigEnable(m map[string]any) bool {
26+
if val, ok := m["enable"]; ok {
27+
if boolVal, ok := val.(bool); ok {
28+
return boolVal
29+
}
30+
}
31+
return false
32+
}
33+
34+
// getPayloadConfigLimit extracts the "limit" integer value from a payload config map
35+
func getPayloadConfigLimit(m map[string]any) int {
36+
if val, ok := m["limit"]; ok {
37+
switch v := val.(type) {
38+
case int:
39+
return v
40+
case float64:
41+
return int(v)
42+
case string:
43+
if intVal, err := strconv.Atoi(v); err == nil {
44+
return intVal
45+
}
46+
}
47+
}
48+
return 0
49+
}
50+
2451
// ListOwnerHooks lists the webhooks of the provided owner
2552
func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) {
2653
opts := &webhook.ListWebhookOptions{
@@ -227,6 +254,44 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
227254
IsActive: form.Active,
228255
Type: form.Type,
229256
}
257+
258+
// Set webhook meta settings
259+
if form.MetaSettings != nil {
260+
metaSettings := webhook.MetaSettings{}
261+
262+
// Parse payload config
263+
if payloadOptMap, ok := form.MetaSettings["payload_config"].(map[string]any); ok {
264+
payloadOptConfig := webhook.PayloadConfig{}
265+
266+
// Parse files config
267+
if filesConfig, ok := payloadOptMap["files"].(map[string]any); ok {
268+
payloadOptConfig.Files = webhook.PayloadConfigItem{
269+
Enable: getPayloadConfigEnable(filesConfig),
270+
Limit: getPayloadConfigLimit(filesConfig),
271+
}
272+
} else {
273+
payloadOptConfig.Files = webhook.PayloadConfigItem{Enable: false, Limit: 0}
274+
}
275+
276+
// Parse commits config
277+
if commitsConfig, ok := payloadOptMap["commits"].(map[string]any); ok {
278+
payloadOptConfig.Commits = webhook.PayloadConfigItem{
279+
Enable: getPayloadConfigEnable(commitsConfig),
280+
Limit: getPayloadConfigLimit(commitsConfig),
281+
}
282+
} else {
283+
payloadOptConfig.Commits = webhook.PayloadConfigItem{Enable: false, Limit: 0}
284+
}
285+
286+
metaSettings.PayloadConfig = payloadOptConfig
287+
}
288+
289+
if err := w.SetMetaSettings(metaSettings); err != nil {
290+
ctx.APIErrorInternal(err)
291+
return nil, false
292+
}
293+
}
294+
230295
err := w.SetHeaderAuthorization(form.AuthorizationHeader)
231296
if err != nil {
232297
ctx.APIErrorInternal(err)
@@ -391,6 +456,43 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
391456
w.IsActive = *form.Active
392457
}
393458

459+
// Update webhook meta settings
460+
if form.MetaSettings != nil {
461+
metaSettings := webhook.MetaSettings{}
462+
463+
// Parse payload config
464+
if payloadOptMap, ok := (*form.MetaSettings)["payload_config"].(map[string]any); ok {
465+
payloadOptConfig := webhook.PayloadConfig{}
466+
467+
// Parse files config
468+
if filesConfig, ok := payloadOptMap["files"].(map[string]any); ok {
469+
payloadOptConfig.Files = webhook.PayloadConfigItem{
470+
Enable: getPayloadConfigEnable(filesConfig),
471+
Limit: getPayloadConfigLimit(filesConfig),
472+
}
473+
} else {
474+
payloadOptConfig.Files = webhook.PayloadConfigItem{Enable: false, Limit: 0}
475+
}
476+
477+
// Parse commits config
478+
if commitsConfig, ok := payloadOptMap["commits"].(map[string]any); ok {
479+
payloadOptConfig.Commits = webhook.PayloadConfigItem{
480+
Enable: getPayloadConfigEnable(commitsConfig),
481+
Limit: getPayloadConfigLimit(commitsConfig),
482+
}
483+
} else {
484+
payloadOptConfig.Commits = webhook.PayloadConfigItem{Enable: false, Limit: 0}
485+
}
486+
487+
metaSettings.PayloadConfig = payloadOptConfig
488+
}
489+
490+
if err := w.SetMetaSettings(metaSettings); err != nil {
491+
ctx.APIErrorInternal(err)
492+
return false
493+
}
494+
}
495+
394496
if err := webhook.UpdateWebhook(ctx, w); err != nil {
395497
ctx.APIErrorInternal(err)
396498
return false

0 commit comments

Comments
 (0)