feat: 시즌 번호 필드 추가 및 메타 데이터 처리 로직 개선

This commit is contained in:
2026-01-29 17:53:36 +00:00
parent 9244062989
commit 19c359d0b2
3 changed files with 64 additions and 5 deletions
@@ -34,6 +34,7 @@ export interface ScenarioInstallOptions {
joinMode?: 'full' | 'onlyRandom';
autorunUser?: ScenarioAutorunOptions | null;
preopenAt?: Date | null;
season?: number;
}
export interface ScenarioSeedOptions {
@@ -254,6 +255,10 @@ export const seedScenarioToDatabase = async (options: ScenarioSeedOptions): Prom
lastTurnTime: formatDateTime(now),
};
if (typeof install?.season === 'number' && Number.isFinite(install.season)) {
worldMeta.season = Math.floor(install.season);
}
const integrationSeed = process.env[INTEGRATION_WORLD_SEED_ENV];
if (typeof integrationSeed === 'string' && integrationSeed.trim().length > 0) {
worldMeta.hiddenSeed = integrationSeed.trim();
+32 -1
View File
@@ -4,7 +4,7 @@ import path from 'node:path';
import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import { resolvePostgresConfigFromEnv } from '@sammo-ts/infra';
import { createGamePostgresConnector, resolvePostgresConfigFromEnv } from '@sammo-ts/infra';
import { procedure, router } from './trpc.js';
import { listScenarioPreviews, resolveGitCommitSha } from './scenario/scenarioCatalog.js';
@@ -329,6 +329,20 @@ const readMetaObject = (value: unknown): Record<string, unknown> => {
return value as Record<string, unknown>;
};
const readMetaNumber = (meta: Record<string, unknown>, key: string): number | null => {
const raw = meta[key];
if (typeof raw === 'number' && Number.isFinite(raw)) {
return Math.floor(raw);
}
if (typeof raw === 'string') {
const parsed = Number(raw);
if (Number.isFinite(parsed)) {
return Math.floor(parsed);
}
}
return null;
};
const applyMetaPatch = (
meta: Record<string, unknown>,
patch: Record<string, unknown | null | undefined>
@@ -850,6 +864,22 @@ export const adminRouter = router({
schema: updatedProfile.profile,
}).url;
const resourcesRoot = path.resolve(process.cwd(), 'resources');
const profileMeta = readMetaObject(updatedProfile.meta);
const nextSeasonIdx = readMetaNumber(profileMeta, 'nextSeasonIdx');
let baseSeason: number | null = null;
const connector = createGamePostgresConnector({ url: databaseUrl });
try {
await connector.connect();
const row = await connector.prisma.worldState.findFirst({
select: { meta: true },
});
if (row) {
baseSeason = readMetaNumber(readMetaObject(row.meta), 'season');
}
} finally {
await connector.disconnect();
}
const season = nextSeasonIdx ?? baseSeason ?? 1;
await seedProfileDatabase({
databaseUrl,
@@ -866,6 +896,7 @@ export const adminRouter = router({
showImgLevel: input.install.showImgLevel,
tournamentTrig: input.install.tournamentTrig,
joinMode: input.install.joinMode,
season,
autorunUser: input.install.autorunUser
? {
limitMinutes: input.install.autorunUser.limitMinutes,
@@ -113,6 +113,20 @@ interface GatewayAdminActionResult {
const normalizeMeta = (value: unknown): Record<string, unknown> => (isRecord(value) ? value : {});
const readMetaNumber = (meta: Record<string, unknown>, key: string): number | null => {
const raw = meta[key];
if (typeof raw === 'number' && Number.isFinite(raw)) {
return Math.floor(raw);
}
if (typeof raw === 'string') {
const parsed = Number(raw);
if (Number.isFinite(parsed)) {
return Math.floor(parsed);
}
}
return null;
};
const normalizeStatus = (value: unknown): GatewayAdminActionStatus | null => {
if (typeof value === 'string') {
return value as GatewayAdminActionStatus;
@@ -615,6 +629,10 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
if (!seedInfo.scenarioId) {
return { status: 'FAILED', detail: 'scenarioId is missing' };
}
const profileMeta = normalizeMeta(profile.meta);
const nextSeasonIdx = readMetaNumber(profileMeta, 'nextSeasonIdx');
const baseSeason = readMetaNumber(normalizeMeta(seedInfo.meta), 'season');
const season = nextSeasonIdx ?? baseSeason ?? 1;
const seedTime =
openAt ??
(action.scheduledAt && action.action === 'RESET_SCHEDULED' ? new Date(action.scheduledAt) : this.now());
@@ -650,7 +668,10 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
scenarioId: seedInfo.scenarioId,
tickSeconds: seedInfo.tickSeconds,
now: seedTime,
installOptions: installOptions ?? undefined,
installOptions: {
...(installOptions ?? {}),
season,
},
scenarioOptions: { scenarioRoot: path.join(resourceRoot, 'scenario') },
mapOptions: { mapRoot: path.join(resourceRoot, 'map') },
unitSetOptions: { unitSetRoot: path.join(resourceRoot, 'unitset') },
@@ -685,18 +706,19 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
private async resolveResetSeedInfo(
profile: GatewayProfileRecord,
overrides?: { scenarioId?: number | null; tickSeconds?: number }
): Promise<{ databaseUrl: string; scenarioId: number | null; tickSeconds?: number }> {
): Promise<{ databaseUrl: string; scenarioId: number | null; tickSeconds?: number; meta: Record<string, unknown> } > {
const databaseUrl = resolvePostgresConfigFromEnv({
env: this.processConfig.baseEnv ?? process.env,
schema: profile.profile,
}).url;
let scenarioId = overrides?.scenarioId ?? parseScenarioId(profile.scenario);
let tickSeconds: number | undefined = overrides?.tickSeconds;
let meta: Record<string, unknown> = {};
const connector = createGamePostgresConnector({ url: databaseUrl });
await connector.connect();
try {
const row = await connector.prisma.worldState.findFirst({
select: { scenarioCode: true, tickSeconds: true },
select: { scenarioCode: true, tickSeconds: true, meta: true },
});
if (row) {
if (scenarioId === null) {
@@ -708,11 +730,12 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
if (tickSeconds === undefined && typeof row.tickSeconds === 'number' && Number.isFinite(row.tickSeconds)) {
tickSeconds = row.tickSeconds;
}
meta = normalizeMeta(row.meta);
}
} finally {
await connector.disconnect();
}
return { databaseUrl, scenarioId, tickSeconds };
return { databaseUrl, scenarioId, tickSeconds, meta };
}
private async runBuildCommands(