Skip to content
Snippets Groups Projects
Commit b3bea64d authored by tpgus2603's avatar tpgus2603
Browse files

refactor,feature: 트랜잭션 적용 로직 및 검증로직 보강(#12)

parent a8df7a09
No related branches found
No related tags found
2 merge requests!31Develop,!19미팅방 서비스 관련 검증 로직 보강 및 트랜잭션 적용
This commit is part of merge request !19. Comments created here will be created in the context of that merge request.
// controllers/meetingController.js
const MeetingService = require('../services/meetingService');
const CreateMeetingRequestDTO = require('../dtos/CreateMeetingRequestDTO');
class MeetingController {
/**
* 번개 모임 생성
* POST /api/meetings
*/
async createMeeting(req, res) {
try {
const result = await MeetingService.createMeeting(req.body);
const userId = req.userId; // 인증 미들웨어를 통해 설정된 사용자 ID
const meetingData = { ...req.body, created_by: userId };
// CreateMeetingRequestDTO를 사용하여 요청 데이터 검증
const createMeetingDTO = new CreateMeetingRequestDTO(meetingData);
createMeetingDTO.validate();
const result = await MeetingService.createMeeting(meetingData);
res.status(201).json(result);
} catch (err) {
console.error('번개 모임 생성 오류:', err);
......@@ -11,14 +25,14 @@ class MeetingController {
}
}
/**
* 번개 모임 목록 조회
* GET /api/meetings
*/
async getMeetings(req, res) {
const { userId } = req.query;
if (!userId) {
return res.status(400).json({ error: '사용자 ID가 필요합니다.' });
}
try {
const userId = req.userId; // 인증 미들웨어를 통해 설정된 사용자 ID
const meetings = await MeetingService.getMeetings(userId);
res.status(200).json(meetings);
} catch (err) {
......@@ -27,6 +41,10 @@ class MeetingController {
}
}
/**
* 번개 모임 마감
* PATCH /api/meetings/:meetingId/close
*/
async closeMeeting(req, res) {
const { meetingId } = req.params;
......@@ -39,12 +57,16 @@ class MeetingController {
}
}
/**
* 번개 모임 참가
* POST /api/meetings/:meetingId/join
*/
async joinMeeting(req, res) {
try {
const { meetingId } = req.params;
const { user_id } = req.body;
const userId = req.userId; // 인증 미들웨어를 통해 설정된 사용자 ID
try {
await MeetingService.joinMeeting(meetingId, user_id);
await MeetingService.joinMeeting(meetingId, userId);
res.status(200).json({ message: '모임 및 채팅방 참가 완료' });
} catch (err) {
console.error('모임 참가 오류:', err);
......@@ -52,6 +74,10 @@ class MeetingController {
}
}
/**
* 번개 모임 상세 조회
* GET /api/meetings/:meetingId
*/
async getMeetingDetail(req, res) {
const { meetingId } = req.params;
......
// dtos/CreateMeetingRequestDTO.js
const Joi = require('joi');
class CreateMeetingRequestDTO {
constructor({ title, description, start_time, end_time, location, deadline, type, created_by }) {
this.title = title;
this.description = description;
this.start_time = start_time;
this.end_time = end_time;
this.location = location;
this.deadline = deadline;
this.type = type;
this.created_by = created_by;
}
validate() {
const schema = Joi.object({
title: Joi.string().min(1).max(255).required(),
description: Joi.string().allow('', null).optional(),
start_time: Joi.date().iso().required(),
end_time: Joi.date().iso().greater(Joi.ref('start_time')).required(),
location: Joi.string().allow('', null).optional(),
deadline: Joi.date().iso().greater(Joi.ref('start_time')).optional(),
type: Joi.string().valid('OPEN', 'CLOSE').required(),
created_by: Joi.number().integer().positive().required()
});
const { error } = schema.validate(this, { abortEarly: false });
if (error) {
const errorMessages = error.details.map(detail => detail.message).join(', ');
throw new Error(`Validation error: ${errorMessages}`);
}
return true;
}
}
module.exports = CreateMeetingRequestDTO;
class MeetingDetailResponse {
// dtos/MeetingDetailResponseDTO.js
class MeetingDetailResponseDTO {
constructor(meeting) {
this.id = meeting.id;
this.title = meeting.title;
......@@ -17,4 +19,4 @@ class MeetingDetailResponse {
}
}
module.exports = MeetingDetailResponse;
\ No newline at end of file
module.exports = MeetingDetailResponseDTO;
// dtos/MeetingResponse.js
// dtos/MeetingResponseDTO.js
class MeetingResponse {
class MeetingResponseDTO {
constructor(meeting, isParticipant, isScheduleConflict, creatorName) {
this.id = meeting.id;
this.title = meeting.title;
......@@ -16,4 +16,4 @@ class MeetingResponse {
}
}
module.exports = MeetingResponse;
\ No newline at end of file
module.exports = MeetingResponseDTO;
// services/meetingService.js
const { v4: uuidv4 } = require('uuid');
const { Meeting, MeetingParticipant, User } = require('../models');
const { Op } = require('sequelize');
const { Meeting, MeetingParticipant, User, Schedule } = require('../models');
const ChatRoom = require('../models/chatRooms');
const chatController = require('../controllers/chatController');
const MeetingResponse = require('../dtos/MeetingResponse');
const MeetingResponseDTO = require('../dtos/MeetingResponseDTO');
const MeetingDetailResponseDTO = require('../dtos/MeetingDetailResponseDTO');
const CreateMeetingRequestDTO = require('../dtos/CreateMeetingRequestDTO');
const ScheduleService = require('./scheduleService'); // ScheduleService 임포트
class MeetingService {
/**
* 번개 모임 생성
* @returns 생성된 모임 ID와 채팅방 ID
*/
async createMeeting(meetingData) {
// DTO를 사용하여 요청 데이터 검증
const createMeetingDTO = new CreateMeetingRequestDTO(meetingData);
createMeetingDTO.validate();
const { title, description, start_time, end_time, location, deadline, type, created_by } = meetingData;
// 사용자 존재 여부 확인
const user = await User.findOne({ where: { id: created_by } });
if (!user) {
throw new Error('사용자를 찾을 수 없습니다.');
}
// 스케줄 충돌 확인
const hasConflict = await ScheduleService.checkScheduleOverlap(
created_by,
new Date(start_time),
new Date(end_time)
);
if (hasConflict) {
throw new Error('스케줄이 겹칩니다. 다른 시간을 선택해주세요.');
}
// 트랜잭션을 사용하여 모임 생성과 스케줄 추가를 원자적으로 처리
const result = await Meeting.sequelize.transaction(async (transaction) => {
// 채팅방 생성
const chatRoomData = {
participants: [user.name],
};
......@@ -25,6 +52,7 @@ class MeetingService {
const chatRoomId = chatRoomResponse.chatRoomId;
// 모임 생성
const newMeeting = await Meeting.create({
title,
description,
......@@ -35,16 +63,33 @@ class MeetingService {
type,
created_by,
chatRoomId,
});
}, { transaction });
// 모임 참가자 추가 (생성자 자신)
await MeetingParticipant.create({
meeting_id: newMeeting.id,
user_id: created_by,
}, { transaction });
// 스케줄 추가
await ScheduleService.createSchedule({
userId: created_by,
title: `번개 모임: ${title}`,
start_time: new Date(start_time),
end_time: new Date(end_time),
is_fixed: true,
});
return { meeting_id: newMeeting.id, chatRoomId };
});
return { meeting_id: newMeeting.id, chatRoomId: chatRoomResponse.chatRoomId };
return result;
}
/**
* 번개 모임 목록 조회
* @return:모임 목록 DTO 배열
*/
async getMeetings(userId) {
const meetings = await Meeting.findAll({
attributes: ['id', 'title', 'description', 'start_time', 'end_time', 'location', 'deadline', 'type'],
......@@ -66,15 +111,19 @@ class MeetingService {
const creatorName = meeting.creator ? meeting.creator.name : 'Unknown';
const isParticipant = meeting.participants.some(participant => participant.user_id === parseInt(userId, 10));
return new MeetingResponse(
return new MeetingResponseDTO(
meeting,
isParticipant,
false,
false, // isScheduleConflict: 필요 시 추가 로직 구현
creatorName
);
});
}
/**
* 번개 모임 마감
* @returns 마감된 모임 객체
*/
async closeMeeting(meetingId) {
const meeting = await Meeting.findByPk(meetingId);
if (!meeting) {
......@@ -90,6 +139,8 @@ class MeetingService {
return meeting;
}
//번개모임 참가
async joinMeeting(meetingId, userId) {
const meeting = await Meeting.findByPk(meetingId);
if (!meeting) {
......@@ -112,10 +163,33 @@ class MeetingService {
throw new Error('이미 참가한 사용자입니다.');
}
await MeetingParticipant.create({ meeting_id: meetingId, user_id: userId });
// 트랜잭션을 사용하여 참가자 추가 및 스케줄 업데이트를 원자적으로 처리
await Meeting.sequelize.transaction(async (transaction) => {
// 참가자 추가
await MeetingParticipant.create({ meeting_id: meetingId, user_id: userId }, { transaction });
// 스케줄 충돌 확인
const hasConflict = await ScheduleService.checkScheduleOverlap(
userId,
new Date(meeting.start_time),
new Date(meeting.end_time)
);
if (hasConflict) {
throw new Error('스케줄이 겹칩니다. 다른 모임에 참가하세요.');
}
// 스케줄 추가
await ScheduleService.createSchedule({
userId: userId,
title: `번개 모임: ${meeting.title}`,
start_time: new Date(meeting.start_time),
end_time: new Date(meeting.end_time),
is_fixed: true,
});
// 채팅방 참가
const user = await User.findOne({ where: { id: userId } });
const chatRoom = await ChatRoom.findOne({ meeting_id: meetingId });
const chatRoom = await ChatRoom.findOne({ where: { meeting_id: meetingId } });
if (chatRoom && !chatRoom.participants.includes(user.name)) {
chatRoom.participants.push(user.name);
......@@ -124,8 +198,13 @@ class MeetingService {
chatRoom.lastReadLogId.set(user.name, null);
await chatRoom.save();
}
});
}
/**
* 번개 모임 상세 조회
* @return 모임 상세 DTO
*/
async getMeetingDetail(meetingId) {
const meeting = await Meeting.findByPk(meetingId, {
include: [
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment