fix(gateway): preserve configured server display order

This commit is contained in:
2026-08-08 16:48:40 +00:00
parent 09ea198420
commit 0ef8b5ea20
5 changed files with 95 additions and 4 deletions
+3 -2
View File
@@ -20,6 +20,7 @@ import {
import type { GatewayApiContext } from './context.js';
import { resolveLocalAccountProfilePolicy } from './auth/localAccountPolicy.js';
import { GATEWAY_BUILD_STATUSES, GATEWAY_PROFILE_STATUSES } from './orchestrator/profileRepository.js';
import { orderGatewayProfiles } from './profileOrder.js';
import { purifyGatewayNoticeHtml } from './security/gatewayNoticeHtml.js';
const zProfileStatus = z.enum(GATEWAY_PROFILE_STATUSES);
@@ -643,7 +644,7 @@ export const adminRouter = router({
if (!user) {
throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found.' });
}
const profiles = await ctx.profiles.listProfiles();
const profiles = orderGatewayProfiles(await ctx.profiles.listProfiles());
const specialAccessGrants = await ctx.users.listSpecialAccessGrants(user.id);
return {
kakaoVerified: user.oauthType === 'KAKAO' && Boolean(user.kakaoVerifiedAt),
@@ -1352,7 +1353,7 @@ export const adminRouter = router({
profiles: router({
list: adminProcedure.query(async ({ ctx }) => {
const adminAuth = requireAdminAuth(ctx);
const profiles = (await ctx.profiles.listProfiles()).filter((profile) =>
const profiles = orderGatewayProfiles(await ctx.profiles.listProfiles()).filter((profile) =>
canReadProfile(adminAuth, profile.profileName)
);
const profileNames = profiles.map((profile) => profile.profileName);
@@ -4,6 +4,7 @@ import type {
GatewayProfileRepository,
GatewayProfileStatus,
} from '../orchestrator/profileRepository.js';
import { orderGatewayProfiles } from '../profileOrder.js';
export type LobbyMapSnapshot = {
updatedAt: string | null;
@@ -47,7 +48,7 @@ export class InMemoryProfileStatusService implements GatewayProfileStatusService
}
async listLobbyProfiles(): Promise<LobbyProfileStatus[]> {
return this.profiles;
return orderGatewayProfiles(this.profiles);
}
setProfiles(profiles: LobbyProfileStatus[]): void {
@@ -63,7 +64,7 @@ export class RepositoryProfileStatusService implements GatewayProfileStatusServi
) {}
async listLobbyProfiles(): Promise<LobbyProfileStatus[]> {
const rows = await this.profiles.listProfiles();
const rows = orderGatewayProfiles(await this.profiles.listProfiles());
const runtimeStates = await this.orchestrator.listRuntimeStates(rows.map((profile) => profile.profileName));
const runtimeMap = new Map(runtimeStates.map((state) => [state.profileName, state]));
return rows.map((row) => this.mapProfile(row, runtimeMap));
+21
View File
@@ -0,0 +1,21 @@
export const GATEWAY_PROFILE_ORDER = ['che', 'kwe', 'pwe', 'twe', 'nya', 'pya', 'hwe'] as const;
const gatewayProfileOrder = new Map<string, number>(GATEWAY_PROFILE_ORDER.map((profile, index) => [profile, index]));
export const compareGatewayProfiles = (
left: { profile: string; scenario: string },
right: { profile: string; scenario: string }
): number => {
const unknownRank = GATEWAY_PROFILE_ORDER.length;
const profileOrder =
(gatewayProfileOrder.get(left.profile) ?? unknownRank) -
(gatewayProfileOrder.get(right.profile) ?? unknownRank);
if (profileOrder !== 0) return profileOrder;
const profileNameOrder = left.profile.localeCompare(right.profile);
if (profileNameOrder !== 0) return profileNameOrder;
return left.scenario.localeCompare(right.scenario);
};
export const orderGatewayProfiles = <T extends { profile: string; scenario: string }>(profiles: readonly T[]): T[] =>
[...profiles].sort(compareGatewayProfiles);
+31
View File
@@ -0,0 +1,31 @@
import { describe, expect, it } from 'vitest';
import { GATEWAY_PROFILE_ORDER, orderGatewayProfiles } from '../src/profileOrder.js';
describe('orderGatewayProfiles', () => {
it('uses the public server order instead of alphabetical profile order', () => {
const profiles = ['hwe', 'pya', 'che', 'nya', 'twe', 'pwe', 'kwe'].map((profile) => ({
profile,
scenario: 'default',
}));
expect(orderGatewayProfiles(profiles).map(({ profile }) => profile)).toEqual(GATEWAY_PROFILE_ORDER);
});
it('orders scenarios within a profile and places unknown profiles afterward', () => {
const profiles = [
{ profile: 'zeta', scenario: 'default' },
{ profile: 'che', scenario: '20' },
{ profile: 'alpha', scenario: 'default' },
{ profile: 'che', scenario: '10' },
];
expect(orderGatewayProfiles(profiles)).toEqual([
{ profile: 'che', scenario: '10' },
{ profile: 'che', scenario: '20' },
{ profile: 'alpha', scenario: 'default' },
{ profile: 'zeta', scenario: 'default' },
]);
expect(profiles[0]?.profile).toBe('zeta');
});
});