import { createError, readBody } from 'h3' import { z } from 'zod' import { requireAdminSession } from '../../../../utils/admin-auth' import { createPostExportJob, queuePostExportJobRun } from '../../../../repositories/post-export-repository' const postExportJobInputSchema = z.object({ dateRangeMode: z.enum(['all', 'year', 'month', 'custom']).optional().default('all'), year: z.number().int().min(1970).max(9999).optional(), month: z.number().int().min(1).max(12).optional(), dateFrom: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(), dateTo: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(), chunkSize: z.number().int().min(1).max(500).optional(), maxFileSizeBytes: z.number().int().min(10485760).max(2147483648).optional(), retentionDays: z.number().int().min(1).max(100).optional() }).default({}) /** * KST 기준 날짜 시작 시각을 만든다. * @param {string} value - YYYY-MM-DD 날짜 * @returns {Date} 날짜 시작 시각 */ const createKstDateStart = (value) => new Date(`${value}T00:00:00+09:00`) /** * KST 기준 다음 날짜 시작 시각을 만든다. * @param {string} value - YYYY-MM-DD 날짜 * @returns {Date} 다음 날짜 시작 시각 */ const createKstNextDateStart = (value) => { const date = createKstDateStart(value) date.setUTCDate(date.getUTCDate() + 1) return date } /** * 두 자리 숫자 문자열을 만든다. * @param {number} value - 숫자 * @returns {string} 두 자리 문자열 */ const pad2 = (value) => String(value).padStart(2, '0') /** * 요청 입력에서 Export 날짜 범위를 만든다. * @param {z.infer} input - 요청 입력 * @returns {{ dateFrom: Date|null, dateTo: Date|null, rangeLabel: string }} 날짜 범위 */ const createExportDateRange = (input) => { if (input.dateRangeMode === 'year') { const year = input.year || new Date().getFullYear() return { dateFrom: createKstDateStart(`${year}-01-01`), dateTo: createKstDateStart(`${year + 1}-01-01`), rangeLabel: `${year}` } } if (input.dateRangeMode === 'month') { const now = new Date() const year = input.year || now.getFullYear() const month = input.month || now.getMonth() + 1 const nextYear = month === 12 ? year + 1 : year const nextMonth = month === 12 ? 1 : month + 1 return { dateFrom: createKstDateStart(`${year}-${pad2(month)}-01`), dateTo: createKstDateStart(`${nextYear}-${pad2(nextMonth)}-01`), rangeLabel: `${year}-${pad2(month)}` } } if (input.dateRangeMode === 'custom') { if (!input.dateFrom || !input.dateTo) { throw createError({ statusCode: 400, statusMessage: '날짜 범위를 선택해 주세요.' }) } const dateFrom = createKstDateStart(input.dateFrom) const dateTo = createKstNextDateStart(input.dateTo) if (dateFrom >= dateTo) { throw createError({ statusCode: 400, statusMessage: '시작일은 종료일보다 늦을 수 없습니다.' }) } return { dateFrom, dateTo, rangeLabel: `${input.dateFrom}_${input.dateTo}` } } return { dateFrom: null, dateTo: null, rangeLabel: '전체' } } /** * 관리자 게시물 Export 작업 요청 API * @param {import('h3').H3Event} event - 요청 이벤트 * @returns {Promise} 생성된 Export 작업 */ export default defineEventHandler(async (event) => { const adminSession = requireAdminSession(event) const input = postExportJobInputSchema.parse(await readBody(event)) const dateRange = createExportDateRange(input) const job = await createPostExportJob({ requestedBy: adminSession.userId, requestedEmail: adminSession.email, scope: 'all', dateFrom: dateRange.dateFrom, dateTo: dateRange.dateTo, rangeLabel: dateRange.rangeLabel, chunkSize: input.chunkSize, maxFileSizeBytes: input.maxFileSizeBytes, retentionDays: input.retentionDays }) if (job.status === 'queued') { queuePostExportJobRun(job.id) } return job })