import { KOREAN_STOCK_INDEX } from "@/features/trade/data/korean-stocks"; import type { DashboardStockOverviewResponse } from "@/features/trade/types/trade.types"; import type { KisCredentialInput } from "@/lib/kis/config"; 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 { DOMESTIC_KIS_SESSION_OVERRIDE_HEADER, parseDomesticKisSession, } from "@/lib/kis/domestic-market-session"; /** * @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 NextResponse.json({ error: "로그인이 필요합니다." }, { status: 401 }); } const { searchParams } = new URL(request.url); const symbol = (searchParams.get("symbol") ?? "").trim(); if (!/^\d{6}$/.test(symbol)) { return NextResponse.json({ error: "symbol은 6자리 숫자여야 합니다." }, { status: 400 }); } const credentials = readKisCredentialsFromHeaders(request.headers); if (!hasKisConfig(credentials)) { return NextResponse.json( { error: "대시보드 상단에서 KIS API 키를 입력하고 검증해 주세요. 키 정보가 없어서 시세를 조회할 수 없습니다.", }, { status: 400 }, ); } 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) { const message = error instanceof Error ? error.message : "KIS 조회 중 오류가 발생했습니다."; return NextResponse.json({ error: message }, { status: 500 }); } } /** * 요청 헤더에서 KIS 키를 읽어옵니다. * @param headers 요청 헤더 * @returns credentials */ function readKisCredentialsFromHeaders(headers: Headers): KisCredentialInput { const appKey = headers.get("x-kis-app-key")?.trim(); const appSecret = headers.get("x-kis-app-secret")?.trim(); const tradingEnv = normalizeTradingEnv(headers.get("x-kis-trading-env") ?? undefined); return { appKey, appSecret, tradingEnv, }; } function readSessionOverrideFromHeaders(headers: Headers) { if (process.env.NODE_ENV === "production") return null; const raw = headers.get(DOMESTIC_KIS_SESSION_OVERRIDE_HEADER); return parseDomesticKisSession(raw); }