diff --git a/.gitignore b/.gitignore index c95fa60cf8457ba1592eea59de29eea125ba3f81..24ef659c2cf61dbd74c43493f226e7aae3e13a59 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ resources/ app.js output.log weblog.log +start.sh +start2.sh diff --git a/controllers/chatController.js b/controllers/chatController.js index bfcca38df3a7ab0b513d3c4bee2c46533226b09b..33eac1e96479dcaa47901914a117d3a51b173d36 100644 --- a/controllers/chatController.js +++ b/controllers/chatController.js @@ -13,7 +13,12 @@ exports.createChatRoom = async (params) => { // 梨꾪똿諛� 紐⑸줉 議고쉶 exports.getChatRooms = async (req, res) => { try { - const roomData = await chatService.getChatRooms(); + + const name = req.user.name; // Google 濡쒓렇�몄뿉�� 媛��몄삩 email + console.log("name", name); + + // 蹂몄씤�� 李멸��먮줈 �ы븿�� 梨꾪똿諛⑸쭔 媛��몄삤湲� + const roomData = await chatService.getChatRooms(name); res.json(roomData); } catch (err) { console.error('Error fetching rooms:', err); diff --git a/controllers/friendController.js b/controllers/friendController.js index 09cbf577aa6ce94f6e9bc4ac52d902c5d97e6ae5..19dcb98da85bcb90787b6571b6453dec88ab2d71 100644 --- a/controllers/friendController.js +++ b/controllers/friendController.js @@ -12,7 +12,7 @@ class friendController { async sendFriendRequest(req, res) { try { return await performanceMonitor.measureAsync('sendFriendRequest', async () => { - const email = req.body; + const { email } = req.body; const userId = req.user.id; if (!userId || !email) { @@ -97,9 +97,29 @@ class friendController { async acceptRequest(req, res) { try { return await performanceMonitor.measureAsync('acceptFriendRequest', async () => { + if (!req.user || !req.user.id) { + return res.status(401).json({ + success: false, + error: { + message: '�몄쬆�섏� �딆� �ъ슜�먯엯�덈떎.', + code: 'UNAUTHORIZED' + } + }); + } + const userId = req.user.id; - const { friendId } = req.params; - + const friendId = parseInt(req.params.friendId, 10); + + if (!friendId || isNaN(friendId)) { + return res.status(400).json({ + success: false, + error: { + message: '�좏슚�섏� �딆� 移쒓뎄 ID�낅땲��.', + code: 'INVALID_FRIEND_ID' + } + }); + } + const result = await FriendService.acceptFriendRequest(userId, friendId); return res.status(200).json({ success: true, @@ -107,6 +127,7 @@ class friendController { }); }); } catch (error) { + console.error('Friend request accept error:', error); return res.status(400).json({ success: false, error: { @@ -162,7 +183,9 @@ class friendController { return res.status(200).json({ success: true, - data: friends + data: { + ...friends + } }); }); } catch (error) { diff --git a/controllers/meetingController.js b/controllers/meetingController.js index 78f4464ff4d7bebb4c3a425ce3154bb4c1e5c042..ec2379a1dbf4a3f6cad3520006d4c0c54b1d0a1c 100644 --- a/controllers/meetingController.js +++ b/controllers/meetingController.js @@ -108,8 +108,9 @@ class MeetingController { */ async getMeetingDetail(req, res) { const { meetingId } = req.params; + const userId=req.user.id; try { - const meetingDetail = await MeetingService.getMeetingDetail(meetingId); + const meetingDetail = await MeetingService.getMeetingDetail(meetingId,userId); res.status(200).json(meetingDetail); } catch (err) { console.error('紐⑥엫 �곸꽭 議고쉶 �ㅻ쪟:', err); @@ -165,6 +166,24 @@ class MeetingController { } } + /** + * 踰덇컻 紐⑥엫 ��젣 + * DELETE /api/meeting/:meetingId + */ + // controllers/meetingController.js + async deleteMeeting(req, res) { + const { meetingId } = req.params; + const userId = req.user.id; + + try { + await MeetingService.deleteMeeting(meetingId, userId); + res.status(200).json({ message: '紐⑥엫�� ��젣�섏뿀�듬땲��.' }); + } catch (err) { + console.error('紐⑥엫 ��젣 �ㅻ쪟:', err); + res.status(500).json({ error: err.message || '紐⑥엫 ��젣 �ㅽ뙣' }); + } + } + } module.exports = new MeetingController(); diff --git a/dtos/CreateMeetingRequestDTO.js b/dtos/CreateMeetingRequestDTO.js index c61b0691683a7b2bdd7e96de219c732072065ec1..5d3feebfbc1e511e1d020773d399020956920ca6 100644 --- a/dtos/CreateMeetingRequestDTO.js +++ b/dtos/CreateMeetingRequestDTO.js @@ -22,7 +22,7 @@ class CreateMeetingRequestDTO { time_idx_start: Joi.number().integer().min(0).required(), time_idx_end: Joi.number().integer().greater(Joi.ref('time_idx_start')).required(), location: Joi.string().allow('', null).optional(), - time_idx_deadline: Joi.number().integer().min(0).less(Joi.ref('time_idx_start')).optional(), + time_idx_deadline: Joi.number().integer().min(0).max(Joi.ref('time_idx_start')).optional(), type: Joi.string().valid('OPEN', 'CLOSE').required(), created_by: Joi.number().integer().positive().required(), }); diff --git a/dtos/MeetingDetailResponseDTO.js b/dtos/MeetingDetailResponseDTO.js index c9a07edd124db0d59702cc02460122e4a9f23d40..ef60bc6c6a6a82dbcc5c1247b190a50e2080aff8 100644 --- a/dtos/MeetingDetailResponseDTO.js +++ b/dtos/MeetingDetailResponseDTO.js @@ -13,8 +13,8 @@ class MeetingDetailResponseDTO { this.isScheduleConflict = isScheduleConflict; this.participants = meeting.participants.map(participant => ({ userId: participant.user_id, - name: participant.participantUser ? participant.participantUser.name : 'Unknown', - email: participant.participantUser ? participant.participantUser.email : 'Unknown' + name: participant.user ? participant.user.name : 'Unknown', + email: participant.user ? participant.user.email : 'Unknown' })); } } diff --git a/models/index.js b/models/index.js index 7caa67277dc86e3f1dcc043a15e8897cbc7ee641..0383c37c1f0a4a7ede0dc1066ed69fe6cc943cc8 100644 --- a/models/index.js +++ b/models/index.js @@ -78,6 +78,7 @@ User.hasMany(Schedule, { onDelete: 'CASCADE', }); + // Invite 愿�怨� �ㅼ젙 Invite.belongsTo(Meeting, { foreignKey: 'meeting_id', @@ -109,7 +110,17 @@ Meeting.hasMany(Invite, { as: 'invites', onDelete: 'CASCADE', }); - +FcmToken.belongsTo(User,{ + foreignKey:'userId', + as:'user', + onDelete:'CASCADE', + }); +User.hasMany(FcmToken, + { + foreignKey:'userId', + as:'fcmTokenList', + onDelete:'CASCADE', + }); module.exports = { sequelize, @@ -119,5 +130,6 @@ module.exports = { Meeting, MeetingParticipant, Friend, + Invite, FcmToken, }; diff --git a/models/meeting.js b/models/meeting.js index 6ba87388c0db9f04498bcd1618c2d9c6726226a9..99c06b0a83ea722e65abae1cff717513566a54f1 100644 --- a/models/meeting.js +++ b/models/meeting.js @@ -24,6 +24,7 @@ const Meeting = sequelize.define('Meeting', { }, time_idx_deadline: { type: DataTypes.INTEGER, + defaultValue:671, }, type: { type: DataTypes.ENUM('OPEN', 'CLOSE'), @@ -37,7 +38,7 @@ const Meeting = sequelize.define('Meeting', { max_num: { type: DataTypes.INTEGER, allowNull: false, - defaultValue: 10, // 湲곕낯媛� �ㅼ젙 (�꾩슂�� �곕씪 議곗젙) + defaultValue: 20, // 湲곕낯媛� �ㅼ젙 (�꾩슂�� �곕씪 議곗젙) }, cur_num: { type: DataTypes.INTEGER, diff --git a/pushServer.js b/pushServer.js index 76d593d7a5375a3d4278c06a6b7eabf6e29a7093..ea323cea41752696429d8f080ea1316385b6edda 100644 --- a/pushServer.js +++ b/pushServer.js @@ -42,7 +42,7 @@ async function startPushServer() { body: `${sender}: ${messageContent}`, }, data: { - click_action: `http://localhost:3000/chat/chatRoom/${chatRoomId}`, // �대┃ �� �대룞�� URL + click_action: `${process.env.FRONT_URL}/mypage`, // �대┃ �� �대룞�� URL }, android: { priority: 'high' }, apns: { payload: { aps: { sound: 'default' } } }, @@ -71,24 +71,42 @@ async function startPushServer() { channel.consume(meetingQueue, async (msg) => { if (msg !== null) { const event = JSON.parse(msg.content.toString()); - const { meetingTitle, inviterName, inviteeTokens } = event; + const { meetingTitle, inviterName, inviteeTokens, type } = event; console.log('Meeting �몄떆 �뚮┝ �붿껌 �섏떊:', event); console.log("�몄떆 �뚮┝ 蹂대궡�� fcmToken", inviteeTokens); if (inviteeTokens.length > 0) { - const message = { - tokens: inviteeTokens, - notification: { - title: '踰덇컻 紐⑥엫 珥덈�', - body: `${inviterName}�섏씠 ${meetingTitle} 紐⑥엫�� 珥덈��덉뒿�덈떎.`, - }, - data: { - click_action: `http://localhost:3000`, // �대┃ �� �대룞�� URL - }, - android: { priority: 'high' }, - apns: { payload: { aps: { sound: 'default' } } }, - }; + let message; + + // �대깽�� ���낆뿉 �곕씪 �뚮┝ �댁슜 援ъ꽦 + if (type === 'invite') { + message = { + tokens: inviteeTokens, + notification: { + title: '踰덇컻 紐⑥엫 珥덈�', + body: `${inviterName}�섏씠 ${meetingTitle} 踰덇컻紐⑥엫�� 珥덈��덉뒿�덈떎.`, + }, + data: { + click_action: `${process.env.FRONT_URL}/meeting`, // �대┃ �� �대룞�� URL + }, + android: { priority: 'high' }, + apns: { payload: { aps: { sound: 'default' } } }, + }; + } else if (type === 'join') { + message = { + tokens: inviteeTokens, + notification: { + title: `${meetingTitle}`, + body: `${inviterName}�섏씠 ${meetingTitle} 紐⑥엫�� 李멸��덉뒿�덈떎.`, + }, + data: { + click_action: `${process.env.FRONT_URL}/meeting`, // �대┃ �� �대룞�� URL + }, + android: { priority: 'high' }, + apns: { payload: { aps: { sound: 'default' } } }, + }; + } try { const response = await admin.messaging().sendEachForMulticast(message); diff --git a/routes/chatRoute.js b/routes/chatRoute.js index ed55aa560c36105aff3706f702bda26fe0c6f234..555144746f1380785867d9ec5b332cb2a2d62c37 100644 --- a/routes/chatRoute.js +++ b/routes/chatRoute.js @@ -4,7 +4,6 @@ const chatController = require('../controllers/chatController'); const { isLoggedIn } = require('../middlewares/auth'); router.post('/create-room', chatController.createChatRoom); -router.get('/rooms', chatController.getChatRooms); router.post('/update-status', chatController.updateStatus); router.post('/update-read-status', chatController.updateReadStatus); router.get('/unread-messages/:nickname', chatController.getUnreadMessages); @@ -13,6 +12,7 @@ router.post('/update-status-and-logid', chatController.updateStatusAndLogId); router.post('/update-read-log-id', chatController.updateReadLogId); router.use(isLoggedIn); +router.get('/rooms', chatController.getChatRooms); router.post('/:chatRoomId/notices', chatController.addNotice); router.get('/:chatRoomId/notices/latest', chatController.getLatestNotice); diff --git a/routes/meetingRoute.js b/routes/meetingRoute.js index a85d20297d3f8b8f51fd5c11645ac829c9a3d680..edf3593fabd15026cec39c9e5916d84926f6f3c9 100644 --- a/routes/meetingRoute.js +++ b/routes/meetingRoute.js @@ -28,6 +28,7 @@ router.get('/:meetingId', MeetingController.getMeetingDetail); // 踰덇컻 紐⑥엫 �덊눜 router.delete('/:meetingId/leave', MeetingController.leaveMeeting); - +// 踰덇컻 紐⑥엫 ��젣 +router.delete('/:meetingId', MeetingController.deleteMeeting); module.exports = router; \ No newline at end of file diff --git a/services/chatService.js b/services/chatService.js index f67ee428d4efc761df9dd467600ec25eb93e4cbc..f3c810b66ffb91c469ca67d9e4ef3b8a75c5285a 100644 --- a/services/chatService.js +++ b/services/chatService.js @@ -30,15 +30,18 @@ class ChatService { } // 梨꾪똿諛� 紐⑸줉 議고쉶 - async getChatRooms() { - const rooms = await ChatRooms.find({}, { chatRoomId: 1, chatRoomName: 1, messages: { $slice: -1 } }); + async getChatRooms(name) { + const rooms = await ChatRooms.find( + { "participants.name": name }, + { chatRoomId: 1, chatRoomName: 1, messages: { $slice: -1 } } + ); return rooms.map(room => { const lastMessage = room.messages[0] || {}; return { chatRoomId: room.chatRoomId, chatRoomName: room.chatRoomName, lastMessage: { - sender: lastMessage.sender || '�놁쓬', + sender: lastMessage.sender || '�뚮┝', message: lastMessage.message || '硫붿떆吏� �놁쓬', timestamp: lastMessage.timestamp || null, }, diff --git a/services/meetingService.js b/services/meetingService.js index bc7b7f784f9027f83f0ae5f777e50384c0a82e1d..18d9bc726cbc3371917ada6d01cbbc2051195b49 100644 --- a/services/meetingService.js +++ b/services/meetingService.js @@ -39,13 +39,22 @@ class MeetingService { return totalIdx; } - async sendMeetingPushNotificationRequest(meetingTitle, inviterName, inviteeTokens) { + // async sendMeetingPushNotificationRequest(meetingTitle, inviterName, inviteeTokens) { + // const event = { + // meetingTitle, + // inviterName, + // inviteeTokens, + // }; + // await this.publishToQueue('meeting_push_notifications', event); // meeting_push_notifications �먯뿉 硫붿떆吏� 諛쒗뻾 + // } + async sendMeetingPushNotificationRequest(meetingTitle, inviterName, inviteeTokens, type) { const event = { meetingTitle, inviterName, inviteeTokens, + type, // �대깽�� ���� ('invite' �먮뒗 'join') }; - await this.publishToQueue('meeting_push_notifications', event); // meeting_push_notifications �먯뿉 硫붿떆吏� 諛쒗뻾 + await this.publishToQueue('meeting_push_notifications', event); // �먯뿉 硫붿떆吏� 諛쒗뻾 } @@ -67,7 +76,7 @@ class MeetingService { // �ъ슜�먯� FCM �좏겙 議고쉶 const user = await this._findUserWithFcmTokens(created_by); - console.log("user", user); + console.log("user", user); const userFcmTokens = user.fcmTokenList.map((fcmToken) => fcmToken.token); @@ -119,8 +128,8 @@ class MeetingService { ); const time_indices = Array.from( - { length: time_idx_end - time_idx_start + 1 }, - (_, i) => time_idx_start + i + { length: parseInt(time_idx_end) - parseInt(time_idx_start) + 1 }, + (_, i) => (parseInt(time_idx_start) + i).toString() ); await ScheduleService.createSchedules({ userId: created_by, @@ -147,7 +156,12 @@ class MeetingService { // RabbitMQ 硫붿떆吏� 諛쒗뻾 (�몄떆 �뚮┝ �붿껌) if (inviteeTokens.length > 0) { - await this.sendMeetingPushNotificationRequest(title, user.name, inviteeTokens); + await this.sendMeetingPushNotificationRequest( + title, + user.name, + inviteeTokens, + 'invite' + ); } const chatRoom = await ChatRooms.findOne({ chatRoomId: chatRoomId }); @@ -215,7 +229,7 @@ class MeetingService { return availableFriendIds; } - + async joinMeeting(meetingId, userId) { const meeting = await Meeting.findByPk(meetingId); console.log(`李몄뿬�섎젮�� 紐⑥엫: ${JSON.stringify(meeting)}`); @@ -240,81 +254,104 @@ class MeetingService { // �몃옖��뀡�� �ъ슜�섏뿬 李멸��� 異붽� 諛� �ㅼ�以� �낅뜲�댄듃瑜� �먯옄�곸쑝濡� 泥섎━ 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 } - ); - - const time_indices = Array.from( - { length: meeting.time_idx_end - meeting.time_idx_start + 1 }, - (_, i) => meeting.time_idx_start + i - ); - - await ScheduleService.createSchedules({ - userId: userId, - title: `踰덇컻 紐⑥엫: ${meeting.title}`, - is_fixed: false, - time_indices: time_indices, - }, transaction); - - // 梨꾪똿諛� 李멸� (MongoDB) - const user = await User.findOne({ - where: { id: userId }, - include: [ - { - model: FcmToken, - as: 'fcmTokenList', // FCM �좏겙 媛��몄삤湲� - attributes: ['token'], - }, - ], - transaction - }); - - const userFcmTokens = user.fcmTokenList.map((token) => token.token); - - const chatRoom = await ChatRooms.findOne({ - chatRoomId: meeting.chatRoomId, - }); - - console.log("�ш린源뚯�"); - console.log("user.name", user.name); - console.log("李멸��섎뒗 �좎� fcm", userFcmTokens); - if (chatRoom && !chatRoom.participants.includes(user.name)) { - // 李멸��� 異붽� - chatRoom.participants.push({ - name: user.name, - fcmTokens: userFcmTokens, // FCM �좏겙 異붽� + 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 } + ); + + const time_indices = Array.from( + { length: meeting.time_idx_end - meeting.time_idx_start + 1 }, + (_, i) => meeting.time_idx_start + i + ); + + await ScheduleService.createSchedules({ + userId: userId, + title: `踰덇컻 紐⑥엫: ${meeting.title}`, + is_fixed: false, + time_indices: time_indices, + }, transaction); + + // 梨꾪똿諛� 李멸� (MongoDB) + const user = await User.findOne({ + where: { id: userId }, + include: [ + { + model: FcmToken, + as: 'fcmTokenList', // FCM �좏겙 媛��몄삤湲� + attributes: ['token'], + }, + ], + transaction }); - 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 }); + const userFcmTokens = user.fcmTokenList.map((token) => token.token); + + const chatRoom = await ChatRooms.findOne({ + chatRoomId: meeting.chatRoomId, + }); + + console.log("�ш린源뚯�"); + console.log("user.name", user.name); + console.log("李멸��섎뒗 �좎� fcm", userFcmTokens); + if (chatRoom && !chatRoom.participants.includes(user.name)) { + // 李멸��� 異붽� + chatRoom.participants.push({ + name: user.name, + fcmTokens: userFcmTokens, // FCM �좏겙 異붽� + }); + chatRoom.isOnline.set(user.name, false); + chatRoom.lastReadAt.set(user.name, new Date()); + chatRoom.lastReadLogId.set(user.name, null); + + const joinMessage = { + message: `${user.name}�섏씠 李멸��덉뒿�덈떎.`, + timestamp: new Date(), + type: 'join' + }; + + chatRoom.messages.push(joinMessage); + + // 湲곗〈 李멸��� FCM �좏겙 媛��몄삤湲� + const otherParticipants = chatRoom.participants.filter(participant => participant.name !== user.name); + const otherParticipantTokens = otherParticipants.flatMap(participant => participant.fcmTokens); + + if (otherParticipantTokens.length > 0) { + // RabbitMQ 硫붿떆吏� 諛쒗뻾 + await this.sendMeetingPushNotificationRequest( + meeting.title, + user.name, + otherParticipantTokens, + 'join' + ); + } + + await chatRoom.save(); + } + + // �꾩옱 �몄썝 �� 利앷� + await meeting.increment("cur_num", { by: 1, transaction }); }); } - + async getMeetings(userId, pagination) { const { limit = 20, offset = 0 } = pagination; - + try { const meetings = await Meeting.findAll({ attributes: [ @@ -337,7 +374,7 @@ class MeetingService { offset, distinct: true }); - + const hasNext = meetings.length > limit; const content = await Promise.all( meetings.slice(0, limit).map(async (meeting) => { @@ -347,13 +384,13 @@ class MeetingService { user_id: userId } }); - + const hasConflict = await ScheduleService.checkScheduleOverlapByTime( userId, meeting.time_idx_start, meeting.time_idx_end ); - + return new MeetingResponseDTO( meeting, !!isParticipant, @@ -362,7 +399,7 @@ class MeetingService { ); }) ); - + return { content, hasNext }; } catch (error) { console.error('getMeetings error:', error); @@ -372,7 +409,7 @@ class MeetingService { async getMyMeetings(userId, pagination) { const { limit = 20, offset = 0 } = pagination; - + try { const meetings = await Meeting.findAll({ attributes: [ @@ -401,7 +438,7 @@ class MeetingService { offset, distinct: true }); - + const hasNext = meetings.length > limit; const content = meetings.slice(0, limit).map(meeting => { return new MeetingResponseDTO( @@ -411,14 +448,14 @@ class MeetingService { meeting.creator?.name || 'Unknown' ); }); - + return { content, hasNext }; } catch (error) { console.error('getMyMeetings error:', error); throw new Error('Failed to fetch my meetings'); } } - + async getMeetingDetail(meetingId, userId) { const meeting = await Meeting.findByPk(meetingId, { include: [ @@ -440,17 +477,17 @@ class MeetingService { } ] }); - + if (!meeting) { throw new Error("紐⑥엫�� 李얠쓣 �� �놁뒿�덈떎."); } - + const hasConflict = await ScheduleService.checkScheduleOverlapByTime( userId, meeting.time_idx_start, meeting.time_idx_end ); - + return new MeetingDetailResponseDTO(meeting, hasConflict); } @@ -526,13 +563,13 @@ class MeetingService { // ���� chatRoom.save(); } - + async leaveMeeting(meetingId, userId) { const meeting = await Meeting.findByPk(meetingId); if (!meeting) { throw new Error('紐⑥엫�� 李얠쓣 �� �놁뒿�덈떎.'); } - + await sequelize.transaction(async (transaction) => { // 李멸��� �뺤씤 const participant = await MeetingParticipant.findOne({ @@ -542,16 +579,16 @@ class MeetingService { }, transaction }); - + if (!participant) { throw new Error('李멸��섏� �딆� 紐⑥엫�낅땲��.'); } - + // �앹꽦�먮뒗 �덊눜�� �� �놁쓬 if (meeting.created_by === userId) { throw new Error('紐⑥엫 �앹꽦�먮뒗 �덊눜�� �� �놁뒿�덈떎.'); } - + // 李멸��� �쒓굅 await MeetingParticipant.destroy({ where: { @@ -560,7 +597,7 @@ class MeetingService { }, transaction }); - + // 愿��� �ㅼ�以� ��젣 await Schedule.destroy({ where: { @@ -572,7 +609,7 @@ class MeetingService { }, transaction }); - + // 梨꾪똿諛⑹뿉�� �쒓굅 const chatRoom = await ChatRooms.findOne({ chatRoomId: meeting.chatRoomId @@ -583,13 +620,61 @@ class MeetingService { chatRoom.isOnline.delete(user.name); chatRoom.lastReadAt.delete(user.name); chatRoom.lastReadLogId.delete(user.name); + + const leaveMessage = { + message: `${user.name}�섏씠 �댁옣�덉뒿�덈떎.`, + timestamp: new Date(), + type: 'leave' + }; + + chatRoom.messages.push(leaveMessage); + await chatRoom.save(); } - + // �꾩옱 �몄썝 �� 媛먯냼 await meeting.decrement('cur_num', { by: 1, transaction }); }); } + + async deleteMeeting(meetingId, userId) { + const meeting = await Meeting.findByPk(meetingId); + + if (!meeting) { + throw new Error('紐⑥엫�� 李얠쓣 �� �놁뒿�덈떎.'); + } + + if (meeting.created_by !== userId) { + throw new Error('紐⑥엫 �앹꽦�먮쭔 ��젣�� �� �덉뒿�덈떎.'); + } + + return await sequelize.transaction(async (transaction) => { + const participants = await MeetingParticipant.findAll({ + where: { meeting_id: meetingId }, + attributes: ['user_id'], + transaction + }); + + const participantIds = participants.map(p => p.user_id); + + // 紐⑤뱺 李멸��먯쓽 �ㅼ�以� ��젣 + await Schedule.destroy({ + where: { + user_id: { [Op.in]: participantIds }, + title: `踰덇컻 紐⑥엫: ${meeting.title}`, + time_idx: { + [Op.between]: [meeting.time_idx_start, meeting.time_idx_end] + } + }, + transaction + }); + + await ChatRooms.deleteOne({ chatRoomId: meeting.chatRoomId }); + + // 紐⑥엫 愿��� �곗씠�� ��젣 + await meeting.destroy({ transaction }); + }); + } } module.exports = new MeetingService(); \ No newline at end of file diff --git a/services/scheduleService.js b/services/scheduleService.js index 6c66d7ff0398f922ddec76baccc00c2d89fb2a40..b5cd9cd4578efb9e2d563d6edf3fb9052a3e84d0 100644 --- a/services/scheduleService.js +++ b/services/scheduleService.js @@ -10,11 +10,17 @@ class ScheduleService { * @param {object} [transaction] - Sequelize �몃옖��뀡 媛앹껜 -> 誘명똿諛⑹뿉�� �곌린�꾪빐 �몃옖��뀡�� �섍꺼諛쏅뒗嫄� 異붽� */ async createSchedules({ userId, title, is_fixed, time_indices }, transaction = null) { + const parsedTimeIndices = time_indices.map(idx => parseInt(idx, 10)); + + if (!userId || !title || !parsedTimeIndices.length) { + throw new Error('Required parameters missing'); + } + const overlaps = await Schedule.findAll({ where: { user_id: userId, time_idx: { - [Op.in]: time_indices + [Op.in]: parsedTimeIndices } }, transaction @@ -24,7 +30,7 @@ class ScheduleService { throw new Error(`Schedule overlaps at time_idx ${overlaps[0].time_idx}`); } - const scheduleData = time_indices.map(time_idx => ({ + const scheduleData = parsedTimeIndices.map(time_idx => ({ user_id: userId, title, time_idx, @@ -43,7 +49,7 @@ class ScheduleService { user_id: userId, title, is_fixed, - time_indices, + time_indices: parsedTimeIndices, createdAt: createdSchedules[0].createdAt, updatedAt: createdSchedules[0].updatedAt }; diff --git a/sync.js b/sync.js index 3890bf2b4eb0b96a4d61b840044ded103e2623a5..1d947ffe5b13905292566acc048a83c5f2e25711 100644 --- a/sync.js +++ b/sync.js @@ -7,16 +7,15 @@ async function syncRdb() { try { // �곗씠�곕쿋�댁뒪 �곌껐 �뚯뒪�� await sequelize.authenticate(); - console.log('Rdb�곗씠�곕쿋�댁뒪 �곌껐 �깃났.'); + console.log('Rdb �곗씠�곕쿋�댁뒪 �곌껐 �깃났.'); - // 紐⑤뱺 紐⑤뜽 �숆린�� - await sequelize.sync({ force: true }); - console.log('紐⑤뱺 紐⑤뜽�� �깃났�곸쑝濡� �숆린�붾릺�덉뒿�덈떎.'); + await sequelize.sync({alter :true}); + console.log('紐⑤뱺 紐⑤뜽�� �깃났�곸쑝濡� �숆린�붾맖.'); } catch (error) { - console.error('Rdb�곗씠�곕쿋�댁뒪 �곌껐 �ㅽ뙣:', error); + console.error('Rdb �곗씠�곕쿋�댁뒪 �곌껐 �ㅽ뙣:', error); } } -module.exports = syncRdb; \ No newline at end of file +module.exports = syncRdb; diff --git a/wsServer.js b/wsServer.js index 79e7b6d6550cd9baa4269da37be15fe8ea754f1a..ac63ef4b44616fb91922f736a744ebadf97886ae 100644 --- a/wsServer.js +++ b/wsServer.js @@ -94,6 +94,7 @@ function startWebSocketServer() { }); server.on('upgrade', (req, socket, head) => { + console.log('WebSocket �낃렇�덉씠�� �붿껌 �섏떊:', req.headers); handleWebSocketUpgrade(req, socket); }); @@ -103,19 +104,22 @@ function startWebSocketServer() { } function handleWebSocketUpgrade(req, socket) { - const key = req.headers['sec-websocket-key']; - const acceptKey = generateAcceptValue(key); - const responseHeaders = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: websocket', - 'Connection: Upgrade', - `Sec-WebSocket-Accept: ${acceptKey}` - ]; - - socket.write(responseHeaders.join('\r\n') + '\r\n\r\n'); - - // �대씪�댁뼵�몃� clients 諛곗뿴�� 異붽� - clients.push(socket); + try { + const key = req.headers['sec-websocket-key']; + const acceptKey = generateAcceptValue(key); + const responseHeaders = [ + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + `Sec-WebSocket-Accept: ${acceptKey}`, + `Access-Control-Allow-Origin: ${process.env.FRONT_URL}`, // �섍꼍蹂��섏뿉�� 媛��몄샂 + 'Access-Control-Allow-Credentials: true' + ]; + socket.write(responseHeaders.join('\r\n') + '\r\n\r\n'); + clients.push(socket); + } catch (error) { + console.error('WebSocket �낃렇�덉씠�� �ㅽ뙣:', error); + } socket.on('data', async buffer => { try {