diff --git a/backend/package.json b/backend/package.json index 7de15a91..9acd4662 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,34 +1,35 @@ -{ - "name": "ddara-backend", - "private": true, - "workspaces": [ - "frontend", - "backend" - ], - "version": "0.0.0", - "type": "module", - "description": "따라따라의 선따라길따라 BackEnd 코드", - "main": "index.js", - "scripts": { - "dev": "node src/index.js", - "test": "vitest", - "test:watch": "vitest --watch", - "test:coverage": "vitest run --coverage", - "lint": "pnpm lint-staged" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "bcrypt": "^5.1.1", - "dotenv": "^16.4.5", - "express": "^4.21.1", - "express-validator": "^7.2.0", - "jsonwebtoken": "^9.0.2", - "pg": "^8.13.1", - "swagger-jsdoc": "^6.2.8", - "swagger-ui-express": "^5.0.1", - "uuid": "^11.0.3", - "ws": "^8.11.0" - } -} +{ + "name": "ddara-backend", + "private": true, + "workspaces": [ + "frontend", + "backend" + ], + "version": "0.0.0", + "type": "module", + "description": "따라따라의 선따라길따라 BackEnd 코드", + "main": "index.js", + "scripts": { + "dev": "node src/index.js", + "test": "vitest", + "test:watch": "vitest --watch", + "test:coverage": "vitest run --coverage", + "lint": "pnpm lint-staged" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "bcrypt": "^5.1.1", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.21.1", + "express-validator": "^7.2.0", + "jsonwebtoken": "^9.0.2", + "pg": "^8.13.1", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", + "uuid": "^11.0.3", + "ws": "^8.11.0" + } +} diff --git a/backend/src/index.js b/backend/src/index.js index 355e0673..29dac205 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -1,6 +1,7 @@ import express from 'express'; import swaggerUi from 'swagger-ui-express'; import http from 'http'; +import cors from 'cors'; import { specs } from '../swaggerConfig.js'; import { PORT } from './constants/constants.js'; import { initializeWebSocketServer } from './websocketServer.js'; @@ -10,6 +11,15 @@ import { channelRouter } from './routes/channelRouter.js'; const app = express(); app.use(express.json()); +// TODO: 프론트 배포 후 origin url 변경 +app.use( + cors({ + origin: ['http://localhost:5173', 'http://223.130.151.43'], + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + credentials: true, + }), +); + app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs)); app.use('/api/auth', authRouter); diff --git a/frontend/src/api/auth.api.ts b/frontend/src/api/auth.api.ts new file mode 100644 index 00000000..905bed49 --- /dev/null +++ b/frontend/src/api/auth.api.ts @@ -0,0 +1,27 @@ +import { getApiClient } from '@/api/client.api.ts'; +import { ResponseDto } from '@/api/dto/response.dto.ts'; +import { LoginResEntity } from '@/api/dto/auth.dto.ts'; + +export const doLogin = (id: string, password: string): Promise> => { + const promiseFn = ( + fnResolve: (value: ResponseDto) => void, + fnReject: (reason?: any) => void, + ) => { + const apiClient = getApiClient(); + apiClient + .post('/auth/login', { id, password }) + .then(res => { + if (!res.data.success) { + console.error(res); + fnReject(`msg.${res.data.resultMsg}`); + } else { + fnResolve(new ResponseDto(res.data)); + } + }) + .catch(err => { + console.error(err); + fnReject('msg.RESULT_FAILED'); + }); + }; + return new Promise(promiseFn); +}; diff --git a/frontend/src/api/client.api.ts b/frontend/src/api/client.api.ts new file mode 100644 index 00000000..78bdadb1 --- /dev/null +++ b/frontend/src/api/client.api.ts @@ -0,0 +1,31 @@ +import { AppConfig } from '@/constants'; +import { loadLocalData } from '@/utils/common/manageLocalData.ts'; +import axios from 'axios'; + +let apiClient = axios.create({ + baseURL: AppConfig.API_SERVER, + headers: { + 'Content-type': 'application/json', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Credentials': true, + }, +}); + +export function setApiToken(token: string): void { + if (apiClient) apiClient.defaults.headers.common.Authorization = `Bearer ${token}`; +} + +export const getApiClient = () => { + const token = loadLocalData(AppConfig.KEYS.LOGIN_TOKEN); + if (token) setApiToken(token); + if (!apiClient) { + apiClient = axios.create({ + baseURL: AppConfig.API_SERVER, + headers: { + 'Content-type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + }); + } + return apiClient; +}; diff --git a/frontend/src/api/dto/auth.dto.ts b/frontend/src/api/dto/auth.dto.ts new file mode 100644 index 00000000..da567559 --- /dev/null +++ b/frontend/src/api/dto/auth.dto.ts @@ -0,0 +1,5 @@ +export class LoginResEntity { + token: string | undefined; + + userId: string | undefined; +} diff --git a/frontend/src/api/dto/response.dto.ts b/frontend/src/api/dto/response.dto.ts new file mode 100644 index 00000000..a071613b --- /dev/null +++ b/frontend/src/api/dto/response.dto.ts @@ -0,0 +1,16 @@ +export class ResponseDto { + resultCode = 0; + + resultMsg = ''; + + data?: T; + + totalCount = 0; + + constructor(data?: any) { + if (data.resultCode) this.resultCode = data.resultCode; + if (data.resultMsg) this.resultMsg = data.resultMsg; + if (data.data) this.data = data.data; + if (data.totalCount) this.totalCount = data.totalCount; + } +} diff --git a/frontend/src/component/authmodal/AuthModal.tsx b/frontend/src/component/authmodal/AuthModal.tsx index a13689a8..a0d3c114 100644 --- a/frontend/src/component/authmodal/AuthModal.tsx +++ b/frontend/src/component/authmodal/AuthModal.tsx @@ -1,5 +1,8 @@ import React, { useState } from 'react'; import { Modal } from '@/component/common/modal/Modal'; +import { doLogin } from '@/api/auth.api.ts'; +import { saveLocalData } from '@/utils/common/manageLocalData.ts'; +import { AppConfig } from '@/constants.ts'; interface IAuthModalProps { /** 모달이 열려 있는지 여부를 나타냅니다. */ @@ -43,7 +46,13 @@ export const AuthModal = (props: IAuthModalProps) => { }; const handleLoginClick = () => { - console.log('로그인 데이터:', loginData); + doLogin(loginData.id, loginData.pw).then(el => { + if (el.data?.token && el.data?.userId) { + saveLocalData(AppConfig.KEYS.LOGIN_TOKEN, el.data.token); + saveLocalData(AppConfig.KEYS.LOGIN_USER, el.data.userId); + } + window.location.reload(); + }); }; const handleSignUpClick = () => { @@ -63,7 +72,7 @@ export const AuthModal = (props: IAuthModalProps) => { }; return ( - + {modalType === 'login' ? ( <> diff --git a/frontend/src/constants.ts b/frontend/src/constants.ts new file mode 100644 index 00000000..1499d2fe --- /dev/null +++ b/frontend/src/constants.ts @@ -0,0 +1,17 @@ +export const KEYS = { + LOGIN_USER: 'LUT', + LOGIN_TOKEN: 'LUT_TK', +}; + +export const AppConfig = { + /** + * API SERVER + */ + API_SERVER: 'http://223.130.151.43:3001/api', + // API_SERVER: 'http://localhost:3001/api', + + /** + * ETC + */ + KEYS, +}; diff --git a/frontend/src/pages/Main.tsx b/frontend/src/pages/Main.tsx index 42bd85bd..f3c99bb5 100644 --- a/frontend/src/pages/Main.tsx +++ b/frontend/src/pages/Main.tsx @@ -1,4 +1,4 @@ -import React, { Fragment } from 'react'; +import { Fragment } from 'react'; import { getUserLocation } from '@/hooks/getUserLocation'; import { Map } from '@/component/maps/Map'; import { BottomSheet } from '@/component/BottomSheet/BottomSheet'; diff --git a/frontend/src/utils/common/manageLocalData.ts b/frontend/src/utils/common/manageLocalData.ts new file mode 100644 index 00000000..c74b7317 --- /dev/null +++ b/frontend/src/utils/common/manageLocalData.ts @@ -0,0 +1,53 @@ +/* eslint-disable */ +import lzString from 'lz-string' + +export function saveLocalData(key: string, val: string): void { + if (typeof window !== 'undefined') { + const storage = window.localStorage; + + if (storage) { + try { + storage.setItem(key, lzString.compressToUTF16(val)); + } catch (e) { + console.error('Storage Full ... clean old data...'); + for (const k in storage) { + if (k.indexOf('DATA_MESSAGE_DETAIL_') > -1) { + storage.removeItem(k); + } + } + storage.setItem(key, lzString.compressToUTF16(val)); + } + } + } +} + +export function loadLocalData(key: string): string | null { + if (typeof window !== undefined) { + const storage = window.localStorage; + + if (storage) { + const keyValue = storage.getItem(key); + if (keyValue) return lzString.decompressFromUTF16(keyValue); + } + } + return null; +} + +export function clearLocalData() { + if (typeof window !== undefined) { + const storage = window.localStorage; + + if (storage) { + storage.clear(); + } + } +} + +export function removeLocalData(key: string): void { + if (typeof window !== undefined) { + const storage = window.localStorage; + if (storage) { + storage.removeItem(key); + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91c80184..8ee03355 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,6 +111,9 @@ importers: bcrypt: specifier: ^5.1.1 version: 5.1.1 + cors: + specifier: ^2.8.5 + version: 2.8.5 dotenv: specifier: ^16.4.5 version: 16.4.5 @@ -3314,6 +3317,10 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + cose-base@1.0.3: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} @@ -11932,6 +11939,11 @@ snapshots: core-util-is@1.0.3: {} + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cose-base@1.0.3: dependencies: layout-base: 1.0.2