File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
09-17
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<string, unknown> {
return value !== null && typeof value === 'object';
}
function readString(source: Record<string, unknown>, 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<AdminNotification[]> {
const accessToken = await getSessionAccessToken();
if (!accessToken) {
return [];
}
try {
const result = await backendFetch<unknown>(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<void> {
const accessToken = await getSessionAccessToken();
const result = await backendFetch<null>(
`${NOTIFICATION_PATH}/${encodeURIComponent(id)}`,
{
method: 'PUT',
accessToken: accessToken ?? undefined,
cache: 'no-store',
canHaveNullData: true,
}
);
if (!result.ok) {
throw new BackendRequestError(result);
}
}