105 lines
2.8 KiB
TypeScript
105 lines
2.8 KiB
TypeScript
import type {
|
|
DashboardChartTimeframe,
|
|
DashboardStockChartResponse,
|
|
} from "@/features/trade/types/trade.types";
|
|
import type { KisCredentialInput } from "@/lib/kis/config";
|
|
import { hasKisConfig, normalizeTradingEnv } from "@/lib/kis/config";
|
|
import { getDomesticChart } from "@/lib/kis/domestic";
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
import { hasKisApiSession } from "@/app/api/kis/_session";
|
|
|
|
const VALID_TIMEFRAMES: DashboardChartTimeframe[] = [
|
|
"1m",
|
|
"30m",
|
|
"1h",
|
|
"1d",
|
|
"1w",
|
|
];
|
|
|
|
/**
|
|
* @file app/api/kis/domestic/chart/route.ts
|
|
* @description 국내주식 차트(분봉/일봉/주봉) 조회 API
|
|
*/
|
|
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();
|
|
const timeframe = (
|
|
searchParams.get("timeframe") ?? "1d"
|
|
).trim() as DashboardChartTimeframe;
|
|
const cursor = (searchParams.get("cursor") ?? "").trim() || undefined;
|
|
|
|
if (!/^\d{6}$/.test(symbol)) {
|
|
return NextResponse.json(
|
|
{ error: "symbol은 6자리 숫자여야 합니다." },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
if (!VALID_TIMEFRAMES.includes(timeframe)) {
|
|
return NextResponse.json(
|
|
{ error: "지원하지 않는 timeframe입니다." },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const credentials = readKisCredentialsFromHeaders(request.headers);
|
|
if (!hasKisConfig(credentials)) {
|
|
return NextResponse.json(
|
|
{
|
|
error:
|
|
"대시보드 상단에서 KIS API 키를 입력하고 검증해 주세요. 키 정보가 없어서 차트를 조회할 수 없습니다.",
|
|
},
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
try {
|
|
const chart = await getDomesticChart(
|
|
symbol,
|
|
timeframe,
|
|
credentials,
|
|
cursor,
|
|
);
|
|
|
|
const response: DashboardStockChartResponse = {
|
|
symbol,
|
|
timeframe,
|
|
candles: chart.candles,
|
|
nextCursor: chart.nextCursor,
|
|
hasMore: chart.hasMore,
|
|
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 });
|
|
}
|
|
}
|
|
|
|
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,
|
|
};
|
|
}
|