From 7dd2b9fc1856306652f311d19697d9880955bfab Mon Sep 17 00:00:00 2001 From: dujinkim Date: Fri, 22 Aug 2025 02:12:15 +0000 Subject: (최겸) 공지사항, 인포메이션 기능 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/notice/repository.ts | 155 +-------------------------------- lib/notice/service.ts | 215 +++++++++------------------------------------- lib/notice/validations.ts | 57 +----------- 3 files changed, 45 insertions(+), 382 deletions(-) (limited to 'lib/notice') diff --git a/lib/notice/repository.ts b/lib/notice/repository.ts index 84e64f00..fb941ac9 100644 --- a/lib/notice/repository.ts +++ b/lib/notice/repository.ts @@ -1,160 +1,7 @@ -import { asc, desc, eq, ilike, and, count, sql } from "drizzle-orm" +import { desc, eq, and, sql } from "drizzle-orm" import db from "@/db/db" import { notice, users, type Notice, type NewNotice } from "@/db/schema" -// 최신 패턴: 트랜잭션을 지원하는 공지사항 조회 -export async function selectNoticeLists( - tx: typeof db, - params: { - where?: ReturnType - orderBy?: (ReturnType | ReturnType)[] - offset?: number - limit?: number - } -) { - const { where, orderBy, offset = 0, limit = 10 } = params - - return tx - .select({ - id: notice.id, - pagePath: notice.pagePath, - title: notice.title, - content: notice.content, - authorId: notice.authorId, - isActive: notice.isActive, - createdAt: notice.createdAt, - updatedAt: notice.updatedAt, - authorName: users.name, - authorEmail: users.email, - }) - .from(notice) - .leftJoin(users, eq(notice.authorId, users.id)) - .where(where) - .orderBy(...(orderBy ?? [desc(notice.createdAt)])) - .offset(offset) - .limit(limit) -} - -// 최신 패턴: 트랜잭션을 지원하는 카운트 조회 -export async function countNoticeLists( - tx: typeof db, - where?: ReturnType -) { - const res = await tx - .select({ count: count() }) - .from(notice) - .where(where) - - return res[0]?.count ?? 0 -} - -// 기존 패턴 (하위 호환성을 위해 유지) -export async function selectNotice(input: { page: number; per_page: number; sort?: string; pagePath?: string; title?: string; authorId?: number; isActive?: boolean; from?: string; to?: string }) { - const { page, per_page = 50, sort, pagePath, title, authorId, isActive, from, to } = input - - const conditions = [] - - if (pagePath) { - conditions.push(ilike(notice.pagePath, `%${pagePath}%`)) - } - - if (title) { - conditions.push(ilike(notice.title, `%${title}%`)) - } - - if (authorId) { - conditions.push(eq(notice.authorId, authorId)) - } - - if (isActive !== null && isActive !== undefined) { - conditions.push(eq(notice.isActive, isActive)) - } - - if (from) { - conditions.push(sql`${notice.createdAt} >= ${from}`) - } - - if (to) { - conditions.push(sql`${notice.createdAt} <= ${to}`) - } - - const offset = (page - 1) * per_page - - // 정렬 설정 - let orderBy = desc(notice.createdAt); - - if (sort && Array.isArray(sort) && sort.length > 0) { - const sortItem = sort[0]; - if (sortItem.id === "createdAt") { - orderBy = sortItem.desc ? desc(notice.createdAt) : asc(notice.createdAt); - } - } - - const whereClause = conditions.length > 0 ? and(...conditions) : undefined - - const data = await db - .select({ - id: notice.id, - pagePath: notice.pagePath, - title: notice.title, - content: notice.content, - authorId: notice.authorId, - isActive: notice.isActive, - createdAt: notice.createdAt, - updatedAt: notice.updatedAt, - authorName: users.name, - authorEmail: users.email, - }) - .from(notice) - .leftJoin(users, eq(notice.authorId, users.id)) - .where(whereClause) - .orderBy(orderBy) - .limit(per_page) - .offset(offset) - - return data -} - -// 기존 패턴: 공지사항 총 개수 조회 -export async function countNotice(input: { pagePath?: string; title?: string; authorId?: number; isActive?: boolean; from?: string; to?: string }) { - const { pagePath, title, authorId, isActive, from, to } = input - - const conditions = [] - - if (pagePath) { - conditions.push(ilike(notice.pagePath, `%${pagePath}%`)) - } - - if (title) { - conditions.push(ilike(notice.title, `%${title}%`)) - } - - if (authorId) { - conditions.push(eq(notice.authorId, authorId)) - } - - if (isActive !== null && isActive !== undefined) { - conditions.push(eq(notice.isActive, isActive)) - } - - if (from) { - conditions.push(sql`${notice.createdAt} >= ${from}`) - } - - if (to) { - conditions.push(sql`${notice.createdAt} <= ${to}`) - } - - const whereClause = conditions.length > 0 ? and(...conditions) : undefined - - const result = await db - .select({ count: count() }) - .from(notice) - .where(whereClause) - - return result[0]?.count ?? 0 -} - // 페이지 경로별 공지사항 조회 (활성화된 것만, 작성자 정보 포함) export async function getNoticesByPagePath(pagePath: string): Promise> { const result = await db diff --git a/lib/notice/service.ts b/lib/notice/service.ts index c261cd2e..9c05b98f 100644 --- a/lib/notice/service.ts +++ b/lib/notice/service.ts @@ -1,203 +1,81 @@ "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 { desc, eq } from "drizzle-orm" import db from "@/db/db" -import { notice, pageInformation, menuAssignments } from "@/db/schema" +import { notice, pageInformation, menuAssignments, users } from "@/db/schema" import type { CreateNoticeSchema, - UpdateNoticeSchema, - GetNoticeSchema + UpdateNoticeSchema } from "./validations" import { - selectNotice, - countNotice, getNoticesByPagePath, insertNotice, updateNotice, deleteNoticeById, deleteNoticeByIds, - getNoticeById, - selectNoticeLists, - countNoticeLists + getNoticeById } from "./repository" import type { Notice } from "@/db/schema/notice" -export async function getNoticeLists(input: GetNoticeSchema) { - return unstable_cache( - async () => { - try { - // 고급 검색 로직 - const { page, perPage, search, filters, joinOperator, pagePath, title, content, authorId, isActive } = input - - // 기본 검색 조건들 - const conditions = [] - - // 검색어가 있으면 여러 필드에서 검색 - if (search && search.trim()) { - const searchConditions = [ - ilike(notice.pagePath, `%${search}%`), - ilike(notice.title, `%${search}%`), - ilike(notice.content, `%${search}%`) - ] - conditions.push(or(...searchConditions)) - } - - // 개별 필드 조건들 - if (pagePath && pagePath.trim()) { - conditions.push(ilike(notice.pagePath, `%${pagePath}%`)) - } - - if (title && title.trim()) { - conditions.push(ilike(notice.title, `%${title}%`)) - } - - if (content && content.trim()) { - conditions.push(ilike(notice.content, `%${content}%`)) - } - - if (authorId !== null && authorId !== undefined) { - conditions.push(eq(notice.authorId, authorId)) - } - - if (isActive !== null && isActive !== undefined) { - conditions.push(eq(notice.isActive, isActive)) - } - // 고급 필터 처리 - if (filters && filters.length > 0) { - const advancedConditions = filters.map(() => - filterColumns({ - table: notice, - 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(notice.createdAt) : asc(notice.createdAt) - } else if (item.id === "updatedAt") { - return item.desc ? desc(notice.updatedAt) : asc(notice.updatedAt) - } else if (item.id === "pagePath") { - return item.desc ? desc(notice.pagePath) : asc(notice.pagePath) - } else if (item.id === "title") { - return item.desc ? desc(notice.title) : asc(notice.title) - } else if (item.id === "authorId") { - return item.desc ? desc(notice.authorId) : asc(notice.authorId) - } else if (item.id === "isActive") { - return item.desc ? desc(notice.isActive) : asc(notice.isActive) - } else { - return desc(notice.createdAt) // 기본값 - } - }) - : [desc(notice.createdAt)] - - // 트랜잭션 내부에서 Repository 호출 - const { data, total } = await db.transaction(async (tx) => { - const data = await selectNoticeLists(tx, { - where: finalWhere, - orderBy, - offset, - limit: input.perPage, - }) - - const total = await countNoticeLists(tx, finalWhere) - return { data, total } - }) - - const pageCount = Math.ceil(total / input.perPage) - - return { data, pageCount, total } - } catch (err) { - console.error("Failed to get notice lists:", err) - // 에러 발생 시 기본값 반환 - return { data: [], pageCount: 0, total: 0 } - } - }, - [JSON.stringify(input)], - { - revalidate: 3600, - tags: ["notice-lists"], - } - )() -} - -// 기존 패턴 (하위 호환성을 위해 유지) -export async function getNoticeList(input: Partial<{ page: number; per_page: number; sort?: string; pagePath?: string; title?: string; authorId?: number; isActive?: boolean; from?: string; to?: string }> & { page: number; per_page: number }) { - unstable_noStore() - +// 간단한 공지사항 목록 조회 (페이지네이션 없이 전체 조회) +export async function getNoticeLists(): Promise<{ data: Array }> { try { - const [data, total] = await Promise.all([ - selectNotice(input), - countNotice(input) - ]) - - const pageCount = Math.ceil(total / input.per_page) - - return { - data, - pageCount, - total - } - } catch (error) { - console.error("Failed to get notice list:", error) - throw new Error(getErrorMessage(error)) + // 전체 데이터 조회 (작성자 정보 포함, 클라이언트에서 검색 처리) + const data = await db + .select({ + id: notice.id, + pagePath: notice.pagePath, + title: notice.title, + content: notice.content, + authorId: notice.authorId, + isActive: notice.isActive, + createdAt: notice.createdAt, + updatedAt: notice.updatedAt, + authorName: users.name, + authorEmail: users.email, + }) + .from(notice) + .leftJoin(users, eq(notice.authorId, users.id)) + .orderBy(desc(notice.createdAt)) + + return { data } + } catch (err) { + console.error("Failed to get notice lists:", err) + return { data: [] } } } // 페이지별 공지사항 조회 (일반 사용자용) export async function getPageNotices(pagePath: string): Promise> { try { - return await getNoticesByPagePath(pagePath) + console.log('🔍 Notice Service - 조회 시작:', { pagePath }) + const result = await getNoticesByPagePath(pagePath) + console.log('📊 Notice Service - 조회 결과:', { + pagePath, + noticesCount: result.length, + notices: result.map(n => ({ id: n.id, title: n.title, pagePath: n.pagePath })) + }) + return result } catch (error) { console.error(`Failed to get notices for page ${pagePath}:`, error) return [] } } -// 캐시된 페이지별 공지사항 조회 -export const getCachedPageNotices = unstable_cache( - async (pagePath: string) => getPageNotices(pagePath), - ["page-notices"], - { - tags: ["page-notices"], - revalidate: 3600, // 1시간 캐시 - } -) +// 페이지별 공지사항 조회 (직접 호출용) +export async function getPageNoticesDirect(pagePath: string) { + return await getPageNotices(pagePath) +} // 공지사항 생성 export async function createNotice(input: CreateNoticeSchema) { try { const result = await insertNotice(input) - revalidateTag("page-notices") - revalidateTag("notice-lists") - return { success: true, data: result, @@ -225,9 +103,6 @@ export async function updateNoticeData(input: UpdateNoticeSchema) { } } - revalidateTag("page-notices") - revalidateTag("notice-lists") - return { success: true, message: "공지사항이 성공적으로 수정되었습니다." @@ -253,9 +128,6 @@ export async function deleteNotice(id: number) { } } - revalidateTag("page-notices") - revalidateTag("notice-lists") - return { success: true, message: "공지사항이 성공적으로 삭제되었습니다." @@ -274,9 +146,6 @@ export async function deleteMultipleNotices(ids: number[]) { try { const deletedCount = await deleteNoticeByIds(ids) - revalidateTag("page-notices") - revalidateTag("notice-lists") - return { success: true, deletedCount, @@ -311,7 +180,7 @@ export async function getPagePathList(): Promise ({ pagePath: item.pagePath, @@ -349,8 +218,6 @@ export async function syncNoticeFromMenuAssignments() { processedCount++; } - revalidateTag("notice"); - return { success: true, message: `공지사항 경로 동기화 확인 완료: ${processedCount}개 확인, ${missingPaths.length}개 누락`, diff --git a/lib/notice/validations.ts b/lib/notice/validations.ts index 05e84af9..146f8e09 100644 --- a/lib/notice/validations.ts +++ b/lib/notice/validations.ts @@ -1,14 +1,4 @@ import { z } from "zod" -import { - createSearchParamsCache, - parseAsArrayOf, - parseAsInteger, - parseAsString, - parseAsStringEnum, - parseAsBoolean, -} from "nuqs/server" -import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers" -import { Notice } from "@/db/schema/notice" // 공지사항 생성 스키마 export const createNoticeSchema = z.object({ @@ -28,53 +18,12 @@ export const updateNoticeSchema = z.object({ isActive: z.boolean().default(true), }) -// 현대적인 검색 파라미터 캐시 -export const searchParamsNoticeCache = createSearchParamsCache({ - flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault([]), - page: parseAsInteger.withDefault(1), - perPage: parseAsInteger.withDefault(10), - sort: getSortingStateParser().withDefault([ - { id: "createdAt", desc: true }, - ]), - - // 기본 검색 필드들 - pagePath: parseAsString.withDefault(""), - title: parseAsString.withDefault(""), - content: parseAsString.withDefault(""), - authorId: parseAsInteger, - isActive: parseAsBoolean, - - // 고급 필터 - filters: getFiltersStateParser().withDefault([]), - joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), - search: parseAsString.withDefault(""), - - // 날짜 범위 - from: parseAsString.withDefault(""), - to: parseAsString.withDefault(""), -}) - -// 타입 추출 -export type CreateNoticeSchema = z.infer -export type UpdateNoticeSchema = z.infer -export type GetNoticeSchema = Awaited> - -// 기존 스키마 (하위 호환성을 위해 유지) -export const getNoticeSchema = z.object({ - page: z.coerce.number().default(1), - per_page: z.coerce.number().default(10), - sort: z.string().optional(), - pagePath: z.string().optional(), - title: z.string().optional(), - authorId: z.coerce.number().optional(), - isActive: z.coerce.boolean().optional(), - from: z.string().optional(), - to: z.string().optional(), -}) - // 페이지 경로별 공지사항 조회 스키마 export const getPageNoticeSchema = z.object({ pagePath: z.string().min(1, "페이지 경로를 입력해주세요"), }) +// 타입 추출 +export type CreateNoticeSchema = z.infer +export type UpdateNoticeSchema = z.infer export type GetPageNoticeSchema = z.infer \ No newline at end of file -- cgit v1.2.3