diff --git a/fluxer_api/src/api/admin/services/AdminSearchService.ts b/fluxer_api/src/api/admin/services/AdminSearchService.ts index 0703247dd..b9b9a2613 100644 --- a/fluxer_api/src/api/admin/services/AdminSearchService.ts +++ b/fluxer_api/src/api/admin/services/AdminSearchService.ts @@ -11,6 +11,7 @@ import {Logger} from '@app/api/Logger'; import {getGuildSearchService, getUserSearchService} from '@app/api/SearchFactory'; import {FeatureTemporarilyDisabledError} from '@fluxer/errors/src/domains/core/FeatureTemporarilyDisabledError'; import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError'; +import type {UserSearchFilters} from '@fluxer/schema/src/contracts/search/SearchDocumentTypes'; import type {WorkerJobPayload} from '@pkgs/worker/src/contracts/WorkerTypes'; interface RefreshSearchIndexJobPayload extends WorkerJobPayload { @@ -130,16 +131,28 @@ export class AdminSearchService { throw new FeatureTemporarilyDisabledError(); } const query = data.query?.trim() || ''; + const isBrowseAll = query === '' || query === '*'; + const searchFilters: UserSearchFilters = isBrowseAll + ? {sortBy: 'createdAt', sortOrder: 'asc'} + : {sortBy: 'relevance'}; const directUserId = /^\d+$/.test(query) ? createUserID(BigInt(query)) : null; const canResolveDirectUser = directUserId !== null && !isSyntheticUserId(directUserId) && data.offset === 0; const [searchResult, directUser] = await Promise.all([ - userSearchService.search(query, {}, {limit: data.limit, offset: data.offset}), + userSearchService.search(query, searchFilters, {limit: data.limit, offset: data.offset}), canResolveDirectUser ? userRepository.findUnique(directUserId).catch(() => null) : Promise.resolve(null), ]); const {hits, total} = searchResult; const userIds = hits.map((hit) => createUserID(BigInt(hit.id))); const users = await userRepository.listUsers(userIds); - const response = await Promise.all(users.map((user) => mapUserToAdminResponse(user, cacheService, acls))); + const usersById = new Map(users.map((user) => [user.id.toString(), user])); + const orderedUsers = []; + for (const userId of userIds) { + const user = usersById.get(userId.toString()); + if (user) { + orderedUsers.push(user); + } + } + const response = await Promise.all(orderedUsers.map((user) => mapUserToAdminResponse(user, cacheService, acls))); if (directUser && data.offset === 0) { const directId = directUser.id.toString(); if (!response.some((u) => u.id === directId)) { diff --git a/fluxer_api/src/api/search/meilisearch/MeilisearchDomainAdapters.ts b/fluxer_api/src/api/search/meilisearch/MeilisearchDomainAdapters.ts index b2b7e886a..a055d6c07 100644 --- a/fluxer_api/src/api/search/meilisearch/MeilisearchDomainAdapters.ts +++ b/fluxer_api/src/api/search/meilisearch/MeilisearchDomainAdapters.ts @@ -52,7 +52,8 @@ function snowflakeSeconds(snowflake: string): number { function buildSort(sortBy: string, sortOrder: 'asc' | 'desc' | undefined): Array | undefined { if (sortBy === 'relevance') return undefined; - return [`${sortBy}:${sortOrder ?? 'desc'}`, 'id:desc']; + const direction = sortOrder ?? 'desc'; + return [`${sortBy}:${direction}`, `id:${direction}`]; } function buildTimestampSort(filters: MessageSearchFilters | AuditLogSearchFilters): Array | undefined { diff --git a/fluxer_api/src/api/search/meilisearch/MeilisearchIndexAdapter.test.ts b/fluxer_api/src/api/search/meilisearch/MeilisearchIndexAdapter.test.ts index 1383506bf..98f0a4d21 100644 --- a/fluxer_api/src/api/search/meilisearch/MeilisearchIndexAdapter.test.ts +++ b/fluxer_api/src/api/search/meilisearch/MeilisearchIndexAdapter.test.ts @@ -117,7 +117,7 @@ describe('MeilisearchMessageAdapter', () => { '(guildId = "guild-1") AND ((channelId = "channel-\\"quoted\\"" OR channelId = "channel-2")) AND (mentionedUserIds = "user-1")', limit: 10, offset: 20, - sort: ['createdAt:asc', 'id:desc'], + sort: ['createdAt:asc', 'id:asc'], attributesToSearchOn: ['content', 'embedContent'], showRankingScore: false, },