"use server" import { revalidateTag, unstable_noStore } from "next/cache" import { getErrorMessage } from "@/lib/handle-error" import { unstable_cache } from "@/lib/unstable-cache" import { filterColumns } from "@/lib/filter-columns" import { asc, desc, ilike, and, or, eq } from "drizzle-orm" import db from "@/db/db" import { pageInformation, menuAssignments } from "@/db/schema" import type { CreateInformationSchema, UpdateInformationSchema, GetInformationSchema } from "./validations" import { selectInformation, countInformation, getInformationByPageCode, insertInformation, updateInformation, deleteInformationById, deleteInformationByIds, getInformationById, selectInformationLists, countInformationLists } from "./repository" import type { PageInformation } from "@/db/schema/information" // 최신 패턴: 고급 필터링과 캐싱을 지원하는 인포메이션 목록 조회 export async function getInformationLists(input: GetInformationSchema) { return unstable_cache( async () => { try { const offset = (input.page - 1) * input.perPage // 고급 필터링 const advancedWhere = filterColumns({ table: pageInformation, filters: input.filters, joinOperator: input.joinOperator, }) // 전역 검색 let globalWhere if (input.search) { const s = `%${input.search}%` globalWhere = or( ilike(pageInformation.pageCode, s), ilike(pageInformation.pageName, s), ilike(pageInformation.title, s), ilike(pageInformation.description, s) ) } // 기본 필터들 let basicWhere const basicConditions = [] if (input.pageCode) { basicConditions.push(ilike(pageInformation.pageCode, `%${input.pageCode}%`)) } if (input.pageName) { basicConditions.push(ilike(pageInformation.pageName, `%${input.pageName}%`)) } if (input.title) { basicConditions.push(ilike(pageInformation.title, `%${input.title}%`)) } if (input.isActive !== undefined && input.isActive !== null) { basicConditions.push(eq(pageInformation.isActive, input.isActive)) } if (basicConditions.length > 0) { basicWhere = and(...basicConditions) } // 최종 where 조건 const finalWhere = and( advancedWhere, globalWhere, basicWhere ) // 정렬 처리 const orderBy = input.sort.length > 0 ? input.sort.map((item) => { if (item.id === "createdAt") { return item.desc ? desc(pageInformation.createdAt) : asc(pageInformation.createdAt) } else if (item.id === "updatedAt") { return item.desc ? desc(pageInformation.updatedAt) : asc(pageInformation.updatedAt) } else if (item.id === "pageCode") { return item.desc ? desc(pageInformation.pageCode) : asc(pageInformation.pageCode) } else if (item.id === "pageName") { return item.desc ? desc(pageInformation.pageName) : asc(pageInformation.pageName) } else if (item.id === "title") { return item.desc ? desc(pageInformation.title) : asc(pageInformation.title) } else if (item.id === "isActive") { return item.desc ? desc(pageInformation.isActive) : asc(pageInformation.isActive) } else { return desc(pageInformation.createdAt) // 기본값 } }) : [desc(pageInformation.createdAt)] // 트랜잭션 내부에서 Repository 호출 const { data, total } = await db.transaction(async (tx) => { const data = await selectInformationLists(tx, { where: finalWhere, orderBy, offset, limit: input.perPage, }) const total = await countInformationLists(tx, finalWhere) return { data, total } }) const pageCount = Math.ceil(total / input.perPage) return { data, pageCount, total } } catch (err) { console.error("Failed to get information lists:", err) // 에러 발생 시 기본값 반환 return { data: [], pageCount: 0, total: 0 } } }, [JSON.stringify(input)], // 캐싱 키 { revalidate: 3600, tags: ["information-lists"], } )() } // 기존 패턴 (하위 호환성을 위해 유지) export async function getInformationList(input: Partial & { page: number; per_page: number }) { unstable_noStore() try { const [data, total] = await Promise.all([ selectInformation(input as Parameters[0]), countInformation(input as Parameters[0]) ]) const pageCount = Math.ceil(total / input.per_page) return { data, pageCount, total } } catch (error) { console.error("Failed to get information list:", error) throw new Error(getErrorMessage(error)) } } // 페이지별 인포메이션 조회 (일반 사용자용) export async function getPageInformation(pageCode: string): Promise { try { return await getInformationByPageCode(pageCode) } catch (error) { console.error(`Failed to get information for page ${pageCode}:`, error) return null } } // 캐시된 페이지별 인포메이션 조회 export const getCachedPageInformation = unstable_cache( async (pageCode: string) => getPageInformation(pageCode), ["page-information"], { tags: ["page-information"], revalidate: 3600, // 1시간 캐시 } ) // 인포메이션 생성 export async function createInformation(input: CreateInformationSchema) { try { const result = await insertInformation(input) revalidateTag("page-information") revalidateTag("information-lists") revalidateTag("information-edit-permission") return { success: true, data: result, message: "인포메이션이 성공적으로 생성되었습니다." } } catch (error) { console.error("Failed to create information:", error) return { success: false, message: getErrorMessage(error) } } } // 인포메이션 수정 export async function updateInformationData(input: UpdateInformationSchema) { try { const { id, ...updateData } = input const result = await updateInformation(id, updateData) if (!result) { return { success: false, message: "인포메이션을 찾을 수 없거나 수정에 실패했습니다." } } revalidateTag("page-information") revalidateTag("information-lists") revalidateTag("information-edit-permission") // 편집 권한 캐시 무효화 return { success: true, message: "인포메이션이 성공적으로 수정되었습니다." } } catch (error) { console.error("Failed to update information:", error) return { success: false, message: getErrorMessage(error) } } } // 인포메이션 삭제 export async function deleteInformation(id: number) { try { const success = await deleteInformationById(id) if (!success) { return { success: false, message: "인포메이션을 찾을 수 없거나 삭제에 실패했습니다." } } revalidateTag("page-information") revalidateTag("information-lists") return { success: true, message: "인포메이션이 성공적으로 삭제되었습니다." } } catch (error) { console.error("Failed to delete information:", error) return { success: false, message: getErrorMessage(error) } } } // 인포메이션 다중 삭제 export async function deleteMultipleInformation(ids: number[]) { try { const deletedCount = await deleteInformationByIds(ids) revalidateTag("page-information") revalidateTag("information-lists") return { success: true, deletedCount, message: `${deletedCount}개의 인포메이션이 성공적으로 삭제되었습니다.` } } catch (error) { console.error("Failed to delete multiple information:", error) return { success: false, message: getErrorMessage(error) } } } // ID로 인포메이션 조회 export async function getInformationDetail(id: number): Promise { try { return await getInformationById(id) } catch (error) { console.error(`Failed to get information detail for id ${id}:`, error) return null } } // 인포메이션 편집 권한 확인 export async function checkInformationEditPermission(pageCode: string, userId: string): Promise { try { // pageCode를 menuPath로 변환 (pageCode가 menuPath의 마지막 부분이라고 가정) // 예: pageCode "vendor-list" -> menuPath "/evcp/vendor-list" 또는 "/partners/vendor-list" const menuPathQueries = [ `/evcp/${pageCode}`, `/partners/${pageCode}`, `/${pageCode}`, // 루트 경로 pageCode // 정확한 매칭 ] // menu_assignments에서 해당 pageCode와 매칭되는 메뉴 찾기 const menuAssignment = await db .select() .from(menuAssignments) .where( or( ...menuPathQueries.map(path => eq(menuAssignments.menuPath, path)) ) ) .limit(1) if (menuAssignment.length === 0) { // 매칭되는 메뉴가 없으면 권한 없음 return false } const assignment = menuAssignment[0] const userIdNumber = parseInt(userId) // 현재 사용자가 manager1 또는 manager2인지 확인 return assignment.manager1Id === userIdNumber || assignment.manager2Id === userIdNumber } catch (error) { console.error("Failed to check information edit permission:", error) return false } } // 캐시된 권한 확인 export const getCachedEditPermission = unstable_cache( async (pageCode: string, userId: string) => checkInformationEditPermission(pageCode, userId), ["information-edit-permission"], { tags: ["information-edit-permission"], revalidate: 300, // 5분 캐시 } )