UNPKG

mobile-react-infinite-calendar

Version:

A mobile-optimized infinite scroll calendar component for React

239 lines (238 loc) 8.39 kB
/** * 한국 공공데이터포털 공휴일 API 서비스 */ import { API_CONSTANTS } from '../constants/calendar'; import { createCustomHolidayServiceKey } from '../utils/envUtils'; import { logger } from '../utils/logger'; class HolidayCache { constructor() { this.cache = new Map(); this.CACHE_DURATION = API_CONSTANTS.CACHE_DURATION; } set(key, holidays) { this.cache.set(key, { holidays, timestamp: Date.now() }); } get(key) { const data = this.cache.get(key); if (!data) return null; // 캐시 유효성 검사 if (Date.now() - data.timestamp > this.CACHE_DURATION) { this.cache.delete(key); return null; } return data.holidays; } clear() { this.cache.clear(); } } const holidayCache = new HolidayCache(); export class HolidayService { constructor(serviceKey, locale = 'ko-KR') { this.serviceKey = createCustomHolidayServiceKey(serviceKey || ''); this.locale = locale; } /** * 특정 년월의 공휴일 데이터 조회 */ async getHolidays(year, month) { // 유효성 검사 if (!this.isKoreanLocale() || !this.serviceKey) { logger.debug('공휴일 데이터 로드 스킵: 비한국 로케일 또는 서비스키 없음'); return []; } const monthCacheKey = `${year}-${month.toString().padStart(2, '0')}`; const monthCached = holidayCache.get(monthCacheKey); if (monthCached) { logger.debug(`공휴일 캐시 히트: ${monthCacheKey}`, { count: monthCached.length }); return monthCached; } try { const yearCacheKey = `year-${year}`; let yearHolidays = holidayCache.get(yearCacheKey); if (!yearHolidays) { logger.info(`공휴일 API 호출 시작: ${year}년`); yearHolidays = await this.fetchYearHolidays(year); holidayCache.set(yearCacheKey, yearHolidays); logger.info(`공휴일 API 호출 완료: ${year}년`, { count: yearHolidays.length }); } else { logger.debug(`공휴일 연도 캐시 히트: ${year}년`); } const monthHolidays = this.filterHolidaysByMonth(yearHolidays, month); holidayCache.set(monthCacheKey, monthHolidays); return monthHolidays; } catch (error) { logger.error('공휴일 데이터 조회 실패:', error); return []; } } /** * 한국 로케일 확인 */ isKoreanLocale() { return this.locale === 'ko-KR' || this.locale === 'ko'; } /** * 연도별 공휴일 데이터 가져오기 */ async fetchYearHolidays(year) { var _a, _b, _c, _d, _e; const url = this.buildApiUrl(year); const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await this.parseResponse(response); // API 응답 검증 if (((_b = (_a = data.response) === null || _a === void 0 ? void 0 : _a.header) === null || _b === void 0 ? void 0 : _b.resultCode) !== '00') { logger.error('API 응답 오류:', (_c = data.response) === null || _c === void 0 ? void 0 : _c.header); throw new Error(`API Error: ${((_e = (_d = data.response) === null || _d === void 0 ? void 0 : _d.header) === null || _e === void 0 ? void 0 : _e.resultMsg) || 'Unknown error'}`); } return this.transformApiData(data); } /** * API URL 생성 */ buildApiUrl(year) { // URL 인코딩하지 않고 직접 문자열로 구성 const baseUrl = 'https://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo'; const params = [ `solYear=${year}`, `ServiceKey=${this.serviceKey}`, '_type=json', `numOfRows=${API_CONSTANTS.MAX_ROWS_PER_REQUEST}` ]; return `${baseUrl}?${params.join('&')}`; } /** * API 응답 파싱 */ async parseResponse(response) { const contentType = response.headers.get('content-type'); if (contentType === null || contentType === void 0 ? void 0 : contentType.includes('application/json')) { const data = await response.json(); return data; } // XML 또는 텍스트 응답 처리 const text = await response.text(); // XML 오류 확인 if (this.isXmlError(text)) { this.logXmlError(text); throw new Error('API returned XML error'); } // JSON 파싱 시도 try { const data = JSON.parse(text); return data; } catch (error) { throw new Error('Failed to parse API response'); } } /** * XML 오류 응답 확인 */ isXmlError(text) { return text.includes('<OpenAPI_ServiceResponse>') && text.includes('<errMsg>'); } /** * XML 오류 로깅 */ logXmlError(text) { var _a, _b; const errMsg = (_a = text.match(/<errMsg>(.*?)<\/errMsg>/)) === null || _a === void 0 ? void 0 : _a[1]; const authMsg = (_b = text.match(/<returnAuthMsg>(.*?)<\/returnAuthMsg>/)) === null || _b === void 0 ? void 0 : _b[1]; logger.error('공휴일 API 오류:', { errMsg, authMsg }); if (authMsg === 'SERVICE_KEY_IS_NOT_REGISTERED_ERROR') { logger.error('서비스 키가 등록되지 않았습니다.'); } } /** * 월별 공휴일 필터링 */ filterHolidaysByMonth(holidays, month) { return holidays.filter(holiday => { const holidayDate = new Date(holiday.date); return holidayDate.getMonth() + 1 === month; }); } /** * 여러 개월의 공휴일 데이터 일괄 조회 */ async getHolidaysForRange(startDate, endDate) { // 한국 로케일이 아닌 경우 빈 배열 반환 const isKorean = this.locale === 'ko-KR' || this.locale === 'ko'; if (!isKorean) { return []; } const promises = []; const current = new Date(startDate.getFullYear(), startDate.getMonth(), 1); const end = new Date(endDate.getFullYear(), endDate.getMonth(), 1); while (current <= end) { promises.push(this.getHolidays(current.getFullYear(), current.getMonth() + 1)); current.setMonth(current.getMonth() + 1); } try { const results = await Promise.all(promises); return results.flat(); } catch (error) { logger.error('공휴일 범위 조회 실패:', error); return []; } } /** * API 응답 데이터를 내부 Holiday 타입으로 변환 */ transformApiData(data) { var _a, _b, _c; const items = (_c = (_b = (_a = data.response) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.items) === null || _c === void 0 ? void 0 : _c.item; if (!items || !Array.isArray(items)) { return []; } return items .filter(this.isHolidayItem) .map(this.convertToHoliday.bind(this)); } /** * 공휴일 여부 확인 */ isHolidayItem(item) { return item.isHoliday === 'Y'; } /** * API 아이템을 Holiday 타입으로 변환 */ convertToHoliday(item) { return { id: `holiday-${item.locdate}`, name: item.dateName, date: this.formatDate(item.locdate), color: 'red' }; } /** * YYYYMMDD 형식을 YYYY-MM-DD 형식으로 변환 */ formatDate(locdate) { const dateStr = locdate.toString(); const year = dateStr.substring(0, 4); const month = dateStr.substring(4, 6); const day = dateStr.substring(6, 8); return `${year}-${month}-${day}`; } /** * 캐시 클리어 */ clearCache() { holidayCache.clear(); } } // 기본 인스턴스 export export const holidayService = new HolidayService();