Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions models/actions/run_job_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ type FindRunJobOptions struct {
Statuses []Status
UpdatedBefore timeutil.TimeStamp
ConcurrencyGroup string
OrderBy db.SearchOrderBy
}

var JobOrderByMap = map[string]map[string]db.SearchOrderBy{
"asc": {"id": "`action_run_job`.id ASC"},
"desc": {"id": "`action_run_job`.id DESC"},
}

func (opts FindRunJobOptions) ToConds() builder.Cond {
Expand Down Expand Up @@ -140,3 +146,9 @@ func (opts FindRunJobOptions) ToJoins() []db.JoinFunc {
}
return nil
}

func (opts FindRunJobOptions) ToOrders() string {
return string(opts.OrderBy)
}

var _ db.FindOptionsOrder = FindRunJobOptions{}
10 changes: 10 additions & 0 deletions routers/api/v1/admin/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,23 @@ func ListWorkflowJobs(ctx *context.APIContext) {
// in: query
// description: page size of results
// type: integer
// - name: sort
// in: query
// description: sort jobs by attribute. Supported values are "id". Default is "id"
// type: string
// - name: order
// in: query
// description: sort order, either "asc" (ascending) or "desc" (descending). Default is "asc"
// type: string
// responses:
// "200":
// "$ref": "#/responses/WorkflowJobsList"
// "400":
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"

shared.ListJobs(ctx, 0, 0, 0, nil)
}
Expand Down
21 changes: 3 additions & 18 deletions routers/api/v1/admin/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,24 +464,9 @@ func SearchUsers(ctx *context.APIContext) {

listOptions := utils.GetListOptions(ctx)

orderBy := db.SearchOrderByAlphabetically
sortMode := ctx.FormString("sort")
if len(sortMode) > 0 {
sortOrder := ctx.FormString("order")
if len(sortOrder) == 0 {
sortOrder = "asc"
}
if searchModeMap, ok := user_model.AdminUserOrderByMap[sortOrder]; ok {
if order, ok := searchModeMap[sortMode]; ok {
orderBy = order
} else {
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort mode: \"%s\"", sortMode))
return
}
} else {
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort order: \"%s\"", sortOrder))
return
}
orderBy, ok := utils.ResolveSortOrder(ctx, user_model.AdminUserOrderByMap, db.SearchOrderByAlphabetically)
if !ok {
return
}

var visible []api.VisibleType
Expand Down
20 changes: 20 additions & 0 deletions routers/api/v1/repo/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -707,13 +707,23 @@ func (Action) ListWorkflowJobs(ctx *context.APIContext) {
// in: query
// description: page size of results
// type: integer
// - name: sort
// in: query
// description: sort jobs by attribute. Supported values are "id". Default is "id"
// type: string
// - name: order
// in: query
// description: sort order, either "asc" (ascending) or "desc" (descending). Default is "asc"
// type: string
// responses:
// "200":
// "$ref": "#/responses/WorkflowJobsList"
// "400":
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"

repoID := ctx.Repo.Repository.ID

Expand Down Expand Up @@ -1527,13 +1537,23 @@ func ListWorkflowRunJobs(ctx *context.APIContext) {
// in: query
// description: page size of results
// type: integer
// - name: sort
// in: query
// description: sort jobs by attribute. Supported values are "id". Default is "id"
// type: string
// - name: order
// in: query
// description: sort order, either "asc" (ascending) or "desc" (descending). Default is "asc"
// type: string
// responses:
// "200":
// "$ref": "#/responses/WorkflowJobsList"
// "400":
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"

repoID, runID := ctx.Repo.Repository.ID, ctx.PathParamInt64("run")

Expand Down
21 changes: 4 additions & 17 deletions routers/api/v1/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,24 +187,11 @@ func Search(ctx *context.APIContext) {
opts.IsPrivate = optional.Some(ctx.FormBool("is_private"))
}

sortMode := ctx.FormString("sort")
if len(sortMode) > 0 {
sortOrder := ctx.FormString("order")
if len(sortOrder) == 0 {
sortOrder = "asc"
}
if searchModeMap, ok := repo_model.OrderByMap[sortOrder]; ok {
if orderBy, ok := searchModeMap[sortMode]; ok {
opts.OrderBy = orderBy
} else {
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort mode: \"%s\"", sortMode))
return
}
} else {
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort order: \"%s\"", sortOrder))
return
}
orderBy, ok := utils.ResolveSortOrder(ctx, repo_model.OrderByMap, "")
if !ok {
return
}
opts.OrderBy = orderBy

