merge: 최신 main을 정적 프런트엔드 작업에 통합한다

This commit is contained in:
2026-08-22 09:33:52 +00:00
23 changed files with 1265 additions and 79 deletions
+37
View File
@@ -21,6 +21,7 @@ import { openPassword, zDisplayName, zPasswordEnvelope, zRegistrationUsername }
import { resolveEffectiveAccountIcon } from './auth/accountIconProjection.js';
import { purifyGatewayNoticeHtml } from './security/gatewayNoticeHtml.js';
import type { GatewayApiContext } from './context.js';
import { listScenarioPreviews } from './scenario/scenarioCatalog.js';
import {
KakaoVerificationError,
mergeRequiredKakaoScopes,
@@ -193,6 +194,42 @@ export const appRouter = router({
})
);
}),
scenarios: procedure
.input(z.object({ profileName: z.string().min(1).max(64) }))
.query(async ({ ctx, input }) => {
const provided = ctx.requestHeaders['x-session-token'];
const sessionToken = Array.isArray(provided) ? provided[0] : provided;
if (!sessionToken) {
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Session token is required.' });
}
const session = await ctx.sessions.getSession(sessionToken);
const user = session ? await ctx.users.findById(session.userId) : null;
if (!session || !user) {
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Session is not valid.' });
}
const visibleProfiles = await ctx.profileStatus.listLobbyProfiles({ userId: user.id });
if (!visibleProfiles.some((profile) => profile.profileName === input.profileName)) {
throw new TRPCError({ code: 'NOT_FOUND', message: 'Profile not found.' });
}
const profile = await ctx.profiles.getProfile(input.profileName);
const activeBuildCommit = profile?.buildCommitSha?.trim();
if (!profile || !activeBuildCommit) {
throw new TRPCError({
code: 'PRECONDITION_FAILED',
message: 'The profile has no active build commit.',
});
}
try {
return await listScenarioPreviews({ gitRef: activeBuildCommit });
} catch {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'The active build scenario catalog could not be read.',
});
}
}),
}),
admin: adminRouter,
account: accountRouter,
@@ -22,6 +22,8 @@ export interface ScenarioPreview {
id: number;
title: string;
year: number | null;
defaultStatTotal: number;
fiction: number | null;
npcCount: number;
npcExCount: number;
npcNeutralCount: number;
@@ -227,6 +229,8 @@ const buildScenarioPreview = async (scenarioId: number): Promise<ScenarioPreview
id: scenarioId,
title: scenario.title,
year: scenario.startYear ?? null,
defaultStatTotal: scenario.config.stat.total,
fiction: scenario.fiction,
npcCount: scenario.generals.length,
npcExCount: scenario.generalsEx.length,
npcNeutralCount: scenario.generalsNeutral.length,
@@ -272,6 +276,8 @@ const buildScenarioPreviewFromGit = async (commitSha: string, scenarioId: number
id: scenarioId,
title: scenario.title,
year: scenario.startYear ?? null,
defaultStatTotal: scenario.config.stat.total,
fiction: scenario.fiction,
npcCount: scenario.generals.length,
npcExCount: scenario.generalsEx.length,
npcNeutralCount: scenario.generalsNeutral.length,