import { NextResponse } from "next/server"; import { z } from "zod"; import { AUTOTRADE_API_ERROR_CODE, createAutotradeErrorResponse, getAutotradeUserId, hasAutotradeKisRuntimeHeaders, readJsonBody, sanitizeAutotradeError, upsertAutotradeSession, } from "@/app/api/autotrade/_shared"; const startRequestSchema = z.object({ symbol: z.string().trim().regex(/^\d{6}$/), leaderTabId: z.string().trim().min(1).max(100), effectiveAllocationAmount: z.number().int().positive(), effectiveDailyLossLimit: z.number().int().positive(), strategySummary: z.string().trim().min(1).max(320), }); export async function POST(request: Request) { const userId = await getAutotradeUserId(request.headers); if (!userId) { return createAutotradeErrorResponse({ status: 401, code: AUTOTRADE_API_ERROR_CODE.AUTH_REQUIRED, message: "로그인이 필요합니다.", }); } if (!hasAutotradeKisRuntimeHeaders(request.headers)) { return createAutotradeErrorResponse({ status: 400, code: AUTOTRADE_API_ERROR_CODE.CREDENTIAL_REQUIRED, message: "자동매매 시작에는 KIS 인증 헤더가 필요합니다.", }); } const rawBody = await readJsonBody(request); const parsed = startRequestSchema.safeParse(rawBody); if (!parsed.success) { return createAutotradeErrorResponse({ status: 400, code: AUTOTRADE_API_ERROR_CODE.INVALID_REQUEST, message: parsed.error.issues[0]?.message ?? "세션 시작 입력값이 올바르지 않습니다.", }); } try { const now = new Date().toISOString(); const session = upsertAutotradeSession({ userId, sessionId: crypto.randomUUID(), symbol: parsed.data.symbol, runtimeState: "RUNNING", leaderTabId: parsed.data.leaderTabId, startedAt: now, lastHeartbeatAt: now, endedAt: null, stopReason: null, effectiveAllocationAmount: parsed.data.effectiveAllocationAmount, effectiveDailyLossLimit: parsed.data.effectiveDailyLossLimit, strategySummary: parsed.data.strategySummary, }); return NextResponse.json({ ok: true, session, }); } catch (error) { return createAutotradeErrorResponse({ status: 500, code: AUTOTRADE_API_ERROR_CODE.INTERNAL, message: sanitizeAutotradeError(error, "자동매매 세션 시작 중 오류가 발생했습니다."), }); } }