Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ on:
types:
- opened
- reopened
- synchronize
paths-ignore:
- 'README.md'

Expand Down
31 changes: 15 additions & 16 deletions src/domains/auth/signup/signupController.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const signupService = require('./signupService');
const { signupValidation } = require('./signupValidation');
const logger = require('../../../config/logger');
const { validationResult } = require('express-validator');
const SignupDto = require('./signupDto');

const signupController = [
...signupValidation, // 유효성 검사 미들웨어 추가
Expand All @@ -14,30 +15,28 @@ const signupController = [
const errors = validationResult(req);
if (!errors.isEmpty()) {
const statusCode = 400;
logger.warn(`(${statusCode}) Validation failed: ${errors.array().map(err => err.msg).join(', ')}`); // 검증 실패 로깅
return res.status(400).json({ message: errors.array().map(err => err.msg).join(', ') });
logger.warn(`(${statusCode}) user validation failed: ${errors.array().map(err => err.msg).join(', ')}`); // 검증 실패 로깅
return res.status(400).json({
"Invalid values": errors.array().map(err => ({ message: err.msg }))
});
}

// gender 값 변환
let gender = req.body.gender;
if (gender === '남성') gender = 'm';
if (gender === '여성') gender = 'f';
if (gender === '비공개') gender = 'n';

const signupData = {
...req.body,
gender,
};
const userData = new SignupDto(req.body.username, req.body.password, req.body.name, req.body.gender, req.body.birthDate);

// 회원가입 처리
const user = await signupService.signupUser(signupData);
const user = await signupService.createUser(userData);
logger.info(`User created successfully: ID=${user.id}, username=${user.username}`); // 성공 로깅
const { password, ...userWithoutPassword } = user;
return res.status(201).json(userWithoutPassword);

const responseUser = {
...user,
gender: SignupDto.convertGenderToKorean(user.gender),
password: undefined
};

return res.status(201).json(responseUser);
} catch (error) {
const statusCode = 500;
logger.error(`(${statusCode}) Signup failed: ${error.message}`); // 오류 로깅
logger.error(`(${statusCode}) Signup failed: ${error.message}\nStack trace: ${error.stack}`);
return res.status(500).json({ message: '서버 오류', error: error.message });
}
}
Expand Down
20 changes: 18 additions & 2 deletions src/domains/auth/signup/signupDto.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
class SignupDto {
class signupDto {
constructor(username, password, name, gender, birthDate) {
this.username = username;
this.password = password;
this.name = name;
this.gender = gender;
this.birthDate = birthDate;
}

// DB에 저장할 때
static convertGenderToEnglish(gender) {
if (gender === '남성') return 'm';
if (gender === '여성') return 'f';
if (gender === '비공개') return 'n';
return gender; // 유효하지 않은 값은 그대로 반환
}

// DB에서 값을 가져올 때
static convertGenderToKorean(gender) {
if (gender === 'm') return '남성';
if (gender === 'f') return '여성';
if (gender === 'n') return '비공개';
return gender; // 유효하지 않은 값은 그대로 반환
}
}

module.exports = SignupDto;
module.exports = signupDto;
1 change: 0 additions & 1 deletion src/domains/auth/signup/signupRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const createUser = async (SignupDto) => {
});
return user;
} catch (error) {
console.error('Error stack trace:', error.stack); // 에러 스택을 콘솔에 출력
logger.error(`createUser failed: ${error.message}`);
throw new Error('createUser 실패');
}
Expand Down
27 changes: 14 additions & 13 deletions src/domains/auth/signup/signupService.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,23 @@ const bcrypt = require('bcryptjs');
const signupRepository = require('./signupRepository');
const SignupDto = require('./signupDto');

