Skip to content
Snippets Groups Projects
Commit 40950690 authored by 세현 임's avatar 세현 임
Browse files

[#16] 미팅방 컬럼 추가 및 초대로직

parents 9a073dfb 82f600bc
No related branches found
No related tags found
2 merge requests!31Develop,!24[#16] 미팅방 컬럼 추가 및 초대로직
// models/Invite.js
const { DataTypes } = require('sequelize');
const sequelize = require('../config/sequelize');
const User = require('./User');
const Meeting = require('./Meeting');
const Invite = sequelize.define('Invite', {
status: {
type: DataTypes.ENUM('PENDING', 'ACCEPTED', 'DECLINED'),
allowNull: false,
defaultValue: 'PENDING',
},
}, {
tableName: 'Invites',
timestamps: true,
underscored: true,
indexes: [
{
unique: true,
fields: ['meeting_id', 'invitee_id']
},
{
fields: ['status']
}
]
});
// 관계 설정
// Invite.belongsTo(Meeting, { foreignKey: 'meeting_id', as: 'meeting' });
// Invite.belongsTo(User, { foreignKey: 'inviter_id', as: 'inviter' }); // 초대한 사용자
// Invite.belongsTo(User, { foreignKey: 'invitee_id', as: 'invitee' }); // 초대받은 사용자
// User.hasMany(Invite, { foreignKey: 'inviter_id', as: 'sentInvites' }); // 보낸 초대 목록
// User.hasMany(Invite, { foreignKey: 'invitee_id', as: 'receivedInvites' }); // 받은 초대 목록
// Meeting.hasMany(Invite, { foreignKey: 'meeting_id', as: 'invites' }); // 해당 미팅의 모든 초대
module.exports = Invite;
\ No newline at end of file
// models/Meeting.js
const { DataTypes } = require('sequelize');
const sequelize = require('../config/sequelize');
const User = require('./user');
const sequelize = require('../config/sequelize');
const User = require('./User');
const Meeting = sequelize.define('Meeting', {
title: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.TEXT,
},
time_idx_start: {
type: DataTypes.INTEGER,
allowNull: false,
},
time_idx_end: {
type: DataTypes.INTEGER,
allowNull: false,
},
location: {
type: DataTypes.STRING,
},
time_idx_deadline: {
type: DataTypes.INTEGER,
},
type: {
type: DataTypes.ENUM('OPEN', 'CLOSE'),
allowNull: false,
},
title: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.TEXT,
},
time_idx_start: {
type: DataTypes.INTEGER,
allowNull: false,
},
time_idx_end: {
type: DataTypes.INTEGER,
allowNull: false,
},
location: {
type: DataTypes.STRING,
},
time_idx_deadline: {
type: DataTypes.INTEGER,
},
type: {
type: DataTypes.ENUM('OPEN', 'CLOSE'),
allowNull: false,
defaultValue: 'OPEN',
},
chatRoomId: {
type: DataTypes.UUID,
allowNull: false,
},
max_num: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 10, // 기본값 설정 (필요에 따라 조정)
},
cur_num: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1, // 생성자 자신 포함
},
}, {
tableName: 'Meetings',
timestamps: false,
tableName: 'Meetings',
timestamps: true,
underscored: true,
});
module.exports = Meeting;
// routes/inviteRoutes.js
const express = require('express');
const router = express.Router();
const inviteController = require('../controllers/inviteController');
const { isLoggedIn } = require('../middlewares/auth');
router.use(isLoggedIn);
// 초대 응답
router.post('/respond', async (req, res) => {
const { inviteId, response } = req.body;
const userId = req.user.id; // 인증된 사용자 ID
try {
const result = await inviteController.respondToInvite(inviteId, userId, response);
res.status(200).json({ success: true, result });
} catch (error) {
res.status(400).json({ success: false, message: error.message });
}
});
// 받은 초대 조회
router.get('/received', async (req, res) => {
const userId = req.user.id; // 인증된 사용자 ID
try {
const invites = await inviteController.getReceivedInvites(userId);
res.status(200).json({ success: true, invites });
} catch (error) {
res.status(400).json({ success: false, message: error.message });
}
});
module.exports = router;
......@@ -2,9 +2,8 @@
const { v4: uuidv4 } = require('uuid');
const { Op } = require('sequelize');
const sequelize = require('../config/sequelize'); // 트랜잭션 관리를 위해 sequelize 인스턴스 필요
const { Meeting, MeetingParticipant, User, Schedule } = require('../models');
const { Meeting, MeetingParticipant, User, Schedule, Invite, Friend } = require('../models');
const ChatRooms = require('../models/ChatRooms');
const MeetingResponseDTO = require('../dtos/MeetingResponseDTO');
const MeetingDetailResponseDTO = require('../dtos/MeetingDetailResponseDTO');
const CreateMeetingRequestDTO = require('../dtos/CreateMeetingRequestDTO');
......@@ -27,11 +26,6 @@ class MeetingService {
return totalIdx;
}
/**
* 번개 모임 생성
* @param {object} meetingData - 모임 생성 데이터
* @returns {Promise<object>} - 생성된 모임 ID와 채팅방 ID
*/
async createMeeting(meetingData) {
// DTO를 사용하여 요청 데이터 검증
const createMeetingDTO = new CreateMeetingRequestDTO(meetingData);
......@@ -46,6 +40,7 @@ class MeetingService {
time_idx_deadline,
type,
created_by,
max_num,
} = meetingData;
// 사용자 존재 여부 확인
......@@ -81,6 +76,8 @@ class MeetingService {
type,
created_by,
chatRoomId,
max_num, // max_num 추가
cur_num: 1, // 생성자 자신 포함
},
{ transaction }
);
......@@ -99,7 +96,6 @@ class MeetingService {
for (let idx = time_idx_start; idx <= time_idx_end; idx++) {
events.push({ time_idx: idx });
}
await ScheduleService.createSchedules(
{
userId: created_by,
......@@ -110,186 +106,326 @@ class MeetingService {
transaction
);
return { meeting_id: newMeeting.id, chatRoomId };
// 친구 초대 로직 호출
const invitedFriendIds = await this.sendInvites({
meetingId: newMeeting.id,
creatorId: created_by,
time_idx_start,
time_idx_end,
}, transaction);
return { meeting_id: newMeeting.id, chatRoomId, invitedFriendIds };
});
return result;
}
/**
* 번개 모임 목록 조회
* @param {number} userId - 사용자 ID
* @returns {Promise<Array<MeetingResponseDTO>>} - 모임 목록 DTO 배열
*/
async getMeetings(userId) {
const meetings = await Meeting.findAll({
attributes: [
'id',
'title',
'description',
'time_idx_start',
'time_idx_end',
'location',
'time_idx_deadline',
'type',
],
include: [
{
model: MeetingParticipant,
as: 'participants',
where: { user_id: userId }, // userId와 매핑된 미팅만 가져옴
attributes: [], // MeetingParticipant 테이블의 데이터는 필요 없으므로 제외
},
{
model: User,
as: 'creator',
attributes: ['name'], // 미팅 생성자의 이름만 필요
},
],
});
async sendInvites({ meetingId, creatorId, time_idx_start, time_idx_end }, transaction) {
// 1. 친구 목록 가져오기 (ACCEPTED 상태)
const friends = await Friend.findAll({
where: {
[Op.or]: [
{ requester_id: creatorId, status: 'ACCEPTED' },
{ receiver_id: creatorId, status: 'ACCEPTED' },
],
},
transaction,
});
const friendIds = friends.map(friend =>
friend.requester_id === creatorId ? friend.receiver_id : friend.requester_id
);
if (friendIds.length === 0) {
// 친구가 없거나 모든 친구가 초대받지 못함
return [];
}
const schedules = await Schedule.findAll({
where: {
user_id: { [Op.in]: friendIds },
time_idx: {
[Op.between]: [time_idx_start, time_idx_end],
},
},
transaction,
});
// 스케줄이 겹치는 친구 ID를 추출
const conflictedFriendIds = schedules.map(schedule => schedule.user_id);
// 스케줄이 겹치지 않는 친구 ID 필터링
const availableFriendIds = friendIds.filter(friendId => !conflictedFriendIds.includes(friendId));
if (availableFriendIds.length === 0) {
// 스케줄이 겹치는 친구가 모두 있음
return [];
}
const invitePromises = availableFriendIds.map(inviteeId => {
return Invite.create({
meeting_id: meetingId,
inviter_id: creatorId,
invitee_id: inviteeId,
status: 'PENDING',
}, { transaction });
});
await Promise.all(invitePromises);
return availableFriendIds;
}
return meetings.map((meeting) => {
const creatorName = meeting.creator ? meeting.creator.name : 'Unknown';
return new MeetingResponseDTO(meeting, true, false, creatorName);
});
}
async joinMeeting(meetingId, userId) {
const meeting = await Meeting.findByPk(meetingId);
console.log(`참여하려는 모임: ${JSON.stringify(meeting)}`);
if (!meeting) {
throw new Error('모임을 찾을 수 없습니다.');
}
if (meeting.type === 'CLOSE') {
throw new Error('이미 마감된 모임입니다.');
}
if (meeting.time_idx_deadline !== undefined) {
const currentTimeIdx = this.getCurrentTimeIdx(); // 현재 시간 인덱스
if (currentTimeIdx >= meeting.time_idx_deadline) {
throw new Error('참가 신청이 마감되었습니다.');
}
}
const existingParticipant = await MeetingParticipant.findOne({
where: { meeting_id: meetingId, user_id: userId },
});
if (existingParticipant) {
throw new Error('이미 참가한 사용자입니다.');
}
/**
* 번개 모임 마감
* @param {number} meetingId - 모임 ID
* @returns {Promise<Meeting>} - 마감된 모임 객체
*/
// 트랜잭션을 사용하여 참가자 추가 및 스케줄 업데이트를 원자적으로 처리
await sequelize.transaction(async (transaction) => {
if (meeting.cur_num >= meeting.max_num) {
throw new Error("모임 인원이 모두 찼습니다.");
}
// 스케줄 충돌 확인
const hasConflict = await ScheduleService.checkScheduleOverlapByTime(
userId,
meeting.time_idx_start,
meeting.time_idx_end,
transaction
);
console.log(`스케줄 충돌 결과: ${hasConflict}`);
if (hasConflict) {
throw new Error("스케줄이 겹칩니다. 다른 모임에 참가하세요.");
}
await MeetingParticipant.create(
{ meeting_id: meetingId, user_id: userId },
{ transaction }
);
// 스케줄 생성 (모임 시간 범위 내 모든 time_idx에 대해 생성)
const events = [];
for (
let idx = meeting.time_idx_start;
idx <= meeting.time_idx_end;
idx++
) {
events.push({ time_idx: idx });
}
await ScheduleService.createSchedules(
{
userId: userId,
title: `번개 모임: ${meeting.title}`,
is_fixed: true,
events: events,
},
transaction
);
// 채팅방 참가 (MongoDB)
const user = await User.findOne({
where: { id: userId },
transaction,
});
const chatRoom = await ChatRooms.findOne({
chatRoomId: meeting.chatRoomId,
});
if (chatRoom && !chatRoom.participants.includes(user.name)) {
chatRoom.participants.push(user.name);
chatRoom.isOnline.set(user.name, true);
chatRoom.lastReadAt.set(user.name, new Date());
chatRoom.lastReadLogId.set(user.name, null);
await chatRoom.save();
}
// 현재 인원 수 증가
await meeting.increment("cur_num", { by: 1, transaction });
});
}
async getMeetings(userId) {
const meetings = await Meeting.findAll({
attributes: [
'id',
'title',
'description',
'time_idx_start',
'time_idx_end',
'location',
'time_idx_deadline',
'type',
'max_num',
'cur_num',
],
include: [
{
model: MeetingParticipant,
as: 'participants',
where: { user_id: userId }, // userId와 매핑된 미팅만 가져옴
attributes: [],
},
{
model: User,
as: 'creator',
attributes: ['name'], // 미팅 생성자의 이름만 필요
},
],
});
return meetings.map((meeting) => {
const creatorName = meeting.creator ? meeting.creator.name : 'Unknown';
return new MeetingResponseDTO(meeting, true, false, creatorName);
});
}
async closeMeeting(meetingId) {
const meeting = await Meeting.findByPk(meetingId);
if (!meeting) {
throw new Error('모임을 찾을 수 없습니다.');
}
if (meeting.type === 'CLOSE') {
throw new Error('이미 마감된 모임입니다.');
}
meeting.type = 'CLOSE';
await meeting.save();
return meeting;
}
/**
* 번개 모임 참가
* @param {number} meetingId - 모임 ID
* @param {number} userId - 사용자 ID
* @returns {Promise<void>}
*/
async joinMeeting(meetingId, userId) {
const meeting = await Meeting.findByPk(meetingId);
console.log(`참여하려는 모임: ${JSON.stringify(meeting)}`);
if (!meeting) {
throw new Error('모임을 찾을 수 없습니다.');
}
if (meeting.type === 'CLOSE') {
throw new Error('이미 마감된 모임입니다.');
}
if (meeting.time_idx_deadline !== undefined) {
const currentTimeIdx = this.getCurrentTimeIdx(); // 현재 시간 인덱스
if (currentTimeIdx >= meeting.time_idx_deadline) {
throw new Error('참가 신청이 마감되었습니다.');
}
}
const existingParticipant = await MeetingParticipant.findOne({
where: { meeting_id: meetingId, user_id: userId },
});
if (existingParticipant) {
throw new Error('이미 참가한 사용자입니다.');
}
// 트랜잭션을 사용하여 참가자 추가 및 스케줄 업데이트를 원자적으로 처리
await sequelize.transaction(async (transaction) => {
// 스케줄 충돌 확인
const hasConflict = await ScheduleService.checkScheduleOverlapByTime(
userId,
meeting.time_idx_start,
meeting.time_idx_end,
transaction
);
console.log(`스케줄 충돌 결과: ${hasConflict}`);
if (hasConflict) {
throw new Error('스케줄이 겹칩니다. 다른 모임에 참가하세요.');
}
// 스케줄 충돌 확인
// 현재 인원 수 확인
if (meeting.cur_num >= meeting.max_num) {
throw new Error("모임 인원이 모두 찼습니다.");
}
// 참가자 추가
await MeetingParticipant.create(
{ meeting_id: meetingId, user_id: userId },
{ transaction }
);
const hasConflict = await ScheduleService.checkScheduleOverlapByTime(
userId,
meeting.time_idx_start,
meeting.time_idx_end,
transaction
);
console.log(`스케줄 충돌 결과: ${hasConflict}`);
if (hasConflict) {
throw new Error("스케줄이 겹칩니다. 다른 모임에 참가하세요.");
}
// 스케줄 생성 (모임 시간 범위 내 모든 time_idx에 대해 생성)
const events = [];
for (let idx = meeting.time_idx_start; idx <= meeting.time_idx_end; idx++) {
events.push({ time_idx: idx });
}
// 참가자 추가
await MeetingParticipant.create(
{ meeting_id: meetingId, user_id: userId },
{ transaction }
);
await ScheduleService.createSchedules(
{
userId: userId,
title: `번개 모임: ${meeting.title}`,
is_fixed: true,
events: events,
},
transaction
);
// 스케줄 생성 (모임 시간 범위 내 모든 time_idx에 대해 생성)
const events = [];
for (
let idx = meeting.time_idx_start;
idx <= meeting.time_idx_end;
idx++
) {
events.push({ time_idx: idx });
}
await ScheduleService.createSchedules(
{
userId: userId,
title: `번개 모임: ${meeting.title}`,
is_fixed: true,
events: events,
},
transaction
);
// 채팅방 참가 (MongoDB)
const user = await User.findOne({ where: { id: userId }, transaction });
const chatRoom = await ChatRooms.findOne({ chatRoomId: meeting.chatRoomId });
// 채팅방 참가 (MongoDB)
const user = await User.findOne({
where: { id: userId },
transaction,
});
const chatRoom = await ChatRooms.findOne({
chatRoomId: meeting.chatRoomId,
});
if (chatRoom && !chatRoom.participants.includes(user.name)) {
chatRoom.participants.push(user.name);
chatRoom.isOnline.set(user.name, true);
chatRoom.lastReadAt.set(user.name, new Date());
chatRoom.lastReadLogId.set(user.name, null);
await chatRoom.save();
}
if (chatRoom && !chatRoom.participants.includes(user.name)) {
chatRoom.participants.push(user.name);
chatRoom.isOnline.set(user.name, true);
chatRoom.lastReadAt.set(user.name, new Date());
chatRoom.lastReadLogId.set(user.name, null);
await chatRoom.save();
}
// 현재 인원 수 증가
await meeting.increment("cur_num", { by: 1, transaction });
});
}
/**
* 번개 모임 상세 조회
* @param {number} meetingId - 모임 ID
* @returns {Promise<MeetingDetailResponseDTO>} - 모임 상세 DTO
*/
// services/meetingService.js
async getMeetingDetail(meetingId) {
const meeting = await Meeting.findByPk(meetingId, {
include: [
{
model: User,
as: "creator",
attributes: ["name"],
},
{
model: MeetingParticipant,
as: "participants",
include: [
{
model: User,
as: "user", // 'participantUser'에서 'user'로 수정
attributes: ["name", "email"],
},
],
},
],
});
async getMeetingDetail(meetingId) {
const meeting = await Meeting.findByPk(meetingId, {
include: [
{
model: User,
as: "creator",
attributes: ["name"],
},
{
model: MeetingParticipant,
as: "participants",
include: [
{
model: User,
as: "user", // 'participantUser'에서 'user'로 수정
attributes: ["name", "email"],
},
],
},
],
});
if (!meeting) {
throw new Error("모임을 찾을 수 없습니다.");
}
if (!meeting) {
throw new Error("모임을 찾을 수 없습니다.");
}
return new MeetingDetailResponseDTO(meeting);
}
return new MeetingDetailResponseDTO(meeting);
}
}
module.exports = new MeetingService();
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment