From 403a73dca0f66728e5f1df6559bc18c0fa9c69dd Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Fri, 6 Feb 2026 14:12:05 +0100 Subject: [PATCH] Add paging headers (#36521) Adds support for paging in admin/hooks api endpoint fixes: https://github.com/go-gitea/gitea/issues/36516 --------- Co-authored-by: techknowlogick Co-authored-by: techknowlogick --- models/issues/issue.go | 6 ++--- models/webhook/webhook_system.go | 33 +++++++++++++++++-------- models/webhook/webhook_system_test.go | 13 +++++----- routers/api/v1/admin/hooks.go | 9 ++++++- routers/api/v1/notify/repo.go | 2 +- routers/api/v1/notify/user.go | 1 + routers/api/v1/org/action.go | 6 +++-- routers/api/v1/org/member.go | 4 ++- routers/api/v1/org/team.go | 18 ++++++++++---- routers/api/v1/repo/action.go | 15 ++++++++--- routers/api/v1/repo/issue_dependency.go | 20 +++++---------- routers/api/v1/repo/status.go | 5 ++-- routers/api/v1/repo/wiki.go | 2 ++ routers/api/v1/shared/action.go | 12 ++++++--- routers/api/v1/shared/block.go | 4 ++- routers/api/v1/user/action.go | 5 ++-- routers/api/v1/user/follower.go | 8 ++++-- routers/api/v1/user/key.go | 18 +++++++------- routers/api/v1/user/star.go | 2 ++ routers/api/v1/user/watch.go | 3 ++- routers/api/v1/utils/hook.go | 5 ++-- routers/api/v1/utils/page.go | 2 +- routers/web/repo/issue_view.go | 2 +- 23 files changed, 123 insertions(+), 72 deletions(-) diff --git a/models/issues/issue.go b/models/issues/issue.go index 053b96dceb..f6f27588b3 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -682,7 +682,7 @@ func (issue *Issue) GetParticipantIDsByIssue(ctx context.Context) ([]int64, erro } // BlockedByDependencies finds all Dependencies an issue is blocked by -func (issue *Issue) BlockedByDependencies(ctx context.Context, opts db.ListOptions) (issueDeps []*DependencyInfo, err error) { +func (issue *Issue) BlockedByDependencies(ctx context.Context, opts db.ListOptions) (issueDeps []*DependencyInfo, total int64, err error) { sess := db.GetEngine(ctx). Table("issue"). Join("INNER", "repository", "repository.id = issue.repo_id"). @@ -693,13 +693,13 @@ func (issue *Issue) BlockedByDependencies(ctx context.Context, opts db.ListOptio if opts.Page > 0 { sess = db.SetSessionPagination(sess, &opts) } - err = sess.Find(&issueDeps) + total, err = sess.FindAndCount(&issueDeps) for _, depInfo := range issueDeps { depInfo.Issue.Repo = &depInfo.Repository } - return issueDeps, err + return issueDeps, total, err } // BlockingDependencies returns all blocking dependencies, aka all other issues a given issue blocks diff --git a/models/webhook/webhook_system.go b/models/webhook/webhook_system.go index 58d9d4a5c1..e8b5040c96 100644 --- a/models/webhook/webhook_system.go +++ b/models/webhook/webhook_system.go @@ -9,19 +9,32 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/optional" + + "xorm.io/builder" ) -// GetSystemOrDefaultWebhooks returns webhooks by given argument or all if argument is missing. -func GetSystemOrDefaultWebhooks(ctx context.Context, isSystemWebhook optional.Option[bool]) ([]*Webhook, error) { - webhooks := make([]*Webhook, 0, 5) - if !isSystemWebhook.Has() { - return webhooks, db.GetEngine(ctx).Where("repo_id=? AND owner_id=?", 0, 0). - Find(&webhooks) - } +// ListSystemWebhookOptions options for listing system or default webhooks +type ListSystemWebhookOptions struct { + db.ListOptions + IsActive optional.Option[bool] + IsSystem optional.Option[bool] +} - return webhooks, db.GetEngine(ctx). - Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, isSystemWebhook.Value()). - Find(&webhooks) +func (opts ListSystemWebhookOptions) ToConds() builder.Cond { + cond := builder.NewCond() + cond = cond.And(builder.Eq{"webhook.repo_id": 0}, builder.Eq{"webhook.owner_id": 0}) + if opts.IsActive.Has() { + cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.Value()}) + } + if opts.IsSystem.Has() { + cond = cond.And(builder.Eq{"is_system_webhook": opts.IsSystem.Value()}) + } + return cond +} + +// GetGlobalWebhooks returns global (default and/or system) webhooks +func GetGlobalWebhooks(ctx context.Context, opts *ListSystemWebhookOptions) ([]*Webhook, int64, error) { + return db.FindAndCount[Webhook](ctx, opts) } // GetDefaultWebhooks returns all admin-default webhooks. diff --git a/models/webhook/webhook_system_test.go b/models/webhook/webhook_system_test.go index 8aac693995..d0013c6873 100644 --- a/models/webhook/webhook_system_test.go +++ b/models/webhook/webhook_system_test.go @@ -12,23 +12,24 @@ import ( "github.com/stretchr/testify/assert" ) -func TestGetSystemOrDefaultWebhooks(t *testing.T) { +func TestListSystemWebhookOptions(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - - hooks, err := GetSystemOrDefaultWebhooks(t.Context(), optional.None[bool]()) + opts := ListSystemWebhookOptions{IsSystem: optional.None[bool]()} + hooks, _, err := GetGlobalWebhooks(t.Context(), &opts) assert.NoError(t, err) if assert.Len(t, hooks, 2) { assert.Equal(t, int64(5), hooks[0].ID) assert.Equal(t, int64(6), hooks[1].ID) } - - hooks, err = GetSystemOrDefaultWebhooks(t.Context(), optional.Some(true)) + opts.IsSystem = optional.Some(true) + hooks, _, err = GetGlobalWebhooks(t.Context(), &opts) assert.NoError(t, err) if assert.Len(t, hooks, 1) { assert.Equal(t, int64(5), hooks[0].ID) } - hooks, err = GetSystemOrDefaultWebhooks(t.Context(), optional.Some(false)) + opts.IsSystem = optional.Some(false) + hooks, _, err = GetGlobalWebhooks(t.Context(), &opts) assert.NoError(t, err) if assert.Len(t, hooks, 1) { assert.Equal(t, int64(6), hooks[0].ID) diff --git a/routers/api/v1/admin/hooks.go b/routers/api/v1/admin/hooks.go index a687541be5..6170e7343a 100644 --- a/routers/api/v1/admin/hooks.go +++ b/routers/api/v1/admin/hooks.go @@ -57,8 +57,13 @@ func ListHooks(ctx *context.APIContext) { case "all": isSystemWebhook = optional.None[bool]() } + listOptions := utils.GetListOptions(ctx) + opts := &webhook.ListSystemWebhookOptions{ + ListOptions: listOptions, + IsSystem: isSystemWebhook, + } - sysHooks, err := webhook.GetSystemOrDefaultWebhooks(ctx, isSystemWebhook) + sysHooks, total, err := webhook.GetGlobalWebhooks(ctx, opts) if err != nil { ctx.APIErrorInternal(err) return @@ -72,6 +77,8 @@ func ListHooks(ctx *context.APIContext) { } hooks[i] = h } + ctx.SetLinkHeader(int(total), listOptions.PageSize) + ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, hooks) } diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go index e87054e26c..51695a52c8 100644 --- a/routers/api/v1/notify/repo.go +++ b/routers/api/v1/notify/repo.go @@ -125,8 +125,8 @@ func ListRepoNotifications(ctx *context.APIContext) { return } + ctx.SetLinkHeader(int(totalCount), opts.PageSize) ctx.SetTotalCountHeader(totalCount) - ctx.JSON(http.StatusOK, convert.ToNotifications(ctx, nl)) } diff --git a/routers/api/v1/notify/user.go b/routers/api/v1/notify/user.go index 3ebb678835..82cedd418b 100644 --- a/routers/api/v1/notify/user.go +++ b/routers/api/v1/notify/user.go @@ -86,6 +86,7 @@ func ListNotifications(ctx *context.APIContext) { return } + ctx.SetLinkHeader(int(totalCount), opts.PageSize) ctx.SetTotalCountHeader(totalCount) ctx.JSON(http.StatusOK, convert.ToNotifications(ctx, nl)) } diff --git a/routers/api/v1/org/action.go b/routers/api/v1/org/action.go index 3ae5e60585..59d8d3f2b4 100644 --- a/routers/api/v1/org/action.go +++ b/routers/api/v1/org/action.go @@ -67,6 +67,7 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) { } } + ctx.SetLinkHeader(int(count), opts.PageSize) ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, apiSecrets) } @@ -240,9 +241,10 @@ func (Action) ListVariables(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" + listOptions := utils.GetListOptions(ctx) vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{ OwnerID: ctx.Org.Organization.ID, - ListOptions: utils.GetListOptions(ctx), + ListOptions: listOptions, }) if err != nil { ctx.APIErrorInternal(err) @@ -259,7 +261,7 @@ func (Action) ListVariables(ctx *context.APIContext) { Description: v.Description, } } - + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, variables) } diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go index 1c12b0cc94..b72cafee0c 100644 --- a/routers/api/v1/org/member.go +++ b/routers/api/v1/org/member.go @@ -20,11 +20,12 @@ import ( // listMembers list an organization's members func listMembers(ctx *context.APIContext, isMember bool) { + listOptions := utils.GetListOptions(ctx) opts := &organization.FindOrgMembersOpts{ Doer: ctx.Doer, IsDoerMember: isMember, OrgID: ctx.Org.Organization.ID, - ListOptions: utils.GetListOptions(ctx), + ListOptions: listOptions, } count, err := organization.CountOrgMembers(ctx, opts) @@ -44,6 +45,7 @@ func listMembers(ctx *context.APIContext, isMember bool) { apiMembers[i] = convert.ToUser(ctx, member, ctx.Doer) } + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, apiMembers) } diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index 1a1710750a..7d43db1e9b 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -54,8 +54,9 @@ func ListTeams(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" + listOptions := utils.GetListOptions(ctx) teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{ - ListOptions: utils.GetListOptions(ctx), + ListOptions: listOptions, OrgID: ctx.Org.Organization.ID, }) if err != nil { @@ -69,6 +70,7 @@ func ListTeams(ctx *context.APIContext) { return } + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, apiTeams) } @@ -93,8 +95,9 @@ func ListUserTeams(ctx *context.APIContext) { // "200": // "$ref": "#/responses/TeamList" + listOptions := utils.GetListOptions(ctx) teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{ - ListOptions: utils.GetListOptions(ctx), + ListOptions: listOptions, UserID: ctx.Doer.ID, }) if err != nil { @@ -108,6 +111,7 @@ func ListUserTeams(ctx *context.APIContext) { return } + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, apiTeams) } @@ -392,8 +396,9 @@ func GetTeamMembers(ctx *context.APIContext) { return } + listOptions := utils.GetListOptions(ctx) teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{ - ListOptions: utils.GetListOptions(ctx), + ListOptions: listOptions, TeamID: ctx.Org.Team.ID, }) if err != nil { @@ -406,6 +411,7 @@ func GetTeamMembers(ctx *context.APIContext) { members[i] = convert.ToUser(ctx, member, ctx.Doer) } + ctx.SetLinkHeader(ctx.Org.Team.NumMembers, listOptions.PageSize) ctx.SetTotalCountHeader(int64(ctx.Org.Team.NumMembers)) ctx.JSON(http.StatusOK, members) } @@ -559,8 +565,9 @@ func GetTeamRepos(ctx *context.APIContext) { // "$ref": "#/responses/notFound" team := ctx.Org.Team + listOptions := utils.GetListOptions(ctx) teamRepos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{ - ListOptions: utils.GetListOptions(ctx), + ListOptions: listOptions, TeamID: team.ID, }) if err != nil { @@ -576,6 +583,7 @@ func GetTeamRepos(ctx *context.APIContext) { } repos[i] = convert.ToRepo(ctx, repo, permission) } + ctx.SetLinkHeader(team.NumRepos, listOptions.PageSize) ctx.SetTotalCountHeader(int64(team.NumRepos)) ctx.JSON(http.StatusOK, repos) } @@ -874,7 +882,7 @@ func ListTeamActivityFeeds(ctx *context.APIContext) { ctx.APIErrorInternal(err) return } + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) - ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer)) } diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index 25aabe6dd2..03ce0d3aab 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -69,10 +69,11 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) { // "$ref": "#/responses/notFound" repo := ctx.Repo.Repository + listOptions := utils.GetListOptions(ctx) opts := &secret_model.FindSecretsOptions{ RepoID: repo.ID, - ListOptions: utils.GetListOptions(ctx), + ListOptions: listOptions, } secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts) @@ -89,7 +90,7 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) { Created: v.CreatedUnix.AsTime(), } } - + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, apiSecrets) } @@ -482,9 +483,11 @@ func (Action) ListVariables(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" + listOptions := utils.GetListOptions(ctx) + vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{ RepoID: ctx.Repo.Repository.ID, - ListOptions: utils.GetListOptions(ctx), + ListOptions: listOptions, }) if err != nil { ctx.APIErrorInternal(err) @@ -502,6 +505,7 @@ func (Action) ListVariables(ctx *context.APIContext) { } } + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, variables) } @@ -807,9 +811,10 @@ func ListActionTasks(ctx *context.APIContext) { // "$ref": "#/responses/conflict" // "422": // "$ref": "#/responses/validationError" + listOptions := utils.GetListOptions(ctx) tasks, total, err := db.FindAndCount[actions_model.ActionTask](ctx, &actions_model.FindTaskOptions{ - ListOptions: utils.GetListOptions(ctx), + ListOptions: listOptions, RepoID: ctx.Repo.Repository.ID, }) if err != nil { @@ -830,6 +835,8 @@ func ListActionTasks(ctx *context.APIContext) { res.Entries[i] = convertedTask } + ctx.SetLinkHeader(int(total), listOptions.PageSize) + ctx.SetTotalCountHeader(total) // Duplicates api response field but it's better to set it for consistency ctx.JSON(http.StatusOK, &res) } diff --git a/routers/api/v1/repo/issue_dependency.go b/routers/api/v1/repo/issue_dependency.go index b34e325e5d..6c66e719eb 100644 --- a/routers/api/v1/repo/issue_dependency.go +++ b/routers/api/v1/repo/issue_dependency.go @@ -7,13 +7,13 @@ package repo import ( "net/http" - "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/convert" ) @@ -77,23 +77,14 @@ func GetIssueDependencies(ctx *context.APIContext) { return } - page := max(ctx.FormInt("page"), 1) - limit := ctx.FormInt("limit") - if limit == 0 { - limit = setting.API.DefaultPagingNum - } else if limit > setting.API.MaxResponseItems { - limit = setting.API.MaxResponseItems - } + listOptions := utils.GetListOptions(ctx) canWrite := ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) - blockerIssues := make([]*issues_model.Issue, 0, limit) + blockerIssues := make([]*issues_model.Issue, 0, listOptions.PageSize) // 2. Get the issues this issue depends on, i.e. the `<#b>`: ` <- <#b>` - blockersInfo, err := issue.BlockedByDependencies(ctx, db.ListOptions{ - Page: page, - PageSize: limit, - }) + blockersInfo, total, err := issue.BlockedByDependencies(ctx, listOptions) if err != nil { ctx.APIErrorInternal(err) return @@ -149,7 +140,8 @@ func GetIssueDependencies(ctx *context.APIContext) { } blockerIssues = append(blockerIssues, &blocker.Issue) } - + ctx.SetLinkHeader(int(total), listOptions.PageSize) + ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, blockerIssues)) } diff --git a/routers/api/v1/repo/status.go b/routers/api/v1/repo/status.go index d632d5b5e1..e69d4468de 100644 --- a/routers/api/v1/repo/status.go +++ b/routers/api/v1/repo/status.go @@ -257,8 +257,8 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) { } repo := ctx.Repo.Repository - - statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String(), utils.GetListOptions(ctx)) + listOptions := utils.GetListOptions(ctx) + statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String(), listOptions) if err != nil { ctx.APIErrorInternal(fmt.Errorf("GetLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err)) return @@ -269,6 +269,7 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) { ctx.APIErrorInternal(fmt.Errorf("CountLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err)) return } + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) combiStatus := convert.ToCombinedStatus(ctx, refCommit.Commit.ID.String(), statuses, diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go index baf5e0189f..90dd08394e 100644 --- a/routers/api/v1/repo/wiki.go +++ b/routers/api/v1/repo/wiki.go @@ -333,6 +333,7 @@ func ListWikiPages(ctx *context.APIContext) { pages = append(pages, wiki_service.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository)) } + ctx.SetLinkHeader(len(entries), limit) ctx.SetTotalCountHeader(int64(len(entries))) ctx.JSON(http.StatusOK, pages) } @@ -445,6 +446,7 @@ func ListPageRevisions(ctx *context.APIContext) { return } + // FIXME: SetLinkHeader missing ctx.SetTotalCountHeader(commitsCount) ctx.JSON(http.StatusOK, convert.ToWikiCommitList(commitsHistory, commitsCount)) } diff --git a/routers/api/v1/shared/action.go b/routers/api/v1/shared/action.go index c97e9419fd..108fca787b 100644 --- a/routers/api/v1/shared/action.go +++ b/routers/api/v1/shared/action.go @@ -32,11 +32,12 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) { if ownerID != 0 && repoID != 0 { setting.PanicInDevOrTesting("ownerID and repoID should not be both set") } + listOptions := utils.GetListOptions(ctx) opts := actions_model.FindRunJobOptions{ OwnerID: ownerID, RepoID: repoID, RunID: runID, - ListOptions: utils.GetListOptions(ctx), + ListOptions: listOptions, } for _, status := range ctx.FormStrings("status") { values, err := convertToInternal(status) @@ -78,7 +79,8 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) { } res.Entries[i] = convertedWorkflowJob } - + ctx.SetLinkHeader(int(total), listOptions.PageSize) + ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, &res) } @@ -120,10 +122,11 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) { if ownerID != 0 && repoID != 0 { setting.PanicInDevOrTesting("ownerID and repoID should not be both set") } + listOptions := utils.GetListOptions(ctx) opts := actions_model.FindRunOptions{ OwnerID: ownerID, RepoID: repoID, - ListOptions: utils.GetListOptions(ctx), + ListOptions: listOptions, } if event := ctx.FormString("event"); event != "" { @@ -182,6 +185,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) { } res.Entries[i] = convertedRun } - + ctx.SetLinkHeader(int(total), listOptions.PageSize) + ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, &res) } diff --git a/routers/api/v1/shared/block.go b/routers/api/v1/shared/block.go index b22f8a74fd..19ad552e20 100644 --- a/routers/api/v1/shared/block.go +++ b/routers/api/v1/shared/block.go @@ -16,8 +16,9 @@ import ( ) func ListBlocks(ctx *context.APIContext, blocker *user_model.User) { + listOptions := utils.GetListOptions(ctx) blocks, total, err := user_model.FindBlockings(ctx, &user_model.FindBlockingOptions{ - ListOptions: utils.GetListOptions(ctx), + ListOptions: listOptions, BlockerID: blocker.ID, }) if err != nil { @@ -35,6 +36,7 @@ func ListBlocks(ctx *context.APIContext, blocker *user_model.User) { users = append(users, convert.ToUser(ctx, b.Blockee, blocker)) } + ctx.SetLinkHeader(int(total), listOptions.PageSize) ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, &users) } diff --git a/routers/api/v1/user/action.go b/routers/api/v1/user/action.go index e934d02aa7..069d5e39b6 100644 --- a/routers/api/v1/user/action.go +++ b/routers/api/v1/user/action.go @@ -333,10 +333,10 @@ func ListVariables(ctx *context.APIContext) { // "$ref": "#/responses/error" // "404": // "$ref": "#/responses/notFound" - + listOptions := utils.GetListOptions(ctx) vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{ OwnerID: ctx.Doer.ID, - ListOptions: utils.GetListOptions(ctx), + ListOptions: listOptions, }) if err != nil { ctx.APIErrorInternal(err) @@ -354,6 +354,7 @@ func ListVariables(ctx *context.APIContext) { } } + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, variables) } diff --git a/routers/api/v1/user/follower.go b/routers/api/v1/user/follower.go index 339b994af4..48c624ced9 100644 --- a/routers/api/v1/user/follower.go +++ b/routers/api/v1/user/follower.go @@ -24,12 +24,14 @@ func responseAPIUsers(ctx *context.APIContext, users []*user_model.User) { } func listUserFollowers(ctx *context.APIContext, u *user_model.User) { - users, count, err := user_model.GetUserFollowers(ctx, u, ctx.Doer, utils.GetListOptions(ctx)) + listOptions := utils.GetListOptions(ctx) + users, count, err := user_model.GetUserFollowers(ctx, u, ctx.Doer, listOptions) if err != nil { ctx.APIErrorInternal(err) return } + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) responseAPIUsers(ctx, users) } @@ -88,12 +90,14 @@ func ListFollowers(ctx *context.APIContext) { } func listUserFollowing(ctx *context.APIContext, u *user_model.User) { - users, count, err := user_model.GetUserFollowing(ctx, u, ctx.Doer, utils.GetListOptions(ctx)) + listOptions := utils.GetListOptions(ctx) + users, count, err := user_model.GetUserFollowing(ctx, u, ctx.Doer, listOptions) if err != nil { ctx.APIErrorInternal(err) return } + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) responseAPIUsers(ctx, users) } diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go index 08aa182ca1..de0ac7b1e4 100644 --- a/routers/api/v1/user/key.go +++ b/routers/api/v1/user/key.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2026 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package user @@ -53,11 +54,11 @@ func composePublicKeysAPILink() string { func listPublicKeys(ctx *context.APIContext, user *user_model.User) { var keys []*asymkey_model.PublicKey var err error - var count int + var count int64 fingerprint := ctx.FormString("fingerprint") username := ctx.PathParam("username") - + listOptions := utils.GetListOptions(ctx) if fingerprint != "" { var userID int64 // Unrestricted // Querying not just listing @@ -65,20 +66,18 @@ func listPublicKeys(ctx *context.APIContext, user *user_model.User) { // Restrict to provided uid userID = user.ID } - keys, err = db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{ + keys, count, err = db.FindAndCount[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{ + ListOptions: listOptions, OwnerID: userID, Fingerprint: fingerprint, }) - count = len(keys) } else { - var total int64 // Use ListPublicKeys - keys, total, err = db.FindAndCount[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{ - ListOptions: utils.GetListOptions(ctx), + keys, count, err = db.FindAndCount[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{ + ListOptions: listOptions, OwnerID: user.ID, NotKeytype: asymkey_model.KeyTypePrincipal, }) - count = int(total) } if err != nil { @@ -95,7 +94,8 @@ func listPublicKeys(ctx *context.APIContext, user *user_model.User) { } } - ctx.SetTotalCountHeader(int64(count)) + ctx.SetLinkHeader(int(count), listOptions.PageSize) + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, &apiKeys) } diff --git a/routers/api/v1/user/star.go b/routers/api/v1/user/star.go index ee5d63063b..5c0d976527 100644 --- a/routers/api/v1/user/star.go +++ b/routers/api/v1/user/star.go @@ -76,6 +76,7 @@ func GetStarredRepos(ctx *context.APIContext) { return } + ctx.SetLinkHeader(ctx.ContextUser.NumStars, utils.GetListOptions(ctx).PageSize) ctx.SetTotalCountHeader(int64(ctx.ContextUser.NumStars)) ctx.JSON(http.StatusOK, &repos) } @@ -107,6 +108,7 @@ func GetMyStarredRepos(ctx *context.APIContext) { ctx.APIErrorInternal(err) } + ctx.SetLinkHeader(ctx.Doer.NumStars, utils.GetListOptions(ctx).PageSize) ctx.SetTotalCountHeader(int64(ctx.Doer.NumStars)) ctx.JSON(http.StatusOK, &repos) } diff --git a/routers/api/v1/user/watch.go b/routers/api/v1/user/watch.go index 844eac2c67..1ce0f3f529 100644 --- a/routers/api/v1/user/watch.go +++ b/routers/api/v1/user/watch.go @@ -71,6 +71,7 @@ func GetWatchedRepos(ctx *context.APIContext) { ctx.APIErrorInternal(err) } + ctx.SetLinkHeader(int(total), utils.GetListOptions(ctx).PageSize) ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, &repos) } @@ -99,7 +100,7 @@ func GetMyWatchedRepos(ctx *context.APIContext) { if err != nil { ctx.APIErrorInternal(err) } - + ctx.SetLinkHeader(int(total), utils.GetListOptions(ctx).PageSize) ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, &repos) } diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index 6f598f14c8..9f0447a80b 100644 --- a/routers/api/v1/utils/hook.go +++ b/routers/api/v1/utils/hook.go @@ -23,8 +23,9 @@ import ( // ListOwnerHooks lists the webhooks of the provided owner func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) { + listOptions := GetListOptions(ctx) opts := &webhook.ListWebhookOptions{ - ListOptions: GetListOptions(ctx), + ListOptions: listOptions, OwnerID: owner.ID, } @@ -42,7 +43,7 @@ func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) { return } } - + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, apiHooks) } diff --git a/routers/api/v1/utils/page.go b/routers/api/v1/utils/page.go index 024ba7b8d9..2c7b64967a 100644 --- a/routers/api/v1/utils/page.go +++ b/routers/api/v1/utils/page.go @@ -12,7 +12,7 @@ import ( // GetListOptions returns list options using the page and limit parameters func GetListOptions(ctx *context.APIContext) db.ListOptions { return db.ListOptions{ - Page: ctx.FormInt("page"), + Page: max(ctx.FormInt("page"), 1), PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), } } diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index 7670660e31..1354c2d6f9 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -469,7 +469,7 @@ func prepareIssueViewSidebarDependency(ctx *context.Context, issue *issues_model ctx.Data["AllowCrossRepositoryDependencies"] = setting.Service.AllowCrossRepositoryDependencies // Get Dependencies - blockedBy, err := issue.BlockedByDependencies(ctx, db.ListOptions{}) + blockedBy, _, err := issue.BlockedByDependencies(ctx, db.ListOptions{}) if err != nil { ctx.ServerError("BlockedByDependencies", err) return