const signupUser = async (signupData) => {
const createUser = async (userData) => {
// 비밀번호 해싱
const hashedPassword = await bcrypt.hash(signupData.password, 10);
const hashedPassword = await bcrypt.hash(userData.password, 10);

const signupDto = new SignupDto(
signupData.username,
hashedPassword,
signupData.name,
signupData.gender,
signupData.birthDate
);
const gender = SignupDto.convertGenderToEnglish(userData.gender);

// 유저 생성
const newUser = await signupRepository.createUser(signupDto);
// DB에 저장할 객체 생성
const newUser = {
username: userData.username,
password: hashedPassword,
name: userData.name,
gender: gender,
birthDate: userData.birthDate,
};

return newUser;
// 유저 생성
return await signupRepository.createUser(newUser);
};

module.exports = { signupUser };
module.exports = { createUser };
83 changes: 50 additions & 33 deletions src/domains/auth/signup/signupValidation.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,60 @@ const { prisma } = require('../../../config/db');

const signupValidation = [
body('username')
.isString()
.notEmpty()
.withMessage('Username is required.')
.isLength({ min: 5, max: 20 })
.withMessage('Username must be between 5 and 20 characters.')
.custom(async (value) => {
// Prisma를 사용해 username 중복 확인
const existingUser = await prisma.user.findUnique({
where: { username: value },
});

if (existingUser) {
throw new Error('Username already exists. Please choose another.');
}
}),
.notEmpty().withMessage('Username is required.')
.isString().withMessage('Username must be a string.')
.isLength({ min: 5, max: 20 }).withMessage('Username must be between 5 and 20 characters.')
.custom(async (value) => {
const existingUser = await prisma.user.findUnique({
where: { username: value },
});
if (existingUser) {
throw new Error('Username already exists. Please choose another.');
}
}),

body('password')
.isString()
.notEmpty()
.withMessage('Password is required.')
.isLength({ min: 8 })
.withMessage('Password must be at least 8 characters long.'),

body('gender')
.isIn(['남성', '여성', '비공개'])
.withMessage('Gender must be either 남성, 여성, or 비공개.'),

body('birthDate')
.matches(/^\d{4}-\d{2}-\d{2}$/) // YYYY-MM-DD 형식을 검증하는 정규 표현식
.withMessage('Birthdate must be a valid date in YYYY-MM-DD format.'),
.notEmpty().withMessage('Password is required.')
.custom((value) => {
if (value && value.length < 8) {
throw new Error('Password must be at least 8 characters long.');
}
return true;
}),

body('name')
.isString()
.notEmpty()
.withMessage('Name is required.')
.isLength({ max: 50 })
.withMessage('Name must not exceed 50 characters.')
.custom((value) => {
if (!value) {
throw new Error('Name is required.');
}
if (typeof value !== 'string') {
throw new Error('Name must be a string.');
}
if (value.length > 50) {
throw new Error('Name must not exceed 50 characters.');
}
return true;
}),

body('gender')
.notEmpty().withMessage('Gender is required.')
.custom((value) => {
if (value && !['남성', '여성', '비공개'].includes(value)) {
throw new Error('Gender must be either 남성, 여성, or 비공개.');
}
return true;
}),

body('birthDate')
.custom((value) => {
if (!value) {
throw new Error('Birthdate is required.');
}
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
throw new Error('Birthdate must be a valid date in YYYY-MM-DD format.');
}
return true;
}),
];

module.exports = { signupValidation };
12 changes: 7 additions & 5 deletions src/domains/timeline/controller/timelineController.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const timelineService = require('../service/timelineService');
const createTimeline = async (req, res) => {
const userId = req.user.id; // JWT 토큰에서 사용자 정보 가져오기
const { name, items } = req.body; // 요청 본문에서 타임라인 이름과 아이템들 받기
logger.info(`Received request to create timeline: userId = ${userId}, name = ${name}`);
logger.info(`Received request to create timeline: ${req.method} ${req.originalUrl}, userId=${userId}, name=${name}`);

if (!name) {
return res.status(400).json({ success: false, message: 'Timeline name is required' });
Expand All @@ -26,7 +26,7 @@ const createTimeline = async (req, res) => {
const deleteTimeline = async (req, res) => {
const userId = req.user.id;
const { timelineId } = req.params;
logger.info(`Received request to delete timeline: userId = ${userId}, timelineId = ${timelineId}`);
logger.info(`Received request to delete timeline: ${req.method} ${req.originalUrl}, userId=${userId}, timelineId=${timelineId}`);

try {
const isDeleted = await timelineService.deleteTimeline(userId, timelineId);
Expand All @@ -47,7 +47,7 @@ const deleteTimeline = async (req, res) => {
// 타임라인 목록 조회
const getTimelines = async (req, res) => {
const userId = req.user.id;
logger.info(`Received request to fetch timelines for user: ${userId}`);
logger.info(`Received request to fetch timelines for user: ${req.method} ${req.originalUrl}, userId=${userId}`);

try {
const timelines = await timelineService.getTimelines(userId);
Expand All @@ -63,7 +63,7 @@ const getTimelines = async (req, res) => {
const getTimelineById = async (req, res) => {
const { timelineId } = req.params;
const userId = req.user.id;
logger.info(`Received request to fetch a timeline: userId = ${userId}, timelineId = ${timelineId}`);
logger.info(`Received request to fetch a timeline: ${req.method} ${req.originalUrl}, userId=${userId}, timelineId=${timelineId}`);

try {
const timeline = await timelineService.getTimelineById(timelineId, userId);
Expand All @@ -86,7 +86,7 @@ const updateTimeline = async (req, res) => {
const { timelineId } = req.params;
const userId = req.user.id;
const { name, items } = req.body;
logger.info(`Received request to update a timeline: userId = ${userId}, timelineId = ${timelineId}`);
logger.info(`Received request to update a timeline: ${req.method} ${req.originalUrl}, userId=${userId}, timelineId=${timelineId}`);

try {
const updatedTimeline = await timelineService.updateTimeline(userId, timelineId, name, items);
Expand All @@ -101,6 +101,7 @@ const updateTimeline = async (req, res) => {
} catch (error) {
// 순서 이상 오류 처리
if (error.code === 'INVALID_POSITIONS') {
logger.warn(`Invalid positions error: ${error.message}`);
return res.status(400).json({
success: false,
message: 'Positions must be consecutive starting from 1 and cannot have duplicates.',
Expand All @@ -109,6 +110,7 @@ const updateTimeline = async (req, res) => {

// 중복된 scrapId 오류 처리
if (error.code === 'DUPLICATE_SCRAPS') {
logger.warn(`Duplicate scrapId error: ${error.message}`);
return res.status(400).json({
success: false,
message: 'Duplicate scrapId found within the same timeline.',
Expand Down
8 changes: 4 additions & 4 deletions src/domains/timeline/service/timelineService.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ const deleteTimeline = async (userId, timelineId) => {
logger.info(`Successfully deleted timeline ${timelineId} for user ${userId}`);
return true;
} catch (error) {
logger.error(`Error deleting timeline ${timelineId}: ${error.message}`);
logger.error(`Error deleting timeline ${timelineId}: ${error.message}\nStack trace: ${error.stack}`);
throw error;
}
};
Expand All @@ -137,7 +137,7 @@ const getTimelines = async (userId) => {

return timelines;
} catch (error) {
logger.error(`Error retrieving timelines for user: ${userId}, Error: ${error.message}`);
logger.error(`Error retrieving timelines for user: ${userId}, Error: ${error.message}\nStack trace: ${error.stack}`);
throw error;
}
};
Expand All @@ -163,7 +163,7 @@ const getTimelineById = async (timelineId, userId) => {
logger.info(`Successfully retrieved timeline with ID: ${timelineId} for user: ${userId}`);
return { ...timeline, items: itemsWithArticles };
} catch (error) {
logger.error(`Error retrieving timeline with ID: ${timelineId} for user: ${userId}, Error: ${error.message}`);
logger.error(`Error retrieving timeline with ID: ${timelineId} for user: ${userId}, Error: ${error.message}\nStack trace: ${error.stack}`);
throw error;
}
};
Expand Down Expand Up @@ -233,7 +233,7 @@ const updateTimeline = async (userId, timelineId, name, items) => {
return updatedTimeline;
});
} catch (error) {
logger.error(`Error updating timeline: ${error.message}, userId = ${userId}, timelineId = ${timelineId}`);
logger.error(`Error updating timeline: ${error.message}, userId = ${userId}, timelineId = ${timelineId}\nStack trace: ${error.stack}`);
throw error;
}
};
Expand Down