/** * CRUD Server Action 팩토리 * * 정형적인 CRUD actions.ts 파일의 보일러플레이트를 제거합니다. * executeServerAction 위에 한 단계 더 추상화하여 * getList / create / update / remove / reorder 함수를 자동 생성합니다. * * 주의: 이 파일은 'use server'가 아닙니다. * 각 도메인의 actions.ts ('use server')에서 import하여 사용합니다. * * @example * ```typescript * // RankManagement/actions.ts * 'use server'; * const service = createCrudService({ * basePath: '/api/v1/positions', * transform: (api) => ({ id: api.id, name: api.name, ... }), * entityName: '직급', * defaultQueryParams: { type: 'rank' }, * defaultCreateBody: { type: 'rank' }, * }); * export async function getRanks(params?) { return service.getList(params); } * ``` */ import { executeServerAction, type ActionResult } from './execute-server-action'; // ===== 설정 타입 ===== export interface CrudServiceConfig { /** API 경로 (예: '/api/v1/positions') */ basePath: string; /** API → Frontend 데이터 변환 함수 */ transform: (apiData: TApi) => TFrontend; /** 엔티티 한글명 (에러 메시지용, 예: '직급') */ entityName: string; /** 목록 조회 시 기본 쿼리 파라미터 (예: { type: 'rank' }) */ defaultQueryParams?: Record; /** 생성 시 body에 추가할 기본값 (예: { type: 'rank' }) */ defaultCreateBody?: Record; } // ===== 서비스 반환 타입 ===== export interface CrudService { getList(params?: { is_active?: boolean; q?: string; }): Promise>; create(body: Record): Promise>; update( id: number, body: Record ): Promise>; remove(id: number): Promise; reorder( items: { id: number; sort_order: number }[] ): Promise; } // ===== 팩토리 함수 ===== export function createCrudService( config: CrudServiceConfig ): CrudService { const { basePath, transform, entityName, defaultQueryParams, defaultCreateBody, } = config; // API URL은 호출 시점에 resolve (SSR 안전) const getBaseUrl = () => `${process.env.NEXT_PUBLIC_API_URL}${basePath}`; return { async getList(params) { const searchParams = new URLSearchParams(); if (defaultQueryParams) { Object.entries(defaultQueryParams).forEach(([k, v]) => searchParams.set(k, v) ); } if (params?.is_active !== undefined) { searchParams.set('is_active', params.is_active.toString()); } if (params?.q) { searchParams.set('q', params.q); } return executeServerAction({ url: `${getBaseUrl()}?${searchParams.toString()}`, transform: (data: TApi[]) => data.map(transform), errorMessage: `${entityName} 목록 조회에 실패했습니다.`, }); }, async create(body) { return executeServerAction({ url: getBaseUrl(), method: 'POST', body: { ...defaultCreateBody, ...body }, transform, errorMessage: `${entityName} 생성에 실패했습니다.`, }); }, async update(id, body) { return executeServerAction({ url: `${getBaseUrl()}/${id}`, method: 'PUT', body, transform, errorMessage: `${entityName} 수정에 실패했습니다.`, }); }, async remove(id) { return executeServerAction({ url: `${getBaseUrl()}/${id}`, method: 'DELETE', errorMessage: `${entityName} 삭제에 실패했습니다.`, }); }, async reorder(items) { return executeServerAction({ url: `${getBaseUrl()}/reorder`, method: 'PUT', body: { items }, errorMessage: '순서 변경에 실패했습니다.', }); }, }; } // ActionResult 재export (actions.ts에서 import 편의) export type { ActionResult } from './execute-server-action';