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 <techknowlogick@gitea.com>
Co-authored-by: techknowlogick <matti@mdranta.net>
This commit is contained in:
TheFox0x7
2026-02-06 14:12:05 +01:00
committed by GitHub
parent ef9c19691d
commit 403a73dca0
23 changed files with 123 additions and 72 deletions

View File

@@ -682,7 +682,7 @@ func (issue *Issue) GetParticipantIDsByIssue(ctx context.Context) ([]int64, erro
} }
// BlockedByDependencies finds all Dependencies an issue is blocked by // 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). sess := db.GetEngine(ctx).
Table("issue"). Table("issue").
Join("INNER", "repository", "repository.id = issue.repo_id"). 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 { if opts.Page > 0 {
sess = db.SetSessionPagination(sess, &opts) sess = db.SetSessionPagination(sess, &opts)
} }
err = sess.Find(&issueDeps) total, err = sess.FindAndCount(&issueDeps)
for _, depInfo := range issueDeps { for _, depInfo := range issueDeps {
depInfo.Issue.Repo = &depInfo.Repository 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 // BlockingDependencies returns all blocking dependencies, aka all other issues a given issue blocks

View File

@@ -9,19 +9,32 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"xorm.io/builder"
) )
// GetSystemOrDefaultWebhooks returns webhooks by given argument or all if argument is missing. // ListSystemWebhookOptions options for listing system or default webhooks
func GetSystemOrDefaultWebhooks(ctx context.Context, isSystemWebhook optional.Option[bool]) ([]*Webhook, error) { type ListSystemWebhookOptions struct {
webhooks := make([]*Webhook, 0, 5) db.ListOptions
if !isSystemWebhook.Has() { IsActive optional.Option[bool]
return webhooks, db.GetEngine(ctx).Where("repo_id=? AND owner_id=?", 0, 0). IsSystem optional.Option[bool]
Find(&webhooks)
} }
return webhooks, db.GetEngine(ctx). func (opts ListSystemWebhookOptions) ToConds() builder.Cond {
Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, isSystemWebhook.Value()). cond := builder.NewCond()
Find(&webhooks) 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. // GetDefaultWebhooks returns all admin-default webhooks.

View File

@@ -12,23 +12,24 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestGetSystemOrDefaultWebhooks(t *testing.T) { func TestListSystemWebhookOptions(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
opts := ListSystemWebhookOptions{IsSystem: optional.None[bool]()}
hooks, err := GetSystemOrDefaultWebhooks(t.Context(), optional.None[bool]()) hooks, _, err := GetGlobalWebhooks(t.Context(), &opts)
assert.NoError(t, err) assert.NoError(t, err)
if assert.Len(t, hooks, 2) { if assert.Len(t, hooks, 2) {
assert.Equal(t, int64(5), hooks[0].ID) assert.Equal(t, int64(5), hooks[0].ID)
assert.Equal(t, int64(6), hooks[1].ID) assert.Equal(t, int64(6), hooks[1].ID)
} }
opts.IsSystem = optional.Some(true)
hooks, err = GetSystemOrDefaultWebhooks(t.Context(), optional.Some(true)) hooks, _, err = GetGlobalWebhooks(t.Context(), &opts)
assert.NoError(t, err) assert.NoError(t, err)
if assert.Len(t, hooks, 1) { if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(5), hooks[0].ID) 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) assert.NoError(t, err)
if assert.Len(t, hooks, 1) { if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(6), hooks[0].ID) assert.Equal(t, int64(6), hooks[0].ID)

View File

@@ -57,8 +57,13 @@ func ListHooks(ctx *context.APIContext) {
case "all": case "all":
isSystemWebhook = optional.None[bool]() 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 { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return return
@@ -72,6 +77,8 @@ func ListHooks(ctx *context.APIContext) {
} }
hooks[i] = h hooks[i] = h
} }
ctx.SetLinkHeader(int(total), listOptions.PageSize)
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, hooks) ctx.JSON(http.StatusOK, hooks)
} }

View File

@@ -125,8 +125,8 @@ func ListRepoNotifications(ctx *context.APIContext) {
return return
} }
ctx.SetLinkHeader(int(totalCount), opts.PageSize)
ctx.SetTotalCountHeader(totalCount) ctx.SetTotalCountHeader(totalCount)
ctx.JSON(http.StatusOK, convert.ToNotifications(ctx, nl)) ctx.JSON(http.StatusOK, convert.ToNotifications(ctx, nl))
} }

View File

@@ -86,6 +86,7 @@ func ListNotifications(ctx *context.APIContext) {
return return
} }
ctx.SetLinkHeader(int(totalCount), opts.PageSize)
ctx.SetTotalCountHeader(totalCount) ctx.SetTotalCountHeader(totalCount)
ctx.JSON(http.StatusOK, convert.ToNotifications(ctx, nl)) ctx.JSON(http.StatusOK, convert.ToNotifications(ctx, nl))
} }

View File

@@ -67,6 +67,7 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
} }
} }
ctx.SetLinkHeader(int(count), opts.PageSize)
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, apiSecrets) ctx.JSON(http.StatusOK, apiSecrets)
} }
@@ -240,9 +241,10 @@ func (Action) ListVariables(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
listOptions := utils.GetListOptions(ctx)
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{ vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
OwnerID: ctx.Org.Organization.ID, OwnerID: ctx.Org.Organization.ID,
ListOptions: utils.GetListOptions(ctx), ListOptions: listOptions,
}) })
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
@@ -259,7 +261,7 @@ func (Action) ListVariables(ctx *context.APIContext) {
Description: v.Description, Description: v.Description,
} }
} }
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, variables) ctx.JSON(http.StatusOK, variables)
} }

View File

@@ -20,11 +20,12 @@ import (
// listMembers list an organization's members // listMembers list an organization's members
func listMembers(ctx *context.APIContext, isMember bool) { func listMembers(ctx *context.APIContext, isMember bool) {
listOptions := utils.GetListOptions(ctx)
opts := &organization.FindOrgMembersOpts{ opts := &organization.FindOrgMembersOpts{
Doer: ctx.Doer, Doer: ctx.Doer,
IsDoerMember: isMember, IsDoerMember: isMember,
OrgID: ctx.Org.Organization.ID, OrgID: ctx.Org.Organization.ID,
ListOptions: utils.GetListOptions(ctx), ListOptions: listOptions,
} }
count, err := organization.CountOrgMembers(ctx, opts) 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) apiMembers[i] = convert.ToUser(ctx, member, ctx.Doer)
} }
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, apiMembers) ctx.JSON(http.StatusOK, apiMembers)
} }

View File