repos, count, err := repo_model.SearchRepository(ctx, opts)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions routers/api/v1/shared/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,16 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64, runAttemptI
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
}
listOptions := utils.GetListOptions(ctx)
orderBy, ok := utils.ResolveSortOrder(ctx, actions_model.JobOrderByMap, actions_model.JobOrderByMap["asc"]["id"])
if !ok {
return
}
opts := actions_model.FindRunJobOptions{
OwnerID: ownerID,
RepoID: repoID,
RunID: runID,
ListOptions: listOptions,
OrderBy: orderBy,
}
if runID > 0 {
opts.RunAttemptID = runAttemptID
Expand Down
10 changes: 10 additions & 0 deletions routers/api/v1/user/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,14 @@ func ListWorkflowJobs(ctx *context.APIContext) {
// in: query
// description: page size of results
// type: integer
// - name: sort
// in: query
// description: sort jobs by attribute. Supported values are "id". Default is "id"
// type: string
// - name: order
// in: query
// description: sort order, either "asc" (ascending) or "desc" (descending). Default is "asc"
// type: string
// produces:
// - application/json
// responses:
Expand All @@ -438,6 +446,8 @@ func ListWorkflowJobs(ctx *context.APIContext) {
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"

shared.ListJobs(ctx, ctx.Doer.ID, 0, 0, nil)
}
38 changes: 38 additions & 0 deletions routers/api/v1/utils/sort.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package utils

import (
"fmt"
"net/http"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/services/context"
)

// ResolveSortOrder reads "sort" and "order" query params and returns the matching
// SearchOrderBy from orderByMap. When "sort" is absent, returns defaultOrder.
// On invalid input it writes a 422 response and returns ok=false; callers should
// then return immediately.
func ResolveSortOrder(ctx *context.APIContext, orderByMap map[string]map[string]db.SearchOrderBy, defaultOrder db.SearchOrderBy) (db.SearchOrderBy, bool) {
sortMode := ctx.FormString("sort")
if sortMode == "" {
return defaultOrder, true
}
sortOrder := ctx.FormString("order")
if sortOrder == "" {
sortOrder = "asc"
}
orderMap, ok := orderByMap[sortOrder]
if !ok {
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort order: %q", sortOrder))
return "", false
}
orderBy, ok := orderMap[sortMode]
if !ok {
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort mode: %q", sortMode))
return "", false
}
return orderBy, true
}
44 changes: 44 additions & 0 deletions routers/api/v1/utils/sort_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package utils

import (
"net/http"
"testing"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/services/contexttest"

"github.com/stretchr/testify/assert"
)

func TestResolveSortOrder(t *testing.T) {
m := map[string]map[string]db.SearchOrderBy{
"asc": {"id": "id ASC"},
"desc": {"id": "id DESC"},
}
defaultOrder := db.SearchOrderBy("default")

cases := []struct {
path string
wantOK bool
wantOrder db.SearchOrderBy
wantStatus int
}{
{"GET /", true, defaultOrder, 0},
{"GET /?sort=id", true, "id ASC", 0},
{"GET /?sort=id&order=desc", true, "id DESC", 0},
{"GET /?sort=bogus", false, "", http.StatusUnprocessableEntity},
{"GET /?sort=id&order=bogus", false, "", http.StatusUnprocessableEntity},
}
for _, tc := range cases {
t.Run(tc.path, func(t *testing.T) {
ctx, _ := contexttest.MockAPIContext(t, tc.path)
got, ok := ResolveSortOrder(ctx, m, defaultOrder)
assert.Equal(t, tc.wantOK, ok)
assert.Equal(t, tc.wantOrder, got)
assert.Equal(t, tc.wantStatus, ctx.Resp.WrittenStatus())
})
}
}
60 changes: 60 additions & 0 deletions templates/swagger/v1_json.tmpl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading