server.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. // 서버 유틸리티 함수들
  2. 'use server';
  3. import axios, { AxiosRequestConfig } from 'axios';
  4. import https from 'https';
  5. import { cookies } from 'next/headers'
  6. import { ResultDto, TokenData } from '@/dtos/response/common';
  7. import BoardManager from '@/types/forum/boardManager';
  8. import { CLAIM_NAME_IDENTIFIER, CLAIM_EMAIL, CLAIM_NAME } from '@/constants/common';
  9. import { fetchMemberInfo } from '@/lib/api/account';
  10. const API_URL = process.env.API_URL;
  11. const agent = new https.Agent({
  12. rejectUnauthorized: false
  13. });
  14. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  15. /*
  16. export async function fetchJson<T = any>(url: string, options: AxiosRequestConfig = {}): Promise<ResultDto<T>> {
  17. try {
  18. const actionURL = `${API_URL}${url.startsWith("/") ? url : `/${url}`}`;
  19. const httpsAgent = (process.env.NODE_ENV === 'production' ? undefined : agent);
  20. const cookieHeader = (await cookies()).getAll().map(c => `${c.name}=${c.value}`).join("; ");
  21. const res = await axios({
  22. url: actionURL,
  23. ...options,
  24. httpsAgent: httpsAgent,
  25. // timeout: 10000,
  26. headers: {
  27. ...(options.headers || {}),
  28. Cookie: cookieHeader,
  29. }
  30. });
  31. const setCookieHeader = res.headers['set-cookie'];
  32. if (setCookieHeader) {
  33. for (const cookieString of setCookieHeader) {
  34. const [cookieName, cookieValue] = cookieString.split("=");
  35. (await cookies()).set(cookieName, cookieValue.split(";")[0]);
  36. }
  37. }
  38. return res.data as ResultDto<T>;
  39. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  40. } catch (err: any) {
  41. let message = '알 수 없는 오류가 발생했습니다.';
  42. let status = 500;
  43. if (axios.isAxiosError(err)) {
  44. message = err.response?.data?.message || err.message;
  45. status = err.response?.status || 500;
  46. } else if (err instanceof Error) {
  47. message = err.message;
  48. }
  49. console.warn(`[${status}] ${message}`);
  50. return {
  51. ok: false,
  52. status: status,
  53. message: message,
  54. data: null,
  55. errors: null
  56. } satisfies ResultDto<T>;
  57. }
  58. }
  59. */
  60. export async function fetchJson<T>(url: string, options: RequestInit = {}): Promise<ResultDto<T>> {
  61. try {
  62. const actionURL = `${API_URL}${url.startsWith('/') ? url : `/${url}`}`;
  63. const cookie = (await cookies()).getAll().map(c => `${c.name}=${c.value}`).join("; ");
  64. const res = await fetch(actionURL, {
  65. ...options,
  66. headers: {
  67. ...(options.headers || {}),
  68. Cookie: cookie,
  69. 'Accept': 'application/json'
  70. },
  71. cache: 'no-store'
  72. });
  73. if (res.status === 204) {
  74. throw new Error('No Content');
  75. }
  76. return await res.json() as ResultDto<T>;
  77. } catch (err) {
  78. let message = '서버와 통신이 불가합니다.';
  79. const status = 500;
  80. if (err instanceof Error) {
  81. message = err.message;
  82. }
  83. console.warn(`[${status}] ${message}`);
  84. return {
  85. ok: false,
  86. status: status,
  87. message: message,
  88. data: null,
  89. errors: null
  90. } satisfies ResultDto<T>;
  91. }
  92. }
  93. export async function getAccessToken(): Promise<string|null> {
  94. return (await cookies()).get('accessToken')?.value ?? null;
  95. }
  96. export async function getRefreshToken(): Promise<string|null> {
  97. return (await cookies()).get('refreshToken')?.value ?? null;
  98. }
  99. // 로그인 전력 확인
  100. export async function isAuthenticated(): Promise<boolean> {
  101. return Boolean(getAccessToken());
  102. }
  103. // API 서버 URL 조회
  104. export async function getAPIUrl(): Promise<string> {
  105. return process.env.API_URL as string;
  106. }
  107. // SignalR 서버 URL 조회
  108. export async function getSignalRUrl(): Promise<string> {
  109. return process.env.SIGNALR_URL as string;
  110. }
  111. // JWT 토큰에서 사용자 정보 추출
  112. export async function getTokenData(): Promise<TokenData|null> {
  113. try {
  114. const token = await getAccessToken();
  115. if (!token) {
  116. throw new Error('Access token not found');
  117. }
  118. const base64URL = token.split('.')[1]; // JWT의 Payload 부분
  119. const base64 = base64URL.replace(/-/g, '+').replace(/_/g, '/'); // Base64 형식 변환
  120. const jsonPayload = decodeURIComponent(
  121. atob(base64).split('').map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join('')
  122. );
  123. const payload = JSON.parse(jsonPayload);
  124. return {
  125. id: payload[CLAIM_NAME_IDENTIFIER] || null,
  126. email: payload[CLAIM_EMAIL] || null,
  127. name: payload[CLAIM_NAME] || null
  128. };
  129. } catch {
  130. return null;
  131. }
  132. }
  133. // 첫번째 오류 조회
  134. export async function getFirstError(errors: Record<string, string[]> | null): Promise<string|null> {
  135. return (errors ? Object.values(errors).flat()[0] ?? null : null);
  136. }
  137. // 서버 응답 메시지 분석
  138. export async function throwError<T>(res: ResultDto<T>): Promise<void> {
  139. if (res.ok) {
  140. return;
  141. }
  142. let message:string|null = await getFirstError(res.errors);
  143. if (!message && res.message) {
  144. message = res.message;
  145. }
  146. switch (res.status) {
  147. case 400:
  148. throw Error(message || '잘못된 요청입니다.');
  149. case 401:
  150. throw Error('로그인 후 이용해 주세요.');
  151. case 403:
  152. throw Error('권한이 없습니다.');
  153. case 404:
  154. throw Error('요청하신 페이지를 찾을 수 없습니다.');
  155. }
  156. if (message) {
  157. throw Error(message);
  158. }
  159. }
  160. // 게시판, 게시글, 댓글 등 권한 확인
  161. export async function checkPermission(permission: number, boardManager: BoardManager[]): Promise<boolean> {
  162. if (permission > -1) {
  163. const member = await fetchMemberInfo();
  164. if (member.ok && member.data) {
  165. if (
  166. // 게시판 접근 권한이 회원 등급보다 높거나
  167. permission < member.data.memberGrade.order ||
  168. // 게시판 관리자에 포함되어 있으면 권한이 있음
  169. boardManager.some(manager => member.data!.id && manager.user.email == member.data!.email)
  170. ) {
  171. return true;
  172. }
  173. }
  174. return false;
  175. }
  176. return true;
  177. }