diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-08-21 06:57:36 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-08-21 06:57:36 +0000 |
| commit | 02b1cf005cf3e1df64183d20ba42930eb2767a9f (patch) | |
| tree | e932c54d5260b0e6fda2b46be2a6ba1c3ee30434 /lib/information/service.ts | |
| parent | d78378ecd7ceede1429359f8058c7a99ac34b1b7 (diff) | |
(대표님, 최겸) 설계메뉴추가, 작업사항 업데이트
설계메뉴 - 문서관리
설계메뉴 - 벤더 데이터
gtc 메뉴 업데이트
정보시스템 - 메뉴리스트 및 정보 업데이트
파일 라우트 업데이트
엑셀임포트 개선
기본계약 개선
벤더 가입과정 변경 및 개선
벤더 기본정보 - pq
돌체 오류 수정 및 개선
벤더 로그인 과정 이메일 오류 수정
Diffstat (limited to 'lib/information/service.ts')
| -rw-r--r-- | lib/information/service.ts | 618 |
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 |
