'use server'; import { revalidatePath } from 'next/cache'; import { redirect, unstable_rethrow } from 'next/navigation'; import { verifySession } from '@/lib/auth/dal'; import { BackendRequestError } from '@/lib/http/backend-fetch'; import { createBannedWord, deleteBannedWord, uploadBannedWordExcel, } from '@/lib/data/repositories/banned-word-repository'; import { DUPLICATE_WORD_MESSAGE, validateBannedWord, type BannedWordFormState, type ExcelUploadResult, } from '@/lib/domain/banned-word-form'; import { BANNED_WORDS_PATH } from '@/lib/domain/banned-word-query'; const SAVE_FAILED_MESSAGE = '등록하지 못했습니다. 잠시 후 다시 시도해 주세요.'; const DELETE_FAILED_MESSAGE = '삭제하지 못했습니다. 잠시 후 다시 시도해 주세요.'; const UPLOAD_FAILED_MESSAGE = '엑셀 파일을 등록하지 못했습니다. 잠시 후 다시 시도해 주세요.'; function failureMessage(error: unknown, fallback: string): string { return error instanceof BackendRequestError ? error.message : fallback; } function readString(formData: FormData, key: string): string { const value = formData.get(key); return typeof value === 'string' ? value : ''; } /** 기획 SYS_WRD_002 ② — 등록이 끝나면 목록으로 돌아간다. */ export async function createBannedWordAction( _prevState: BannedWordFormState, formData: FormData ): Promise { await verifySession(); const validation = validateBannedWord(readString(formData, 'word')); if (!validation.ok) { return { status: 'error', errors: validation.errors }; } let result; try { result = await createBannedWord(validation.value); } catch (error) { // 이슈: 세션이 밀렸을 때 backendFetch가 던지는 redirect를 이 catch가 삼키면 안 된다. unstable_rethrow(error); return { status: 'error', message: failureMessage(error, SAVE_FAILED_MESSAGE) }; } if (result.status === 'duplicate') { return { status: 'error', errors: { word: DUPLICATE_WORD_MESSAGE } }; } revalidatePath(BANNED_WORDS_PATH); redirect(BANNED_WORDS_PATH); } export async function deleteBannedWordAction( id: number ): Promise<{ status: 'success' } | { status: 'error'; message: string }> { await verifySession(); try { await deleteBannedWord(id); } catch (error) { unstable_rethrow(error); return { status: 'error', message: failureMessage(error, DELETE_FAILED_MESSAGE) }; } revalidatePath(BANNED_WORDS_PATH); return { status: 'success' }; } export async function uploadBannedWordExcelAction( formData: FormData ): Promise< | { status: 'success'; result: ExcelUploadResult } | { status: 'error'; message: string } > { await verifySession(); const file = formData.get('file'); if (!(file instanceof File) || file.size === 0) { return { status: 'error', message: '엑셀 파일을 첨부해 주세요.' }; } let result: ExcelUploadResult; try { result = await uploadBannedWordExcel(file); } catch (error) { unstable_rethrow(error); return { status: 'error', message: failureMessage(error, UPLOAD_FAILED_MESSAGE) }; } revalidatePath(BANNED_WORDS_PATH); return { status: 'success', result }; }