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 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) 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) + } + } +}