feat: Zod를 사용한 월드 상태 구성 및 메타 타입 추가, 사용자 ID 처리 개선
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
import type { GameSessionTokenPayload } from '@sammo-ts/common';
|
import type { GameSessionTokenPayload } from '@sammo-ts/common';
|
||||||
import type { DatabaseClient as InfraDatabaseClient, RedisConnector } from '@sammo-ts/infra';
|
import type { DatabaseClient as InfraDatabaseClient, RedisConnector } from '@sammo-ts/infra';
|
||||||
|
|
||||||
@@ -10,6 +11,21 @@ export interface GameProfile {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const zWorldStateConfig = z.object({
|
||||||
|
maxUserCnt: z.number().optional(),
|
||||||
|
fictionMode: z.string().optional(),
|
||||||
|
});
|
||||||
|
export type WorldStateConfig = z.infer<typeof zWorldStateConfig>;
|
||||||
|
|
||||||
|
export const zWorldStateMeta = z.object({
|
||||||
|
starttime: z.string().optional(),
|
||||||
|
opentime: z.string().optional(),
|
||||||
|
turntime: z.string().optional(),
|
||||||
|
otherTextInfo: z.string().optional(),
|
||||||
|
isUnited: z.number().optional(),
|
||||||
|
});
|
||||||
|
export type WorldStateMeta = z.infer<typeof zWorldStateMeta>;
|
||||||
|
|
||||||
export interface WorldStateRow {
|
export interface WorldStateRow {
|
||||||
scenarioCode: string;
|
scenarioCode: string;
|
||||||
currentYear: number;
|
currentYear: number;
|
||||||
@@ -22,7 +38,7 @@ export interface WorldStateRow {
|
|||||||
|
|
||||||
export interface GeneralRow {
|
export interface GeneralRow {
|
||||||
id: number;
|
id: number;
|
||||||
userId: number | null;
|
userId: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
nationId: number;
|
nationId: number;
|
||||||
cityId: number;
|
cityId: number;
|
||||||
@@ -49,6 +65,7 @@ export interface GeneralRow {
|
|||||||
atmos: number;
|
atmos: number;
|
||||||
age: number;
|
age: number;
|
||||||
npcState: number;
|
npcState: number;
|
||||||
|
picture: string | null;
|
||||||
meta: unknown;
|
meta: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+21
-14
@@ -1,7 +1,8 @@
|
|||||||
import { TRPCError } from '@trpc/server';
|
import { TRPCError } from '@trpc/server';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import type { WorldStateRow } from './context.js';
|
import { zWorldStateConfig, zWorldStateMeta } from './context.js';
|
||||||
|
import type { GameApiContext, WorldStateRow } from './context.js';
|
||||||
import { authedProcedure, procedure, router } from './trpc.js';
|
import { authedProcedure, procedure, router } from './trpc.js';
|
||||||
import { buildTurnCommandTable } from './turns/commandTable.js';
|
import { buildTurnCommandTable } from './turns/commandTable.js';
|
||||||
import {
|
import {
|
||||||
@@ -67,12 +68,12 @@ const toWorldStateSnapshot = (row: WorldStateRow) => ({
|
|||||||
updatedAt: row.updatedAt.toISOString(),
|
updatedAt: row.updatedAt.toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const getMyGeneral = async (ctx: { db: any, auth: any }) => {
|
const getMyGeneral = async (ctx: Pick<GameApiContext, 'db' | 'auth'>) => {
|
||||||
if (!ctx.auth?.user.id) {
|
if (!ctx.auth?.user.id) {
|
||||||
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
||||||
}
|
}
|
||||||
const general = await ctx.db.general.findFirst({
|
const general = await ctx.db.general.findFirst({
|
||||||
where: { userId: parseInt(ctx.auth.user.id) },
|
where: { userId: ctx.auth.user.id },
|
||||||
});
|
});
|
||||||
if (!general) {
|
if (!general) {
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'General not found' });
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'General not found' });
|
||||||
@@ -90,14 +91,20 @@ export const appRouter = router({
|
|||||||
}),
|
}),
|
||||||
lobby: router({
|
lobby: router({
|
||||||
info: procedure.query(async ({ ctx }) => {
|
info: procedure.query(async ({ ctx }) => {
|
||||||
const worldState = await ctx.db.worldState.findFirst();
|
const rawWorldState = await ctx.db.worldState.findFirst();
|
||||||
if (!worldState) {
|
if (!rawWorldState) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'NOT_FOUND',
|
code: 'NOT_FOUND',
|
||||||
message: 'World state not found',
|
message: 'World state not found',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const worldState = {
|
||||||
|
...rawWorldState,
|
||||||
|
config: zWorldStateConfig.parse(rawWorldState.config),
|
||||||
|
meta: zWorldStateMeta.parse(rawWorldState.meta),
|
||||||
|
};
|
||||||
|
|
||||||
const userCnt = await ctx.db.general.count({ where: { npcState: 0 } });
|
const userCnt = await ctx.db.general.count({ where: { npcState: 0 } });
|
||||||
const npcCnt = await ctx.db.general.count({ where: { npcState: { gt: 0 } } });
|
const npcCnt = await ctx.db.general.count({ where: { npcState: { gt: 0 } } });
|
||||||
const nationCnt = await ctx.db.nation.count({ where: { level: { gt: 0 } } });
|
const nationCnt = await ctx.db.nation.count({ where: { level: { gt: 0 } } });
|
||||||
@@ -111,8 +118,8 @@ export const appRouter = router({
|
|||||||
});
|
});
|
||||||
if (general) {
|
if (general) {
|
||||||
myGeneral = {
|
myGeneral = {
|
||||||
name: (general as any).name,
|
name: general.name,
|
||||||
picture: (general as any).picture,
|
picture: general.picture,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,16 +128,16 @@ export const appRouter = router({
|
|||||||
year: worldState.currentYear,
|
year: worldState.currentYear,
|
||||||
month: worldState.currentMonth,
|
month: worldState.currentMonth,
|
||||||
userCnt,
|
userCnt,
|
||||||
maxUserCnt: (worldState.config as any).maxUserCnt ?? 500,
|
maxUserCnt: worldState.config.maxUserCnt ?? 500,
|
||||||
npcCnt,
|
npcCnt,
|
||||||
nationCnt,
|
nationCnt,
|
||||||
turnTerm: worldState.tickSeconds / 60,
|
turnTerm: worldState.tickSeconds / 60,
|
||||||
fictionMode: (worldState.config as any).fictionMode ?? '사실',
|
fictionMode: worldState.config.fictionMode ?? '사실',
|
||||||
starttime: (worldState.meta as any).starttime ?? '',
|
starttime: worldState.meta.starttime ?? '',
|
||||||
opentime: (worldState.meta as any).opentime ?? '',
|
opentime: worldState.meta.opentime ?? '',
|
||||||
turntime: (worldState.meta as any).turntime ?? '',
|
turntime: worldState.meta.turntime ?? '',
|
||||||
otherTextInfo: (worldState.meta as any).otherTextInfo ?? '',
|
otherTextInfo: worldState.meta.otherTextInfo ?? '',
|
||||||
isUnited: (worldState.meta as any).isUnited ?? 0,
|
isUnited: worldState.meta.isUnited ?? 0,
|
||||||
myGeneral,
|
myGeneral,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export const createGameApiServer = async () => {
|
|||||||
const token = extractBearerToken(req.headers.authorization);
|
const token = extractBearerToken(req.headers.authorization);
|
||||||
const auth = token ? tokenVerifier.verify(token) : null;
|
const auth = token ? tokenVerifier.verify(token) : null;
|
||||||
return createGameApiContext({
|
return createGameApiContext({
|
||||||
db: postgres.prisma as unknown as DatabaseClient,
|
db: postgres.prisma,
|
||||||
redis: redis.client,
|
redis: redis.client,
|
||||||
turnDaemon,
|
turnDaemon,
|
||||||
battleSim,
|
battleSim,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
createGamePostgresConnector,
|
createGamePostgresConnector,
|
||||||
type InputJsonValue,
|
type InputJsonValue,
|
||||||
type TurnEngineDatabaseClient,
|
|
||||||
type TurnEngineEventCreateManyInput,
|
type TurnEngineEventCreateManyInput,
|
||||||
} from '@sammo-ts/infra';
|
} from '@sammo-ts/infra';
|
||||||
import {
|
import {
|
||||||
@@ -125,7 +124,7 @@ export const seedScenarioToDatabase = async (
|
|||||||
|
|
||||||
await connector.connect();
|
await connector.connect();
|
||||||
try {
|
try {
|
||||||
const prisma = connector.prisma as unknown as TurnEngineDatabaseClient;
|
const prisma = connector.prisma;
|
||||||
|
|
||||||
if (options.resetTables ?? true) {
|
if (options.resetTables ?? true) {
|
||||||
await prisma.event.deleteMany();
|
await prisma.event.deleteMany();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
createGamePostgresConnector,
|
createGamePostgresConnector,
|
||||||
type InputJsonValue,
|
type InputJsonValue,
|
||||||
type TurnEngineCityUpdateInput,
|
type TurnEngineCityUpdateInput,
|
||||||
type TurnEngineDatabaseClient,
|
|
||||||
type TurnEngineDiplomacyCreateManyInput,
|
type TurnEngineDiplomacyCreateManyInput,
|
||||||
type TurnEngineDiplomacyUpdateInput,
|
type TurnEngineDiplomacyUpdateInput,
|
||||||
type TurnEngineGeneralCreateManyInput,
|
type TurnEngineGeneralCreateManyInput,
|
||||||
@@ -238,7 +237,7 @@ export const createDatabaseTurnHooks = async (
|
|||||||
// 턴 처리 결과를 DB에 반영하는 훅을 만든다.
|
// 턴 처리 결과를 DB에 반영하는 훅을 만든다.
|
||||||
const connector = createGamePostgresConnector({ url: databaseUrl });
|
const connector = createGamePostgresConnector({ url: databaseUrl });
|
||||||
await connector.connect();
|
await connector.connect();
|
||||||
const prisma = connector.prisma as unknown as TurnEngineDatabaseClient;
|
const prisma = connector.prisma;
|
||||||
|
|
||||||
const hooks: TurnDaemonHooks = {
|
const hooks: TurnDaemonHooks = {
|
||||||
flushChanges: async () => {
|
flushChanges: async () => {
|
||||||
@@ -346,8 +345,10 @@ export const createDatabaseTurnHooks = async (
|
|||||||
.map((entry) =>
|
.map((entry) =>
|
||||||
prisma.diplomacy.update({
|
prisma.diplomacy.update({
|
||||||
where: {
|
where: {
|
||||||
srcNationId: entry.fromNationId,
|
srcNationId_destNationId: {
|
||||||
destNationId: entry.toNationId,
|
srcNationId: entry.fromNationId,
|
||||||
|
destNationId: entry.toNationId,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data: buildDiplomacyUpdate(entry),
|
data: buildDiplomacyUpdate(entry),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -36,15 +36,6 @@ export interface GatewayAdminActionConsumer {
|
|||||||
stop(): Promise<void>;
|
stop(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type GatewayProfileRow = {
|
|
||||||
meta: unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
type GatewayProfileClient = {
|
|
||||||
findUnique(args: unknown): Promise<GatewayProfileRow | null>;
|
|
||||||
update(args: unknown): Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_POLL_MS = 5000;
|
const DEFAULT_POLL_MS = 5000;
|
||||||
|
|
||||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||||
@@ -75,9 +66,7 @@ export const createGatewayAdminActionConsumer = async (
|
|||||||
url: options.gatewayDatabaseUrl ?? options.databaseUrl,
|
url: options.gatewayDatabaseUrl ?? options.databaseUrl,
|
||||||
});
|
});
|
||||||
await connector.connect();
|
await connector.connect();
|
||||||
const prisma = connector.prisma as unknown as {
|
const prisma = connector.prisma;
|
||||||
gatewayProfile: GatewayProfileClient;
|
|
||||||
};
|
|
||||||
|
|
||||||
let timer: NodeJS.Timeout | null = null;
|
let timer: NodeJS.Timeout | null = null;
|
||||||
let inFlight = false;
|
let inFlight = false;
|
||||||
|
|||||||
@@ -18,15 +18,6 @@ const DEFAULT_CACHE_MS = 2000;
|
|||||||
const isRunningStatus = (status: string | null | undefined): boolean =>
|
const isRunningStatus = (status: string | null | undefined): boolean =>
|
||||||
status === 'RUNNING';
|
status === 'RUNNING';
|
||||||
|
|
||||||
type GatewayProfileRow = {
|
|
||||||
status: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type GatewayProfileClient = {
|
|
||||||
findUnique(args: unknown): Promise<GatewayProfileRow | null>;
|
|
||||||
update(args: unknown): Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createGatewayProfileGate = async (
|
export const createGatewayProfileGate = async (
|
||||||
options: GatewayProfileGateOptions
|
options: GatewayProfileGateOptions
|
||||||
): Promise<GatewayProfileGate> => {
|
): Promise<GatewayProfileGate> => {
|
||||||
@@ -34,9 +25,7 @@ export const createGatewayProfileGate = async (
|
|||||||
url: options.gatewayDatabaseUrl ?? options.databaseUrl,
|
url: options.gatewayDatabaseUrl ?? options.databaseUrl,
|
||||||
});
|
});
|
||||||
await connector.connect();
|
await connector.connect();
|
||||||
const prisma = connector.prisma as unknown as {
|
const prisma = connector.prisma;
|
||||||
gatewayProfile: GatewayProfileClient;
|
|
||||||
};
|
|
||||||
let lastCheckedAt = 0;
|
let lastCheckedAt = 0;
|
||||||
let cachedPause = false;
|
let cachedPause = false;
|
||||||
|
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ export const createReservedTurnStore = async (
|
|||||||
const connector = createGamePostgresConnector({ url: options.databaseUrl });
|
const connector = createGamePostgresConnector({ url: options.databaseUrl });
|
||||||
await connector.connect();
|
await connector.connect();
|
||||||
const store = new InMemoryReservedTurnStore(
|
const store = new InMemoryReservedTurnStore(
|
||||||
connector.prisma as unknown as ReservedTurnDatabaseClient,
|
connector.prisma,
|
||||||
{
|
{
|
||||||
maxGeneralTurns: options.maxGeneralTurns ?? DEFAULT_GENERAL_TURNS,
|
maxGeneralTurns: options.maxGeneralTurns ?? DEFAULT_GENERAL_TURNS,
|
||||||
maxNationTurns: options.maxNationTurns ?? DEFAULT_NATION_TURNS,
|
maxNationTurns: options.maxNationTurns ?? DEFAULT_NATION_TURNS,
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ export const loadTurnWorldFromDatabase = async (
|
|||||||
const connector = createGamePostgresConnector({ url: options.databaseUrl });
|
const connector = createGamePostgresConnector({ url: options.databaseUrl });
|
||||||
await connector.connect();
|
await connector.connect();
|
||||||
try {
|
try {
|
||||||
const prisma = connector.prisma as unknown as TurnEngineDatabaseClient;
|
const prisma: TurnEngineDatabaseClient = connector.prisma;
|
||||||
const worldState = await prisma.worldState.findFirst();
|
const worldState = await prisma.worldState.findFirst();
|
||||||
if (!worldState) {
|
if (!worldState) {
|
||||||
throw new Error('world_state row is required to start turn daemon.');
|
throw new Error('world_state row is required to start turn daemon.');
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export class RepositoryProfileStatusService implements GatewayProfileStatusServi
|
|||||||
row: GatewayProfileRecord,
|
row: GatewayProfileRecord,
|
||||||
runtimeMap: Map<string, { apiRunning: boolean; daemonRunning: boolean }>
|
runtimeMap: Map<string, { apiRunning: boolean; daemonRunning: boolean }>
|
||||||
): LobbyProfileStatus {
|
): LobbyProfileStatus {
|
||||||
const meta = row.meta as Record<string, any>;
|
const meta = row.meta;
|
||||||
return {
|
return {
|
||||||
profileName: row.profileName,
|
profileName: row.profileName,
|
||||||
profile: row.profile,
|
profile: row.profile,
|
||||||
@@ -85,8 +85,8 @@ export class RepositoryProfileStatusService implements GatewayProfileStatusServi
|
|||||||
apiRunning: false,
|
apiRunning: false,
|
||||||
daemonRunning: false,
|
daemonRunning: false,
|
||||||
},
|
},
|
||||||
korName: (meta.korName as string) ?? row.profile,
|
korName: (meta.korName as string | undefined) ?? row.profile,
|
||||||
color: (meta.color as string) ?? '#ffffff',
|
color: (meta.color as string | undefined) ?? '#ffffff',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -587,15 +587,7 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
|
|||||||
const connector = createGamePostgresConnector({ url: databaseUrl });
|
const connector = createGamePostgresConnector({ url: databaseUrl });
|
||||||
await connector.connect();
|
await connector.connect();
|
||||||
try {
|
try {
|
||||||
const prisma = connector.prisma as unknown as {
|
const row = await connector.prisma.worldState.findFirst({
|
||||||
worldState: {
|
|
||||||
findFirst: (args: unknown) => Promise<{
|
|
||||||
scenarioCode: string | null;
|
|
||||||
tickSeconds: number | null;
|
|
||||||
} | null>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const row = await prisma.worldState.findFirst({
|
|
||||||
select: { scenarioCode: true, tickSeconds: true },
|
select: { scenarioCode: true, tickSeconds: true },
|
||||||
});
|
});
|
||||||
if (row) {
|
if (row) {
|
||||||
|
|||||||
@@ -122,15 +122,6 @@ type GatewayProfileRow = {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
type GatewayProfileClient = {
|
|
||||||
findMany(args: unknown): Promise<GatewayProfileRow[]>;
|
|
||||||
findUnique(args: unknown): Promise<GatewayProfileRow | null>;
|
|
||||||
findFirst(args: unknown): Promise<GatewayProfileRow | null>;
|
|
||||||
upsert(args: unknown): Promise<GatewayProfileRow>;
|
|
||||||
update(args: unknown): Promise<GatewayProfileRow>;
|
|
||||||
updateMany(args: unknown): Promise<unknown>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapProfile = (row: GatewayProfileRow): GatewayProfileRecord => ({
|
const mapProfile = (row: GatewayProfileRow): GatewayProfileRecord => ({
|
||||||
profileName: row.profileName,
|
profileName: row.profileName,
|
||||||
profile: row.profile,
|
profile: row.profile,
|
||||||
@@ -161,23 +152,20 @@ export const createGatewayProfileRepository = (
|
|||||||
prisma: GatewayPrismaClient
|
prisma: GatewayPrismaClient
|
||||||
): GatewayProfileRepository => ({
|
): GatewayProfileRepository => ({
|
||||||
async listProfiles(): Promise<GatewayProfileRecord[]> {
|
async listProfiles(): Promise<GatewayProfileRecord[]> {
|
||||||
const gatewayProfile = prisma.gatewayProfile as unknown as GatewayProfileClient;
|
const rows = await prisma.gatewayProfile.findMany({
|
||||||
const rows = await gatewayProfile.findMany({
|
|
||||||
orderBy: [{ profile: 'asc' }, { scenario: 'asc' }],
|
orderBy: [{ profile: 'asc' }, { scenario: 'asc' }],
|
||||||
});
|
});
|
||||||
return rows.map(mapProfile);
|
return rows.map(mapProfile);
|
||||||
},
|
},
|
||||||
async getProfile(profileName: string): Promise<GatewayProfileRecord | null> {
|
async getProfile(profileName: string): Promise<GatewayProfileRecord | null> {
|
||||||
const gatewayProfile = prisma.gatewayProfile as unknown as GatewayProfileClient;
|
const row = await prisma.gatewayProfile.findUnique({
|
||||||
const row = await gatewayProfile.findUnique({
|
|
||||||
where: { profileName },
|
where: { profileName },
|
||||||
});
|
});
|
||||||
return row ? mapProfile(row) : null;
|
return row ? mapProfile(row) : null;
|
||||||
},
|
},
|
||||||
async upsertProfile(input: GatewayProfileUpsertInput): Promise<GatewayProfileRecord> {
|
async upsertProfile(input: GatewayProfileUpsertInput): Promise<GatewayProfileRecord> {
|
||||||
const gatewayProfile = prisma.gatewayProfile as unknown as GatewayProfileClient;
|
|
||||||
const profileName = buildProfileName(input.profile, input.scenario);
|
const profileName = buildProfileName(input.profile, input.scenario);
|
||||||
const row = await gatewayProfile.upsert({
|
const row = await prisma.gatewayProfile.upsert({
|
||||||
where: { profileName },
|
where: { profileName },
|
||||||
create: {
|
create: {
|
||||||
profileName,
|
profileName,
|
||||||
@@ -229,7 +217,7 @@ export const createGatewayProfileRepository = (
|
|||||||
scheduledStartAt?: string | null;
|
scheduledStartAt?: string | null;
|
||||||
}
|
}
|
||||||
): Promise<GatewayProfileRecord | null> {
|
): Promise<GatewayProfileRecord | null> {
|
||||||
const gatewayProfile = prisma.gatewayProfile as unknown as GatewayProfileClient;
|
const gatewayProfile = prisma.gatewayProfile;
|
||||||
const row = await gatewayProfile.update({
|
const row = await gatewayProfile.update({
|
||||||
where: { profileName },
|
where: { profileName },
|
||||||
data: {
|
data: {
|
||||||
@@ -269,7 +257,7 @@ export const createGatewayProfileRepository = (
|
|||||||
lastUsedAt?: string | null;
|
lastUsedAt?: string | null;
|
||||||
}
|
}
|
||||||
): Promise<GatewayProfileRecord | null> {
|
): Promise<GatewayProfileRecord | null> {
|
||||||
const gatewayProfile = prisma.gatewayProfile as unknown as GatewayProfileClient;
|
const gatewayProfile = prisma.gatewayProfile;
|
||||||
const row = await gatewayProfile.update({
|
const row = await gatewayProfile.update({
|
||||||
where: { profileName },
|
where: { profileName },
|
||||||
data: {
|
data: {
|
||||||
@@ -311,7 +299,7 @@ export const createGatewayProfileRepository = (
|
|||||||
profileName: string,
|
profileName: string,
|
||||||
meta: Record<string, unknown>
|
meta: Record<string, unknown>
|
||||||
): Promise<GatewayProfileRecord | null> {
|
): Promise<GatewayProfileRecord | null> {
|
||||||
const gatewayProfile = prisma.gatewayProfile as unknown as GatewayProfileClient;
|
const gatewayProfile = prisma.gatewayProfile;
|
||||||
const row = await gatewayProfile.update({
|
const row = await gatewayProfile.update({
|
||||||
where: { profileName },
|
where: { profileName },
|
||||||
data: {
|
data: {
|
||||||
@@ -321,7 +309,7 @@ export const createGatewayProfileRepository = (
|
|||||||
return row ? mapProfile(row) : null;
|
return row ? mapProfile(row) : null;
|
||||||
},
|
},
|
||||||
async listReservedToStart(now: Date): Promise<GatewayProfileRecord[]> {
|
async listReservedToStart(now: Date): Promise<GatewayProfileRecord[]> {
|
||||||
const gatewayProfile = prisma.gatewayProfile as unknown as GatewayProfileClient;
|
const gatewayProfile = prisma.gatewayProfile;
|
||||||
const rows = await gatewayProfile.findMany({
|
const rows = await gatewayProfile.findMany({
|
||||||
where: {
|
where: {
|
||||||
status: 'RESERVED',
|
status: 'RESERVED',
|
||||||
@@ -333,7 +321,7 @@ export const createGatewayProfileRepository = (
|
|||||||
return rows.map(mapProfile);
|
return rows.map(mapProfile);
|
||||||
},
|
},
|
||||||
async findQueuedBuild(): Promise<GatewayProfileRecord | null> {
|
async findQueuedBuild(): Promise<GatewayProfileRecord | null> {
|
||||||
const gatewayProfile = prisma.gatewayProfile as unknown as GatewayProfileClient;
|
const gatewayProfile = prisma.gatewayProfile;
|
||||||
const row = await gatewayProfile.findFirst({
|
const row = await gatewayProfile.findFirst({
|
||||||
where: { buildStatus: 'QUEUED' },
|
where: { buildStatus: 'QUEUED' },
|
||||||
orderBy: { buildRequestedAt: 'asc' },
|
orderBy: { buildRequestedAt: 'asc' },
|
||||||
@@ -341,7 +329,7 @@ export const createGatewayProfileRepository = (
|
|||||||
return row ? mapProfile(row) : null;
|
return row ? mapProfile(row) : null;
|
||||||
},
|
},
|
||||||
async updateLastError(profileName: string, lastError: string | null): Promise<void> {
|
async updateLastError(profileName: string, lastError: string | null): Promise<void> {
|
||||||
const gatewayProfile = prisma.gatewayProfile as unknown as GatewayProfileClient;
|
const gatewayProfile = prisma.gatewayProfile;
|
||||||
await gatewayProfile.update({
|
await gatewayProfile.update({
|
||||||
where: { profileName },
|
where: { profileName },
|
||||||
data: { lastError },
|
data: { lastError },
|
||||||
@@ -352,7 +340,7 @@ export const createGatewayProfileRepository = (
|
|||||||
workspace: string,
|
workspace: string,
|
||||||
lastUsedAt: string
|
lastUsedAt: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const gatewayProfile = prisma.gatewayProfile as unknown as GatewayProfileClient;
|
const gatewayProfile = prisma.gatewayProfile;
|
||||||
await gatewayProfile.update({
|
await gatewayProfile.update({
|
||||||
where: { profileName },
|
where: { profileName },
|
||||||
data: {
|
data: {
|
||||||
@@ -365,7 +353,7 @@ export const createGatewayProfileRepository = (
|
|||||||
if (!profileNames.length) {
|
if (!profileNames.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const gatewayProfile = prisma.gatewayProfile as unknown as GatewayProfileClient;
|
const gatewayProfile = prisma.gatewayProfile;
|
||||||
await gatewayProfile.updateMany({
|
await gatewayProfile.updateMany({
|
||||||
where: {
|
where: {
|
||||||
profileName: { in: profileNames },
|
profileName: { in: profileNames },
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
export interface UserInfo {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
displayName: string;
|
||||||
|
roles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
const user = ref(null);
|
const user = ref<UserInfo | null>(null);
|
||||||
const isLoggedIn = ref(false);
|
const isLoggedIn = ref(false);
|
||||||
|
|
||||||
function setUser(userData: any) {
|
function setUser(userData: UserInfo | null) {
|
||||||
user.value = userData;
|
user.value = userData;
|
||||||
isLoggedIn.value = !!userData;
|
isLoggedIn.value = !!userData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
export type JsonValue =
|
import type { GamePrisma, LogCategory, LogScope } from './gamePrisma.js';
|
||||||
| null
|
|
||||||
| boolean
|
|
||||||
| number
|
|
||||||
| string
|
|
||||||
| JsonValue[]
|
|
||||||
| { [key: string]: JsonValue };
|
|
||||||
|
|
||||||
export type InputJsonValue = JsonValue;
|
export type JsonValue = GamePrisma.JsonValue;
|
||||||
|
export type InputJsonValue = GamePrisma.InputJsonValue;
|
||||||
|
|
||||||
export interface TurnEngineWorldStateRow {
|
export interface TurnEngineWorldStateRow {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -148,7 +143,7 @@ export interface TurnEngineGeneralUpdateInput {
|
|||||||
name: string;
|
name: string;
|
||||||
nationId: number;
|
nationId: number;
|
||||||
cityId: number;
|
cityId: number;
|
||||||
troopId: number | null;
|
troopId: number;
|
||||||
leadership: number;
|
leadership: number;
|
||||||
strength: number;
|
strength: number;
|
||||||
intel: number;
|
intel: number;
|
||||||
@@ -181,7 +176,7 @@ export interface TurnEngineGeneralCreateManyInput {
|
|||||||
name: string;
|
name: string;
|
||||||
nationId: number;
|
nationId: number;
|
||||||
cityId: number;
|
cityId: number;
|
||||||
troopId?: number | null;
|
troopId?: number;
|
||||||
npcState: number;
|
npcState: number;
|
||||||
leadership: number;
|
leadership: number;
|
||||||
strength: number;
|
strength: number;
|
||||||
@@ -323,8 +318,8 @@ export interface TurnEngineEventCreateManyInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TurnEngineLogEntryCreateManyInput {
|
export interface TurnEngineLogEntryCreateManyInput {
|
||||||
scope: string;
|
scope: LogScope;
|
||||||
category: string;
|
category: LogCategory;
|
||||||
subType: string | null;
|
subType: string | null;
|
||||||
year: number;
|
year: number;
|
||||||
month: number;
|
month: number;
|
||||||
@@ -387,7 +382,12 @@ export interface TurnEngineDatabaseClient {
|
|||||||
data: TurnEngineDiplomacyCreateManyInput[];
|
data: TurnEngineDiplomacyCreateManyInput[];
|
||||||
}): Promise<unknown>;
|
}): Promise<unknown>;
|
||||||
update(args: {
|
update(args: {
|
||||||
where: { srcNationId: number; destNationId: number };
|
where: {
|
||||||
|
srcNationId_destNationId: {
|
||||||
|
srcNationId: number;
|
||||||
|
destNationId: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
data: TurnEngineDiplomacyUpdateInput;
|
data: TurnEngineDiplomacyUpdateInput;
|
||||||
}): Promise<unknown>;
|
}): Promise<unknown>;
|
||||||
deleteMany(args?: unknown): Promise<unknown>;
|
deleteMany(args?: unknown): Promise<unknown>;
|
||||||
@@ -416,36 +416,13 @@ export interface TurnEngineDatabaseClient {
|
|||||||
}): Promise<unknown>;
|
}): Promise<unknown>;
|
||||||
};
|
};
|
||||||
generalTurn: {
|
generalTurn: {
|
||||||
findMany(args?: {
|
findMany(args?: unknown): Promise<TurnEngineGeneralTurnRow[]>;
|
||||||
where?: { generalId?: number };
|
deleteMany(args?: unknown): Promise<unknown>;
|
||||||
orderBy?: { turnIdx: 'asc' | 'desc' }[];
|
createMany(args?: unknown): Promise<unknown>;
|
||||||
}): Promise<TurnEngineGeneralTurnRow[]>;
|
|
||||||
deleteMany(args: { where: { generalId: number } }): Promise<unknown>;
|
|
||||||
createMany(args: {
|
|
||||||
data: Array<{
|
|
||||||
generalId: number;
|
|
||||||
turnIdx: number;
|
|
||||||
actionCode: string;
|
|
||||||
arg: InputJsonValue;
|
|
||||||
}>;
|
|
||||||
}): Promise<unknown>;
|
|
||||||
};
|
};
|
||||||
nationTurn: {
|
nationTurn: {
|
||||||
findMany(args?: {
|
findMany(args?: unknown): Promise<TurnEngineNationTurnRow[]>;
|
||||||
where?: { nationId?: number; officerLevel?: number };
|
deleteMany(args?: unknown): Promise<unknown>;
|
||||||
orderBy?: { turnIdx: 'asc' | 'desc' }[];
|
createMany(args?: unknown): Promise<unknown>;
|
||||||
}): Promise<TurnEngineNationTurnRow[]>;
|
|
||||||
deleteMany(args: {
|
|
||||||
where: { nationId: number; officerLevel: number };
|
|
||||||
}): Promise<unknown>;
|
|
||||||
createMany(args: {
|
|
||||||
data: Array<{
|
|
||||||
nationId: number;
|
|
||||||
officerLevel: number;
|
|
||||||
turnIdx: number;
|
|
||||||
actionCode: string;
|
|
||||||
arg: InputJsonValue;
|
|
||||||
}>;
|
|
||||||
}): Promise<unknown>;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,6 @@ export interface TurnCommandModule<TSpec extends TurnCommandSpecBase = TurnComma
|
|||||||
commandSpec: TSpec;
|
commandSpec: TSpec;
|
||||||
ActionDefinition: new (...args: any[]) => GeneralActionDefinition;
|
ActionDefinition: new (...args: any[]) => GeneralActionDefinition;
|
||||||
ActionResolver?: new (...args: any[]) => GeneralActionResolver;
|
ActionResolver?: new (...args: any[]) => GeneralActionResolver;
|
||||||
CommandResolver?: new (...args: any[]) => unknown;
|
CommandResolver?: new (...args: any[]) => any;
|
||||||
actionContextBuilder?: ActionContextBuilder;
|
actionContextBuilder?: ActionContextBuilder;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ export interface CityDevelopmentEnvironment {
|
|||||||
amount?: number;
|
amount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NumberKeys<T> = { [K in keyof T]: T[K] extends number ? K : never }[keyof T];
|
||||||
|
|
||||||
export interface CityDevelopmentConfig {
|
export interface CityDevelopmentConfig {
|
||||||
key: string;
|
key: string;
|
||||||
name: string;
|
name: string;
|
||||||
statKey: keyof City;
|
statKey: NumberKeys<City>;
|
||||||
maxKey: keyof City;
|
maxKey: NumberKeys<City>;
|
||||||
label: string;
|
label: string;
|
||||||
baseAmount: number;
|
baseAmount: number;
|
||||||
}
|
}
|
||||||
@@ -74,8 +76,8 @@ export class CityDevelopmentActionDefinition<
|
|||||||
occupiedCity(),
|
occupiedCity(),
|
||||||
suppliedCity(),
|
suppliedCity(),
|
||||||
remainCityCapacityByMax(
|
remainCityCapacityByMax(
|
||||||
String(this.config.statKey),
|
this.config.statKey,
|
||||||
String(this.config.maxKey),
|
this.config.maxKey,
|
||||||
this.config.label
|
this.config.label
|
||||||
),
|
),
|
||||||
reqGeneralGold(getRequiredGold),
|
reqGeneralGold(getRequiredGold),
|
||||||
@@ -105,7 +107,7 @@ export class CityDevelopmentActionDefinition<
|
|||||||
const costGold = this.env.develCost ?? 0;
|
const costGold = this.env.develCost ?? 0;
|
||||||
|
|
||||||
// 직접 수정 (Immer Draft)
|
// 직접 수정 (Immer Draft)
|
||||||
(city as any)[this.config.statKey] = nextValue;
|
city[this.config.statKey] = nextValue;
|
||||||
general.gold = Math.max(0, general.gold - costGold);
|
general.gold = Math.max(0, general.gold - costGold);
|
||||||
|
|
||||||
const logMessage = `${this.config.label}이 ${nextValue - current} 증가했습니다.`;
|
const logMessage = `${this.config.label}이 ${nextValue - current} 증가했습니다.`;
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export const suppliedDestCity = (): Constraint => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const remainCityCapacity = (
|
export const remainCityCapacity = (
|
||||||
key: string,
|
key: keyof City,
|
||||||
label: string
|
label: string
|
||||||
): Constraint => ({
|
): Constraint => ({
|
||||||
name: 'RemainCityCapacity',
|
name: 'RemainCityCapacity',
|
||||||
@@ -158,11 +158,10 @@ export const remainCityCapacity = (
|
|||||||
const req: RequirementKey = { kind: 'city', id: ctx.cityId };
|
const req: RequirementKey = { kind: 'city', id: ctx.cityId };
|
||||||
return unknownOrDeny(ctx, [req], '도시 정보가 없습니다.');
|
return unknownOrDeny(ctx, [req], '도시 정보가 없습니다.');
|
||||||
}
|
}
|
||||||
const record = city as unknown as Record<string, number | undefined>;
|
const maxKey = `${String(key)}Max` as keyof City;
|
||||||
const maxKey = `${key}_max`;
|
const current = city[key];
|
||||||
const current = record[key];
|
const max = city[maxKey];
|
||||||
const max = record[maxKey];
|
if (typeof current !== 'number' || typeof max !== 'number') {
|
||||||
if (current === undefined || max === undefined) {
|
|
||||||
return unknownOrDeny(ctx, [], '도시 정보가 없습니다.');
|
return unknownOrDeny(ctx, [], '도시 정보가 없습니다.');
|
||||||
}
|
}
|
||||||
if (current < max) {
|
if (current < max) {
|
||||||
@@ -173,8 +172,8 @@ export const remainCityCapacity = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const remainCityCapacityByMax = (
|
export const remainCityCapacityByMax = (
|
||||||
key: string,
|
key: keyof City,
|
||||||
maxKey: string,
|
maxKey: keyof City,
|
||||||
label: string
|
label: string
|
||||||
): Constraint => ({
|
): Constraint => ({
|
||||||
name: 'RemainCityCapacityByMax',
|
name: 'RemainCityCapacityByMax',
|
||||||
@@ -189,10 +188,9 @@ export const remainCityCapacityByMax = (
|
|||||||
const req: RequirementKey = { kind: 'city', id: ctx.cityId };
|
const req: RequirementKey = { kind: 'city', id: ctx.cityId };
|
||||||
return unknownOrDeny(ctx, [req], '도시 정보가 없습니다.');
|
return unknownOrDeny(ctx, [req], '도시 정보가 없습니다.');
|
||||||
}
|
}
|
||||||
const record = city as unknown as Record<string, number | undefined>;
|
const current = city[key];
|
||||||
const current = record[key];
|
const max = city[maxKey];
|
||||||
const max = record[maxKey];
|
if (typeof current !== 'number' || typeof max !== 'number') {
|
||||||
if (current === undefined || max === undefined) {
|
|
||||||
return unknownOrDeny(ctx, [], '도시 정보가 없습니다.');
|
return unknownOrDeny(ctx, [], '도시 정보가 없습니다.');
|
||||||
}
|
}
|
||||||
if (current < max) {
|
if (current < max) {
|
||||||
@@ -203,7 +201,7 @@ export const remainCityCapacityByMax = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const reqCityCapacity = (
|
export const reqCityCapacity = (
|
||||||
key: string,
|
key: keyof City,
|
||||||
label: string,
|
label: string,
|
||||||
required: number | string
|
required: number | string
|
||||||
): Constraint => ({
|
): Constraint => ({
|
||||||
@@ -219,16 +217,15 @@ export const reqCityCapacity = (
|
|||||||
const req: RequirementKey = { kind: 'city', id: ctx.cityId };
|
const req: RequirementKey = { kind: 'city', id: ctx.cityId };
|
||||||
return unknownOrDeny(ctx, [req], '도시 정보가 없습니다.');
|
return unknownOrDeny(ctx, [req], '도시 정보가 없습니다.');
|
||||||
}
|
}
|
||||||
const record = city as unknown as Record<string, number | undefined>;
|
const current = city[key];
|
||||||
const current = record[key];
|
if (typeof current !== 'number') {
|
||||||
if (current === undefined) {
|
|
||||||
return unknownOrDeny(ctx, [], '도시 정보가 없습니다.');
|
return unknownOrDeny(ctx, [], '도시 정보가 없습니다.');
|
||||||
}
|
}
|
||||||
if (typeof required === 'string') {
|
if (typeof required === 'string') {
|
||||||
const ratio = parsePercent(required);
|
const ratio = parsePercent(required);
|
||||||
const maxKey = `${key}Max`;
|
const maxKey = `${String(key)}Max` as keyof City;
|
||||||
const max = record[maxKey];
|
const max = city[maxKey];
|
||||||
if (ratio === null || max === undefined) {
|
if (ratio === null || typeof max !== 'number') {
|
||||||
return unknownOrDeny(ctx, [], '도시 정보가 없습니다.');
|
return unknownOrDeny(ctx, [], '도시 정보가 없습니다.');
|
||||||
}
|
}
|
||||||
if (current >= max * ratio) {
|
if (current >= max * ratio) {
|
||||||
@@ -256,7 +253,7 @@ export const reqCityTrust = (minTrust: number): Constraint => ({
|
|||||||
}
|
}
|
||||||
const trust =
|
const trust =
|
||||||
readMetaNumberFromUnknown(
|
readMetaNumberFromUnknown(
|
||||||
city.meta as Record<string, unknown>,
|
city.meta,
|
||||||
'trust'
|
'trust'
|
||||||
) ?? null;
|
) ?? null;
|
||||||
if (trust === null) {
|
if (trust === null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user