@@ -54,8 +54,9 @@ func ListTeams(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
listOptions := utils.GetListOptions(ctx)
teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{ teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
ListOptions: utils.GetListOptions(ctx), ListOptions: listOptions,
OrgID: ctx.Org.Organization.ID, OrgID: ctx.Org.Organization.ID,
}) })
if err != nil { if err != nil {
@@ -69,6 +70,7 @@ func ListTeams(ctx *context.APIContext) {
return return
} }
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, apiTeams) ctx.JSON(http.StatusOK, apiTeams)
} }
@@ -93,8 +95,9 @@ func ListUserTeams(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/TeamList" // "$ref": "#/responses/TeamList"
listOptions := utils.GetListOptions(ctx)
teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{ teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
ListOptions: utils.GetListOptions(ctx), ListOptions: listOptions,
UserID: ctx.Doer.ID, UserID: ctx.Doer.ID,
}) })
if err != nil { if err != nil {
@@ -108,6 +111,7 @@ func ListUserTeams(ctx *context.APIContext) {
return return
} }
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, apiTeams) ctx.JSON(http.StatusOK, apiTeams)
} }
@@ -392,8 +396,9 @@ func GetTeamMembers(ctx *context.APIContext) {
return return
} }
listOptions := utils.GetListOptions(ctx)
teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{ teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{
ListOptions: utils.GetListOptions(ctx), ListOptions: listOptions,
TeamID: ctx.Org.Team.ID, TeamID: ctx.Org.Team.ID,
}) })
if err != nil { if err != nil {
@@ -406,6 +411,7 @@ func GetTeamMembers(ctx *context.APIContext) {
members[i] = convert.ToUser(ctx, member, ctx.Doer) members[i] = convert.ToUser(ctx, member, ctx.Doer)
} }
ctx.SetLinkHeader(ctx.Org.Team.NumMembers, listOptions.PageSize)
ctx.SetTotalCountHeader(int64(ctx.Org.Team.NumMembers)) ctx.SetTotalCountHeader(int64(ctx.Org.Team.NumMembers))
ctx.JSON(http.StatusOK, members) ctx.JSON(http.StatusOK, members)
} }
@@ -559,8 +565,9 @@ func GetTeamRepos(ctx *context.APIContext) {
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
team := ctx.Org.Team team := ctx.Org.Team
listOptions := utils.GetListOptions(ctx)
teamRepos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{ teamRepos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{
ListOptions: utils.GetListOptions(ctx), ListOptions: listOptions,
TeamID: team.ID, TeamID: team.ID,
}) })
if err != nil { if err != nil {
@@ -576,6 +583,7 @@ func GetTeamRepos(ctx *context.APIContext) {
} }
repos[i] = convert.ToRepo(ctx, repo, permission) repos[i] = convert.ToRepo(ctx, repo, permission)
} }
ctx.SetLinkHeader(team.NumRepos, listOptions.PageSize)
ctx.SetTotalCountHeader(int64(team.NumRepos)) ctx.SetTotalCountHeader(int64(team.NumRepos))
ctx.JSON(http.StatusOK, repos) ctx.JSON(http.StatusOK, repos)
} }
@@ -874,7 +882,7 @@ func ListTeamActivityFeeds(ctx *context.APIContext) {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return return
} }
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer)) ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer))
} }

View File

@@ -69,10 +69,11 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
listOptions := utils.GetListOptions(ctx)
opts := &secret_model.FindSecretsOptions{ opts := &secret_model.FindSecretsOptions{
RepoID: repo.ID, RepoID: repo.ID,
ListOptions: utils.GetListOptions(ctx), ListOptions: listOptions,
} }
secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts) secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
@@ -89,7 +90,7 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
Created: v.CreatedUnix.AsTime(), Created: v.CreatedUnix.AsTime(),
} }
} }
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, apiSecrets) ctx.JSON(http.StatusOK, apiSecrets)
} }
@@ -482,9 +483,11 @@ func (Action) ListVariables(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
listOptions := utils.GetListOptions(ctx)
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{ vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
ListOptions: utils.GetListOptions(ctx), ListOptions: listOptions,
}) })
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
@@ -502,6 +505,7 @@ func (Action) ListVariables(ctx *context.APIContext) {
} }
} }
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, variables) ctx.JSON(http.StatusOK, variables)
} }
@@ -807,9 +811,10 @@ func ListActionTasks(ctx *context.APIContext) {
// "$ref": "#/responses/conflict" // "$ref": "#/responses/conflict"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
listOptions := utils.GetListOptions(ctx)
tasks, total, err := db.FindAndCount[actions_model.ActionTask](ctx, &actions_model.FindTaskOptions{ tasks, total, err := db.FindAndCount[actions_model.ActionTask](ctx, &actions_model.FindTaskOptions{
ListOptions: utils.GetListOptions(ctx), ListOptions: listOptions,
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
}) })
if err != nil { if err != nil {
@@ -830,6 +835,8 @@ func ListActionTasks(ctx *context.APIContext) {
res.Entries[i] = convertedTask 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) ctx.JSON(http.StatusOK, &res)
} }

View File

@@ -7,13 +7,13 @@ package repo
import ( import (
"net/http" "net/http"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web" "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/context"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
) )
@@ -77,23 +77,14 @@ func GetIssueDependencies(ctx *context.APIContext) {
return return
} }
page := max(ctx.FormInt("page"), 1) listOptions := utils.GetListOptions(ctx)
limit := ctx.FormInt("limit")
if limit == 0 {
limit = setting.API.DefaultPagingNum
} else if limit > setting.API.MaxResponseItems {
limit = setting.API.MaxResponseItems
}
canWrite := ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) 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>`: `<issue> <- <#b>` // 2. Get the issues this issue depends on, i.e. the `<#b>`: `<issue> <- <#b>`
blockersInfo, err := issue.BlockedByDependencies(ctx, db.ListOptions{ blockersInfo, total, err := issue.BlockedByDependencies(ctx, listOptions)
Page: page,
PageSize: limit,
})
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return return
@@ -149,7 +140,8 @@ func GetIssueDependencies(ctx *context.APIContext) {
} }
blockerIssues = append(blockerIssues, &blocker.Issue) blockerIssues = append(blockerIssues, &blocker.Issue)
} }
ctx.SetLinkHeader(int(total), listOptions.PageSize)
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, blockerIssues)) ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, blockerIssues))
} }

