# User API 연동 ν˜„ν™© ## πŸ“‹ 연동 μ™„λ£Œ μš”μ•½ User APIλŠ” **μ™„μ „νžˆ κ΅¬ν˜„λ˜μ–΄ 있으며**, 둜그인 및 νšŒμ›κ°€μž… κΈ°λŠ₯이 μ •μƒμ μœΌλ‘œ μž‘λ™ν•©λ‹ˆλ‹€. ### βœ… κ΅¬ν˜„ μ™„λ£Œ ν•­λͺ© 1. **API ν΄λΌμ΄μ–ΈνŠΈ μ„€μ •** - Gatewayλ₯Ό ν†΅ν•œ λ°±μ—”λ“œ 직접 연동 - 토큰 기반 인증 μ‹œμŠ€ν…œ - Request/Response 인터셉터 2. **νƒ€μž… μ •μ˜** - LoginRequest/Response - RegisterRequest/Response - ProfileResponse - User 및 AuthState μΈν„°νŽ˜μ΄μŠ€ 3. **인증 둜직** - useAuth μ»€μŠ€ν…€ ν›… - AuthProvider Context - localStorage 기반 μ„Έμ…˜ 관리 4. **UI νŽ˜μ΄μ§€** - 둜그인 νŽ˜μ΄μ§€ (/login) - νšŒμ›κ°€μž… νŽ˜μ΄μ§€ (/register) - 3단계 νšŒμ›κ°€μž… ν”Œλ‘œμš° --- ## πŸ—οΈ μ•„ν‚€ν…μ²˜ ꡬ쑰 ``` ν”„λ‘ νŠΈμ—”λ“œ Gateway λ°±μ—”λ“œ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ HTTP β”‚ β”‚ HTTP β”‚ β”‚ β”‚ Browser β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚Gateway β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚ User API β”‚ β”‚ β”‚<───────────── β”‚<────────────── β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ JSON+JWT β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ JSON+JWT β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### API ν΄λΌμ΄μ–ΈνŠΈ μ„€μ • ```typescript // src/shared/api/client.ts const GATEWAY_HOST = 'http://kt-event-marketing-api.20.214.196.128.nip.io'; const API_HOSTS = { user: GATEWAY_HOST, event: GATEWAY_HOST, content: GATEWAY_HOST, ai: GATEWAY_HOST, participation: GATEWAY_HOST, distribution: GATEWAY_HOST, analytics: GATEWAY_HOST, }; // User APIλŠ” apiClientλ₯Ό 톡해 직접 Gateway에 μ—°κ²° export const apiClient: AxiosInstance = axios.create({ baseURL: API_HOSTS.user, timeout: 90000, headers: { 'Content-Type': 'application/json', }, }); ``` **πŸ’‘ ν”„λ‘μ‹œ 라우트 λΆˆν•„μš”**: User APIλŠ” Next.js ν”„λ‘μ‹œλ₯Ό κ±°μΉ˜μ§€ μ•Šκ³  λΈŒλΌμš°μ €μ—μ„œ Gateway둜 직접 μš”μ²­ν•©λ‹ˆλ‹€. --- ## πŸ“‘ User API μ—”λ“œν¬μΈνŠΈ ### 1. 둜그인 ```http POST /api/v1/users/login Content-Type: application/json { "email": "user@example.com", "password": "password123" } ``` **응닡:** ```json { "token": "eyJhbGciOiJIUzI1NiIs...", "userId": "550e8400-e29b-41d4-a716-446655440000", "userName": "홍길동", "role": "USER", "email": "user@example.com" } ``` ### 2. νšŒμ›κ°€μž… ```http POST /api/v1/users/register Content-Type: application/json { "name": "홍길동", "phoneNumber": "01012345678", "email": "user@example.com", "password": "password123", "storeName": "홍길동 κ³ κΉƒμ§‘", "industry": "restaurant", "address": "μ„œμšΈνŠΉλ³„μ‹œ 강남ꡬ", "businessHours": "09:00-18:00" } ``` **응닡:** ```json { "token": "eyJhbGciOiJIUzI1NiIs...", "userId": "550e8400-e29b-41d4-a716-446655440000", "userName": "홍길동", "storeId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "storeName": "홍길동 κ³ κΉƒμ§‘" } ``` ### 3. λ‘œκ·Έμ•„μ›ƒ ```http POST /api/v1/users/logout Authorization: Bearer {token} ``` **응닡:** ```json { "success": true, "message": "λ‘œκ·Έμ•„μ›ƒλ˜μ—ˆμŠ΅λ‹ˆλ‹€" } ``` ### 4. ν”„λ‘œν•„ 쑰회 ```http GET /api/v1/users/profile Authorization: Bearer {token} ``` **응닡:** ```json { "userId": "550e8400-e29b-41d4-a716-446655440000", "userName": "홍길동", "phoneNumber": "01012345678", "email": "user@example.com", "role": "USER", "storeId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "storeName": "홍길동 κ³ κΉƒμ§‘", "industry": "restaurant", "address": "μ„œμšΈνŠΉλ³„μ‹œ 강남ꡬ", "businessHours": "09:00-18:00", "createdAt": "2025-01-01T00:00:00", "lastLoginAt": "2025-01-10T12:00:00" } ``` ### 5. ν”„λ‘œν•„ μˆ˜μ • ```http PUT /api/v1/users/profile Authorization: Bearer {token} Content-Type: application/json { "name": "홍길동", "phoneNumber": "01012345678", "storeName": "홍길동 κ³ κΉƒμ§‘", "industry": "restaurant", "address": "μ„œμšΈνŠΉλ³„μ‹œ 강남ꡬ", "businessHours": "09:00-18:00" } ``` ### 6. λΉ„λ°€λ²ˆν˜Έ λ³€κ²½ ```http PUT /api/v1/users/password Authorization: Bearer {token} Content-Type: application/json { "currentPassword": "oldpassword", "newPassword": "newpassword123" } ``` --- ## πŸ” 인증 ν”Œλ‘œμš° ### 둜그인 ν”Œλ‘œμš° ``` 1. μ‚¬μš©μžκ°€ 이메일/λΉ„λ°€λ²ˆν˜Έ μž…λ ₯ 2. userApi.login() 호좜 3. μ„œλ²„μ—μ„œ JWT 토큰 λ°œκΈ‰ 4. localStorage에 토큰 μ €μž₯ 5. userApi.getProfile() 호좜 (storeId ν¬ν•¨λœ 전체 정보 νšλ“) 6. localStorage에 User 정보 μ €μž₯ 7. AuthContext μƒνƒœ μ—…λ°μ΄νŠΈ 8. 메인 νŽ˜μ΄μ§€λ‘œ λ¦¬λ””λ ‰μ…˜ ``` ### νšŒμ›κ°€μž… ν”Œλ‘œμš° ``` 1. 3단계 폼 μž‘μ„± - Step 1: 계정 정보 (이메일, λΉ„λ°€λ²ˆν˜Έ) - Step 2: 개인 정보 (이름, μ „ν™”λ²ˆν˜Έ) - Step 3: 사업μž₯ 정보 (μƒν˜Έλͺ…, μ—…μ’…, μ£Όμ†Œ λ“±) 2. userApi.register() 호좜 3. μ„œλ²„μ—μ„œ μ‚¬μš©μž 생성 및 JWT 토큰 λ°œκΈ‰ 4. localStorage에 토큰 및 User 정보 μ €μž₯ 5. AuthContext μƒνƒœ μ—…λ°μ΄νŠΈ 6. νšŒμ›κ°€μž… μ™„λ£Œ λ‹€μ΄μ–Όλ‘œκ·Έ ν‘œμ‹œ 7. 메인 νŽ˜μ΄μ§€λ‘œ λ¦¬λ””λ ‰μ…˜ ``` ### λ‘œκ·Έμ•„μ›ƒ ν”Œλ‘œμš° ``` 1. userApi.logout() 호좜 2. μ„œλ²„μ—μ„œ μ„Έμ…˜ λ¬΄νš¨ν™” (μ‹€νŒ¨ν•΄λ„ 계속 μ§„ν–‰) 3. localStorageμ—μ„œ 토큰 및 User 정보 μ‚­μ œ 4. AuthContext μƒνƒœ μ΄ˆκΈ°ν™” 5. 둜그인 νŽ˜μ΄μ§€λ‘œ λ¦¬λ””λ ‰μ…˜ ``` --- ## πŸ“‚ 파일 ꡬ쑰 ### API Layer ``` src/entities/user/ β”œβ”€β”€ api/ β”‚ └── userApi.ts # User API μ„œλΉ„μŠ€ ν•¨μˆ˜ β”œβ”€β”€ model/ β”‚ └── types.ts # TypeScript νƒ€μž… μ •μ˜ └── index.ts # Public exports ``` ### Features Layer ``` src/features/auth/ β”œβ”€β”€ model/ β”‚ β”œβ”€β”€ useAuth.ts # 인증 μ»€μŠ€ν…€ ν›… β”‚ └── AuthProvider.tsx # Context Provider └── index.ts # Public exports ``` ### Pages ``` src/app/(auth)/ β”œβ”€β”€ login/ β”‚ └── page.tsx # 둜그인 νŽ˜μ΄μ§€ └── register/ └── page.tsx # νšŒμ›κ°€μž… νŽ˜μ΄μ§€ (3단계 ν”Œλ‘œμš°) ``` ### Shared ``` src/shared/api/ β”œβ”€β”€ client.ts # Axios ν΄λΌμ΄μ–ΈνŠΈ μ„€μ • └── index.ts # Public exports src/stores/ └── authStore.ts # Zustand 인증 μŠ€ν† μ–΄ (참고용) ``` --- ## πŸ”‘ μ£Όμš” μ½”λ“œ ꡬ쑰 ### 1. User API Service **src/entities/user/api/userApi.ts:** ```typescript const USER_API_BASE = '/api/v1/users'; export const userApi = { login: async (data: LoginRequest): Promise => {...}, register: async (data: RegisterRequest): Promise => {...}, logout: async (): Promise => {...}, getProfile: async (): Promise => {...}, updateProfile: async (data: UpdateProfileRequest): Promise => {...}, changePassword: async (data: ChangePasswordRequest): Promise => {...}, }; ``` ### 2. useAuth Hook **src/features/auth/model/useAuth.ts:** ```typescript export const useAuth = () => { const [authState, setAuthState] = useState({ user: null, token: null, isAuthenticated: false, isLoading: true, }); // 초기 인증 μƒνƒœ 확인 (localStorage 기반) useEffect(() => { const token = localStorage.getItem(TOKEN_KEY); const userStr = localStorage.getItem(USER_KEY); if (token && userStr) { const user = JSON.parse(userStr) as User; setAuthState({ user, token, isAuthenticated: true, isLoading: false, }); } }, []); // 둜그인 ν•¨μˆ˜ const login = async (credentials: LoginRequest) => { const response = await userApi.login(credentials); localStorage.setItem(TOKEN_KEY, response.token); const profile = await userApi.getProfile(); localStorage.setItem(USER_KEY, JSON.stringify(user)); setAuthState({...}); return { success: true, user }; }; // νšŒμ›κ°€μž…, λ‘œκ·Έμ•„μ›ƒ, ν”„λ‘œν•„ μƒˆλ‘œκ³ μΉ¨ ν•¨μˆ˜λ“€... return { ...authState, login, register, logout, refreshProfile, }; }; ``` ### 3. AuthProvider Context **src/features/auth/model/AuthProvider.tsx:** ```typescript const AuthContext = createContext(null); export const AuthProvider = ({ children }: { children: ReactNode }) => { const auth = useAuth(); return {children}; }; export const useAuthContext = () => { const context = useContext(AuthContext); if (!context) { throw new Error('useAuthContext must be used within AuthProvider'); } return context; }; ``` ### 4. RootLayout 적용 **src/app/layout.tsx:** ```typescript export default function RootLayout({ children }) { return ( {children} ); } ``` --- ## πŸ§ͺ ν…ŒμŠ€νŠΈ 방법 ### 1. νšŒμ›κ°€μž… ν…ŒμŠ€νŠΈ 1. 개발 μ„œλ²„ μ‹€ν–‰ ```bash npm run dev ``` 2. λΈŒλΌμš°μ €μ—μ„œ `/register` 접속 3. 3단계 폼 μž‘μ„±: - **Step 1**: 이메일 및 λΉ„λ°€λ²ˆν˜Έ μž…λ ₯ - **Step 2**: 이름 및 μ „ν™”λ²ˆν˜Έ μž…λ ₯ (010-1234-5678 ν˜•μ‹) - **Step 3**: 사업μž₯ 정보 μž…λ ₯ 및 μ•½κ΄€ λ™μ˜ 4. "κ°€μž…μ™„λ£Œ" λ²„νŠΌ 클릭 5. 성곡 μ‹œ: - νšŒμ›κ°€μž… μ™„λ£Œ λ‹€μ΄μ–Όλ‘œκ·Έ ν‘œμ‹œ - localStorage에 토큰 및 μ‚¬μš©μž 정보 μ €μž₯ - 메인 νŽ˜μ΄μ§€λ‘œ λ¦¬λ””λ ‰μ…˜ ### 2. 둜그인 ν…ŒμŠ€νŠΈ 1. λΈŒλΌμš°μ €μ—μ„œ `/login` 접속 2. 이메일 및 λΉ„λ°€λ²ˆν˜Έ μž…λ ₯ 3. "둜그인" λ²„νŠΌ 클릭 4. 성곡 μ‹œ: - localStorage에 토큰 및 μ‚¬μš©μž 정보 μ €μž₯ - 메인 νŽ˜μ΄μ§€λ‘œ λ¦¬λ””λ ‰μ…˜ - 헀더에 μ‚¬μš©μž 정보 ν‘œμ‹œ ### 3. λ‘œκ·Έμ•„μ›ƒ ν…ŒμŠ€νŠΈ 1. 둜그인된 μƒνƒœμ—μ„œ ν”„λ‘œν•„ νŽ˜μ΄μ§€ λ˜λŠ” 헀더 메뉴 μ ‘κ·Ό 2. "λ‘œκ·Έμ•„μ›ƒ" λ²„νŠΌ 클릭 3. 성곡 μ‹œ: - localStorageμ—μ„œ 토큰 및 μ‚¬μš©μž 정보 μ‚­μ œ - 둜그인 νŽ˜μ΄μ§€λ‘œ λ¦¬λ””λ ‰μ…˜ ### 4. 디버깅 λΈŒλΌμš°μ € 개발자 도ꡬ Consoleμ—μ„œ λ‹€μŒ 둜그 확인: ``` πŸ“ž μ „ν™”λ²ˆν˜Έ λ³€ν™˜: 010-1234-5678 -> 01012345678 πŸ“¦ νšŒμ›κ°€μž… μš”μ²­ 데이터: {...} πŸš€ registerUser ν•¨μˆ˜ 호좜 πŸ“₯ registerUser κ²°κ³Ό: {...} βœ… νšŒμ›κ°€μž… 성곡: {...} πŸ’Ύ localStorage에 토큰과 μ‚¬μš©μž 정보 μ €μž₯ μ™„λ£Œ ``` --- ## 🚨 μ£Όμ˜μ‚¬ν•­ ### 1. μ „ν™”λ²ˆν˜Έ ν˜•μ‹ λ³€ν™˜ - **UI μž…λ ₯**: `010-1234-5678` (ν•˜μ΄ν”ˆ 포함) - **API 전솑**: `01012345678` (ν•˜μ΄ν”ˆ 제거) - νšŒμ›κ°€μž… νŽ˜μ΄μ§€μ—μ„œ μžλ™ λ³€ν™˜ 처리됨 ### 2. 토큰 관리 - Access Token은 localStorage에 `accessToken` ν‚€λ‘œ μ €μž₯ - User μ •λ³΄λŠ” localStorage에 `user` ν‚€λ‘œ μ €μž₯ - 401 응닡 μ‹œ μžλ™μœΌλ‘œ 둜그인 νŽ˜μ΄μ§€λ‘œ λ¦¬λ””λ ‰μ…˜ ### 3. ν”„λ‘μ‹œ 라우트 μ—†μŒ - User APIλŠ” Next.js ν”„λ‘μ‹œλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠμŒ - λΈŒλΌμš°μ €μ—μ„œ Gateway둜 직접 μš”μ²­ - CORS 섀정이 Gatewayμ—μ„œ μ²˜λ¦¬λ˜μ–΄μ•Ό 함 ### 4. λ‘œκ·Έμ•„μ›ƒ μ—λŸ¬ 처리 - λ‘œκ·Έμ•„μ›ƒ API μ‹€νŒ¨ν•΄λ„ 둜컬 μƒνƒœλŠ” 정리됨 - μ„œλ²„ μ—λŸ¬ λ°œμƒ μ‹œμ—λ„ μ‚¬μš©μžλŠ” μ •μƒμ μœΌλ‘œ λ‘œκ·Έμ•„μ›ƒλ¨ --- ## πŸ“ νƒ€μž… μ •μ˜ μš”μ•½ ```typescript // 둜그인 interface LoginRequest { email: string; password: string; } interface LoginResponse { token: string; userId: string; // UUID format userName: string; role: string; email: string; } // νšŒμ›κ°€μž… interface RegisterRequest { name: string; phoneNumber: string; // "01012345678" ν˜•μ‹ email: string; password: string; storeName: string; industry?: string; address: string; businessHours?: string; } interface RegisterResponse { token: string; userId: string; // UUID format userName: string; storeId: string; // UUID format storeName: string; } // ν”„λ‘œν•„ interface ProfileResponse { userId: string; // UUID format userName: string; phoneNumber: string; email: string; role: string; storeId: string; // UUID format storeName: string; industry: string; address: string; businessHours: string; createdAt: string; lastLoginAt: string; } // User μƒνƒœ interface User { userId: string; // UUID format userName: string; email: string; role: string; phoneNumber?: string; storeId?: string; // UUID format storeName?: string; industry?: string; address?: string; businessHours?: string; } // 인증 μƒνƒœ interface AuthState { user: User | null; token: string | null; isAuthenticated: boolean; isLoading: boolean; } ``` --- ## βœ… 체크리슀트 - [x] API ν΄λΌμ΄μ–ΈνŠΈ μ„€μ • μ™„λ£Œ - [x] TypeScript νƒ€μž… μ •μ˜ μ™„λ£Œ - [x] useAuth Hook κ΅¬ν˜„ μ™„λ£Œ - [x] AuthProvider Context κ΅¬ν˜„ μ™„λ£Œ - [x] 둜그인 νŽ˜μ΄μ§€ κ΅¬ν˜„ μ™„λ£Œ - [x] νšŒμ›κ°€μž… νŽ˜μ΄μ§€ (3단계) κ΅¬ν˜„ μ™„λ£Œ - [x] localStorage μ„Έμ…˜ 관리 μ™„λ£Œ - [x] Request/Response 인터셉터 μ„€μ • μ™„λ£Œ - [x] 401 μ—λŸ¬ 핸듀링 μ™„λ£Œ - [x] RootLayout에 AuthProvider 적용 μ™„λ£Œ - [x] λΉŒλ“œ ν…ŒμŠ€νŠΈ 톡과 βœ… - [ ] 개발 μ„œλ²„ μ‹€ν–‰ 및 μ‹€μ œ API ν…ŒμŠ€νŠΈ (μ‚¬μš©μžκ°€ μˆ˜ν–‰) --- ## πŸ“š κ΄€λ ¨ 파일 ### 핡심 파일 - `src/entities/user/api/userApi.ts` - User API μ„œλΉ„μŠ€ - `src/entities/user/model/types.ts` - TypeScript νƒ€μž… μ •μ˜ - `src/features/auth/model/useAuth.ts` - 인증 Hook - `src/features/auth/model/AuthProvider.tsx` - Context Provider - `src/app/(auth)/login/page.tsx` - 둜그인 νŽ˜μ΄μ§€ - `src/app/(auth)/register/page.tsx` - νšŒμ›κ°€μž… νŽ˜μ΄μ§€ - `src/shared/api/client.ts` - Axios ν΄λΌμ΄μ–ΈνŠΈ μ„€μ • ### μ°Έκ³  파일 - `src/stores/authStore.ts` - Zustand 인증 μŠ€ν† μ–΄ (참고용, ν˜„μž¬ λ―Έμ‚¬μš©) - `src/app/layout.tsx` - RootLayout with AuthProvider --- ## 🎯 λ‹€μŒ 단계 User API 연동은 μ™„λ£Œλ˜μ—ˆμœΌλ―€λ‘œ λ‹€μŒ μž‘μ—…μ„ μ§„ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€: 1. **개발 μ„œλ²„ ν…ŒμŠ€νŠΈ**: `npm run dev` μ‹€ν–‰ ν›„ μ‹€μ œ νšŒμ›κ°€μž…/둜그인 ν…ŒμŠ€νŠΈ 2. **ν”„λ‘œν•„ νŽ˜μ΄μ§€ κ°œμ„ **: μ‚¬μš©μž 정보 μˆ˜μ • κΈ°λŠ₯ κ°•ν™” 3. **λΉ„λ°€λ²ˆν˜Έ μ°ΎκΈ°**: λΉ„λ°€λ²ˆν˜Έ μž¬μ„€μ • ν”Œλ‘œμš° κ΅¬ν˜„ (ν˜„μž¬ λ―Έκ΅¬ν˜„) 4. **μ†Œμ…œ 둜그인**: μΉ΄μΉ΄μ˜€ν†‘, 넀이버 μ†Œμ…œ 둜그인 κ΅¬ν˜„ (ν˜„μž¬ μ€€λΉ„ 쀑) 5. **κΆŒν•œ 관리**: Role 기반 μ ‘κ·Ό μ œμ–΄ (ADMIN, USER) κ΅¬ν˜„ 6. **μ„Έμ…˜ κ°±μ‹ **: Refresh Token 둜직 μΆ”κ°€ (ν•„μš”μ‹œ) --- ## πŸ“ž 문의 User API κ΄€λ ¨ λ¬Έμ œλ‚˜ κ°œμ„ μ‚¬ν•­μ€ ν”„λ‘œμ νŠΈ νŒ€μ— λ¬Έμ˜ν•˜μ„Έμš”. **λ¬Έμ„œ μž‘μ„±μΌ**: 2025-01-30 **λ§ˆμ§€λ§‰ μ—…λ°μ΄νŠΈ**: 2025-01-30