Merge pull request 'Add sorting functionality to /api/v1/admin/users endpoint' (#6228) from awiteb/forgejo:sort-user-search-endpoint into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6228
Reviewed-by: Otto <otto@codeberg.org>
This commit is contained in:
Otto 2024-12-16 21:00:13 +00:00
commit 6a4a24e2d7
5 changed files with 148 additions and 4 deletions

View file

@ -36,6 +36,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578000
- -
id: 2 id: 2
@ -74,6 +75,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578010
- -
id: 3 id: 3
@ -111,6 +113,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578020
- -
id: 4 id: 4
@ -148,6 +151,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578030
- -
id: 5 id: 5
@ -185,6 +189,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578040
- -
id: 6 id: 6
@ -222,6 +227,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578050
- -
id: 7 id: 7
@ -259,6 +265,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578060
- -
id: 8 id: 8
@ -296,6 +303,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578070
- -
id: 9 id: 9
@ -333,7 +341,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1730468968 created_unix: 1672578080
- -
id: 10 id: 10
@ -371,6 +379,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578090
- -
id: 11 id: 11
@ -408,6 +417,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578100
- -
id: 12 id: 12
@ -445,6 +455,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578110
- -
id: 13 id: 13
@ -482,6 +493,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578120
- -
id: 14 id: 14
@ -519,6 +531,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578130
- -
id: 15 id: 15
@ -556,6 +569,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578140
- -
id: 16 id: 16
@ -593,6 +607,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578150
- -
id: 17 id: 17
@ -630,6 +645,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578160
- -
id: 18 id: 18
@ -667,6 +683,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578170
- -
id: 19 id: 19
@ -704,6 +721,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578180
- -
id: 20 id: 20
@ -741,6 +759,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578190
- -
id: 21 id: 21
@ -778,6 +797,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578200
- -
id: 22 id: 22
@ -815,6 +835,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578210
- -
id: 23 id: 23
@ -852,6 +873,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578220
- -
id: 24 id: 24
@ -889,6 +911,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578230
- -
id: 25 id: 25
@ -926,6 +949,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578240
- -
id: 26 id: 26
@ -963,6 +987,7 @@
repo_admin_change_team_access: true repo_admin_change_team_access: true
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578250
- -
id: 27 id: 27
@ -1000,6 +1025,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578260
- -
id: 28 id: 28
@ -1037,6 +1063,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578270
- -
id: 29 id: 29
@ -1074,6 +1101,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578280
- -
id: 30 id: 30
@ -1111,6 +1139,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578290
- -
id: 31 id: 31
@ -1148,6 +1177,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578300
- -
id: 32 id: 32
@ -1185,6 +1215,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578310
- -
id: 33 id: 33
@ -1222,6 +1253,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578320
- -
id: 34 id: 34
@ -1260,6 +1292,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578330
- -
id: 35 id: 35
@ -1297,6 +1330,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578340
- -
id: 36 id: 36
@ -1334,6 +1368,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578350
- -
id: 37 id: 37
@ -1371,6 +1406,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578360
- -
id: 38 id: 38
@ -1408,6 +1444,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578370
- -
id: 39 id: 39
@ -1445,6 +1482,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578380
- -
id: 40 id: 40
@ -1482,6 +1520,7 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578390
- -
id: 41 id: 41
@ -1519,3 +1558,4 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
created_unix: 1672578400

View file

@ -771,11 +771,11 @@ func TestGetInactiveUsers(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase()) require.NoError(t, unittest.PrepareTestDatabase())
// all inactive users // all inactive users
// user1's createdunix is 1730468968 // user1's createdunix is 1672578000
users, err := user_model.GetInactiveUsers(db.DefaultContext, 0) users, err := user_model.GetInactiveUsers(db.DefaultContext, 0)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, users, 1) assert.Len(t, users, 1)
interval := time.Now().Unix() - 1730468968 + 3600*24 interval := time.Now().Unix() - 1672578000 + 3600*24
users, err = user_model.GetInactiveUsers(db.DefaultContext, time.Duration(interval*int64(time.Second))) users, err = user_model.GetInactiveUsers(db.DefaultContext, time.Duration(interval*int64(time.Second)))
require.NoError(t, err) require.NoError(t, err)
require.Empty(t, users) require.Empty(t, users)

View file

