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