Files
sori.studio/server/routes/admin/api/posts/export-jobs.post.js

134 lines
4.0 KiB
JavaScript

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<typeof postExportJobInputSchema>} 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<Object>} 생성된 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
})