// src/lib/items/service.ts "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 { CreateItemSchema, GetItemsSchema, UpdateItemSchema } from "./validations"; import { Item, items } from "@/db/schema/items"; import { countItems, deleteItemById, deleteItemsByIds, findAllItems, insertItem, selectItems, updateItem } from "./repository"; /* ----------------------------------------------------- 1) 조회 관련 ----------------------------------------------------- */ /** * 복잡한 조건으로 Item 목록을 조회 (+ pagination) 하고, * 총 개수에 따라 pageCount를 계산해서 리턴. * Next.js의 unstable_cache를 사용해 일정 시간 캐시. */ export async function getItems(input: GetItemsSchema) { return unstable_cache( async () => { try { const offset = (input.page - 1) * input.perPage; // const advancedTable = input.flags.includes("advancedTable"); const advancedTable = true; // advancedTable 모드면 filterColumns()로 where 절 구성 const advancedWhere = filterColumns({ table: items, filters: input.filters, joinOperator: input.joinOperator, }); let globalWhere if (input.search) { const s = `%${input.search}%` globalWhere = or(ilike(items.itemCode, s), ilike(items.itemName, s) , ilike(items.description, s) ) // 필요시 여러 칼럼 OR조건 (status, priority, etc) } const finalWhere = and( // advancedWhere or your existing conditions advancedWhere, globalWhere // and()함수로 결합 or or() 등으로 결합 ) // 아니면 ilike, inArray, gte 등으로 where 절 구성 const where = finalWhere const orderBy = input.sort.length > 0 ? input.sort.map((item) => item.desc ? desc(items[item.id]) : asc(items[item.id]) ) : [asc(items.createdAt)]; // 트랜잭션 내부에서 Repository 호출 const { data, total } = await db.transaction(async (tx) => { const data = await selectItems(tx, { where, orderBy, offset, limit: input.perPage, }); const total = await countItems(tx, where); return { data, total }; }); const pageCount = Math.ceil(total / input.perPage); return { data, pageCount }; } catch (err) { // 에러 발생 시 디폴트 return { data: [], pageCount: 0 }; } }, [JSON.stringify(input)], // 캐싱 키 { revalidate: 3600, tags: ["items"], // revalidateTag("items") 호출 시 무효화 } )(); } /* ----------------------------------------------------- 2) 생성(Create) ----------------------------------------------------- */ export interface ItemCreateData { itemCode: string itemName: string description: string | null } /** * Item 생성 후, (가장 오래된 Item 1개) 삭제로 * 전체 Item 개수를 고정 */ export async function createItem(input: ItemCreateData) { unstable_noStore() // Next.js 서버 액션 캐싱 방지 try { if (!input.itemCode || !input.itemName) { return { success: false, message: "아이템 코드와 아이템 명은 필수입니다", data: null, error: "필수 필드 누락" } } // result 변수에 명시적으로 타입과 초기값 할당 let result: any[] = [] // 트랜잭션 결과를 result에 할당 result = await db.transaction(async (tx) => { // 기존 아이템 확인 (itemCode는 unique) const existingItem = await tx.query.items.findFirst({ where: eq(items.itemCode, input.itemCode), }) let txResult if (existingItem) { // 기존 아이템 업데이트 txResult = await updateItem(tx, existingItem.id, { itemName: input.itemName, description: input.description, }) } else { // 새 아이템 생성 txResult = await insertItem(tx, { itemCode: input.itemCode, itemName: input.itemName, description: input.description, }) } return txResult }) // 캐시 무효화 revalidateTag("items") 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 modifyItem(input: UpdateItemSchema & { id: number }) { unstable_noStore(); try { const data = await db.transaction(async (tx) => { const [res] = await updateItem(tx, input.id, { itemCode: input.itemCode, itemName: input.itemName, description: input.description, }); return res; }); revalidateTag("items"); return { data: null, error: null }; } catch (err) { return { data: null, error: getErrorMessage(err) }; } } /** 단건 삭제 */ export async function removeItem(input: { id: number }) { unstable_noStore(); try { await db.transaction(async (tx) => { // 삭제 await deleteItemById(tx, input.id); // 바로 새 Item 생성 }); revalidateTag("items"); return { data: null, error: null }; } catch (err) { return { data: null, error: getErrorMessage(err) }; } } /** 복수 삭제 */ export async function removeItems(input: { ids: number[] }) { unstable_noStore(); try { await db.transaction(async (tx) => { // 삭제 await deleteItemsByIds(tx, input.ids); }); revalidateTag("items"); return { data: null, error: null }; } catch (err) { return { data: null, error: getErrorMessage(err) }; } } export async function getAllItems(): Promise { try { return await findAllItems(); } catch (err) { throw new Error("Failed to get roles"); } }