View File

@@ -257,8 +257,8 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
} }
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
listOptions := utils.GetListOptions(ctx)
statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String(), utils.GetListOptions(ctx)) statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String(), listOptions)
if err != nil { if err != nil {
ctx.APIErrorInternal(fmt.Errorf("GetLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err)) ctx.APIErrorInternal(fmt.Errorf("GetLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err))
return return
@@ -269,6 +269,7 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
ctx.APIErrorInternal(fmt.Errorf("CountLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err)) ctx.APIErrorInternal(fmt.Errorf("CountLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err))
return return
} }
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
combiStatus := convert.ToCombinedStatus(ctx, refCommit.Commit.ID.String(), statuses, combiStatus := convert.ToCombinedStatus(ctx, refCommit.Commit.ID.String(), statuses,

View File

@@ -333,6 +333,7 @@ func ListWikiPages(ctx *context.APIContext) {
pages = append(pages, wiki_service.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository)) pages = append(pages, wiki_service.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository))
} }
ctx.SetLinkHeader(len(entries), limit)
ctx.SetTotalCountHeader(int64(len(entries))) ctx.SetTotalCountHeader(int64(len(entries)))
ctx.JSON(http.StatusOK, pages) ctx.JSON(http.StatusOK, pages)
} }
@@ -445,6 +446,7 @@ func ListPageRevisions(ctx *context.APIContext) {
return return
} }
// FIXME: SetLinkHeader missing
ctx.SetTotalCountHeader(commitsCount) ctx.SetTotalCountHeader(commitsCount)
ctx.JSON(http.StatusOK, convert.ToWikiCommitList(commitsHistory, commitsCount)) ctx.JSON(http.StatusOK, convert.ToWikiCommitList(commitsHistory, commitsCount))
} }

View File

@@ -32,11 +32,12 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) {
if ownerID != 0 && repoID != 0 { if ownerID != 0 && repoID != 0 {
setting.PanicInDevOrTesting("ownerID and repoID should not be both set") setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
} }
listOptions := utils.GetListOptions(ctx)
opts := actions_model.FindRunJobOptions{ opts := actions_model.FindRunJobOptions{
OwnerID: ownerID, OwnerID: ownerID,
RepoID: repoID, RepoID: repoID,
RunID: runID, RunID: runID,
ListOptions: utils.GetListOptions(ctx), ListOptions: listOptions,
} }
for _, status := range ctx.FormStrings("status") { for _, status := range ctx.FormStrings("status") {
values, err := convertToInternal(status) values, err := convertToInternal(status)
@@ -78,7 +79,8 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) {
} }
res.Entries[i] = convertedWorkflowJob res.Entries[i] = convertedWorkflowJob
} }
ctx.SetLinkHeader(int(total), listOptions.PageSize)
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &res) ctx.JSON(http.StatusOK, &res)
} }
@@ -120,10 +122,11 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
if ownerID != 0 && repoID != 0 { if ownerID != 0 && repoID != 0 {
setting.PanicInDevOrTesting("ownerID and repoID should not be both set") setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
} }
listOptions := utils.GetListOptions(ctx)
opts := actions_model.FindRunOptions{ opts := actions_model.FindRunOptions{
OwnerID: ownerID, OwnerID: ownerID,
RepoID: repoID, RepoID: repoID,
ListOptions: utils.GetListOptions(ctx), ListOptions: listOptions,
} }
if event := ctx.FormString("event"); event != "" { if event := ctx.FormString("event"); event != "" {
@@ -182,6 +185,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
} }
res.Entries[i] = convertedRun res.Entries[i] = convertedRun
} }
ctx.SetLinkHeader(int(total), listOptions.PageSize)
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &res) ctx.JSON(http.StatusOK, &res)
} }

