"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 { UpdateInformationSchema, GetInformationSchema } from "./validations" import { selectInformation, countInformation, getInformationByPagePath, updateInformation, getInformationById, selectInformationLists, countInformationLists } from "./repository" import type { PageInformation } from "@/db/schema/information" // 최신 패턴: 고급 필터링과 캐싱을 지원하는 인포메이션 목록 조회 export async function getInformationLists(input: GetInformationSchema) { return unstable_cache( async () => { try { // 고급 검색 로직 const { page, perPage, search, filters, joinOperator, pagePath, pageName, informationContent, isActive } = input // 기본 검색 조건들 const conditions = [] // 검색어가 있으면 여러 필드에서 검색 if (search && search.trim()) { const searchConditions = [ ilike(pageInformation.pagePath, `%${search}%`), ilike(pageInformation.pageName, `%${search}%`), ilike(pageInformation.informationContent, `%${search}%`) ] conditions.push(or(...searchConditions)) } // 개별 필드 조건들 if (pagePath && pagePath.trim()) { conditions.push(ilike(pageInformation.pagePath, `%${pagePath}%`)) } if (pageName && pageName.trim()) { conditions.push(ilike(pageInformation.pageName, `%${pageName}%`)) } if (informationContent && informationContent.trim()) { conditions.push(ilike(pageInformation.informationContent, `%${informationContent}%`)) } if (isActive !== null && isActive !== undefined) { conditions.push(eq(pageInformation.isActive, isActive)) } // 고급 필터 처리 if (filters && filters.length > 0) { const advancedConditions = filters.map(() => filterColumns({ table: pageInformation, filters: filters, joinOperator: joinOperator, }) ) if (advancedConditions.length > 0) { if (joinOperator === "or") { conditions.push(or(...advancedConditions)) } else { conditions.push(and(...advancedConditions)) } } } // 전체 WHERE 조건 조합 const finalWhere = conditions.length > 0 ? (joinOperator === "or" ? or(...conditions) : and(...conditions)) : undefined // 페이지네이션 const offset = (page - 1) * perPage // 정렬 처리 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 === "pagePath") { return item.desc ? desc(pageInformation.pagePath) : asc(pageInformation.pagePath) } else if (item.id === "pageName") { return item.desc ? desc(pageInformation.pageName) : asc(pageInformation.pageName) } else if (item.id === "informationContent") { return item.desc ? desc(pageInformation.informationContent) : asc(pageInformation.informationContent) } 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(pagePath: string): Promise { try { return await getInformationByPagePath(pagePath) } catch (error) { console.error(`Failed to get information for page ${pagePath}:`, error) return null } } // 캐시된 페이지별 인포메이션 조회 export const getCachedPageInformation = unstable_cache( async (pagePath: string) => getPageInformation(pagePath), ["page-information"], { tags: ["page-information"], revalidate: 3600, // 1시간 캐시 } ) // 인포메이션 수정 (내용과 첨부파일만) export async function updateInformationData(input: UpdateInformationSchema) { try { const { id, ...updateData } = input // 수정 가능한 필드만 허용 const allowedFields = { informationContent: updateData.informationContent, attachmentFilePath: updateData.attachmentFilePath, attachmentFileName: updateData.attachmentFileName, updatedAt: new Date() } const result = await updateInformation(id, allowedFields) 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) } } } // 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(pagePath: string, userId: string): Promise { try { // pagePath를 menuPath로 변환 (pagePath가 menuPath의 마지막 부분이라고 가정) // 예: pagePath "vendor-list" -> menuPath "/evcp/vendor-list" 또는 "/partners/vendor-list" const menuPathQueries = [ `/evcp/${pagePath}`, `/partners/${pagePath}`, `/${pagePath}`, // 루트 경로 pagePath // 정확한 매칭 ] // menu_assignments에서 해당 pagePath와 매칭되는 메뉴 찾기 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 (pagePath: string, userId: string) => checkInformationEditPermission(pagePath, userId), ["information-edit-permission"], { tags: ["information-edit-permission"], revalidate: 300, // 5분 캐시 } )