스킬 정리 및 리팩토링

This commit is contained in:
2026-02-26 09:05:17 +09:00
parent 4c52d6d82f
commit 406af7408a
71 changed files with 3776 additions and 3934 deletions

View File

@@ -1,4 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import type { DashboardKisProfileValidateResponse } from "@/features/trade/types/trade.types";
import { hasKisApiSession } from "@/app/api/kis/_session";
import { parseKisAccountParts } from "@/lib/kis/account";
@@ -6,13 +7,18 @@ import { kisGet } from "@/lib/kis/client";
import { normalizeTradingEnv, type KisCredentialInput } from "@/lib/kis/config";
import { validateKisCredentialInput } from "@/lib/kis/request";
import { getKisAccessToken } from "@/lib/kis/token";
import {
createKisApiErrorResponse,
KIS_API_ERROR_CODE,
toKisApiErrorMessage,
} from "@/app/api/kis/_response";
interface KisProfileValidateRequestBody {
appKey?: string;
appSecret?: string;
tradingEnv?: string;
accountNo?: string;
}
const kisProfileValidateBodySchema = z.object({
appKey: z.string().trim().min(1, "앱 키를 입력해 주세요."),
appSecret: z.string().trim().min(1, "앱 시크릿을 입력해 주세요."),
tradingEnv: z.string().optional(),
accountNo: z.string().trim().min(1, "계좌번호를 입력해 주세요."),
});
interface BalanceValidationPreset {
inqrDvsn: "01" | "02";
@@ -50,34 +56,44 @@ export async function POST(request: NextRequest) {
const hasSession = await hasKisApiSession();
if (!hasSession) {
return NextResponse.json(
{
ok: false,
tradingEnv: fallbackTradingEnv,
message: "로그인이 필요합니다.",
} satisfies Pick<DashboardKisProfileValidateResponse, "ok" | "tradingEnv" | "message">,
{ status: 401 },
);
return createKisApiErrorResponse({
status: 401,
code: KIS_API_ERROR_CODE.AUTH_REQUIRED,
message: "로그인이 필요합니다.",
tradingEnv: fallbackTradingEnv,
});
}
let body: KisProfileValidateRequestBody = {};
let rawBody: unknown = {};
try {
body = (await request.json()) as KisProfileValidateRequestBody;
rawBody = (await request.json()) as unknown;
} catch {
return NextResponse.json(
{
ok: false,
tradingEnv: fallbackTradingEnv,
message: "요청 본문(JSON)을 읽을 수 없습니다.",
} satisfies Pick<DashboardKisProfileValidateResponse, "ok" | "tradingEnv" | "message">,
{ status: 400 },
);
return createKisApiErrorResponse({
status: 400,
code: KIS_API_ERROR_CODE.INVALID_REQUEST,
message: "요청 본문(JSON)을 읽을 수 없습니다.",
tradingEnv: fallbackTradingEnv,
});
}
const parsedBody = kisProfileValidateBodySchema.safeParse(rawBody);
if (!parsedBody.success) {
return createKisApiErrorResponse({
status: 400,
code: KIS_API_ERROR_CODE.INVALID_REQUEST,
message:
parsedBody.error.issues[0]?.message ??
"요청 본문 값이 올바르지 않습니다.",
tradingEnv: fallbackTradingEnv,
});
}
const body = parsedBody.data;
const credentials: KisCredentialInput = {
appKey: body.appKey?.trim(),
appSecret: body.appSecret?.trim(),
appKey: body.appKey.trim(),
appSecret: body.appSecret.trim(),
tradingEnv: normalizeTradingEnv(body.tradingEnv),
};
@@ -85,39 +101,25 @@ export async function POST(request: NextRequest) {
const invalidCredentialMessage = validateKisCredentialInput(credentials);
if (invalidCredentialMessage) {
return NextResponse.json(
{
ok: false,
tradingEnv,
message: invalidCredentialMessage,
} satisfies Pick<DashboardKisProfileValidateResponse, "ok" | "tradingEnv" | "message">,
{ status: 400 },
);
return createKisApiErrorResponse({
status: 400,
code: KIS_API_ERROR_CODE.INVALID_REQUEST,
message: invalidCredentialMessage,
tradingEnv,
});
}
const accountNoInput = (body.accountNo ?? "").trim();
if (!accountNoInput) {
return NextResponse.json(
{
ok: false,
tradingEnv,
message: "계좌번호를 입력해 주세요.",
} satisfies Pick<DashboardKisProfileValidateResponse, "ok" | "tradingEnv" | "message">,
{ status: 400 },
);
}
const accountNoInput = body.accountNo.trim();
const accountParts = parseKisAccountParts(accountNoInput);
if (!accountParts) {
return NextResponse.json(
{
ok: false,
tradingEnv,
message: "계좌번호 형식이 올바르지 않습니다. 8-2 형식(예: 12345678-01)으로 입력해 주세요.",
} satisfies Pick<DashboardKisProfileValidateResponse, "ok" | "tradingEnv" | "message">,
{ status: 400 },
);
return createKisApiErrorResponse({
status: 400,
code: KIS_API_ERROR_CODE.ACCOUNT_REQUIRED,
message:
"계좌번호 형식이 올바르지 않습니다. 8-2 형식(예: 12345678-01)으로 입력해 주세요.",
tradingEnv,
});
}
try {
@@ -150,19 +152,12 @@ export async function POST(request: NextRequest) {
},
} satisfies DashboardKisProfileValidateResponse);
} catch (error) {
const message =
error instanceof Error
? error.message
: "계좌 검증 중 오류가 발생했습니다.";
return NextResponse.json(
{
ok: false,
tradingEnv,
message,
} satisfies Pick<DashboardKisProfileValidateResponse, "ok" | "tradingEnv" | "message">,
{ status: 400 },
);
return createKisApiErrorResponse({
status: 400,
code: KIS_API_ERROR_CODE.UNAUTHORIZED,
message: toKisApiErrorMessage(error, "계좌 검증 중 오류가 발생했습니다."),
tradingEnv,
});
}
}