View File

@@ -16,8 +16,9 @@ import (
) )
func ListBlocks(ctx *context.APIContext, blocker *user_model.User) { func ListBlocks(ctx *context.APIContext, blocker *user_model.User) {
listOptions := utils.GetListOptions(ctx)
blocks, total, err := user_model.FindBlockings(ctx, &user_model.FindBlockingOptions{ blocks, total, err := user_model.FindBlockings(ctx, &user_model.FindBlockingOptions{
ListOptions: utils.GetListOptions(ctx), ListOptions: listOptions,
BlockerID: blocker.ID, BlockerID: blocker.ID,
}) })
if err != nil { 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)) users = append(users, convert.ToUser(ctx, b.Blockee, blocker))
} }
ctx.SetLinkHeader(int(total), listOptions.PageSize)
ctx.SetTotalCountHeader(total) ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &users) ctx.JSON(http.StatusOK, &users)
} }

View File

@@ -333,10 +333,10 @@ func ListVariables(ctx *context.APIContext) {
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
listOptions := utils.GetListOptions(ctx)
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{ vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
OwnerID: ctx.Doer.ID, OwnerID: ctx.Doer.ID,
ListOptions: utils.GetListOptions(ctx), ListOptions: listOptions,
}) })
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
@@ -354,6 +354,7 @@ func ListVariables(ctx *context.APIContext) {
} }
} }
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, variables) ctx.JSON(http.StatusOK, variables)
} }

View File

@@ -24,12 +24,14 @@ func responseAPIUsers(ctx *context.APIContext, users []*user_model.User) {
} }
func listUserFollowers(ctx *context.APIContext, u *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 { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return return
} }
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
responseAPIUsers(ctx, users) responseAPIUsers(ctx, users)
} }
@@ -88,12 +90,14 @@ func ListFollowers(ctx *context.APIContext) {
} }
func listUserFollowing(ctx *context.APIContext, u *user_model.User) { 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 { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return return
} }
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
responseAPIUsers(ctx, users) responseAPIUsers(ctx, users)
} }

View File

@@ -1,4 +1,5 @@
// Copyright 2015 The Gogs Authors. All rights reserved. // Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package user package user
@@ -53,11 +54,11 @@ func composePublicKeysAPILink() string {
func listPublicKeys(ctx *context.APIContext, user *user_model.User) { func listPublicKeys(ctx *context.APIContext, user *user_model.User) {
var keys []*asymkey_model.PublicKey var keys []*asymkey_model.PublicKey
var err error var err error
var count int var count int64
fingerprint := ctx.FormString("fingerprint") fingerprint := ctx.FormString("fingerprint")
username := ctx.PathParam("username") username := ctx.PathParam("username")
listOptions := utils.GetListOptions(ctx)
if fingerprint != "" { if fingerprint != "" {
var userID int64 // Unrestricted var userID int64 // Unrestricted
// Querying not just listing // Querying not just listing
@@ -65,20 +66,18 @@ func listPublicKeys(ctx *context.APIContext, user *user_model.User) {
// Restrict to provided uid // Restrict to provided uid
userID = user.ID 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, OwnerID: userID,
Fingerprint: fingerprint, Fingerprint: fingerprint,
}) })
count = len(keys)
} else { } else {
var total int64
// Use ListPublicKeys // Use ListPublicKeys
keys, total, err = db.FindAndCount[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{ keys, count, err = db.FindAndCount[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
ListOptions: utils.GetListOptions(ctx), ListOptions: listOptions,
OwnerID: user.ID, OwnerID: user.ID,
NotKeytype: asymkey_model.KeyTypePrincipal, NotKeytype: asymkey_model.KeyTypePrincipal,
}) })
count = int(total)
} }
if err != nil { 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) ctx.JSON(http.StatusOK, &apiKeys)
} }

