summaryrefslogtreecommitdiff
path: root/lib/information/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/information/service.ts')
-rw-r--r--lib/information/service.ts618
1 files changed, 335 insertions, 283 deletions
diff --git a/lib/information/service.ts b/lib/information/service.ts
index 30a651f1..2826c0e9 100644
--- a/lib/information/service.ts
+++ b/lib/information/service.ts
@@ -1,283 +1,335 @@
-"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<GetInformationSchema> & { page: number; per_page: number }) {
- unstable_noStore()
-
- try {
- const [data, total] = await Promise.all([
- selectInformation(input as Parameters<typeof selectInformation>[0]),
- countInformation(input as Parameters<typeof countInformation>[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<PageInformation | null> {
- 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<PageInformation | null> {
- 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<boolean> {
- 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분 캐시
- }
-) \ No newline at end of file
+"use server"
+
+import { revalidateTag } from "next/cache"
+import { getErrorMessage } from "@/lib/handle-error"
+import { unstable_cache } from "@/lib/unstable-cache"
+import { desc, or, eq } from "drizzle-orm"
+import db from "@/db/db"
+import { pageInformation, menuAssignments } from "@/db/schema"
+import { saveDRMFile } from "@/lib/file-stroage"
+import { decryptWithServerAction } from "@/components/drm/drmUtils"
+
+import type {
+ UpdateInformationSchema
+} from "./validations"
+
+import {
+ getInformationByPagePathWithAttachments,
+ updateInformation,
+ getInformationWithAttachments,
+ addInformationAttachment,
+ deleteInformationAttachment,
+ getAttachmentById
+} from "./repository"
+
+import type { PageInformation, InformationAttachment } from "@/db/schema/information"
+
+// 간단한 인포메이션 목록 조회 (페이지네이션 없이 전체 조회)
+export async function getInformationLists() {
+ try {
+ // 전체 데이터 조회 (클라이언트에서 검색 처리)
+ const data = await db
+ .select()
+ .from(pageInformation)
+ .orderBy(desc(pageInformation.createdAt))
+
+ return { data }
+ } catch (err) {
+ console.error("Failed to get information lists:", err)
+ return { data: [] }
+ }
+}
+
+
+
+// 페이지별 인포메이션 조회 (첨부파일 포함)
+export async function getPageInformation(pagePath: string) {
+ try {
+ return await getInformationByPagePathWithAttachments(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,
+ isActive: updateData.isActive,
+ 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) {
+ try {
+ return await getInformationWithAttachments(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<boolean> {
+ 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분 캐시
+ }
+)
+
+// menu_assignments 기반으로 page_information 동기화
+export async function syncInformationFromMenuAssignments() {
+ try {
+ // menu_assignments에서 모든 메뉴 가져오기
+ const menuItems = await db.select().from(menuAssignments);
+
+ let processedCount = 0;
+
+ // upsert를 사용하여 각 메뉴 항목 처리
+ for (const menu of menuItems) {
+ try {
+ await db.insert(pageInformation)
+ .values({
+ pagePath: menu.menuPath,
+ pageName: menu.menuTitle,
+ informationContent: "",
+ isActive: true // 기본값으로 활성화
+ })
+ .onConflictDoUpdate({
+ target: pageInformation.pagePath,
+ set: {
+ pageName: menu.menuTitle,
+ updatedAt: new Date()
+ }
+ });
+ processedCount++;
+ } catch (itemError: any) {
+ console.warn(`메뉴 항목 처리 실패: ${menu.menuPath}`, itemError);
+ continue;
+ }
+ }
+
+ revalidateTag("information");
+
+ return {
+ success: true,
+ message: `페이지 정보 동기화 완료: ${processedCount}개 처리됨`
+ };
+ } catch (error) {
+ console.error("Information 동기화 오류:", error);
+ return {
+ success: false,
+ message: "페이지 정보 동기화 중 오류가 발생했습니다."
+ };
+ }
+}
+
+// 첨부파일 업로드
+export async function uploadInformationAttachment(formData: FormData) {
+ try {
+ const informationId = parseInt(formData.get("informationId") as string)
+ const file = formData.get("file") as File
+
+ if (!informationId || !file) {
+ return {
+ success: false,
+ message: "필수 매개변수가 누락되었습니다."
+ }
+ }
+
+ // 파일 저장
+ const saveResult = await saveDRMFile(
+ file,
+ decryptWithServerAction,
+ `information/${informationId}`,
+ // userId는 필요시 추가
+ )
+
+ if (!saveResult.success) {
+ return {
+ success: false,
+ message: saveResult.error || "파일 저장에 실패했습니다."
+ }
+ }
+
+ // DB에 첨부파일 정보 저장
+ const attachment = await addInformationAttachment({
+ informationId,
+ fileName: file.name,
+ filePath: saveResult.publicPath || "",
+ fileSize: saveResult.fileSize ? String(saveResult.fileSize) : String(file.size)
+ })
+
+ if (!attachment) {
+ return {
+ success: false,
+ message: "첨부파일 정보 저장에 실패했습니다."
+ }
+ }
+
+ revalidateTag("page-information")
+ revalidateTag("information-lists")
+
+ return {
+ success: true,
+ message: "첨부파일이 성공적으로 업로드되었습니다.",
+ data: attachment
+ }
+ } catch (error) {
+ console.error("Failed to upload attachment:", error)
+ return {
+ success: false,
+ message: getErrorMessage(error)
+ }
+ }
+}
+
+// 첨부파일 삭제
+export async function deleteInformationAttachmentAction(attachmentId: number) {
+ try {
+ const attachment = await getAttachmentById(attachmentId)
+
+ if (!attachment) {
+ return {
+ success: false,
+ message: "첨부파일을 찾을 수 없습니다."
+ }
+ }
+
+ // DB에서 삭제
+ const deleted = await deleteInformationAttachment(attachmentId)
+
+ if (!deleted) {
+ return {
+ success: false,
+ message: "첨부파일 삭제에 실패했습니다."
+ }
+ }
+
+ revalidateTag("page-information")
+ revalidateTag("information-lists")
+
+ return {
+ success: true,
+ message: "첨부파일이 성공적으로 삭제되었습니다."
+ }
+ } catch (error) {
+ console.error("Failed to delete attachment:", error)
+ return {
+ success: false,
+ message: getErrorMessage(error)
+ }
+ }
+}
+
+// 첨부파일 다운로드
+export async function downloadInformationAttachment(attachmentId: number) {
+ try {
+ const attachment = await getAttachmentById(attachmentId)
+
+ if (!attachment) {
+ return {
+ success: false,
+ message: "첨부파일을 찾을 수 없습니다."
+ }
+ }
+
+ // 파일 다운로드 (클라이언트에서 사용)
+ return {
+ success: true,
+ data: {
+ filePath: attachment.filePath,
+ fileName: attachment.fileName,
+ fileSize: attachment.fileSize
+ }
+ }
+ } catch (error) {
+ console.error("Failed to download attachment:", error)
+ return {
+ success: false,
+ message: getErrorMessage(error)
+ }
+ }
+} \ No newline at end of file