'use server'; import { unstable_rethrow } from 'next/navigation'; import { revalidatePath } from 'next/cache'; import { verifySession } from '@/lib/auth/dal'; import { BackendRequestError } from '@/lib/http/backend-fetch'; import { createAdminMember, deleteAdminMember, isAdminLoginIdTaken, updateAdminMember, } from '@/lib/data/repositories/admin-member-repository'; import { formatPhoneNumber, validateAdminLoginId, validateAdminMemberCreate, validateAdminMemberUpdate, type AdminMemberEditableValues, type AdminMemberFormState, } from '@/lib/domain/admin-member-form'; import { ADMIN_MEMBERS_PATH } from '@/lib/domain/admin-member-query'; const INVALID_REQUEST_MESSAGE = '요청이 올바르지 않습니다.'; const SELF_DELETE_MESSAGE = '현재 로그인한 본인 계정은 삭제할 수 없습니다.'; const DUPLICATE_LOGIN_ID_MESSAGE = '이미 사용 중인 ID입니다.'; const LOGIN_ID_CHECK_FAILED_MESSAGE = '중복 확인에 실패했습니다. 잠시 후 다시 시도해 주세요.'; function readString(formData: FormData, key: string): string { const value = formData.get(key); return typeof value === 'string' ? value : ''; } function readEditableValues(formData: FormData): AdminMemberEditableValues { const phoneNumber = formatPhoneNumber(readString(formData, 'phoneNumber')); return { password: readString(formData, 'password'), phoneNumber: phoneNumber ?? '', email: readString(formData, 'email'), roleCode: readString(formData, 'roleCode'), }; } async function runWrite( write: () => Promise ): Promise { try { await write(); return null; } catch (error) { // 이슈: 세션이 밀렸을 때 backendFetch가 던지는 redirect를 이 catch가 삼키면 안 된다. unstable_rethrow(error); if (error instanceof BackendRequestError) { return { status: 'error', message: error.message }; } throw error; } } export async function createAdminMemberAction( _prevState: AdminMemberFormState, formData: FormData ): Promise { await verifySession(); const validation = validateAdminMemberCreate({ ...readEditableValues(formData), name: readString(formData, 'name'), loginId: readString(formData, 'loginId'), }); if (!validation.ok) { return { status: 'error', errors: validation.errors }; } const { name, loginId, password, phoneNumber, email, roleCode } = validation.values; if (await isAdminLoginIdTaken(loginId)) { return { status: 'error', errors: { loginId: DUPLICATE_LOGIN_ID_MESSAGE } }; } const failure = await runWrite(() => createAdminMember({ name, loginId, password, phoneNumber, email, roleCode }) ); if (failure) { return failure; } revalidatePath(ADMIN_MEMBERS_PATH); return { status: 'success' }; } export async function updateAdminMemberAction( _prevState: AdminMemberFormState, formData: FormData ): Promise { await verifySession(); const id = readString(formData, 'id'); if (!id) { return { status: 'error', message: INVALID_REQUEST_MESSAGE }; } const validation = validateAdminMemberUpdate(readEditableValues(formData)); if (!validation.ok) { return { status: 'error', errors: validation.errors }; } const { password, phoneNumber, email, roleCode } = validation.values; const failure = await runWrite(() => updateAdminMember(id, { password, phoneNumber, email, roleCode, isLocked: readString(formData, 'isLocked') === 'true', }) ); if (failure) { return failure; } revalidatePath(ADMIN_MEMBERS_PATH); return { status: 'success' }; } export async function deleteAdminMemberAction( id: string ): Promise { const admin = await verifySession(); if (!id) { return { status: 'error', message: INVALID_REQUEST_MESSAGE }; } if (id === admin.id) { return { status: 'error', message: SELF_DELETE_MESSAGE }; } await deleteAdminMember(id); revalidatePath(ADMIN_MEMBERS_PATH); return { status: 'success' }; } export type LoginIdCheckResult = | { status: 'idle' } | { status: 'available'; loginId: string } | { status: 'unavailable'; message: string } | { status: 'failed'; message: string }; export async function checkAdminLoginId( loginId: string ): Promise { await verifySession(); const normalized = loginId.trim(); const formatError = validateAdminLoginId(normalized); if (formatError) { return { status: 'unavailable', message: formatError }; } try { if (await isAdminLoginIdTaken(normalized)) { return { status: 'unavailable', message: DUPLICATE_LOGIN_ID_MESSAGE }; } } catch (error) { unstable_rethrow(error); if (error instanceof BackendRequestError) { return { status: 'failed', message: error.message }; } return { status: 'failed', message: LOGIN_ID_CHECK_FAILED_MESSAGE }; } return { status: 'available', loginId: normalized }; }