diff --git a/apps/_template/tsconfig.json b/apps/_template/tsconfig.json index dab9b0b8e..e6c49fbe4 100644 --- a/apps/_template/tsconfig.json +++ b/apps/_template/tsconfig.json @@ -19,11 +19,15 @@ } ], "paths": { - "@/*": ["./src/*"], - "@common/components": ["../../packages/common/components/dist/"], - "@common/network": ["../../packages/common/network/"] + "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "../../packages/common/types/**/*.d.ts" + ], + "exclude": ["node_modules", ".next", "dist", "coverage", "storybook-static", "public"] } diff --git a/apps/as/package.json b/apps/as/package.json index cebdf28ee..ed4016352 100644 --- a/apps/as/package.json +++ b/apps/as/package.json @@ -32,7 +32,6 @@ "next": "15.5.18", "react-hook-form": "^7.54.2", "tus-js-client": "^4.3.1", - "uuid": "^14.0.1", "zod": "^3.24.2" }, "devDependencies": { diff --git a/apps/as/src/entities/auth/api/auth-service.ts b/apps/as/src/entities/auth/api/auth-service.ts index b7158bf80..527d72e7a 100644 --- a/apps/as/src/entities/auth/api/auth-service.ts +++ b/apps/as/src/entities/auth/api/auth-service.ts @@ -1,7 +1,11 @@ import { ACCESS_TOKEN, AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "@/shared/config"; -import { type AuthPayload, type AuthService } from "@common/auth"; +import { + type AuthPayload, + type AuthService, + clearDeviceId, + getOrCreateDeviceId, +} from "@common/auth"; -import { clearDeviceId, getOrCreateDeviceId } from "../lib/device-id"; import { clearRefreshTimer, setToken, tokenManager } from "../lib/token-manager"; async function login(payload: AuthPayload): Promise { diff --git a/apps/as/src/entities/auth/lib/device-id.ts b/apps/as/src/entities/auth/lib/device-id.ts deleted file mode 100644 index ce173b141..000000000 --- a/apps/as/src/entities/auth/lib/device-id.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { v4 as uuidv4 } from "uuid"; - -const DEVICE_ID_KEY = "x-device-id"; - -export function getOrCreateDeviceId(): string { - if (typeof window === "undefined") return ""; - const existing = localStorage.getItem(DEVICE_ID_KEY); - if (existing) return existing; - const id = uuidv4(); - localStorage.setItem(DEVICE_ID_KEY, id); - return id; -} - -export function clearDeviceId(): void { - if (typeof window === "undefined") return; - localStorage.removeItem(DEVICE_ID_KEY); -} diff --git a/apps/as/tsconfig.json b/apps/as/tsconfig.json index c1334095f..e6c49fbe4 100644 --- a/apps/as/tsconfig.json +++ b/apps/as/tsconfig.json @@ -22,6 +22,12 @@ "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "../../packages/common/types/**/*.d.ts" + ], + "exclude": ["node_modules", ".next", "dist", "coverage", "storybook-static", "public"] } diff --git a/apps/cm/package.json b/apps/cm/package.json index 1f9f98815..0440c504c 100644 --- a/apps/cm/package.json +++ b/apps/cm/package.json @@ -47,7 +47,6 @@ "sonner": "^2.0.1", "tailwindcss-animate": "^1.0.7", "tus-js-client": "^4.3.1", - "uuid": "^14.0.1", "zod": "^3.24.2", "zustand": "^5.0.3" }, diff --git a/apps/cm/src/entities/auth/api/auth-service.ts b/apps/cm/src/entities/auth/api/auth-service.ts index 073f95fe2..79d99c1e8 100644 --- a/apps/cm/src/entities/auth/api/auth-service.ts +++ b/apps/cm/src/entities/auth/api/auth-service.ts @@ -5,18 +5,17 @@ import { ENDPOINT, } from "@/shared/config"; import { + clearDeviceId, + getOrCreateDeviceId, type AuthPayload, type AuthService, - getRemainingMs, } from "@common/auth"; import { clearRefreshTimer, setToken, - setupRefreshTimer, tokenManager, } from "../lib/token-manager"; -import { clearDeviceId, getOrCreateDeviceId } from "../lib/device-id"; function completeSession(accessToken: string, tokenId: string): void { setToken(CLAIM_NAME, accessToken); diff --git a/apps/cm/src/entities/auth/lib/device-id.ts b/apps/cm/src/entities/auth/lib/device-id.ts deleted file mode 100644 index 3683c7189..000000000 --- a/apps/cm/src/entities/auth/lib/device-id.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { v4 as uuidv4 } from "uuid"; - -const DEVICE_ID_KEY = "x-device-id"; - -export function getOrCreateDeviceId(): string { - if (typeof window === "undefined") return ""; - - const existing = localStorage.getItem(DEVICE_ID_KEY); - if (existing) return existing; - - const id = uuidv4(); - localStorage.setItem(DEVICE_ID_KEY, id); - return id; -} - -export function clearDeviceId(): void { - if (typeof window === "undefined") return; - - localStorage.removeItem(DEVICE_ID_KEY); -} diff --git a/apps/cm/tsconfig.json b/apps/cm/tsconfig.json index 05cbfce70..e475267d6 100644 --- a/apps/cm/tsconfig.json +++ b/apps/cm/tsconfig.json @@ -27,17 +27,18 @@ } }, "include": [ - ".eslintrc.*", "next-env.d.ts", "**/*.ts", "**/*.tsx", - "**/*.mts", - "**/*.js", - "**/*.cjs", - "**/*.mjs", - "**/*.jsx", - "**/*.json", - ".next/types/**/*.ts" + ".next/types/**/*.ts", + "../../packages/common/types/**/*.d.ts" ], - "exclude": ["**/node_modules", "**/.*/*"] + "exclude": [ + "node_modules", + ".next", + "dist", + "coverage", + "storybook-static", + "public" + ] } diff --git a/apps/common/features/tsconfig.json b/apps/common/features/tsconfig.json index f12491cae..c5460bdc2 100644 --- a/apps/common/features/tsconfig.json +++ b/apps/common/features/tsconfig.json @@ -22,26 +22,17 @@ "name": "next" } ], - "baseUrl": "./src", "paths": { - "@/*": ["./*"], - "@common/assets": ["../../../../packages/common/assets/dist/"], - "@common/components": ["../../../../packages/common/components/dist/"], - "@common/network": ["../../../../packages/common/network/"] + "@/*": ["./src/*"] } }, - "include": [ - ".eslintrc.*", - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - "**/*.mts", - "**/*.js", - "**/*.cjs", - "**/*.mjs", - "**/*.jsx", - "**/*.json", - ".next/types/**/*.ts" - ], - "exclude": ["**/node_modules", "**/.*/*"] + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": [ + "node_modules", + ".next", + "dist", + "coverage", + "storybook-static", + "public" + ] } diff --git a/apps/cs/package.json b/apps/cs/package.json index 710032627..d362d1178 100644 --- a/apps/cs/package.json +++ b/apps/cs/package.json @@ -45,7 +45,6 @@ "sonner": "^2.0.1", "tailwindcss-animate": "^1.0.7", "tus-js-client": "^4.3.1", - "uuid": "^14.0.1", "zod": "^3.24.2", "zustand": "^5.0.3" }, diff --git a/apps/cs/src/entities/auth/api/auth-service.ts b/apps/cs/src/entities/auth/api/auth-service.ts index 93c0bc0bb..ce148e37d 100644 --- a/apps/cs/src/entities/auth/api/auth-service.ts +++ b/apps/cs/src/entities/auth/api/auth-service.ts @@ -4,9 +4,13 @@ import { CLAIM_NAME, ENDPOINT, } from "@/shared/config"; -import { type AuthPayload, type AuthService } from "@common/auth"; +import { + type AuthPayload, + type AuthService, + clearDeviceId, + getOrCreateDeviceId, +} from "@common/auth"; -import { clearDeviceId, getOrCreateDeviceId } from "../lib/device-id"; import { clearRefreshTimer, setToken, diff --git a/apps/cs/src/entities/auth/lib/device-id.ts b/apps/cs/src/entities/auth/lib/device-id.ts deleted file mode 100644 index 3683c7189..000000000 --- a/apps/cs/src/entities/auth/lib/device-id.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { v4 as uuidv4 } from "uuid"; - -const DEVICE_ID_KEY = "x-device-id"; - -export function getOrCreateDeviceId(): string { - if (typeof window === "undefined") return ""; - - const existing = localStorage.getItem(DEVICE_ID_KEY); - if (existing) return existing; - - const id = uuidv4(); - localStorage.setItem(DEVICE_ID_KEY, id); - return id; -} - -export function clearDeviceId(): void { - if (typeof window === "undefined") return; - - localStorage.removeItem(DEVICE_ID_KEY); -} diff --git a/apps/cs/tsconfig.json b/apps/cs/tsconfig.json index 05cbfce70..e475267d6 100644 --- a/apps/cs/tsconfig.json +++ b/apps/cs/tsconfig.json @@ -27,17 +27,18 @@ } }, "include": [ - ".eslintrc.*", "next-env.d.ts", "**/*.ts", "**/*.tsx", - "**/*.mts", - "**/*.js", - "**/*.cjs", - "**/*.mjs", - "**/*.jsx", - "**/*.json", - ".next/types/**/*.ts" + ".next/types/**/*.ts", + "../../packages/common/types/**/*.d.ts" ], - "exclude": ["**/node_modules", "**/.*/*"] + "exclude": [ + "node_modules", + ".next", + "dist", + "coverage", + "storybook-static", + "public" + ] } diff --git a/apps/fo/.env.local b/apps/fo/.env.local index e6349c58f..1d6e4893f 100644 --- a/apps/fo/.env.local +++ b/apps/fo/.env.local @@ -19,7 +19,6 @@ NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y} # NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login NEXT_PUBLIC_CM_LOGIN_PAGE_URL = / - -#NEXT_PUBLIC_SERVER_API_URL = http://localhost:8883/operation-service +NEXT_PUBLIC_COOKIE_COMAIN=.mind-one.co.kr REACT_EDITOR=atom \ No newline at end of file diff --git a/apps/fo/.env.mindone b/apps/fo/.env.mindone index 1e7525b27..740770113 100644 --- a/apps/fo/.env.mindone +++ b/apps/fo/.env.mindone @@ -17,4 +17,5 @@ DC_URL = https://apigateway.apps.rsonesoft.com/dc-service SEWERAGE_API_URL = http://con.mind-one.co.kr:11083/sewerage-service NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y} -NEXT_PUBLIC_CM_LOGIN_PAGE_URL = / \ No newline at end of file +NEXT_PUBLIC_CM_LOGIN_PAGE_URL = / +NEXT_PUBLIC_COOKIE_COMAIN=.mind-one.co.kr \ No newline at end of file diff --git a/apps/fo/app/pages/(map-layout)/layout.tsx b/apps/fo/app/pages/(map-layout)/layout.tsx index 9c45eabc8..11ca87e00 100644 --- a/apps/fo/app/pages/(map-layout)/layout.tsx +++ b/apps/fo/app/pages/(map-layout)/layout.tsx @@ -1,9 +1,10 @@ 'use client' import { NoFooterLayout } from "@common/components"; +import { useQueryClient } from "@tanstack/react-query"; import type { MenuItem } from "../../store/common"; import { useFoStore } from "../../store"; import { usePathname, useRouter } from "next/navigation"; -// import { authService } from "@/src/entities/auth"; +import { authService } from "@/src/entities/auth"; export default function RootLayout({ children, @@ -14,12 +15,20 @@ export default function RootLayout({ const { getMenuAuth } = useFoStore((state) => state); const pathname = usePathname(); const router = useRouter(); + const queryClient = useQueryClient(); const handleClick = (id: MenuItem) => { getMenuAuth(id.menuCd); router.push(id.urlPath); }; return ( - {children} + + {children} + ); } diff --git a/apps/fo/package.json b/apps/fo/package.json index 84b192049..8116359df 100644 --- a/apps/fo/package.json +++ b/apps/fo/package.json @@ -35,8 +35,7 @@ "react-hook-form": "^7.54.2", "zod": "^3.24.2", "axios": "^1.8.1", - "proj4": "^2.9.0", - "uuid": "^14.0.1" + "proj4": "^2.9.0" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/apps/fo/src/entities/auth/api/auth-service.ts b/apps/fo/src/entities/auth/api/auth-service.ts index 2ea6bab12..3985fca8c 100644 --- a/apps/fo/src/entities/auth/api/auth-service.ts +++ b/apps/fo/src/entities/auth/api/auth-service.ts @@ -1,47 +1,82 @@ import { - ACCESS_TOKEN, - AUTH_USER_ID, - CLAIM_NAME, - ENDPOINT, + ACCESS_TOKEN, + AUTH_USER_ID, + CLAIM_NAME, + ENDPOINT, } from "@/src/shared/config"; import { getCookie } from "@/src/shared/lib"; import { - type AuthPayload, - type AuthService, - getRemainingMs, + clearDeviceId, + getOrCreateDeviceId, + type AuthPayload, + type AuthService, + getRemainingMs, } from "@common/auth"; -import { clearDeviceId, getOrCreateDeviceId } from "../lib/device-id"; import { - clearRefreshTimer, - setToken, - setupRefreshTimer, - tokenManager, + clearRefreshTimer, + setToken, + setupRefreshTimer, + tokenManager, } from "../lib/token-manager"; - import { clearTabStorePersist } from "@/src/store/tab-store"; async function login(payload: AuthPayload): Promise { - const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, { - method: "POST", + const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Device-Id": getOrCreateDeviceId(), + }, + body: JSON.stringify(payload), + }); + + const data = await response.json(); + + const accessToken = data[ACCESS_TOKEN]; + const tokenId = data[AUTH_USER_ID]; + + if (!accessToken || !tokenId) { + throw new Error("인증 토큰을 받지 못했습니다."); + } + + const prevTokenId = getCookie(AUTH_USER_ID); + if (prevTokenId !== undefined && prevTokenId !== tokenId) { + clearTabStorePersist(); + } + + setToken(CLAIM_NAME, accessToken); + setToken(AUTH_USER_ID, tokenId); + + const remainingMs = getRemainingMs(accessToken); + if (remainingMs > 0) { + setupRefreshTimer(silentRefresh, remainingMs); + } +} + +async function silentRefresh(): Promise { + console.log(`[silentRefresh] Called at ${new Date().toISOString()}`); + try { + const response = await fetch( + `/api/proxy${ENDPOINT.USER_SERVICE.SILENT_REFRESH}`, + { + method: "PUT", headers: { - "Content-Type": "application/json", - "X-Device-Id": getOrCreateDeviceId(), + "X-Device-Id": getOrCreateDeviceId(), }, - body: JSON.stringify(payload), - }); + }, + ); const data = await response.json(); + if (!response.ok) { + throw new Error(data?.message ?? "토큰 갱신에 실패했습니다."); + } + const accessToken = data[ACCESS_TOKEN]; const tokenId = data[AUTH_USER_ID]; if (!accessToken || !tokenId) { - throw new Error("인증 토큰을 받지 못했습니다."); - } - - const prevTokenId = getCookie(AUTH_USER_ID); - if (prevTokenId !== undefined && prevTokenId !== tokenId) { - clearTabStorePersist(); + throw new Error("갱신된 인증 토큰을 받지 못했습니다."); } setToken(CLAIM_NAME, accessToken); @@ -49,67 +84,32 @@ async function login(payload: AuthPayload): Promise { const remainingMs = getRemainingMs(accessToken); if (remainingMs > 0) { - setupRefreshTimer(silentRefresh, remainingMs); + setupRefreshTimer(silentRefresh, remainingMs); } -} - -async function silentRefresh(): Promise { - console.log(`[silentRefresh] Called at ${new Date().toISOString()}`); - try { - const response = await fetch( - `/api/proxy${ENDPOINT.USER_SERVICE.SILENT_REFRESH}`, - { - method: "PUT", - headers: { - "X-Device-Id": getOrCreateDeviceId(), - }, - }, - ); - - const data = await response.json(); - - if (!response.ok) { - throw new Error(data?.message ?? "토큰 갱신에 실패했습니다."); - } - - const accessToken = data[ACCESS_TOKEN]; - const tokenId = data[AUTH_USER_ID]; - - if (!accessToken || !tokenId) { - throw new Error("갱신된 인증 토큰을 받지 못했습니다."); - } - - setToken(CLAIM_NAME, accessToken); - setToken(AUTH_USER_ID, tokenId); - - const remainingMs = getRemainingMs(accessToken); - if (remainingMs > 0) { - setupRefreshTimer(silentRefresh, remainingMs); - } - } catch (error) { - console.error( - `[silentRefresh] Failed at ${new Date().toISOString()}`, - error, - ); - tokenManager.clearAuthToken(); - if (typeof window !== "undefined") { - window.dispatchEvent(new CustomEvent("auth:logout")); - } + } catch (error) { + console.error( + `[silentRefresh] Failed at ${new Date().toISOString()}`, + error, + ); + tokenManager.clearAuthToken(); + if (typeof window !== "undefined") { + window.dispatchEvent(new CustomEvent("auth:logout")); } + } } export async function logout(): Promise { - tokenManager.clearAuthToken(); - clearDeviceId(); + tokenManager.clearAuthToken(); + clearDeviceId(); - if (typeof window !== "undefined") { - window.dispatchEvent(new CustomEvent("auth:logout")); - } + if (typeof window !== "undefined") { + window.dispatchEvent(new CustomEvent("auth:logout")); + } } export const authService: AuthService = { - login, - silentRefresh, - declineSessionExtend: clearRefreshTimer, - logout, + login, + silentRefresh, + declineSessionExtend: clearRefreshTimer, + logout, }; diff --git a/apps/fo/src/entities/auth/lib/device-id.ts b/apps/fo/src/entities/auth/lib/device-id.ts deleted file mode 100644 index f844fcbef..000000000 --- a/apps/fo/src/entities/auth/lib/device-id.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { v4 as uuidv4 } from "uuid"; - -const DEVICE_ID_KEY = "x-device-id"; - -export function getOrCreateDeviceId(): string { - if (typeof window === "undefined") return ""; - - const existing = localStorage.getItem(DEVICE_ID_KEY); - if (existing) return existing; - - const id = uuidv4(); - localStorage.setItem(DEVICE_ID_KEY, id); - return id; -} - -export function clearDeviceId(): void { - if (typeof window === "undefined") return; - - localStorage.removeItem(DEVICE_ID_KEY); -} diff --git a/apps/sn/.env.local b/apps/sn/.env.local index 337c819d2..c603cdbb6 100644 --- a/apps/sn/.env.local +++ b/apps/sn/.env.local @@ -17,3 +17,5 @@ SEWERAGE_API_URL = http://localhost:8883 DC_URL = https://apigateway.apps.rsonesoft.com # NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login NEXT_PUBLIC_CM_LOGIN_PAGE_URL = /auth/login +# NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883 +NEXT_PUBLIC_COOKIE_COMAIN=.mind-one.co.kr \ No newline at end of file diff --git a/apps/sn/.env.mindone b/apps/sn/.env.mindone index 8a39a81a8..da2d0fd5c 100644 --- a/apps/sn/.env.mindone +++ b/apps/sn/.env.mindone @@ -13,4 +13,5 @@ NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y} SEWERAGE_API_URL = http://con.mind-one.co.kr:11083 DC_URL = https://apigateway.apps.rsonesoft.com -NEXT_PUBLIC_CM_LOGIN_PAGE_URL = /auth/login \ No newline at end of file +NEXT_PUBLIC_CM_LOGIN_PAGE_URL = /auth/login +NEXT_PUBLIC_COOKIE_COMAIN=.mind-one.co.kr \ No newline at end of file diff --git a/apps/sn/app/(map-layout)/layout.tsx b/apps/sn/app/(map-layout)/layout.tsx index f840fa131..40565fb97 100644 --- a/apps/sn/app/(map-layout)/layout.tsx +++ b/apps/sn/app/(map-layout)/layout.tsx @@ -20,6 +20,7 @@ import { NoFooterLayout } from "@common/components"; import type { MenuItem } from "@/entities/menu"; import { useMenuStore } from "@/shared/lib"; import useStore from '../store/useStore'; +import { authService } from "@/entities/auth"; export default function MapLayout({ children, @@ -57,6 +58,7 @@ export default function MapLayout({ return ( diff --git a/apps/sn/package.json b/apps/sn/package.json index 8cfd172aa..cc7c4e17f 100644 --- a/apps/sn/package.json +++ b/apps/sn/package.json @@ -37,7 +37,6 @@ "react-hook-form": "^7.54.2", "tailwindcss-animate": "^1.0.7", "tus-js-client": "^4.3.1", - "uuid": "^14.0.1", "xlsx": "^0.18.5", "xlsx-populate": "^1.21.0", "zod": "^3.24.2", diff --git a/apps/sn/src/entities/auth/api/auth-service.ts b/apps/sn/src/entities/auth/api/auth-service.ts index 93b158ef9..0320aa474 100644 --- a/apps/sn/src/entities/auth/api/auth-service.ts +++ b/apps/sn/src/entities/auth/api/auth-service.ts @@ -1,47 +1,83 @@ import { - ACCESS_TOKEN, - AUTH_USER_ID, - CLAIM_NAME, - ENDPOINT, + ACCESS_TOKEN, + AUTH_USER_ID, + CLAIM_NAME, + ENDPOINT, } from "@/shared/config"; import { getCookie } from "@/shared/lib"; import { - type AuthPayload, - type AuthService, - getRemainingMs, + clearDeviceId, + getOrCreateDeviceId, + type AuthPayload, + type AuthService, + getRemainingMs, } from "@common/auth"; -import { clearDeviceId, getOrCreateDeviceId } from "../lib/device-id"; import { - clearRefreshTimer, - setToken, - setupRefreshTimer, - tokenManager, + clearRefreshTimer, + setToken, + setupRefreshTimer, + tokenManager, } from "../lib/token-manager"; import { clearTabStorePersist } from "../../../store/tab-store"; async function login(payload: AuthPayload): Promise { - const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, { - method: "POST", + const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Device-Id": getOrCreateDeviceId(), + }, + body: JSON.stringify(payload), + }); + + const data = await response.json(); + + const accessToken = data[ACCESS_TOKEN]; + const tokenId = data[AUTH_USER_ID]; + + if (!accessToken || !tokenId) { + throw new Error("인증 토큰을 받지 못했습니다."); + } + + const prevTokenId = getCookie(AUTH_USER_ID); + if (prevTokenId !== undefined && prevTokenId !== tokenId) { + clearTabStorePersist(); + } + + setToken(CLAIM_NAME, accessToken); + setToken(AUTH_USER_ID, tokenId); + + const remainingMs = getRemainingMs(accessToken); + if (remainingMs > 0) { + setupRefreshTimer(silentRefresh, remainingMs); + } +} + +async function silentRefresh(): Promise { + console.log(`[silentRefresh] Called at ${new Date().toISOString()}`); + try { + const response = await fetch( + `/api/proxy${ENDPOINT.USER_SERVICE.SILENT_REFRESH}`, + { + method: "PUT", headers: { - "Content-Type": "application/json", - "X-Device-Id": getOrCreateDeviceId(), + "X-Device-Id": getOrCreateDeviceId(), }, - body: JSON.stringify(payload), - }); + }, + ); const data = await response.json(); + if (!response.ok) { + throw new Error(data?.message ?? "토큰 갱신에 실패했습니다."); + } + const accessToken = data[ACCESS_TOKEN]; const tokenId = data[AUTH_USER_ID]; if (!accessToken || !tokenId) { - throw new Error("인증 토큰을 받지 못했습니다."); - } - - const prevTokenId = getCookie(AUTH_USER_ID); - if (prevTokenId !== undefined && prevTokenId !== tokenId) { - clearTabStorePersist(); + throw new Error("갱신된 인증 토큰을 받지 못했습니다."); } setToken(CLAIM_NAME, accessToken); @@ -49,67 +85,32 @@ async function login(payload: AuthPayload): Promise { const remainingMs = getRemainingMs(accessToken); if (remainingMs > 0) { - setupRefreshTimer(silentRefresh, remainingMs); + setupRefreshTimer(silentRefresh, remainingMs); } -} - -async function silentRefresh(): Promise { - console.log(`[silentRefresh] Called at ${new Date().toISOString()}`); - try { - const response = await fetch( - `/api/proxy${ENDPOINT.USER_SERVICE.SILENT_REFRESH}`, - { - method: "PUT", - headers: { - "X-Device-Id": getOrCreateDeviceId(), - }, - }, - ); - - const data = await response.json(); - - if (!response.ok) { - throw new Error(data?.message ?? "토큰 갱신에 실패했습니다."); - } - - const accessToken = data[ACCESS_TOKEN]; - const tokenId = data[AUTH_USER_ID]; - - if (!accessToken || !tokenId) { - throw new Error("갱신된 인증 토큰을 받지 못했습니다."); - } - - setToken(CLAIM_NAME, accessToken); - setToken(AUTH_USER_ID, tokenId); - - const remainingMs = getRemainingMs(accessToken); - if (remainingMs > 0) { - setupRefreshTimer(silentRefresh, remainingMs); - } - } catch (error) { - console.error( - `[silentRefresh] Failed at ${new Date().toISOString()}`, - error, - ); - tokenManager.clearAuthToken(); - if (typeof window !== "undefined") { - window.dispatchEvent(new CustomEvent("auth:logout")); - } + } catch (error) { + console.error( + `[silentRefresh] Failed at ${new Date().toISOString()}`, + error, + ); + tokenManager.clearAuthToken(); + if (typeof window !== "undefined") { + window.dispatchEvent(new CustomEvent("auth:logout")); } + } } export async function logout(): Promise { - tokenManager.clearAuthToken(); - clearDeviceId(); + tokenManager.clearAuthToken(); + clearDeviceId(); - if (typeof window !== "undefined") { - window.dispatchEvent(new CustomEvent("auth:logout")); - } + if (typeof window !== "undefined") { + window.dispatchEvent(new CustomEvent("auth:logout")); + } } export const authService: AuthService = { - login, - silentRefresh, - declineSessionExtend: clearRefreshTimer, - logout, + login, + silentRefresh, + declineSessionExtend: clearRefreshTimer, + logout, }; diff --git a/apps/sn/src/entities/auth/lib/device-id.ts b/apps/sn/src/entities/auth/lib/device-id.ts deleted file mode 100644 index f844fcbef..000000000 --- a/apps/sn/src/entities/auth/lib/device-id.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { v4 as uuidv4 } from "uuid"; - -const DEVICE_ID_KEY = "x-device-id"; - -export function getOrCreateDeviceId(): string { - if (typeof window === "undefined") return ""; - - const existing = localStorage.getItem(DEVICE_ID_KEY); - if (existing) return existing; - - const id = uuidv4(); - localStorage.setItem(DEVICE_ID_KEY, id); - return id; -} - -export function clearDeviceId(): void { - if (typeof window === "undefined") return; - - localStorage.removeItem(DEVICE_ID_KEY); -} diff --git a/apps/st/app/(main)/(with-base-layout)/layout.tsx b/apps/st/app/(main)/(with-base-layout)/layout.tsx index 96468f9d5..19190c901 100644 --- a/apps/st/app/(main)/(with-base-layout)/layout.tsx +++ b/apps/st/app/(main)/(with-base-layout)/layout.tsx @@ -9,7 +9,7 @@ import { } from "@rsone/components"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { ENDPOINT } from "@/shared/config"; +import { ENDPOINT, CLAIM_NAME } from "@/shared/config"; import { getCookie, useAlertStore, useMenuStore } from "@/shared/lib"; import { isBbsNoticeMenu } from "@/shared/lib/bbs-notice-menu"; import { @@ -20,8 +20,10 @@ import { useStatisticsTabUrlSync } from "@/shared/lib/use-statistics-tab-url-syn import { isVerificationSelectionReportMenu } from "@/shared/lib/verification-selection-report"; import { StatisticsPageLayout } from "@/shared/ui/statistics-page-layout"; import { authService } from "@/entities/auth"; +import { getToken } from "@/entities/auth/lib/token-manager"; import type { MenuItem } from "@/entities/menu"; import { MenuApi } from "@/entities/menu"; +import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import { BaseLayout } from "@common/components"; import { getConfig } from "@common/config"; @@ -93,6 +95,13 @@ function WithBaseLayoutInner({ children }: { children: React.ReactNode }) { enabled: isLoggedIn, }); + useEffect(() => { + if (!getToken(CLAIM_NAME)) return; + void statisticsApi.refreshCommonCodes().catch(() => { + // 캐시 동기화 실패는 화면 진입을 막지 않음 + }); + }, []); + useEffect(() => { if (menuListData) { setMenuList(menuListData); diff --git a/apps/st/package.json b/apps/st/package.json index 08f5bde6b..7ae6c9f53 100644 --- a/apps/st/package.json +++ b/apps/st/package.json @@ -57,7 +57,6 @@ "swr": "^2.3.7", "tailwindcss-animate": "^1.0.7", "tus-js-client": "^4.3.1", - "uuid": "^14.0.1", "xlsx": "^0.18.5", "zod": "^3.24.2", "zustand": "^5.0.3" diff --git a/apps/st/src/components/module/SearchForm.tsx b/apps/st/src/components/module/SearchForm.tsx index ccc76bc3d..46c0b50d3 100644 --- a/apps/st/src/components/module/SearchForm.tsx +++ b/apps/st/src/components/module/SearchForm.tsx @@ -69,6 +69,7 @@ function buildSearchFormValues( isDetail: false, isSummary: d.isSummary || "", clsfCd: initialClsfCd, + capacityGbn: d.capacityGbn || "ALL", }; } @@ -85,6 +86,7 @@ function buildSearchFormValues( isDetail: false, isSummary: d?.isSummary || "", clsfCd: initialClsfCd, + capacityGbn: d?.capacityGbn || "ALL", }; } @@ -362,6 +364,26 @@ const SearchForm = (props: SearchFormProps = {}) => { /> )} + {mergedProps.capacityGbn && ( +
+ + +
+ )} {mergedProps.isDetail && (