feat: add ranking and hall of fame features

- Implemented new routes for Best General and Hall of Fame views in the frontend.
- Created BestGeneralView and HallOfFameView components to display rankings based on game data.
- Added new database models for RankData and HallOfFame to store ranking information.
- Introduced ranking types and hall of fame types in common types for better type safety.
- Developed ranking router to handle fetching of best general and hall of fame data.
- Updated database schema with migrations to include new tables for rank data and hall of fame.
- Enhanced the main view with links to the new ranking features.
This commit is contained in:
2026-01-29 18:18:35 +00:00
parent 19c359d0b2
commit 9fff671c2f
16 changed files with 1217 additions and 2 deletions
@@ -1,4 +1,5 @@
import path from 'node:path';
import { randomBytes } from 'node:crypto';
import { type ScenarioInstallOptions } from '@sammo-ts/game-engine';
import { createGamePostgresConnector, resolvePostgresConfigFromEnv } from '@sammo-ts/infra';
@@ -113,6 +114,14 @@ interface GatewayAdminActionResult {
const normalizeMeta = (value: unknown): Record<string, unknown> => (isRecord(value) ? value : {});
const buildServerId = (profileName: string, now: Date): string => {
const year = String(now.getFullYear()).slice(-2);
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const suffix = randomBytes(2).toString('hex');
return `${profileName}_${year}${month}${day}_${suffix}`;
};
const readMetaNumber = (meta: Record<string, unknown>, key: string): number | null => {
const raw = meta[key];
if (typeof raw === 'number' && Number.isFinite(raw)) {
@@ -663,6 +672,7 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
}
const workspace = await this.workspaceManager.prepare(commitSha);
const resourceRoot = path.join(workspace.root, 'resources');
const serverId = buildServerId(profile.profileName, seedTime);
await seedProfileDatabase({
databaseUrl: seedInfo.databaseUrl,
scenarioId: seedInfo.scenarioId,
@@ -671,6 +681,7 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
installOptions: {
...(installOptions ?? {}),
season,
serverId,
},
scenarioOptions: { scenarioRoot: path.join(resourceRoot, 'scenario') },
mapOptions: { mapRoot: path.join(resourceRoot, 'map') },