Skip to content

Commit 81f8006

Browse files
committed
refactor: replace webhook payload optimization with JSON-based configuration
1 parent 11cdba1 commit 81f8006

File tree

15 files changed

+530
-190
lines changed

15 files changed

+530
-190
lines changed

models/migrations/migrations.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ func prepareMigrationTasks() []*migration {
385385
newMigration(320, "Migrate two_factor_policy to login_source table", v1_24.MigrateSkipTwoFactor),
386386
// Gitea 1.24.0 ends at database version 321
387387
newMigration(321, "Use LONGTEXT for some columns and fix review_state.updated_files column", v1_25.UseLongTextInSomeColumnsAndFixBugs),
388-
newMigration(322, "Add webhook payload optimization columns", v1_25.AddWebhookPayloadOptimizationColumns),
388+
newMigration(322, "Add webhook payload optimization JSON field", v1_25.AddWebhookPayloadOptimizationColumns),
389389
}
390390
return preparedMigrations
391391
}

models/migrations/v1_25/v322.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import (
99

1010
func AddWebhookPayloadOptimizationColumns(x *xorm.Engine) error {
1111
type Webhook struct {
12-
ExcludeFilesLimit int `xorm:"exclude_files_limit NOT NULL DEFAULT 0"`
13-
ExcludeCommitsLimit int `xorm:"exclude_commits_limit NOT NULL DEFAULT 0"`
12+
PayloadOptimization string `xorm:"payload_optimization TEXT"`
1413
}
1514
_, err := x.SyncWithOptions(
1615
xorm.SyncOptions{

models/webhook/webhook.go

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

25+
// PayloadOptimizationConfig represents the configuration for webhook payload optimization
26+
type PayloadOptimizationConfig struct {
27+
Files *PayloadOptimizationItem `json:"files,omitempty"` // Files optimization config
28+
Commits *PayloadOptimizationItem `json:"commits,omitempty"` // Commits optimization config
29+
}
30+
31+
// PayloadOptimizationItem represents a single optimization item configuration
32+
type PayloadOptimizationItem struct {
33+
Enable bool `json:"enable"` // Whether to enable optimization for this item
34+
Limit int `json:"limit"` // 0: trim all (none kept), >0: keep N items (forward order), <0: keep N items (reverse order)
35+
}
36+
37+
// DefaultPayloadOptimizationConfig returns the default payload optimization configuration
38+
func DefaultPayloadOptimizationConfig() *PayloadOptimizationConfig {
39+
return &PayloadOptimizationConfig{
40+
Files: &PayloadOptimizationItem{Enable: false, Limit: 0},
41+
Commits: &PayloadOptimizationItem{Enable: false, Limit: 0},
42+
}
43+
}
44+
2545
// ErrWebhookNotExist represents a "WebhookNotExist" kind of error.
2646
type ErrWebhookNotExist struct {
2747
ID int64
@@ -140,8 +160,7 @@ type Webhook struct {
140160
HeaderAuthorizationEncrypted string `xorm:"TEXT"`
141161

142162
// Payload size optimization options
143-
ExcludeFilesLimit int `xorm:"exclude_files_limit"` // -1: trim all (none kept), 0: do not trim, >0: keep N file changes in commit payloads
144-
ExcludeCommitsLimit int `xorm:"exclude_commits_limit"` // -1: trim all (none kept), 0: do not trim, >0: keep N commits in push payloads
163+
PayloadOptimization string `xorm:"payload_optimization TEXT"` // JSON: {"enable": bool, "limit": int}
145164

146165
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
147166
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
@@ -350,3 +369,75 @@ func DeleteWebhookByOwnerID(ctx context.Context, ownerID, id int64) error {
350369
}
351370
return DeleteWebhookByID(ctx, id)
352371
}
372+
373+
// GetPayloadOptimizationConfig returns the payload optimization configuration
374+
func (w *Webhook) GetPayloadOptimizationConfig() *PayloadOptimizationConfig {
375+
if w.PayloadOptimization == "" {
376+
return DefaultPayloadOptimizationConfig()
377+
}
378+
379+
var config PayloadOptimizationConfig
380+
if err := json.Unmarshal([]byte(w.PayloadOptimization), &config); err != nil {
381+
log.Error("Failed to unmarshal payload optimization config: %v", err)
382+
return DefaultPayloadOptimizationConfig()
383+
}
384+
385+
return &config
386+
}
387+
388+
// SetPayloadOptimizationConfig sets the payload optimization configuration
389+
func (w *Webhook) SetPayloadOptimizationConfig(config *PayloadOptimizationConfig) error {
390+
if config == nil {
391+
config = DefaultPayloadOptimizationConfig()
392+
}
393+
394+
data, err := json.Marshal(config)
395+
if err != nil {
396+
return fmt.Errorf("failed to marshal payload optimization config: %w", err)
397+
}
398+
399+
w.PayloadOptimization = string(data)
400+
return nil
401+
}
402+
403+
// IsPayloadOptimizationEnabled returns whether payload optimization is enabled
404+
func (w *Webhook) IsPayloadOptimizationEnabled() bool {
405+
config := w.GetPayloadOptimizationConfig()
406+
return config.Files.Enable || config.Commits.Enable
407+
}
408+
409+
// GetPayloadOptimizationLimit returns the payload optimization limit
410+
func (w *Webhook) GetPayloadOptimizationLimit() int {
411+
config := w.GetPayloadOptimizationConfig()
412+
if config.Files.Enable {
413+
return config.Files.Limit
414+
}
415+
if config.Commits.Enable {
416+
return config.Commits.Limit
417+
}
418+
return 0
419+
}
420+
421+
// IsFilesOptimizationEnabled returns whether files optimization is enabled
422+
func (w *Webhook) IsFilesOptimizationEnabled() bool {
423+
config := w.GetPayloadOptimizationConfig()
424+
return config.Files.Enable
425+
}
426+
427+
// GetFilesOptimizationLimit returns the files optimization limit
428+
func (w *Webhook) GetFilesOptimizationLimit() int {
429+
config := w.GetPayloadOptimizationConfig()
430+
return config.Files.Limit
431+
}
432+
433+
// IsCommitsOptimizationEnabled returns whether commits optimization is enabled
434+
func (w *Webhook) IsCommitsOptimizationEnabled() bool {
435+
config := w.GetPayloadOptimizationConfig()
436+
return config.Commits.Enable
437+
}
438+
439+
// GetCommitsOptimizationLimit returns the commits optimization limit
440+
func (w *Webhook) GetCommitsOptimizationLimit() int {
441+
config := w.GetPayloadOptimizationConfig()
442+
return config.Commits.Limit
443+
}

models/webhook/webhook_test.go

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -332,43 +332,39 @@ func TestCleanupHookTaskTable_OlderThan_LeavesTaskEarlierThanAgeToDelete(t *test
332332
}
333333

334334
func TestWebhookPayloadOptimization(t *testing.T) {
335-
assert.NoError(t, unittest.PrepareTestDatabase())
336-
337-
webhook := &Webhook{
338-
RepoID: 1,
339-
URL: "http://example.com/webhook",
340-
HTTPMethod: "POST",
341-
ContentType: ContentTypeJSON,
342-
Secret: "secret",
343-
IsActive: true,
344-
Type: webhook_module.GITEA,
345-
ExcludeFilesLimit: 1,
346-
ExcludeCommitsLimit: 0,
347-
HookEvent: &webhook_module.HookEvent{
348-
PushOnly: true,
335+
webhook := &Webhook{}
336+
337+
// Test default configuration
338+
config := webhook.GetPayloadOptimizationConfig()
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
345+
newConfig := &PayloadOptimizationConfig{
346+
Files: &PayloadOptimizationItem{
347+
Enable: true,
348+
Limit: 5,
349+
},
350+
Commits: &PayloadOptimizationItem{
351+
Enable: true,
352+
Limit: -3,
349353
},
350354
}
351-
352-
// Test creating webhook with payload optimization options
353-
err := CreateWebhook(db.DefaultContext, webhook)
354-
assert.NoError(t, err)
355-
assert.NotZero(t, webhook.ID)
356-
357-
// Test retrieving webhook and checking payload optimization options
358-
retrievedWebhook, err := GetWebhookByID(db.DefaultContext, webhook.ID)
359-
assert.NoError(t, err)
360-
assert.Equal(t, 1, retrievedWebhook.ExcludeFilesLimit)
361-
assert.Equal(t, 0, retrievedWebhook.ExcludeCommitsLimit)
362-
363-
// Test updating webhook with different payload optimization options
364-
retrievedWebhook.ExcludeFilesLimit = 0
365-
retrievedWebhook.ExcludeCommitsLimit = 2
366-
err = UpdateWebhook(db.DefaultContext, retrievedWebhook)
367-
assert.NoError(t, err)
368-
369-
// Verify the update
370-
updatedWebhook, err := GetWebhookByID(db.DefaultContext, webhook.ID)
371-
assert.NoError(t, err)
372-
assert.Equal(t, 0, updatedWebhook.ExcludeFilesLimit)
373-
assert.Equal(t, 2, updatedWebhook.ExcludeCommitsLimit)
355+
webhook.SetPayloadOptimizationConfig(newConfig)
356+
357+
// Test getting configuration
358+
config = webhook.GetPayloadOptimizationConfig()
359+
assert.True(t, config.Files.Enable)
360+
assert.Equal(t, 5, config.Files.Limit)
361+
assert.True(t, config.Commits.Enable)
362+
assert.Equal(t, -3, config.Commits.Limit)
363+
364+
// Test individual methods
365+
assert.True(t, webhook.IsFilesOptimizationEnabled())
366+
assert.Equal(t, 5, webhook.GetFilesOptimizationLimit())
367+
assert.True(t, webhook.IsCommitsOptimizationEnabled())
368+
assert.Equal(t, -3, webhook.GetCommitsOptimizationLimit())
369+
assert.True(t, webhook.IsPayloadOptimizationEnabled())
374370
}

modules/structs/hook.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ type Hook struct {
2525
Events []string `json:"events"`
2626
AuthorizationHeader string `json:"authorization_header"`
2727
Active bool `json:"active"`
28-
// Payload size optimization options
29-
ExcludeFilesLimit int `json:"exclude_files_limit"` // -1: trim all (none kept), 0: do not trim, >0: keep N file changes
30-
ExcludeCommitsLimit int `json:"exclude_commits_limit"` // -1: trim all (none kept), 0: do not trim, >0: keep N commits
28+
// PayloadOptimization configuration for webhook payload optimization
29+
PayloadOptimization map[string]any `json:"payload_optimization"`
3130
// swagger:strfmt date-time
3231
Updated time.Time `json:"updated_at"`
3332
// swagger:strfmt date-time
@@ -52,8 +51,7 @@ type CreateHookOption struct {
5251
BranchFilter string `json:"branch_filter" binding:"GlobPattern"`
5352
AuthorizationHeader string `json:"authorization_header"`
5453
// Payload size optimization options
55-
ExcludeFilesLimit int `json:"exclude_files_limit"` // -1: trim all (none kept), 0: do not trim, >0: keep N file changes
56-
ExcludeCommitsLimit int `json:"exclude_commits_limit"` // -1: trim all (none kept), 0: do not trim, >0: keep N commits
54+
PayloadOptimization map[string]any `json:"payload_optimization"` // {"enable": bool, "limit": int}
5755
// default: false
5856
Active bool `json:"active"`
5957
}
@@ -65,9 +63,8 @@ type EditHookOption struct {
6563
BranchFilter string `json:"branch_filter" binding:"GlobPattern"`
6664
AuthorizationHeader string `json:"authorization_header"`
6765
// Payload size optimization options
68-
ExcludeFilesLimit *int `json:"exclude_files_limit"` // -1: trim all (none kept), 0: do not trim, >0: keep N file changes
69-
ExcludeCommitsLimit *int `json:"exclude_commits_limit"` // -1: trim all (none kept), 0: do not trim, >0: keep N commits
70-
Active *bool `json:"active"`
66+
PayloadOptimization *map[string]any `json:"payload_optimization"` // {"enable": bool, "limit": int}
67+
Active *bool `json:"active"`
7168
}
7269

7370
// Payloader payload is some part of one hook

options/locale/locale_en-US.ini

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2425,10 +2425,12 @@ settings.event_package_desc = Package created or deleted in a repository.
24252425
settings.branch_filter = Branch filter
24262426
settings.branch_filter_desc = Branch whitelist for push, branch creation and branch deletion events, specified as glob pattern. If empty or <code>*</code>, events for all branches are reported. See <a href="%[1]s">%[2]s</a> documentation for syntax. Examples: <code>master</code>, <code>{master,release*}</code>.
24272427
settings.payload_optimization = Payload Size Optimization
2428-
settings.exclude_files_limit = Limit file changes
2429-
settings.exclude_files_limit_desc = -1: trim all (none kept), 0: do not trim, >0: keep N file changes
2430-
settings.exclude_commits_limit = Limit commits
2431-
settings.exclude_commits_limit_desc = -1: trim all (none kept), 0: do not trim, >0: keep N commits
2428+
settings.payload_optimization_files = Files
2429+
settings.payload_optimization_commits = Commits
2430+
settings.payload_optimization_enable = Enable optimization
2431+
settings.payload_optimization_enable_desc = Enable payload size optimization for this item
2432+
settings.payload_optimization_limit = Limit
2433+
settings.payload_optimization_limit_desc = 0: trim all (none kept), >0: keep N items (forward order), <0: keep N items (reverse order)
24322434
settings.authorization_header = Authorization Header
24332435
settings.authorization_header_desc = Will be included as authorization header for requests when present. Examples: %s.
24342436
settings.active = Active

routers/api/v1/utils/hook.go

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

24+
// getBoolFromMap extracts a boolean value from a map with a default fallback
25+
//
26+
//nolint:unparam // defaultValue is needed for generic helper function
27+
func getBoolFromMap(m map[string]any, defaultValue bool) bool {
28+
if val, ok := m["enable"]; ok {
29+
if boolVal, ok := val.(bool); ok {
30+
return boolVal
31+
}
32+
}
33+
return defaultValue
34+
}
35+
36+
// getIntFromMap extracts an integer value from a map with a default fallback
37+
//
38+
//nolint:unparam // defaultValue is needed for generic helper function
39+
func getIntFromMap(m map[string]any, defaultValue int) int {
40+
if val, ok := m["limit"]; ok {
41+
switch v := val.(type) {
42+
case int:
43+
return v
44+
case float64:
45+
return int(v)
46+
case string:
47+
if intVal, err := strconv.Atoi(v); err == nil {
48+
return intVal
49+
}
50+
}
51+
}
52+
return defaultValue
53+
}
54+
2455
// ListOwnerHooks lists the webhooks of the provided owner
2556
func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) {
2657
opts := &webhook.ListWebhookOptions{
@@ -224,11 +255,40 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
224255
HookEvents: updateHookEvents(form.Events),
225256
BranchFilter: form.BranchFilter,
226257
},
227-
IsActive: form.Active,
228-
Type: form.Type,
229-
ExcludeFilesLimit: form.ExcludeFilesLimit,
230-
ExcludeCommitsLimit: form.ExcludeCommitsLimit,
258+
IsActive: form.Active,
259+
Type: form.Type,
260+
}
261+
262+
// Set payload optimization config
263+
if form.PayloadOptimization != nil {
264+
payloadOptConfig := &webhook.PayloadOptimizationConfig{}
265+
266+
// Parse files config
267+
if filesConfig, ok := form.PayloadOptimization["files"].(map[string]any); ok {
268+
payloadOptConfig.Files = &webhook.PayloadOptimizationItem{
269+
Enable: getBoolFromMap(filesConfig, false),
270+
Limit: getIntFromMap(filesConfig, 0),
271+
}
272+
} else {
273+
payloadOptConfig.Files = &webhook.PayloadOptimizationItem{Enable: false, Limit: 0}
274+
}
275+
276+
// Parse commits config
277+
if commitsConfig, ok := form.PayloadOptimization["commits"].(map[string]any); ok {
278+
payloadOptConfig.Commits = &webhook.PayloadOptimizationItem{
279+
Enable: getBoolFromMap(commitsConfig, false),
280+
Limit: getIntFromMap(commitsConfig, 0),
281+
}
282+
} else {
283+
payloadOptConfig.Commits = &webhook.PayloadOptimizationItem{Enable: false, Limit: 0}
284+
}
285+
286+
if err := w.SetPayloadOptimizationConfig(payloadOptConfig); err != nil {
287+
ctx.APIErrorInternal(err)
288+
return nil, false
289+
}
231290
}
291+
232292
err := w.SetHeaderAuthorization(form.AuthorizationHeader)
233293
if err != nil {
234294
ctx.APIErrorInternal(err)
@@ -393,12 +453,34 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
393453
w.IsActive = *form.Active
394454
}
395455

396-
if form.ExcludeFilesLimit != nil {
397-
w.ExcludeFilesLimit = *form.ExcludeFilesLimit
398-
}
456+
// Update payload optimization config
457+
if form.PayloadOptimization != nil {
458+
payloadOptConfig := &webhook.PayloadOptimizationConfig{}
459+
460+
// Parse files config
461+
if filesConfig, ok := (*form.PayloadOptimization)["files"].(map[string]any); ok {
462+
payloadOptConfig.Files = &webhook.PayloadOptimizationItem{
463+
Enable: getBoolFromMap(filesConfig, false),
464+
Limit: getIntFromMap(filesConfig, 0),
465+
}
466+
} else {
467+
payloadOptConfig.Files = &webhook.PayloadOptimizationItem{Enable: false, Limit: 0}
468+
}
399469

400-
if form.ExcludeCommitsLimit != nil {
401-
w.ExcludeCommitsLimit = *form.ExcludeCommitsLimit
470+
// Parse commits config
471+
if commitsConfig, ok := (*form.PayloadOptimization)["commits"].(map[string]any); ok {
472+
payloadOptConfig.Commits = &webhook.PayloadOptimizationItem{
473+
Enable: getBoolFromMap(commitsConfig, false),
474+
Limit: getIntFromMap(commitsConfig, 0),
475+
}
476+
} else {
477+
payloadOptConfig.Commits = &webhook.PayloadOptimizationItem{Enable: false, Limit: 0}
478+
}
479+
480+
if err := w.SetPayloadOptimizationConfig(payloadOptConfig); err != nil {
481+
ctx.APIErrorInternal(err)
482+
return false
483+
}
402484
}
403485

404486
if err := webhook.UpdateWebhook(ctx, w); err != nil {

0 commit comments

Comments
 (0)