@ -416,6 +416,11 @@ func SearchUsers(ctx *context.APIContext) {
// in: query // in: query
// description: user's login name to search for // description: user's login name to search for
// type: string // type: string
// - name: sort
// in: query
// description: sort order of results
// type: string
// enum: [oldest, newest, alphabetically, reversealphabetically, recentupdate, leastupdate]
// - name: page // - name: page
// in: query // in: query
// description: page number of results to return (1-based) // description: page number of results to return (1-based)
@ -431,6 +436,27 @@ func SearchUsers(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
listOptions := utils.GetListOptions(ctx) listOptions := utils.GetListOptions(ctx)
sort := ctx.FormString("sort")
var orderBy db.SearchOrderBy
switch sort {
case "oldest":
orderBy = db.SearchOrderByOldest
case "newest":
orderBy = db.SearchOrderByNewest
case "alphabetically":
orderBy = db.SearchOrderByAlphabetically
case "reversealphabetically":
orderBy = db.SearchOrderByAlphabeticallyReverse
case "recentupdate":
orderBy = db.SearchOrderByRecentUpdated
case "leastupdate":
orderBy = db.SearchOrderByLeastUpdated
default:
orderBy = db.SearchOrderByAlphabetically
}
intSource, err := strconv.ParseInt(ctx.FormString("source_id"), 10, 64) intSource, err := strconv.ParseInt(ctx.FormString("source_id"), 10, 64)
var sourceID optional.Option[int64] var sourceID optional.Option[int64]
if ctx.FormString("source_id") == "" || err != nil { if ctx.FormString("source_id") == "" || err != nil {
@ -444,7 +470,7 @@ func SearchUsers(ctx *context.APIContext) {
Type: user_model.UserTypeIndividual, Type: user_model.UserTypeIndividual,
LoginName: ctx.FormTrim("login_name"), LoginName: ctx.FormTrim("login_name"),
SourceID: sourceID, SourceID: sourceID,
OrderBy: db.SearchOrderByAlphabetically, OrderBy: orderBy,
ListOptions: listOptions, ListOptions: listOptions,
}) })
if err != nil { if err != nil {

View file

@ -1146,6 +1146,20 @@
"name": "login_name", "name": "login_name",
"in": "query" "in": "query"
}, },
{
"enum": [
"oldest",
"newest",
"alphabetically",
"reversealphabetically",
"recentupdate",
"leastupdate"
],
"type": "string",
"description": "sort order of results",
"name": "sort",
"in": "query"
},
{ {
"type": "integer", "type": "integer",
"description": "page number of results to return (1-based)", "description": "page number of results to return (1-based)",

View file

@ -9,12 +9,15 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"testing" "testing"
"time"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -132,3 +135,64 @@ func TestSourceId(t *testing.T) {
assert.Len(t, users, 1) assert.Len(t, users, 1)
assert.Equal(t, "ausersourceid23", users[0].UserName) assert.Equal(t, "ausersourceid23", users[0].UserName)
} }
func TestAdminViewUsersSorted(t *testing.T) {
defer tests.PrepareTestEnv(t)()
createTimestamp := time.Now().Unix() - 1000
updateTimestamp := time.Now().Unix() - 500
sess := db.GetEngine(context.Background())
// Create 10 users with login source 44
for i := int64(1); i <= 10; i++ {
name := "sorttest" + strconv.Itoa(int(i))
user := &user_model.User{
Name: name,
LowerName: name,
LoginName: name,
Email: name + "@example.com",
Passwd: name + ".password",
Type: user_model.UserTypeIndividual,
LoginType: auth_model.OAuth2,
LoginSource: 44,
CreatedUnix: timeutil.TimeStamp(createTimestamp - i),
UpdatedUnix: timeutil.TimeStamp(updateTimestamp - i),
}
if _, err := sess.NoAutoTime().Insert(user); err != nil {
t.Fatalf("Failed to create user: %v", err)
}
}
session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin)
testCases := []struct {
loginSource int64
sortType string
expectedUsers []string
}{
{0, "alphabetically", []string{"the_34-user.with.all.allowedChars", "user1", "user10", "user11"}},
{0, "reversealphabetically", []string{"user9", "user8", "user5", "user40"}},
{0, "newest", []string{"user40", "user39", "user38", "user37"}},
{0, "oldest", []string{"user1", "user2", "user4", "user5"}},
{44, "recentupdate", []string{"sorttest1", "sorttest2", "sorttest3", "sorttest4"}},
{44, "leastupdate", []string{"sorttest10", "sorttest9", "sorttest8", "sorttest7"}},
}
for _, testCase := range testCases {
req := NewRequest(
t,
"GET",
fmt.Sprintf("/api/v1/admin/users?sort=%s&limit=4&source_id=%d",
testCase.sortType,
testCase.loginSource),
).AddTokenAuth(token)
resp := session.MakeRequest(t, req, http.StatusOK)
var users []api.User
DecodeJSON(t, resp, &users)
assert.Len(t, users, 4)
for i, user := range users {
assert.Equalf(t, testCase.expectedUsers[i], user.UserName, "Sort type: %s, index %d", testCase.sortType, i)
}
}
}