From 06e3e19f877c5cd8585cbd73e4865f6da792ad02 Mon Sep 17 00:00:00 2001 From: Hyein Jeong Date: Wed, 13 Nov 2024 23:17:23 +0900 Subject: [PATCH] =?UTF-8?q?[BE][Feat]=20#159=20:=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 회원가입 api에 대한 swagger 문서 추가 - 회원가입 api 구현 - auth에 대한 jsdoc 추가 - 회원가입시 비밀번호 암호화해서 db에 저장 --- backend/src/controllers/authController.js | 28 +++++++++++++-- backend/src/repositories/userRepository.js | 40 +++++++++++++++++++++ backend/src/routes/authRouter.js | 42 ++++++++++++++++++++-- backend/src/services/authService.js | 28 ++++++++++++++- backend/swaggerConfig.js | 25 +++++++++++++ 5 files changed, 158 insertions(+), 5 deletions(-) diff --git a/backend/src/controllers/authController.js b/backend/src/controllers/authController.js index 4cb92083..a2649e12 100644 --- a/backend/src/controllers/authController.js +++ b/backend/src/controllers/authController.js @@ -1,6 +1,9 @@ -import { loginUser } from '../services/authService.js'; +import { loginUser, registerUser } from '../services/authService.js'; -export const login = async (req, res) => { +/** + * @description 로그인 컨트롤러 + */ +export const loginController = async (req, res) => { const { id, password } = req.body; try { @@ -18,3 +21,24 @@ export const login = async (req, res) => { return res.status(500).json({ success: false, message: 'Server error occurred' }); } }; + +/** + * @description 회원가입 컨트롤러 + */ +export const registerUserController = async (req, res) => { + try { + const { id, name, password, email } = req.body; + const newUser = await registerUser(id, name, password, email); + return res.status(201).json({ + success: true, + message: 'Login successfully', + data: newUser, + }); + } catch (error) { + if (error.message === 'User ID already exists') { + return res.status(409).json({ error: 'User ID already exists' }); + } + console.error('User registration error:', error); + res.status(500).json({ error: 'Server error' }); + } +}; diff --git a/backend/src/repositories/userRepository.js b/backend/src/repositories/userRepository.js index 56e133e5..31ea683b 100644 --- a/backend/src/repositories/userRepository.js +++ b/backend/src/repositories/userRepository.js @@ -1,6 +1,46 @@ import { pool } from '../db/db.js'; +/** + * @description 데이터베이스에 새로운 사용자 생성 + * @param {string} id - 사용자 id + * @returns {object} id로 찾은 사용자 정보 + */ export const findUserById = async id => { const result = await pool.query('SELECT * FROM "main"."user" WHERE id = $1', [id]); return result.rows[0]; }; + +/** + * @description 사용자 ID 중복 여부 확인 + * @param {string} id - 사용자 ID + * @returns {boolean} 중복 여부 + */ +export const isUserIdDuplicate = async id => { + const query = ` + SELECT 1 FROM "main"."user" + WHERE id = $1; + `; + const result = await pool.query(query, [id]); + + return result.rows.length > 0; +}; + +/** + * @description 데이터베이스에 새로운 사용자 생성 + * @param {string} id - 사용자 ID + * @param {string} name - 사용자 이름 + * @param {string} password - 사용자 비밀번호 + * @param {string} email - 사용자 이메일 + * @returns {object} 새로 생성된 사용자 정보 + */ +export const createUserInDB = async (id, name, password, email) => { + const query = ` + INSERT INTO "main"."user" (id, name, password, email) + VALUES ($1, $2, $3, $4) + RETURNING id, name, email; + `; + const values = [id, name, password, email]; + const result = await pool.query(query, values); + + return result.rows[0]; +}; diff --git a/backend/src/routes/authRouter.js b/backend/src/routes/authRouter.js index 582c0254..5c81901c 100644 --- a/backend/src/routes/authRouter.js +++ b/backend/src/routes/authRouter.js @@ -1,6 +1,6 @@ import express from 'express'; import { body } from 'express-validator'; -import { login } from '../controllers/authController.js'; +import { loginController, registerUserController } from '../controllers/authController.js'; import { validationMiddleware } from '../middleware/validationMiddleware.js'; export const authRouter = express.Router(); @@ -11,6 +11,7 @@ export const authRouter = express.Router(); * post: * summary: 사용자 로그인 API * description: 사용자가 로그인할 수 있도록 ID와 비밀번호를 통해 인증 후 토큰을 반환합니다. + * tags: [Auth] * requestBody: * required: true * description: 로그인을 위한 ID와 비밀번호를 포함한 요청 body @@ -41,5 +42,42 @@ authRouter.post( .withMessage('Password must be at least 6 characters long'), ], validationMiddleware, - login, + loginController, +); + +/** + * @swagger + * /user/register: + * post: + * summary: "회원가입 API" + * description: "사용자가 회원가입을 통해 계정을 생성합니다." + * tags: [Auth] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/RegisterRequest' + * responses: + * "201": + * description: "회원가입 성공" + * "400": + * description: "유효성 검사 실패" + * "409": + * description: "중복된 사용자 ID" + * "500": + * description: "서버 오류" + */ +authRouter.post( + '/register', + [ + body('id').isLength({ min: 4 }).withMessage('User ID must be at least 4 characters long'), + body('name').notEmpty().withMessage('Name is required'), + body('password') + .isLength({ min: 6 }) + .withMessage('Password must be at least 6 characters long'), + body('email').isEmail().withMessage('Valid email is required'), + ], + validationMiddleware, + registerUserController, ); diff --git a/backend/src/services/authService.js b/backend/src/services/authService.js index e8f7c36c..40c6bce6 100644 --- a/backend/src/services/authService.js +++ b/backend/src/services/authService.js @@ -1,7 +1,13 @@ import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; -import { findUserById } from '../repositories/userRepository.js'; +import { createUserInDB, findUserById, isUserIdDuplicate } from '../repositories/userRepository.js'; +/** + * @description 로그인 서비스 + * @param {string} name - 사용자 이름 + * @param {string} password - 사용자 비밀번호 + * @returns {object} 로그인된 사용자의 토큰과 id + */ export const loginUser = async (id, password) => { const user = await findUserById(id); if (!user) { @@ -17,3 +23,23 @@ export const loginUser = async (id, password) => { const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' }); return { token, userId: user.id }; }; + +/** + * @description 회원가입 서비스 + * @param {string} id - 사용자 ID + * @param {string} name - 사용자 이름 + * @param {string} password - 사용자 비밀번호 + * @param {string} email - 사용자 이메일 + * @returns {object} 새로 생성된 사용자 정보 + */ +export const registerUser = async (id, name, password, email) => { + const isDuplicate = await isUserIdDuplicate(id); + if (isDuplicate) { + throw new Error('User ID already exists'); + } + + const hashedPassword = await bcrypt.hash(password, 10); + const newUser = await createUserInDB(id, name, hashedPassword, email); + + return newUser; +}; diff --git a/backend/swaggerConfig.js b/backend/swaggerConfig.js index 72896848..ec6ed9ad 100644 --- a/backend/swaggerConfig.js +++ b/backend/swaggerConfig.js @@ -43,6 +43,31 @@ const swaggerDefinition = { }, }, + // 회원가입 요청 스키마 + RegisterRequest: { + type: 'object', + properties: { + id: { + type: 'string', + description: '사용자 고유 ID (중복 불가)', + }, + name: { + type: 'string', + description: '사용자 이름', + }, + password: { + type: 'string', + description: '사용자 비밀번호 (최소 6자 이상)', + }, + email: { + type: 'string', + format: 'email', + description: '사용자 이메일 주소', + }, + }, + required: ['id', 'name', 'password', 'email'], + }, + // 채널 생성 요청 스키마 CreateChannelRequest: { type: 'object',