View File

@@ -76,6 +76,7 @@ func GetStarredRepos(ctx *context.APIContext) {
return return
} }
ctx.SetLinkHeader(ctx.ContextUser.NumStars, utils.GetListOptions(ctx).PageSize)
ctx.SetTotalCountHeader(int64(ctx.ContextUser.NumStars)) ctx.SetTotalCountHeader(int64(ctx.ContextUser.NumStars))
ctx.JSON(http.StatusOK, &repos) ctx.JSON(http.StatusOK, &repos)
} }
@@ -107,6 +108,7 @@ func GetMyStarredRepos(ctx *context.APIContext) {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
} }
ctx.SetLinkHeader(ctx.Doer.NumStars, utils.GetListOptions(ctx).PageSize)
ctx.SetTotalCountHeader(int64(ctx.Doer.NumStars)) ctx.SetTotalCountHeader(int64(ctx.Doer.NumStars))
ctx.JSON(http.StatusOK, &repos) ctx.JSON(http.StatusOK, &repos)
} }

View File

@@ -71,6 +71,7 @@ func GetWatchedRepos(ctx *context.APIContext) {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
} }
ctx.SetLinkHeader(int(total), utils.GetListOptions(ctx).PageSize)
ctx.SetTotalCountHeader(total) ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &repos) ctx.JSON(http.StatusOK, &repos)
} }
@@ -99,7 +100,7 @@ func GetMyWatchedRepos(ctx *context.APIContext) {
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
} }
ctx.SetLinkHeader(int(total), utils.GetListOptions(ctx).PageSize)
ctx.SetTotalCountHeader(total) ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &repos) ctx.JSON(http.StatusOK, &repos)
} }

View File

@@ -23,8 +23,9 @@ import (
// ListOwnerHooks lists the webhooks of the provided owner // ListOwnerHooks lists the webhooks of the provided owner
func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) { func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) {
listOptions := GetListOptions(ctx)
opts := &webhook.ListWebhookOptions{ opts := &webhook.ListWebhookOptions{
ListOptions: GetListOptions(ctx), ListOptions: listOptions,
OwnerID: owner.ID, OwnerID: owner.ID,
} }
@@ -42,7 +43,7 @@ func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) {
return return
} }
} }
ctx.SetLinkHeader(int(count), listOptions.PageSize)
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, apiHooks) ctx.JSON(http.StatusOK, apiHooks)
} }

View File

@@ -12,7 +12,7 @@ import (
// GetListOptions returns list options using the page and limit parameters // GetListOptions returns list options using the page and limit parameters
func GetListOptions(ctx *context.APIContext) db.ListOptions { func GetListOptions(ctx *context.APIContext) db.ListOptions {
return db.ListOptions{ return db.ListOptions{
Page: ctx.FormInt("page"), Page: max(ctx.FormInt("page"), 1),
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
} }
} }

View File

@@ -469,7 +469,7 @@ func prepareIssueViewSidebarDependency(ctx *context.Context, issue *issues_model
ctx.Data["AllowCrossRepositoryDependencies"] = setting.Service.AllowCrossRepositoryDependencies ctx.Data["AllowCrossRepositoryDependencies"] = setting.Service.AllowCrossRepositoryDependencies
// Get Dependencies // Get Dependencies
blockedBy, err := issue.BlockedByDependencies(ctx, db.ListOptions{}) blockedBy, _, err := issue.BlockedByDependencies(ctx, db.ListOptions{})
if err != nil { if err != nil {
ctx.ServerError("BlockedByDependencies", err) ctx.ServerError("BlockedByDependencies", err)
return return