import { KOREAN_STOCK_INDEX } from "@/features/trade/data/korean-stocks"; import type { DashboardStockOverviewResponse } from "@/features/trade/types/trade.types"; import { hasKisConfig, normalizeTradingEnv } from "@/lib/kis/config"; import { hasKisApiSession } from "@/app/api/kis/_session"; import { getDomesticOverview } from "@/lib/kis/domestic"; import { NextRequest, NextResponse } from "next/server"; import { createKisApiErrorResponse, KIS_API_ERROR_CODE, toKisApiErrorMessage, } from "@/app/api/kis/_response"; import { DOMESTIC_KIS_SESSION_OVERRIDE_HEADER, parseDomesticKisSession, } from "@/lib/kis/domestic-market-session"; import { readKisCredentialsFromHeaders } from "@/app/api/kis/domestic/_shared"; /** * @file app/api/kis/domestic/overview/route.ts * @description 국내주식 종목 상세(현재가 + 차트) API */ /** * 국내주식 종목 상세 API * @param request query string의 symbol(6자리 종목코드) 사용 * @returns 대시보드 상세 모델 */ export async function GET(request: NextRequest) { const hasSession = await hasKisApiSession(); if (!hasSession) { return createKisApiErrorResponse({ status: 401, code: KIS_API_ERROR_CODE.AUTH_REQUIRED, message: "로그인이 필요합니다.", }); } const { searchParams } = new URL(request.url); const symbol = (searchParams.get("symbol") ?? "").trim(); if (!/^\d{6}$/.test(symbol)) { return createKisApiErrorResponse({ status: 400, code: KIS_API_ERROR_CODE.INVALID_REQUEST, message: "symbol은 6자리 숫자여야 합니다.", }); } const credentials = readKisCredentialsFromHeaders(request.headers); if (!hasKisConfig(credentials)) { return createKisApiErrorResponse({ status: 400, code: KIS_API_ERROR_CODE.CREDENTIAL_REQUIRED, message: "대시보드 상단에서 KIS API 키를 입력하고 검증해 주세요. 키 정보가 없어서 시세를 조회할 수 없습니다.", }); } const fallbackMeta = KOREAN_STOCK_INDEX.find((item) => item.symbol === symbol); try { const sessionOverride = readSessionOverrideFromHeaders(request.headers); const overview = await getDomesticOverview( symbol, fallbackMeta, credentials, { sessionOverride }, ); const response: DashboardStockOverviewResponse = { stock: overview.stock, source: "kis", priceSource: overview.priceSource, marketPhase: overview.marketPhase, tradingEnv: normalizeTradingEnv(credentials.tradingEnv), fetchedAt: new Date().toISOString(), }; return NextResponse.json(response, { headers: { "cache-control": "no-store", }, }); } catch (error) { return createKisApiErrorResponse({ status: 500, code: KIS_API_ERROR_CODE.UPSTREAM_FAILURE, message: toKisApiErrorMessage(error, "KIS 조회 중 오류가 발생했습니다."), }); } } function readSessionOverrideFromHeaders(headers: Headers) { if (process.env.NODE_ENV === "production") return null; const raw = headers.get(DOMESTIC_KIS_SESSION_OVERRIDE_HEADER); return parseDomesticKisSession(raw); }