From dbbd3599843452f2bc520956150283120968cfc4 Mon Sep 17 00:00:00 2001
From: Awiteb
Date: Tue, 10 Dec 2024 03:54:47 +0300
Subject: [PATCH 1/3] Add sorting functionality to user search endpoint
Signed-off-by: Awiteb
---
routers/api/v1/admin/user.go | 28 +++++++++++-
templates/swagger/v1_json.tmpl | 14 ++++++
tests/integration/admin_user_test.go | 64 ++++++++++++++++++++++++++++
3 files changed, 105 insertions(+), 1 deletion(-)
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index 184024a246..acab883107 100644
--- a/routers/api/v1/admin/user.go
+++ b/routers/api/v1/admin/user.go
@@ -416,6 +416,11 @@ func SearchUsers(ctx *context.APIContext) {
// in: query
// description: user's login name to search for
// type: string
+ // - name: sort
+ // in: query
+ // description: sort order of results
+ // type: string
+ // enum: [oldest, newest, alphabetically, reversealphabetically, recentupdate, leastupdate]
// - name: page
// in: query
// description: page number of results to return (1-based)
@@ -431,6 +436,27 @@ func SearchUsers(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden"
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)
var sourceID optional.Option[int64]
if ctx.FormString("source_id") == "" || err != nil {
@@ -444,7 +470,7 @@ func SearchUsers(ctx *context.APIContext) {
Type: user_model.UserTypeIndividual,
LoginName: ctx.FormTrim("login_name"),
SourceID: sourceID,
- OrderBy: db.SearchOrderByAlphabetically,
+ OrderBy: orderBy,
ListOptions: listOptions,
})
if err != nil {
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index b5fd179d79..3d40345c53 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -1146,6 +1146,20 @@
"name": "login_name",
"in": "query"
},
+ {
+ "enum": [
+ "oldest",
+ "newest",
+ "alphabetically",
+ "reversealphabetically",
+ "recentupdate",
+ "leastupdate"
+ ],
+ "type": "string",
+ "description": "sort order of results",
+ "name": "sort",
+ "in": "query"
+ },
{
"type": "integer",
"description": "page number of results to return (1-based)",
diff --git a/tests/integration/admin_user_test.go b/tests/integration/admin_user_test.go
index e61a46ab07..93499e9139 100644
--- a/tests/integration/admin_user_test.go
+++ b/tests/integration/admin_user_test.go
@@ -9,12 +9,15 @@ import (
"net/http"
"strconv"
"testing"
+ "time"
auth_model "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
@@ -132,3 +135,64 @@ func TestSourceId(t *testing.T) {
assert.Len(t, users, 1)
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)
+ }
+ }
+}
From 60c1fa840bc417b0bd81e62f0f641dc35e8a07a6 Mon Sep 17 00:00:00 2001
From: Awiteb
Date: Tue, 10 Dec 2024 18:34:50 +0000
Subject: [PATCH 2/3] chore: Identify the `created_unix` in user fixtures
Signed-off-by: Awiteb
---
models/fixtures/user.yml | 42 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 41 insertions(+), 1 deletion(-)
diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml
index dbc6fc3e38..c23abbb17e 100644
--- a/models/fixtures/user.yml
+++ b/models/fixtures/user.yml
@@ -36,6 +36,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578000
-
id: 2
@@ -74,6 +75,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578010
-
id: 3
@@ -111,6 +113,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578020
-
id: 4
@@ -148,6 +151,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578030
-
id: 5
@@ -185,6 +189,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578040
-
id: 6
@@ -222,6 +227,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578050
-
id: 7
@@ -259,6 +265,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578060
-
id: 8
@@ -296,6 +303,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578070
-
id: 9
@@ -333,7 +341,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
- created_unix: 1730468968
+ created_unix: 1672578080
-
id: 10
@@ -371,6 +379,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578090
-
id: 11
@@ -408,6 +417,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578100
-
id: 12
@@ -445,6 +455,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578110
-
id: 13
@@ -482,6 +493,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578120
-
id: 14
@@ -519,6 +531,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578130
-
id: 15
@@ -556,6 +569,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578140
-
id: 16
@@ -593,6 +607,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578150
-
id: 17
@@ -630,6 +645,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578160
-
id: 18
@@ -667,6 +683,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578170
-
id: 19
@@ -704,6 +721,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578180
-
id: 20
@@ -741,6 +759,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578190
-
id: 21
@@ -778,6 +797,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578200
-
id: 22
@@ -815,6 +835,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578210
-
id: 23
@@ -852,6 +873,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578220
-
id: 24
@@ -889,6 +911,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578230
-
id: 25
@@ -926,6 +949,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578240
-
id: 26
@@ -963,6 +987,7 @@
repo_admin_change_team_access: true
theme: ""
keep_activity_private: false
+ created_unix: 1672578250
-
id: 27
@@ -1000,6 +1025,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578260
-
id: 28
@@ -1037,6 +1063,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578270
-
id: 29
@@ -1074,6 +1101,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578280
-
id: 30
@@ -1111,6 +1139,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578290
-
id: 31
@@ -1148,6 +1177,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578300
-
id: 32
@@ -1185,6 +1215,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578310
-
id: 33
@@ -1222,6 +1253,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578320
-
id: 34
@@ -1260,6 +1292,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578330
-
id: 35
@@ -1297,6 +1330,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578340
-
id: 36
@@ -1334,6 +1368,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578350
-
id: 37
@@ -1371,6 +1406,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578360
-
id: 38
@@ -1408,6 +1444,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578370
-
id: 39
@@ -1445,6 +1482,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578380
-
id: 40
@@ -1482,6 +1520,7 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578390
-
id: 41
@@ -1519,3 +1558,4 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+ created_unix: 1672578400
From 7603de279ad87b346a67099d31f4b621e95025d6 Mon Sep 17 00:00:00 2001
From: Awiteb
Date: Tue, 10 Dec 2024 20:42:15 +0000
Subject: [PATCH 3/3] chore: Update user1 createdunix
Signed-off-by: Awiteb
---
models/user/user_test.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/models/user/user_test.go b/models/user/user_test.go
index ee456e3ce4..1c734fa926 100644
--- a/models/user/user_test.go
+++ b/models/user/user_test.go
@@ -771,11 +771,11 @@ func TestGetInactiveUsers(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
// all inactive users
- // user1's createdunix is 1730468968
+ // user1's createdunix is 1672578000
users, err := user_model.GetInactiveUsers(db.DefaultContext, 0)
require.NoError(t, err)
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)))
require.NoError(t, err)
require.Empty(t, users)