| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- 'use server';
- import { cookies } from 'next/headers'
- import { ResultDto, TokenData, ApiError } from '@/types/response/common';
- import BoardManager from '@/types/forum/boardManager';
- import { MemberResponse } from '@/types/response/account/member';
- import { CLAIM_NAME_IDENTIFIER, CLAIM_EMAIL, CLAIM_NAME } from '@/constants/common';
- const API_URL = process.env.API_URL;
- export async function fetchJson<T>(url: string, options: RequestInit = {}): Promise<ResultDto<T>> {
- try {
- const actionURL = `${API_URL}${url.startsWith('/') ? url : `/${url}`}`;
- const cookie = await cookies();
- const cookieData = cookie.getAll().filter(c => c.name !== 'member').map(c => `${c.name}=${c.value}`).join("; ");
- const accessToken = cookie.get('accessToken')?.value;
- const res = await fetch(actionURL, {
- ...options,
- headers: {
- ...(options.body && !(options.body instanceof FormData) ? { 'Content-Type': 'application/json' } : {}),
- ...(options.headers || {}),
- ...(accessToken ? { 'Authorization': `Bearer ${accessToken}` } : {}),
- Cookie: cookieData,
- 'Accept': 'application/json'
- },
- cache: 'no-store'
- });
- // Set-Cookie 헤더 처리 — 백엔드 응답 쿠키를 Next.js 쿠키 저장소에 저장
- const setCookieHeader = res.headers.getSetCookie();
- if (setCookieHeader && setCookieHeader.length > 0)
- {
- const cookieStore = await cookies();
- for (const cookieString of setCookieHeader) {
- const parts = cookieString.split(';').map(p => p.trim());
- const [nameValue, ...attributes] = parts;
- const eqIndex = nameValue.indexOf('=');
- if (eqIndex === -1) {
- continue;
- }
- const name = nameValue.substring(0, eqIndex);
- const value = nameValue.substring(eqIndex + 1);
- const options: Record<string, unknown> = {};
- for (const attr of attributes) {
- const [key, val] = attr.split('=');
- const lowerKey = key.toLowerCase().trim();
- if (lowerKey === 'httponly') options.httpOnly = true;
- else if (lowerKey === 'secure') options.secure = true;
- else if (lowerKey === 'path') options.path = val;
- else if (lowerKey === 'samesite') options.sameSite = val?.toLowerCase() as 'lax' | 'strict' | 'none';
- else if (lowerKey === 'max-age') options.maxAge = parseInt(val);
- else if (lowerKey === 'expires') options.expires = new Date(val);
- }
- cookieStore.set(name, value, options);
- }
- }
- if (res.status === 204) {
- return { success: true, status: 204, message: null, data: null, errors: null } satisfies ResultDto<T>;
- }
- const contentType = res.headers.get('content-type');
- if (!contentType || !contentType.includes('application/json')) {
- return {
- success: false,
- status: res.status,
- message: null,
- data: null,
- errors: null
- } satisfies ResultDto<T>;
- }
- return await res.json() as ResultDto<T>;
- } catch (err) {
- let message = '서버와 통신이 불가합니다.';
- const status = 500;
- if (err instanceof Error) {
- message = err.message;
- }
- console.warn(`[${status}] ${message}`);
- return {
- success: false,
- status: status,
- message: message,
- data: null,
- errors: null
- } satisfies ResultDto<T>;
- }
- }
- export async function getAccessToken(): Promise<string|null> {
- return (await cookies()).get('accessToken')?.value ?? null;
- }
- export async function getRefreshToken(): Promise<string|null> {
- return (await cookies()).get('refreshToken')?.value ?? null;
- }
- // API 서버 URL 조회
- export async function getAPIUrl(): Promise<string> {
- return process.env.API_URL as string;
- }
- // SignalR Chat 서버 URL 조회
- export async function getSignalRChatUrl(): Promise<string> {
- return process.env.SIGNALR_CHAT_URL as string;
- }
- // JWT 토큰에서 사용자 정보 추출
- export async function getTokenData(): Promise<TokenData|null> {
- try {
- const token = await getAccessToken();
- if (!token) {
- throw new Error('Access token not found');
- }
- const base64URL = token.split('.')[1]; // JWT의 Payload 부분
- const base64 = base64URL.replace(/-/g, '+').replace(/_/g, '/'); // Base64 형식 변환
- const jsonPayload = decodeURIComponent(
- atob(base64).split('').map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join('')
- );
- const payload = JSON.parse(jsonPayload);
- return {
- id: payload[CLAIM_NAME_IDENTIFIER] || null,
- email: payload[CLAIM_EMAIL] || null,
- name: payload[CLAIM_NAME] || null
- };
- } catch {
- return null;
- }
- }
- // 첫번째 오류 조회
- export async function getFirstError(errors: ApiError[] | null): Promise<string|null> {
- if (!errors || errors.length === 0) {
- return null;
- }
- return errors[0].description ?? null;
- }
- // 서버 응답 메시지 분석
- export async function throwError<T>(res: ResultDto<T>): Promise<void> {
- if (res.success) {
- return;
- }
- let message:string|null = await getFirstError(res.errors);
- if (!message && res.message) {
- message = res.message;
- }
- switch (res.status) {
- case 400:
- throw Error(message || '잘못된 요청입니다.');
- case 401:
- throw Error('로그인 후 이용해 주세요.');
- case 403:
- throw Error('권한이 없습니다.');
- case 404:
- throw Error('요청하신 페이지를 찾을 수 없습니다.');
- }
- if (message) {
- throw Error(message);
- }
- }
- // 게시판, 게시글, 댓글 등 권한 확인
- export async function checkPermission(permission: number, boardManager: BoardManager[]): Promise<boolean> {
- if (permission <= -1) {
- return true;
- }
- const raw = (await cookies()).get('member')?.value;
- const member = raw ? JSON.parse(decodeURIComponent(raw)) as MemberResponse : null;
- if (!member) {
- return false;
- }
- // 최고관리자는 항상 허용
- if (member.isAdmin) {
- return true;
- }
- // 유효 권한 레벨 계산 (매니저=99, 일반=memberGrade.order)
- const isBoardManager = boardManager.some(manager => member.id && manager.user.email === member.email);
- const level = isBoardManager ? 99 : (member.memberGrade?.order ?? 0);
- return permission <= level;
- }
|