From 0042f0b8c9ebe450abf336ac0e6ad649cfc38eae Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 11 Mar 2025 17:34:11 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EC=82=AC=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20api=20=EC=A1=B0=ED=9A=8C=20=EC=9E=91?= =?UTF-8?q?=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/entities/site/api/site-service.ts | 20 +++++++ apps/cs/src/entities/site/index.ts | 1 + .../src/entities/site/model/site-interface.ts | 49 +++++++++++++++++ apps/cs/src/entities/user/api/user-service.ts | 8 +-- apps/cs/src/entities/user/lib/use-user.ts | 3 +- .../src/entities/user/model/user-interface.ts | 6 +-- apps/cs/src/pages/auth/login/api/login.ts | 9 +--- .../src/pages/auth/login/lib/token-manager.ts | 2 +- apps/cs/src/pages/site/model/__mocks__.tsx | 54 +++++++------------ apps/cs/src/pages/site/ui/site-page.tsx | 49 ++++++++++++++--- apps/cs/src/shared/api/client.ts | 3 +- apps/cs/src/shared/api/index.ts | 1 - apps/cs/src/shared/config/api.ts | 3 ++ apps/cs/src/shared/config/index.ts | 2 - 14 files changed, 149 insertions(+), 61 deletions(-) create mode 100644 apps/cs/src/entities/site/api/site-service.ts create mode 100644 apps/cs/src/entities/site/index.ts create mode 100644 apps/cs/src/entities/site/model/site-interface.ts delete mode 100644 apps/cs/src/shared/api/index.ts delete mode 100644 apps/cs/src/shared/config/index.ts diff --git a/apps/cs/src/entities/site/api/site-service.ts b/apps/cs/src/entities/site/api/site-service.ts new file mode 100644 index 000000000..89ba82350 --- /dev/null +++ b/apps/cs/src/entities/site/api/site-service.ts @@ -0,0 +1,20 @@ +import { client } from "@/shared/api/client"; +import { ENDPOINT } from "@/shared/config/api"; +import type { + ISiteQuery, + ISiteInfo, + SiteService, +} from "../model/site-interface"; + +export class SiteApiService implements SiteService { + public async siteInfo(query: ISiteQuery): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.SITES}`, + { params: query }, + ); + + return response.data; + } +} + +export const SiteApi = new SiteApiService(); diff --git a/apps/cs/src/entities/site/index.ts b/apps/cs/src/entities/site/index.ts new file mode 100644 index 000000000..92ccd0ff7 --- /dev/null +++ b/apps/cs/src/entities/site/index.ts @@ -0,0 +1 @@ +export { SiteApi } from "./api/site-service"; diff --git a/apps/cs/src/entities/site/model/site-interface.ts b/apps/cs/src/entities/site/model/site-interface.ts new file mode 100644 index 000000000..8ba2da298 --- /dev/null +++ b/apps/cs/src/entities/site/model/site-interface.ts @@ -0,0 +1,49 @@ +export interface ISiteQuery { + siteNm: string; + siteKndCd: string; + useYn: string; + page: number; + size: number; + sort: string[]; +} + +export interface ISite { + siteId: string; + siteNm: string; + siteKindCode: string; + bscSiteYn: string; + useYn: string; +} + +export interface ISiteInfo { + totalPages: number; + totalElements: number; + number: number; + sort: { + empty: boolean; + sorted: boolean; + unsorted: boolean; + }; + size: number; + content: ISite[]; + pageable: { + sort: { + empty: boolean; + sorted: boolean; + unsorted: boolean; + }; + offset: number; + pageNumber: number; + pageSize: number; + paged: boolean; + unpaged: boolean; + }; + numberOfElements: number; + first: boolean; + last: boolean; + empty: boolean; +} + +export interface SiteService { + siteInfo(query: ISiteQuery): Promise; +} diff --git a/apps/cs/src/entities/user/api/user-service.ts b/apps/cs/src/entities/user/api/user-service.ts index 5d0f57eed..41319bc1f 100644 --- a/apps/cs/src/entities/user/api/user-service.ts +++ b/apps/cs/src/entities/user/api/user-service.ts @@ -1,10 +1,10 @@ -import { client } from "@/shared/api"; -import { ENDPOINT } from "@/shared/config"; +import { client } from "@/shared/api/client"; +import { ENDPOINT } from "@/shared/config/api"; import type { IUser, IUserInfo, UserService } from "../model/user-interface"; export class UsersApiService implements UserService { - public async userInfo(payload: IUserInfo): Promise { - const response = await client.get( + public async userInfo(payload: IUser): Promise { + const response = await client.get( `${ENDPOINT.USER_SERVICE.USERS}/${payload.userUniqId}`, ); diff --git a/apps/cs/src/entities/user/lib/use-user.ts b/apps/cs/src/entities/user/lib/use-user.ts index a48c441c4..98c37594a 100644 --- a/apps/cs/src/entities/user/lib/use-user.ts +++ b/apps/cs/src/entities/user/lib/use-user.ts @@ -1,7 +1,8 @@ import { useQuery } from "@tanstack/react-query"; import { UserApi } from "../api/user-service"; import { useCookie } from "@/shared/providers"; -import { AUTH_USER_ID, ENDPOINT } from "@/shared/config"; +import { ENDPOINT } from "@/shared/config/api"; +import { AUTH_USER_ID } from "@/shared/config/env"; const useUser = () => { const { getToken } = useCookie(); diff --git a/apps/cs/src/entities/user/model/user-interface.ts b/apps/cs/src/entities/user/model/user-interface.ts index 1af75fbb7..cba4a6206 100644 --- a/apps/cs/src/entities/user/model/user-interface.ts +++ b/apps/cs/src/entities/user/model/user-interface.ts @@ -1,8 +1,8 @@ -export interface IUserInfo { +export interface IUser { userUniqId: string; } -export interface IUser { +export interface IUserInfo { regDt: string; mdfcnDt: string; rgtr: string; @@ -37,5 +37,5 @@ export interface IUser { } export interface UserService { - userInfo: (payload: IUserInfo) => Promise; + userInfo: (payload: IUser) => Promise; } diff --git a/apps/cs/src/pages/auth/login/api/login.ts b/apps/cs/src/pages/auth/login/api/login.ts index fc1d94ca1..7e61dc2b5 100644 --- a/apps/cs/src/pages/auth/login/api/login.ts +++ b/apps/cs/src/pages/auth/login/api/login.ts @@ -1,11 +1,6 @@ import { AxiosError } from "axios"; -import { - ACCESS_TOKEN, - AUTH_USER_ID, - CLAIM_NAME, - ENDPOINT, - JWT_EXPIRATION_TIME, -} from "@/shared/config"; +import { ENDPOINT, JWT_EXPIRATION_TIME } from "@/shared/config/api"; +import { ACCESS_TOKEN, AUTH_USER_ID, CLAIM_NAME } from "@/shared/config/env"; import { tokenManager } from "../lib/token-manager"; import type { LoginService, ILogin } from "../model/login"; diff --git a/apps/cs/src/pages/auth/login/lib/token-manager.ts b/apps/cs/src/pages/auth/login/lib/token-manager.ts index c704bbb5c..4aeaf9d95 100644 --- a/apps/cs/src/pages/auth/login/lib/token-manager.ts +++ b/apps/cs/src/pages/auth/login/lib/token-manager.ts @@ -1,4 +1,4 @@ -import { AUTH_USER_ID, CLAIM_NAME } from "@/shared/config"; +import { AUTH_USER_ID, CLAIM_NAME } from "@/shared/config/env"; import { setToken, removeToken } from "@/shared/lib/cookie"; export class TokenManager { diff --git a/apps/cs/src/pages/site/model/__mocks__.tsx b/apps/cs/src/pages/site/model/__mocks__.tsx index f1e13e9f8..2b55da7d8 100644 --- a/apps/cs/src/pages/site/model/__mocks__.tsx +++ b/apps/cs/src/pages/site/model/__mocks__.tsx @@ -1,59 +1,43 @@ import { createColumnHelper } from "@tanstack/react-table"; -import { fakerKO as faker } from "@faker-js/faker"; +import type { ISite } from "@/entities/site/model/site-interface"; -type Site = { - id: number; - 사이트코드: string; - 사이트명: string; - 대표_URL: string; - 기본_사이트_여부: boolean; - 사용_여부: boolean; - 등록자: string; - 등록일: string; -}; - -faker.seed(123); - -const data: Site[] = Array.from({ length: 100 }, (_, i) => ({ - id: i + 1, - 사이트코드: faker.string.alpha(2).toUpperCase(), - 사이트명: faker.lorem.word(), - 대표_URL: faker.internet.url(), - 기본_사이트_여부: faker.datatype.boolean(), - 사용_여부: faker.datatype.boolean(), - 등록자: faker.person.fullName(), - 등록일: faker.date.recent().toISOString().split("T")[0] as string, -})); - -const columnHelper = createColumnHelper(); +const columnHelper = createColumnHelper(); const columns = [ - columnHelper.accessor("id", { + columnHelper.accessor((_, index) => index + 1, { + id: "no", header: "NO", }), - columnHelper.accessor("사이트코드", { + columnHelper.accessor("siteId", { + id: "siteId", header: "사이트코드", }), - columnHelper.accessor("사이트명", { + columnHelper.accessor("siteNm", { + id: "siteNm", header: "사이트명", }), - columnHelper.accessor("대표_URL", { + columnHelper.accessor("siteKindCode", { + id: "unknown1", header: "대표 URL", }), - columnHelper.accessor("기본_사이트_여부", { + columnHelper.accessor("bscSiteYn", { + id: "bscSiteYn", header: "기본 사이트 여부", cell: (info) => (info.getValue() ? "Y" : "N"), }), - columnHelper.accessor("사용_여부", { + columnHelper.accessor("useYn", { + id: "useYn", header: "사용 여부", cell: (info) => (info.getValue() ? "Y" : "N"), }), - columnHelper.accessor("등록자", { + columnHelper.accessor("siteId", { + id: "unknown2", header: "등록자", }), - columnHelper.accessor("등록일", { + columnHelper.accessor("siteId", { + id: "ununknown3", header: "등록일", }), ]; -export { data, columns }; +export { columns }; diff --git a/apps/cs/src/pages/site/ui/site-page.tsx b/apps/cs/src/pages/site/ui/site-page.tsx index 171a7cf07..c13284ce8 100644 --- a/apps/cs/src/pages/site/ui/site-page.tsx +++ b/apps/cs/src/pages/site/ui/site-page.tsx @@ -3,30 +3,67 @@ import "@common/assets/styles/grid.css"; import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { Button, Input, DataTable } from "@common/components"; -import { data, columns } from "../model/__mocks__"; +import { SiteApi } from "@/entities/site/api/site-service"; +import { ENDPOINT } from "@/shared/config/api"; +import { columns } from "../model/__mocks__"; import { SitePageLayout } from "./layout"; +import type { ISiteQuery } from "@/entities/site/model/site-interface"; function SitePage() { + const [siteQuery, setSiteQuery] = useState({ + siteNm: "", + siteKndCd: "", + useYn: "", + page: 0, + size: 0, + sort: [], + }); + + const { data: siteInfo, refetch } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.SITES], + queryFn: () => SiteApi.siteInfo(siteQuery), + enabled: false, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + refetch(); + }; + const router = useRouter(); const table = useReactTable({ - data, + data: siteInfo?.content ?? [], columns, getCoreRowModel: getCoreRowModel(), }); + useEffect(() => { + refetch(); + }, []); + return (
-
+
- - -
+ + setSiteQuery({ ...siteQuery, siteNm: e.target.value }) + } + /> + +
diff --git a/apps/cs/src/shared/api/client.ts b/apps/cs/src/shared/api/client.ts index 231887202..709bb44a5 100644 --- a/apps/cs/src/shared/api/client.ts +++ b/apps/cs/src/shared/api/client.ts @@ -1,5 +1,6 @@ import { Client } from "@common/network"; -import { BASE_URL, SITE_ID } from "../config"; +import { BASE_URL } from "../config/api"; +import { SITE_ID } from "../config/env"; export const client = Client.getInstance({ baseURL: BASE_URL, diff --git a/apps/cs/src/shared/api/index.ts b/apps/cs/src/shared/api/index.ts deleted file mode 100644 index b7b9d6121..000000000 --- a/apps/cs/src/shared/api/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { client } from "./client"; diff --git a/apps/cs/src/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index b02f439be..052eea56a 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -8,6 +8,9 @@ export const ENDPOINT = { USERS: "/user-service/api/v1/users", SILENT_REFRESH: "/user-service/api/v1/users/token/refresh", }, + CMS_SERVICE: { + SITES: "/cms-service/api/v1/sites", + }, }; export const JWT_EXPIRATION_TIME = 1800000; diff --git a/apps/cs/src/shared/config/index.ts b/apps/cs/src/shared/config/index.ts deleted file mode 100644 index cd83447d5..000000000 --- a/apps/cs/src/shared/config/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./api"; -export * from "./env"; -- 2.47.1.windows.1 From 6e41c09157cf9597b1b1f779e1058bffa4f5bb0a Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 12 Mar 2025 10:40:23 +0900 Subject: [PATCH 2/3] =?UTF-8?q?rename:=20=ED=8F=B4=EB=8D=94=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=EB=B0=8F=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/app/api/proxy/[...url]/route.ts | 2 +- apps/cs/src/app/index.tsx | 2 +- .../auth/login/api/login-service.ts} | 2 +- apps/cs/src/entities/auth/login/index.ts | 1 + .../auth/login/lib/token-manager.ts | 16 +++++++++---- .../auth/login/model/login-interface.ts} | 1 + apps/cs/src/entities/user/lib/use-user.ts | 8 +++---- .../cs/src/pages/auth/login/ui/login-page.tsx | 2 +- apps/cs/src/shared/lib/cookie.ts | 23 ++----------------- .../cookie/cookie-provider.tsx | 20 +--------------- .../shared/{providers => provider}/index.ts | 2 +- .../query/query-provider.tsx} | 0 12 files changed, 26 insertions(+), 53 deletions(-) rename apps/cs/src/{pages/auth/login/api/login.ts => entities/auth/login/api/login-service.ts} (97%) create mode 100644 apps/cs/src/entities/auth/login/index.ts rename apps/cs/src/{pages => entities}/auth/login/lib/token-manager.ts (77%) rename apps/cs/src/{pages/auth/login/model/login.ts => entities/auth/login/model/login-interface.ts} (80%) rename apps/cs/src/shared/{providers => provider}/cookie/cookie-provider.tsx (72%) rename apps/cs/src/shared/{providers => provider}/index.ts (62%) rename apps/cs/src/shared/{providers/query/query-client.tsx => provider/query/query-provider.tsx} (100%) diff --git a/apps/cs/app/api/proxy/[...url]/route.ts b/apps/cs/app/api/proxy/[...url]/route.ts index 53f5a6e41..2f1a9140f 100644 --- a/apps/cs/app/api/proxy/[...url]/route.ts +++ b/apps/cs/app/api/proxy/[...url]/route.ts @@ -17,7 +17,7 @@ export async function POST( const headers: Record = { "Content-Type": "application/json", - "X-Site-Id": SITE_ID ?? "", + "X-Site-Id": SITE_ID, }; const cookieStore = await cookies(); diff --git a/apps/cs/src/app/index.tsx b/apps/cs/src/app/index.tsx index a78e1c124..4600d03c2 100644 --- a/apps/cs/src/app/index.tsx +++ b/apps/cs/src/app/index.tsx @@ -1,6 +1,6 @@ import "./styles/base.css"; import "./styles/components.css"; -import { CookieProvider, QueryProvider } from "@/shared/providers"; +import { CookieProvider, QueryProvider } from "@/shared/provider"; const App = ({ children }: Readonly<{ children: React.ReactNode }>) => { return ( diff --git a/apps/cs/src/pages/auth/login/api/login.ts b/apps/cs/src/entities/auth/login/api/login-service.ts similarity index 97% rename from apps/cs/src/pages/auth/login/api/login.ts rename to apps/cs/src/entities/auth/login/api/login-service.ts index 7e61dc2b5..e6db440cf 100644 --- a/apps/cs/src/pages/auth/login/api/login.ts +++ b/apps/cs/src/entities/auth/login/api/login-service.ts @@ -1,8 +1,8 @@ import { AxiosError } from "axios"; import { ENDPOINT, JWT_EXPIRATION_TIME } from "@/shared/config/api"; import { ACCESS_TOKEN, AUTH_USER_ID, CLAIM_NAME } from "@/shared/config/env"; +import type { LoginService, ILogin } from "../model/login-interface"; import { tokenManager } from "../lib/token-manager"; -import type { LoginService, ILogin } from "../model/login"; export class LoginApiService implements LoginService { private static instance: LoginApiService; diff --git a/apps/cs/src/entities/auth/login/index.ts b/apps/cs/src/entities/auth/login/index.ts new file mode 100644 index 000000000..8c7981ac9 --- /dev/null +++ b/apps/cs/src/entities/auth/login/index.ts @@ -0,0 +1 @@ +export { LoginApi } from "./api/login-service"; diff --git a/apps/cs/src/pages/auth/login/lib/token-manager.ts b/apps/cs/src/entities/auth/login/lib/token-manager.ts similarity index 77% rename from apps/cs/src/pages/auth/login/lib/token-manager.ts rename to apps/cs/src/entities/auth/login/lib/token-manager.ts index 4aeaf9d95..e72e00567 100644 --- a/apps/cs/src/pages/auth/login/lib/token-manager.ts +++ b/apps/cs/src/entities/auth/login/lib/token-manager.ts @@ -1,5 +1,5 @@ import { AUTH_USER_ID, CLAIM_NAME } from "@/shared/config/env"; -import { setToken, removeToken } from "@/shared/lib/cookie"; +import { getCookie, setCookie, removeCookie } from "@/shared/lib/cookie"; export class TokenManager { private static instance: TokenManager; @@ -14,13 +14,21 @@ export class TokenManager { return TokenManager.instance; } + public getToken(key: string): string | undefined { + return getCookie(key); + } + public setToken(key: string, value: string): void { - setToken(key, value, { + setCookie(key, value, { secure: window.location.protocol === "https:", sameSite: "lax", }); } + public removeToken(key: string): void { + removeCookie(key); + } + public setupRefreshTimer( callback: () => Promise, expirationTime: number, @@ -43,8 +51,8 @@ export class TokenManager { this.refreshTimer = null; } - removeToken(CLAIM_NAME); - removeToken(AUTH_USER_ID); + this.removeToken(CLAIM_NAME); + this.removeToken(AUTH_USER_ID); } } diff --git a/apps/cs/src/pages/auth/login/model/login.ts b/apps/cs/src/entities/auth/login/model/login-interface.ts similarity index 80% rename from apps/cs/src/pages/auth/login/model/login.ts rename to apps/cs/src/entities/auth/login/model/login-interface.ts index 2fe9acc49..5aaf53044 100644 --- a/apps/cs/src/pages/auth/login/model/login.ts +++ b/apps/cs/src/entities/auth/login/model/login-interface.ts @@ -5,4 +5,5 @@ export interface ILogin { export interface LoginService { login(payload: ILogin): Promise; + silentRefresh(): Promise; } diff --git a/apps/cs/src/entities/user/lib/use-user.ts b/apps/cs/src/entities/user/lib/use-user.ts index 98c37594a..97cd2f034 100644 --- a/apps/cs/src/entities/user/lib/use-user.ts +++ b/apps/cs/src/entities/user/lib/use-user.ts @@ -1,16 +1,16 @@ import { useQuery } from "@tanstack/react-query"; -import { UserApi } from "../api/user-service"; -import { useCookie } from "@/shared/providers"; import { ENDPOINT } from "@/shared/config/api"; import { AUTH_USER_ID } from "@/shared/config/env"; +import { useCookie } from "@/shared/provider"; +import { UserApi } from "../api/user-service"; const useUser = () => { - const { getToken } = useCookie(); + const { getCookie } = useCookie(); const { data } = useQuery({ queryKey: [ENDPOINT.USER_SERVICE.USERS], queryFn: () => UserApi.userInfo({ - userUniqId: getToken(AUTH_USER_ID) ?? "", + userUniqId: getCookie(AUTH_USER_ID) ?? "", }), }); diff --git a/apps/cs/src/pages/auth/login/ui/login-page.tsx b/apps/cs/src/pages/auth/login/ui/login-page.tsx index 41c8f57df..c8eb1e2f9 100644 --- a/apps/cs/src/pages/auth/login/ui/login-page.tsx +++ b/apps/cs/src/pages/auth/login/ui/login-page.tsx @@ -2,7 +2,7 @@ import Image from "next/image"; import { images } from "@common/assets"; -import { LoginApi } from "../api/login"; +import { LoginApi } from "@/entities/auth/login"; import "./login.css"; function LoginPage() { diff --git a/apps/cs/src/shared/lib/cookie.ts b/apps/cs/src/shared/lib/cookie.ts index 3e9c91d16..82db637bd 100644 --- a/apps/cs/src/shared/lib/cookie.ts +++ b/apps/cs/src/shared/lib/cookie.ts @@ -12,7 +12,7 @@ export function getCookie(name: string): string | undefined { export function setCookie( name: string, value: string, - options: Cookies.CookieAttributes, + options?: Cookies.CookieAttributes, ): void { if (typeof window === "undefined") { return; @@ -22,29 +22,10 @@ export function setCookie( export function removeCookie( name: string, - options: Cookies.CookieAttributes, + options?: Cookies.CookieAttributes, ): void { if (typeof window === "undefined") { return; } Cookies.remove(name, options); } - -export function getToken(key: string): string | undefined { - return getCookie(key); -} - -export function setToken( - key: string, - token: string, - options: Cookies.CookieAttributes, -): void { - setCookie(key, token, { - ...options, - path: "/", - }); -} - -export function removeToken(key: string): void { - removeCookie(key, { path: "/" }); -} diff --git a/apps/cs/src/shared/providers/cookie/cookie-provider.tsx b/apps/cs/src/shared/provider/cookie/cookie-provider.tsx similarity index 72% rename from apps/cs/src/shared/providers/cookie/cookie-provider.tsx rename to apps/cs/src/shared/provider/cookie/cookie-provider.tsx index f92ca8a6f..2e7bf4c2f 100644 --- a/apps/cs/src/shared/providers/cookie/cookie-provider.tsx +++ b/apps/cs/src/shared/provider/cookie/cookie-provider.tsx @@ -1,24 +1,9 @@ "use client"; -import { - getCookie, - setCookie, - removeCookie, - getToken, - setToken, - removeToken, -} from "../../lib/cookie"; - import { createContext, useContext } from "react"; +import { getCookie, setCookie, removeCookie } from "../../lib/cookie"; export interface CookieContextType { - getToken: (key: string) => string | undefined; - setToken: ( - key: string, - token: string, - options: Cookies.CookieAttributes, - ) => void; - removeToken: (key: string) => void; getCookie: (name: string) => string | undefined; setCookie: ( name: string, @@ -32,9 +17,6 @@ export const CookieContext = createContext(null); export function CookieProvider({ children }: { children: React.ReactNode }) { const cookieContextValue: CookieContextType = { - getToken, - setToken, - removeToken, getCookie, setCookie, removeCookie, diff --git a/apps/cs/src/shared/providers/index.ts b/apps/cs/src/shared/provider/index.ts similarity index 62% rename from apps/cs/src/shared/providers/index.ts rename to apps/cs/src/shared/provider/index.ts index d3e4a1ff3..7ef95c20c 100644 --- a/apps/cs/src/shared/providers/index.ts +++ b/apps/cs/src/shared/provider/index.ts @@ -3,4 +3,4 @@ export { CookieProvider, useCookie, } from "./cookie/cookie-provider"; -export { QueryProvider } from "./query/query-client"; +export { QueryProvider } from "./query/query-provider"; diff --git a/apps/cs/src/shared/providers/query/query-client.tsx b/apps/cs/src/shared/provider/query/query-provider.tsx similarity index 100% rename from apps/cs/src/shared/providers/query/query-client.tsx rename to apps/cs/src/shared/provider/query/query-provider.tsx -- 2.47.1.windows.1 From 108e2a87463cf5737acf9aa40a1158bb3c4cfae3 Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 12 Mar 2025 13:29:34 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20refresh=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/app/api/proxy/[...url]/route.ts | 37 +++++-- apps/cs/src/app/index.tsx | 6 +- .../entities/auth/login/api/login-service.ts | 7 +- .../entities/auth/login/lib/token-manager.ts | 4 +- apps/cs/src/shared/provider/auth-provider.tsx | 97 +++++++++++++++++++ .../provider/{cookie => }/cookie-provider.tsx | 2 +- apps/cs/src/shared/provider/index.ts | 9 +- .../provider/{query => }/query-provider.tsx | 0 packages/common/network/client.ts | 16 ++- 9 files changed, 154 insertions(+), 24 deletions(-) create mode 100644 apps/cs/src/shared/provider/auth-provider.tsx rename apps/cs/src/shared/provider/{cookie => }/cookie-provider.tsx (92%) rename apps/cs/src/shared/provider/{query => }/query-provider.tsx (100%) diff --git a/apps/cs/app/api/proxy/[...url]/route.ts b/apps/cs/app/api/proxy/[...url]/route.ts index 2f1a9140f..5397e5fe6 100644 --- a/apps/cs/app/api/proxy/[...url]/route.ts +++ b/apps/cs/app/api/proxy/[...url]/route.ts @@ -9,21 +9,21 @@ import { import { cookies } from "next/headers"; import { NextResponse } from "next/server"; -export async function POST( +async function handler( request: Request, - props: { params: Promise<{ url: string[] }> }, + params: { url: string[] }, + method: "GET" | "POST" | "PUT" | "DELETE", ) { - const params = await props.params; - + const cookieStore = await cookies(); const headers: Record = { "Content-Type": "application/json", "X-Site-Id": SITE_ID, }; - - const cookieStore = await cookies(); + let body = {}; + const isRefreshHandler = params.url.includes("refresh"); // silent refresh - if (params.url.includes("refresh")) { + if (isRefreshHandler) { const refreshToken = cookieStore.get(REFRESH_TOKEN); headers[CLAIM_NAME] = refreshToken?.value ?? ""; @@ -33,9 +33,12 @@ export async function POST( } try { - const body = await request.json(); + if (!isRefreshHandler) { + body = await request.json(); + } + const result = await fetch(`${SERVER_API_URL}/${params.url.join("/")}`, { - method: "POST", + method, headers, body: JSON.stringify(body), }); @@ -79,3 +82,19 @@ export async function POST( ); } } + +export async function POST( + request: Request, + props: { params: Promise<{ url: string[] }> }, +) { + const params = await props.params; + return handler(request, params, "POST"); +} + +export async function PUT( + request: Request, + props: { params: Promise<{ url: string[] }> }, +) { + const params = await props.params; + return handler(request, params, "PUT"); +} diff --git a/apps/cs/src/app/index.tsx b/apps/cs/src/app/index.tsx index 4600d03c2..672023d15 100644 --- a/apps/cs/src/app/index.tsx +++ b/apps/cs/src/app/index.tsx @@ -1,13 +1,15 @@ import "./styles/base.css"; import "./styles/components.css"; -import { CookieProvider, QueryProvider } from "@/shared/provider"; +import { AuthProvider, CookieProvider, QueryProvider } from "@/shared/provider"; const App = ({ children }: Readonly<{ children: React.ReactNode }>) => { return ( - {children} + + {children} + diff --git a/apps/cs/src/entities/auth/login/api/login-service.ts b/apps/cs/src/entities/auth/login/api/login-service.ts index e6db440cf..722470184 100644 --- a/apps/cs/src/entities/auth/login/api/login-service.ts +++ b/apps/cs/src/entities/auth/login/api/login-service.ts @@ -31,7 +31,7 @@ export class LoginApiService implements LoginService { const tokenId = data[AUTH_USER_ID]; if (!accessToken || !tokenId) { - throw new Error("Missing required authentication tokens"); + throw new Error("[login] Missing required authentication tokens"); } tokenManager.setToken(CLAIM_NAME, accessToken); @@ -62,13 +62,16 @@ export class LoginApiService implements LoginService { const tokenId = data[AUTH_USER_ID]; if (!accessToken || !tokenId) { - throw new Error("Missing required authentication tokens"); + throw new Error( + "[silentRefresh] Missing required authentication tokens", + ); } tokenManager.setToken(CLAIM_NAME, accessToken); tokenManager.setToken(AUTH_USER_ID, tokenId); } catch (error) { console.warn("Token has expired, logging out."); + tokenManager.clearAuthToken(); if (error instanceof AxiosError) { throw new Error( `Silent refresh failed: ${error.response?.data?.message || error.message}`, diff --git a/apps/cs/src/entities/auth/login/lib/token-manager.ts b/apps/cs/src/entities/auth/login/lib/token-manager.ts index e72e00567..4429c7d1b 100644 --- a/apps/cs/src/entities/auth/login/lib/token-manager.ts +++ b/apps/cs/src/entities/auth/login/lib/token-manager.ts @@ -40,12 +40,12 @@ export class TokenManager { this.refreshTimer = setTimeout(() => { callback().catch((error) => { console.error("Silent refresh failed:", error); - this.clearAuth(); + this.clearAuthToken(); }); }, expirationTime - 60000); } - public clearAuth(): void { + public clearAuthToken(): void { if (this.refreshTimer) { clearTimeout(this.refreshTimer); this.refreshTimer = null; diff --git a/apps/cs/src/shared/provider/auth-provider.tsx b/apps/cs/src/shared/provider/auth-provider.tsx new file mode 100644 index 000000000..7c827e948 --- /dev/null +++ b/apps/cs/src/shared/provider/auth-provider.tsx @@ -0,0 +1,97 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; +import { createContext, useContext, useState } from "react"; +import { client } from "../api/client"; +import { ENDPOINT } from "../config/api"; +import { AUTH_USER_ID } from "../config/env"; +import { getCookie } from "../lib/cookie"; +import { AxiosError } from "axios"; +import { LoginApi } from "@/entities/auth/login"; + +interface IUserInfo { + regDt: string; + mdfcnDt: string; + rgtr: string; + mdfr: string; + userUnqId: string; + userId: string; + userName: string; + email: string; + encryptedPassword: string; + refreshToken: string; + userStateCode: string; + lastLoginDate: string; + loginFailCount: number; + pswdHnt: string; + pswdAns: string; + mphn: string; + telno: string; + zip: string; + addr: string; + daddr: string; + laeSeCd: string; + ogdpSeCd: string; + aprvYn: string; + autzrId: string; + expryYmd: string; + lastPswdChgDt: string; + googleId: string; + kakaoId: string; + naverId: string; + isSocialUser: boolean; + hasPassword: boolean; +} + +export interface AuthContextType { + userInfo: IUserInfo | null; +} + +export const AuthContext = createContext(null); + +export const AuthProvider = ({ children }: { children: React.ReactNode }) => { + const [userInfo, setUserInfo] = useState(null); + + useQuery({ + queryKey: [ENDPOINT.USER_SERVICE.USERS], + queryFn: async () => { + try { + const response = await client.get( + `${ENDPOINT.USER_SERVICE.USERS}/${getCookie(AUTH_USER_ID)}`, + ); + setUserInfo(response.data); + return response; + } catch (error) { + console.error(error); + if (error instanceof AxiosError) { + if (error.response?.status === 401) { + try { + await LoginApi.silentRefresh(); + } catch (refreshError) { + console.error("Token refresh failed:", refreshError); + setUserInfo(null); + } + } + } + } + }, + }); + + const authContextValue: AuthContextType = { + userInfo, + }; + + return ( + + {children} + + ); +}; + +export const useAuth = (): AuthContextType => { + const context = useContext(AuthContext); + if (!context) { + throw new Error("useAuth must be used within an AuthProvider"); + } + return context; +}; diff --git a/apps/cs/src/shared/provider/cookie/cookie-provider.tsx b/apps/cs/src/shared/provider/cookie-provider.tsx similarity index 92% rename from apps/cs/src/shared/provider/cookie/cookie-provider.tsx rename to apps/cs/src/shared/provider/cookie-provider.tsx index 2e7bf4c2f..c50050cf5 100644 --- a/apps/cs/src/shared/provider/cookie/cookie-provider.tsx +++ b/apps/cs/src/shared/provider/cookie-provider.tsx @@ -1,7 +1,7 @@ "use client"; import { createContext, useContext } from "react"; -import { getCookie, setCookie, removeCookie } from "../../lib/cookie"; +import { getCookie, setCookie, removeCookie } from "../lib/cookie"; export interface CookieContextType { getCookie: (name: string) => string | undefined; diff --git a/apps/cs/src/shared/provider/index.ts b/apps/cs/src/shared/provider/index.ts index 7ef95c20c..8d018ff08 100644 --- a/apps/cs/src/shared/provider/index.ts +++ b/apps/cs/src/shared/provider/index.ts @@ -1,6 +1,3 @@ -export { - CookieContext, - CookieProvider, - useCookie, -} from "./cookie/cookie-provider"; -export { QueryProvider } from "./query/query-provider"; +export { AuthProvider, useAuth } from "./auth-provider"; +export { CookieProvider, useCookie } from "./cookie-provider"; +export { QueryProvider } from "./query-provider"; diff --git a/apps/cs/src/shared/provider/query/query-provider.tsx b/apps/cs/src/shared/provider/query-provider.tsx similarity index 100% rename from apps/cs/src/shared/provider/query/query-provider.tsx rename to apps/cs/src/shared/provider/query-provider.tsx diff --git a/packages/common/network/client.ts b/packages/common/network/client.ts index c61ce01e9..705c45d5d 100644 --- a/packages/common/network/client.ts +++ b/packages/common/network/client.ts @@ -1,4 +1,4 @@ -import axios from "axios"; +import axios, { AxiosError } from "axios"; import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; import Cookies from "js-cookie"; import { AUTH_USER_ID, CLAIM_NAME } from "./env"; @@ -27,7 +27,19 @@ class Client { const userId = Cookies.get(AUTH_USER_ID); if (!token || !userId) { - throw new Error("Missing required authentication tokens"); + throw new AxiosError( + "[Client] Missing required authentication tokens", + "UNAUTHORIZED", + config, + null, + { + data: { message: "Missing required authentication tokens" }, + status: 401, + statusText: "Unauthorized", + headers: this.axiosInstance.defaults.headers, + config: config, + } as AxiosResponse + ); } config.headers[CLAIM_NAME] = token; -- 2.47.1.windows.1