"use server"; // Next.js 서버 액션에서 직접 import하려면 (선택) import { revalidateTag, unstable_noStore } from "next/cache"; import db from "@/db/db"; import { customAlphabet } from "nanoid"; import { filterColumns } from "@/lib/filter-columns"; import { unstable_cache } from "@/lib/unstable-cache"; import { getErrorMessage } from "@/lib/handle-error"; import { asc, desc, ilike, inArray, and, gte, lte, not, or, eq } from "drizzle-orm"; import { CreateVendorTypeSchema, GetVendorTypesSchema, UpdateVendorTypeSchema } from "./validations"; import { countVendorTypes, deleteVendorTypeById, deleteVendorTypesByIds, findAllVendorTypes, insertVendorType, selectVendorTypes, updateVendorType } from "./repository"; import { vendorTypes } from "@/db/schema"; /* ----------------------------------------------------- 1) 조회 관련 ----------------------------------------------------- */ /** * 복잡한 조건으로 VendorType 목록을 조회 (+ pagination) 하고, * 총 개수에 따라 pageCount를 계산해서 리턴. * Next.js의 unstable_cache를 사용해 일정 시간 캐시. */ export async function getVendorTypes(input: GetVendorTypesSchema) { return unstable_cache( async () => { try { const offset = (input.page - 1) * input.perPage; // advancedTable 모드면 filterColumns()로 where 절 구성 const advancedWhere = filterColumns({ table: vendorTypes, filters: input.filters, joinOperator: input.joinOperator, }); let globalWhere; if (input.search) { const s = `%${input.search}%`; globalWhere = or( ilike(vendorTypes.nameKo, s), ilike(vendorTypes.nameEn, s), ilike(vendorTypes.code, s) ); } const finalWhere = and( advancedWhere, globalWhere ); // 아니면 ilike, inArray, gte 등으로 where 절 구성 const where = finalWhere; const orderBy = input.sort.length > 0 ? input.sort.map((item) => item.desc ? desc(vendorTypes[item.id]) : asc(vendorTypes[item.id]) ) : [asc(vendorTypes.createdAt)]; // 트랜잭션 내부에서 Repository 호출 const { data, total } = await db.transaction(async (tx) => { const data = await selectVendorTypes(tx, { where, orderBy, offset, limit: input.perPage, }); const total = await countVendorTypes(tx, where); return { data, total }; }); const pageCount = Math.ceil(total / input.perPage); return { data, pageCount }; } catch (err) { console.log(err, "err") // 에러 발생 시 디폴트 return { data: [], pageCount: 0 }; } }, [JSON.stringify(input)], // 캐싱 키 { revalidate: 3600, tags: ["vendorTypes"], // revalidateTag("vendorTypes") 호출 시 무효화 } )(); } /* ----------------------------------------------------- 2) 생성(Create) ----------------------------------------------------- */ export interface VendorTypeCreateData { code?: string; nameKo: string; nameEn: string; } /** * VendorType 생성 */ export async function createVendorType(input: VendorTypeCreateData) { unstable_noStore(); // Next.js 서버 액션 캐싱 방지 try { if (!input.nameKo || !input.nameEn) { return { success: false, message: "한국어 이름과 영어 이름은 필수입니다", data: null, error: "필수 필드 누락" }; } // 코드가 없으면 자동 생성 (예: nameEn의 소문자화 + nanoid) const code = input.code || `${input.nameEn.toLowerCase().replace(/\s+/g, '-')}-${customAlphabet('1234567890abcdef', 6)()}`; // result 변수에 명시적으로 타입과 초기값 할당 let result: any[] = []; // 트랜잭션 결과를 result에 할당 result = await db.transaction(async (tx) => { // 기존 코드 확인 (code는 unique) const existingVendorType = input.code ? await tx.query.vendorTypes.findFirst({ where: eq(vendorTypes.code, input.code), }) : null; let txResult; if (existingVendorType) { // 기존 vendorType 업데이트 txResult = await updateVendorType(tx, existingVendorType.id, { nameKo: input.nameKo, nameEn: input.nameEn, }); } else { // 새 vendorType 생성 txResult = await insertVendorType(tx, { code, nameKo: input.nameKo, nameEn: input.nameEn, }); } return txResult; }); // 캐시 무효화 revalidateTag("vendorTypes"); return { success: true, data: result[0] || null, error: null }; } catch (err) { console.error("협력업체 타입 생성/업데이트 오류:", err); // 중복 키 오류 처리 if (err instanceof Error && err.message.includes("unique constraint")) { return { success: false, message: "이미 존재하는 협력업체 타입 코드입니다", data: null, error: "중복 키 오류" }; } return { success: false, message: getErrorMessage(err), data: null, error: getErrorMessage(err) }; } } /* ----------------------------------------------------- 3) 업데이트 ----------------------------------------------------- */ /** 단건 업데이트 */ export async function modifyVendorType(input: UpdateVendorTypeSchema & { id: number }) { unstable_noStore(); try { const data = await db.transaction(async (tx) => { const [res] = await updateVendorType(tx, input.id, { nameKo: input.nameKo, nameEn: input.nameEn, }); return res; }); revalidateTag("vendorTypes"); return { data, error: null }; } catch (err) { return { data: null, error: getErrorMessage(err) }; } } /** 단건 삭제 */ export async function removeVendorType(input: { id: number }) { unstable_noStore(); try { await db.transaction(async (tx) => { await deleteVendorTypeById(tx, input.id); }); revalidateTag("vendorTypes"); return { data: null, error: null }; } catch (err) { return { data: null, error: getErrorMessage(err) }; } } /** 복수 삭제 */ export async function removeVendorTypes(input: { ids: number[] }) { unstable_noStore(); try { await db.transaction(async (tx) => { await deleteVendorTypesByIds(tx, input.ids); }); revalidateTag("vendorTypes"); return { data: null, error: null }; } catch (err) { return { data: null, error: getErrorMessage(err) }; } }