Standardize KBO team codes and improve API parameter validation.
- KBO API 요청 매개변수에 `TeamCode` enum을 적용하여 타입 안정성을 강화하고 서비스 레이어의 불필요한 매핑 로직을 제거했습니다. - `TEAM_CODES` 상수를 내부 데이터 정규화용 어댑터로 한정하여 외부 API 의존성을 줄였습니다. - 핸들러 단계에서 유효한 팀 코드인지 검증하는 로직을 추가하여 잘못된 요청에 대한 에러 처리를 강화했습니다. - `getPlayerStats` 함수의 인자 전달 방식을 객체 형태로 리팩토링하여 가독성과 유지보수성을 높였습니다.
This commit is contained in:
parent
c94e2eb81b
commit
7496cb7de7
@ -4,7 +4,7 @@ import { getSchedule } from "../services/scheduleService.js";
|
||||
import { getPlayerStats, getValidPlayerTypes } from "../services/playerService.js";
|
||||
import { getGameList } from "../services/gameListService.js";
|
||||
import { getGameDetail } from "../services/gameDetailService.js";
|
||||
import type { PlayerFilters } from "../types/kbo.js";
|
||||
import { TeamCode } from "../types/panit.js";
|
||||
|
||||
enum KboPath {
|
||||
Rank = "rank",
|
||||
@ -59,7 +59,6 @@ export const kbo = onRequest(async (req, res) => {
|
||||
const now = new Date();
|
||||
const year = parseIntParam(req.query.year, now.getFullYear());
|
||||
const month = parseIntParam(req.query.month, now.getMonth() + 1);
|
||||
const team = req.query.team ? String(req.query.team) : undefined;
|
||||
const series = req.query.series ? String(req.query.series) : undefined;
|
||||
const day = req.query.day !== undefined
|
||||
? parseIntParam(req.query.day, NaN)
|
||||
@ -78,6 +77,19 @@ export const kbo = onRequest(async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
let team: TeamCode | undefined;
|
||||
if (req.query.team) {
|
||||
const raw = String(req.query.team);
|
||||
const validCodes = Object.values(TeamCode) as string[];
|
||||
if (!validCodes.includes(raw)) {
|
||||
res.status(400).json({
|
||||
error: `Invalid team: "${raw}". Use: ${validCodes.join(", ")}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
team = raw as TeamCode;
|
||||
}
|
||||
|
||||
const result = await getSchedule(year, month, team, series, day);
|
||||
res.status(200).json(result);
|
||||
return;
|
||||
@ -141,16 +153,30 @@ export const kbo = onRequest(async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const filters: PlayerFilters = {};
|
||||
if (req.query.team) filters.team = String(req.query.team);
|
||||
if (req.query.series) filters.series = String(req.query.series);
|
||||
if (req.query.pos) filters.pos = String(req.query.pos);
|
||||
if (req.query.situation) filters.situation = String(req.query.situation);
|
||||
if (req.query.situationDetail) {
|
||||
filters.situationDetail = String(req.query.situationDetail);
|
||||
let team: TeamCode | undefined;
|
||||
if (req.query.team) {
|
||||
const raw = String(req.query.team);
|
||||
const validCodes = Object.values(TeamCode) as string[];
|
||||
if (!validCodes.includes(raw)) {
|
||||
res.status(400).json({
|
||||
error: `Invalid team: "${raw}". Use: ${validCodes.join(", ")}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
team = raw as TeamCode;
|
||||
}
|
||||
|
||||
const result = await getPlayerStats(type, year, filters, allPages);
|
||||
const result = await getPlayerStats({
|
||||
type,
|
||||
year,
|
||||
team,
|
||||
series: req.query.series ? String(req.query.series) : undefined,
|
||||
pos: req.query.pos ? String(req.query.pos) : undefined,
|
||||
situation: req.query.situation ? String(req.query.situation) : undefined,
|
||||
situationDetail: req.query.situationDetail ?
|
||||
String(req.query.situationDetail) : undefined,
|
||||
allPages,
|
||||
});
|
||||
res.status(200).json(result);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -82,10 +82,24 @@ export interface PlayerStatsResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* KBO 팀 코드 매핑.
|
||||
* 팀 표기명(shortName/한글) → KBO 사이트 코드 매핑.
|
||||
*
|
||||
* KBO 사이트의 팀 드롭다운은 현재 팀명이 아닌 구단 역사상의 코드를 value로 사용한다.
|
||||
* 예: SSG는 SK 시절 코드 "SK", 키움은 우리 히어로즈 시절 코드 "WO" 등.
|
||||
*
|
||||
* ## 존재 이유
|
||||
* 외부 API의 `team` 파라미터는 `TeamCode` enum으로 받기 때문에 요청 단계에서는
|
||||
* 더 이상 매핑이 필요 없다 (TeamCode 값이 곧 KBO 사이트 코드와 동일).
|
||||
*
|
||||
* 그럼에도 이 맵이 필요한 곳:
|
||||
* 1. 응답 파싱 — `kbo/schedule.ts`의 `toTeamCode()`가 스크래핑된 HTML 속
|
||||
* 한국어 팀명("두산", "한화" 등)을 응답의 `awayTeamCode`/`homeTeamCode`
|
||||
* 필드용 코드로 정규화할 때 사용한다.
|
||||
* 2. CLI 입력 편의 — `kbo/cli.ts`에서 사람이 직접 입력하는 한글/shortName을
|
||||
* KBO 코드로 변환할 때 사용한다.
|
||||
*
|
||||
* 즉 이 맵은 "사람이 읽는 표기 → KBO 내부 코드" 어댑터이며,
|
||||
* 서비스 레이어의 외부 API 매개변수에는 더 이상 사용하지 않는다.
|
||||
*/
|
||||
export const TEAM_CODES: Record<string, string> = {
|
||||
KT: "KT",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { fetchPlayerFromKbo } from "../repositories/kboRepository.js";
|
||||
import { TEAM_CODES } from "../types/kbo.js";
|
||||
import type { PlayerFilters, PlayerPageConfig, PlayerStatsResult } from "../types/kbo.js";
|
||||
import { TeamCode } from "../types/panit.js";
|
||||
import { HITTER_CONFIG } from "../kbo/player/hitter.js";
|
||||
import { PITCHER_CONFIG } from "../kbo/player/pitcher.js";
|
||||
import { DEFENSE_CONFIG } from "../kbo/player/defense.js";
|
||||
@ -21,12 +21,22 @@ export function getValidPlayerTypes(): string[] {
|
||||
return Object.keys(PLAYER_CONFIGS);
|
||||
}
|
||||
|
||||
export interface GetPlayerStatsArgs {
|
||||
type: string;
|
||||
year: number;
|
||||
team?: TeamCode;
|
||||
series?: string;
|
||||
pos?: string;
|
||||
situation?: string;
|
||||
situationDetail?: string;
|
||||
allPages: boolean;
|
||||
}
|
||||
|
||||
export async function getPlayerStats(
|
||||
type: string,
|
||||
year: number,
|
||||
filters: PlayerFilters,
|
||||
allPages: boolean
|
||||
args: GetPlayerStatsArgs
|
||||
): Promise<PlayerStatsResult> {
|
||||
const { type, year, team, series, pos, situation, situationDetail, allPages } = args;
|
||||
|
||||
const config = PLAYER_CONFIGS[type];
|
||||
if (!config) {
|
||||
throw new Error(
|
||||
@ -34,10 +44,12 @@ export async function getPlayerStats(
|
||||
);
|
||||
}
|
||||
|
||||
const resolvedFilters = { ...filters };
|
||||
if (resolvedFilters.team) {
|
||||
resolvedFilters.team = TEAM_CODES[resolvedFilters.team] ?? resolvedFilters.team;
|
||||
}
|
||||
const filters: PlayerFilters = {};
|
||||
if (team) filters.team = team;
|
||||
if (series) filters.series = series;
|
||||
if (pos) filters.pos = pos;
|
||||
if (situation) filters.situation = situation;
|
||||
if (situationDetail) filters.situationDetail = situationDetail;
|
||||
|
||||
return fetchPlayerFromKbo(type, config, year, resolvedFilters, allPages);
|
||||
}
|
||||
return fetchPlayerFromKbo(type, config, year, filters, allPages);
|
||||
}
|
||||
@ -1,15 +1,15 @@
|
||||
import { fetchScheduleFromKbo } from "../repositories/kboRepository.js";
|
||||
import { TEAM_CODES, SERIES_CODES } from "../types/kbo.js";
|
||||
import { SERIES_CODES } from "../types/kbo.js";
|
||||
import type { ScheduleResult } from "../types/kbo.js";
|
||||
import type { TeamCode } from "../types/panit.js";
|
||||
|
||||
export async function getSchedule(
|
||||
year: number,
|
||||
month: number,
|
||||
team?: string,
|
||||
team?: TeamCode,
|
||||
series?: string,
|
||||
day?: number
|
||||
): Promise<ScheduleResult> {
|
||||
const resolvedTeam = team ? (TEAM_CODES[team] ?? team) : undefined;
|
||||
const resolvedSeries = series ? (SERIES_CODES[series] ?? series) : undefined;
|
||||
|
||||
return fetchScheduleFromKbo({
|
||||
@ -17,6 +17,6 @@ export async function getSchedule(
|
||||
month,
|
||||
day,
|
||||
series: resolvedSeries,
|
||||
team: resolvedTeam,
|
||||
team,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -22,7 +22,6 @@ export type {
|
||||
PlayerStatsResult,
|
||||
} from "../kbo/player/common.js";
|
||||
|
||||
export { TEAM_CODES } from "../kbo/player/common.js";
|
||||
export { SERIES_CODES } from "../kbo/schedule.js";
|
||||
|
||||
export type {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user