import 'server-only'; import { getSessionAccessToken } from '@/lib/auth/dal'; import { BackendRequestError, backendFetch } from '@/lib/http/backend-fetch'; import type { AdminNotification } from '@/lib/domain/notification'; /** * 관리자 알림 Repository. * * ``` * GET /api/v1/common/ntcn/list 로그인한 사용자의 알림 전체(등록일시 최신순) * PUT /api/v1/common/ntcn/{ntcnSn} 읽음 처리 * ``` * * 관리자 전용(`mngr`)이 아니라 공통 경로다 — 토큰의 주인이 누구인지로 대상이 갈리고, * 인가는 "로그인했으면 통과"라 관리자 토큰으로도 조회된다(SecurityConfig 확인함). * * 지금 적재되는 알림은 「학습자가 1:1문의를 등록했다」 하나다(`CmmBbsServiceImpl`이 등록 시 * 관리자 전원에게 한 건씩 넣는다). * * ⚠️ 읽음 처리(`PUT /{ntcnSn}`)는 **아직 아무것도 바꾸지 못한다** — 백엔드 컨트롤러가 * 경로변수 `ntcnSn`도 `readYn`도 VO에 담지 않아 `WHERE ntcn_sn = NULL`로 어느 행도 갱신되지 * 않고 200만 돌아온다(보고함). 호출은 규약대로 붙여 두었으니 백엔드가 두 줄 * (`setNtcnSn(ntcnSn)`·`setReadYn("Y")`)을 채우면 프론트 수정 없이 동작한다. * * 페이징이 없다. 목록 SQL에 LIMIT이 없어 전체가 온다 — 알림이 쌓이면 백엔드에 상한을 * 요청해야 한다. */ const NOTIFICATION_PATH = '/api/v1/common/ntcn'; const NOTIFICATION_LIST_PATH = `${NOTIFICATION_PATH}/list`; function isRecord(value: unknown): value is Record { return value !== null && typeof value === 'object'; } function readString(source: Record, key: string): string { const value = source[key]; return typeof value === 'string' ? value : ''; } function toNotification(raw: unknown): AdminNotification | null { if (!isRecord(raw)) { return null; } // `ntcnSn`은 숫자로 올 수도 있다 — 키로만 쓰므로 문자열로 맞춘다. const rawId = raw.ntcnSn; const id = typeof rawId === 'string' ? rawId : typeof rawId === 'number' ? String(rawId) : ''; if (!id) { return null; } return { id, title: readString(raw, 'ntcnSj'), content: readString(raw, 'ntcnCn'), isRead: readString(raw, 'readYn') === 'Y', }; } /** * 로그인한 관리자의 알림을 가져온다. * * **실패해도 던지지 않는다.** 헤더는 모든 화면에 붙어 있어, 알림 조회가 안 된다고 화면 * 전체가 죽으면 안 된다. 못 가져오면 빈 목록으로 둔다. */ export async function fetchNotifications(): Promise { const accessToken = await getSessionAccessToken(); if (!accessToken) { return []; } try { const result = await backendFetch(NOTIFICATION_LIST_PATH, { method: 'GET', accessToken, cache: 'no-store', canHaveNullData: true, }); if (!result.ok) { throw new BackendRequestError(result); } return Array.isArray(result.data) ? result.data .map(toNotification) .filter((item): item is AdminNotification => item !== null) : []; } catch { return []; } } export async function markNotificationRead(id: string): Promise { const accessToken = await getSessionAccessToken(); const result = await backendFetch( `${NOTIFICATION_PATH}/${encodeURIComponent(id)}`, { method: 'PUT', accessToken: accessToken ?? undefined, cache: 'no-store', canHaveNullData: true, } ); if (!result.ok) { throw new BackendRequestError(result); } }