Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • websystem1/webback
1 result
Select Git revision
Show changes
Commits on Source (27)
...@@ -5,4 +5,6 @@ resources/ ...@@ -5,4 +5,6 @@ resources/
app.js app.js
output.log output.log
weblog.log weblog.log
start.sh
start2.sh
...@@ -13,7 +13,12 @@ exports.createChatRoom = async (params) => { ...@@ -13,7 +13,12 @@ exports.createChatRoom = async (params) => {
// 채팅방 목록 조회 // 채팅방 목록 조회
exports.getChatRooms = async (req, res) => { exports.getChatRooms = async (req, res) => {
try { 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); res.json(roomData);
} catch (err) { } catch (err) {
console.error('Error fetching rooms:', err); console.error('Error fetching rooms:', err);
......
...@@ -12,7 +12,7 @@ class friendController { ...@@ -12,7 +12,7 @@ class friendController {
async sendFriendRequest(req, res) { async sendFriendRequest(req, res) {
try { try {
return await performanceMonitor.measureAsync('sendFriendRequest', async () => { return await performanceMonitor.measureAsync('sendFriendRequest', async () => {
const email = req.body; const { email } = req.body;
const userId = req.user.id; const userId = req.user.id;
if (!userId || !email) { if (!userId || !email) {
...@@ -97,9 +97,29 @@ class friendController { ...@@ -97,9 +97,29 @@ class friendController {
async acceptRequest(req, res) { async acceptRequest(req, res) {
try { try {
return await performanceMonitor.measureAsync('acceptFriendRequest', async () => { 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 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); const result = await FriendService.acceptFriendRequest(userId, friendId);
return res.status(200).json({ return res.status(200).json({
success: true, success: true,
...@@ -107,6 +127,7 @@ class friendController { ...@@ -107,6 +127,7 @@ class friendController {
}); });
}); });
} catch (error) { } catch (error) {
console.error('Friend request accept error:', error);
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
error: { error: {
...@@ -162,7 +183,9 @@ class friendController { ...@@ -162,7 +183,9 @@ class friendController {
return res.status(200).json({ return res.status(200).json({
success: true, success: true,
data: friends data: {
...friends
}
}); });
}); });
} catch (error) { } catch (error) {
......
...@@ -108,8 +108,9 @@ class MeetingController { ...@@ -108,8 +108,9 @@ class MeetingController {
*/ */
async getMeetingDetail(req, res) { async getMeetingDetail(req, res) {
const { meetingId } = req.params; const { meetingId } = req.params;
const userId=req.user.id;
try { try {
const meetingDetail = await MeetingService.getMeetingDetail(meetingId); const meetingDetail = await MeetingService.getMeetingDetail(meetingId,userId);
res.status(200).json(meetingDetail); res.status(200).json(meetingDetail);
} catch (err) { } catch (err) {
console.error('모임 상세 조회 오류:', err); console.error('모임 상세 조회 오류:', err);
...@@ -165,6 +166,24 @@ class MeetingController { ...@@ -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(); module.exports = new MeetingController();
...@@ -22,7 +22,7 @@ class CreateMeetingRequestDTO { ...@@ -22,7 +22,7 @@ class CreateMeetingRequestDTO {
time_idx_start: Joi.number().integer().min(0).required(), time_idx_start: Joi.number().integer().min(0).required(),
time_idx_end: Joi.number().integer().greater(Joi.ref('time_idx_start')).required(), time_idx_end: Joi.number().integer().greater(Joi.ref('time_idx_start')).required(),
location: Joi.string().allow('', null).optional(), 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(), type: Joi.string().valid('OPEN', 'CLOSE').required(),
created_by: Joi.number().integer().positive().required(), created_by: Joi.number().integer().positive().required(),
}); });
......
...@@ -13,8 +13,8 @@ class MeetingDetailResponseDTO { ...@@ -13,8 +13,8 @@ class MeetingDetailResponseDTO {
this.isScheduleConflict = isScheduleConflict; this.isScheduleConflict = isScheduleConflict;
this.participants = meeting.participants.map(participant => ({ this.participants = meeting.participants.map(participant => ({
userId: participant.user_id, userId: participant.user_id,
name: participant.participantUser ? participant.participantUser.name : 'Unknown', name: participant.user ? participant.user.name : 'Unknown',
email: participant.participantUser ? participant.participantUser.email : 'Unknown' email: participant.user ? participant.user.email : 'Unknown'
})); }));
} }
} }
......
...@@ -78,6 +78,7 @@ User.hasMany(Schedule, { ...@@ -78,6 +78,7 @@ User.hasMany(Schedule, {
onDelete: 'CASCADE', onDelete: 'CASCADE',
}); });
// Invite 관계 설정 // Invite 관계 설정
Invite.belongsTo(Meeting, { Invite.belongsTo(Meeting, {
foreignKey: 'meeting_id', foreignKey: 'meeting_id',
...@@ -109,7 +110,17 @@ Meeting.hasMany(Invite, { ...@@ -109,7 +110,17 @@ Meeting.hasMany(Invite, {
as: 'invites', as: 'invites',
onDelete: 'CASCADE', onDelete: 'CASCADE',
}); });
FcmToken.belongsTo(User,{
foreignKey:'userId',
as:'user',
onDelete:'CASCADE',
});
User.hasMany(FcmToken,
{
foreignKey:'userId',
as:'fcmTokenList',
onDelete:'CASCADE',
});
module.exports = { module.exports = {
sequelize, sequelize,
...@@ -119,5 +130,6 @@ module.exports = { ...@@ -119,5 +130,6 @@ module.exports = {
Meeting, Meeting,
MeetingParticipant, MeetingParticipant,
Friend, Friend,
Invite,
FcmToken, FcmToken,
}; };
...@@ -24,6 +24,7 @@ const Meeting = sequelize.define('Meeting', { ...@@ -24,6 +24,7 @@ const Meeting = sequelize.define('Meeting', {
}, },
time_idx_deadline: { time_idx_deadline: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
defaultValue:671,
}, },
type: { type: {
type: DataTypes.ENUM('OPEN', 'CLOSE'), type: DataTypes.ENUM('OPEN', 'CLOSE'),
...@@ -37,7 +38,7 @@ const Meeting = sequelize.define('Meeting', { ...@@ -37,7 +38,7 @@ const Meeting = sequelize.define('Meeting', {
max_num: { max_num: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false,
defaultValue: 10, // 기본값 설정 (필요에 따라 조정) defaultValue: 20, // 기본값 설정 (필요에 따라 조정)
}, },
cur_num: { cur_num: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
......
...@@ -42,7 +42,7 @@ async function startPushServer() { ...@@ -42,7 +42,7 @@ async function startPushServer() {
body: `${sender}: ${messageContent}`, body: `${sender}: ${messageContent}`,
}, },
data: { data: {
click_action: `http://localhost:3000/chat/chatRoom/${chatRoomId}`, // 클릭 시 이동할 URL click_action: `${process.env.FRONT_URL}/mypage`, // 클릭 시 이동할 URL
}, },
android: { priority: 'high' }, android: { priority: 'high' },
apns: { payload: { aps: { sound: 'default' } } }, apns: { payload: { aps: { sound: 'default' } } },
...@@ -71,24 +71,42 @@ async function startPushServer() { ...@@ -71,24 +71,42 @@ async function startPushServer() {
channel.consume(meetingQueue, async (msg) => { channel.consume(meetingQueue, async (msg) => {
if (msg !== null) { if (msg !== null) {
const event = JSON.parse(msg.content.toString()); const event = JSON.parse(msg.content.toString());
const { meetingTitle, inviterName, inviteeTokens } = event; const { meetingTitle, inviterName, inviteeTokens, type } = event;
console.log('Meeting 푸시 알림 요청 수신:', event); console.log('Meeting 푸시 알림 요청 수신:', event);
console.log("푸시 알림 보내는 fcmToken", inviteeTokens); console.log("푸시 알림 보내는 fcmToken", inviteeTokens);
if (inviteeTokens.length > 0) { if (inviteeTokens.length > 0) {
const message = { let message;
tokens: inviteeTokens,
notification: { // 이벤트 타입에 따라 알림 내용 구성
title: '번개 모임 초대', if (type === 'invite') {
body: `${inviterName}님이 ${meetingTitle} 모임에 초대했습니다.`, message = {
}, tokens: inviteeTokens,
data: { notification: {
click_action: `http://localhost:3000`, // 클릭 시 이동할 URL title: '번개 모임 초대',
}, body: `${inviterName}님이 ${meetingTitle} 번개모임에 초대했습니다.`,
android: { priority: 'high' }, },
apns: { payload: { aps: { sound: 'default' } } }, 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 { try {
const response = await admin.messaging().sendEachForMulticast(message); const response = await admin.messaging().sendEachForMulticast(message);
......
...@@ -4,7 +4,6 @@ const chatController = require('../controllers/chatController'); ...@@ -4,7 +4,6 @@ const chatController = require('../controllers/chatController');
const { isLoggedIn } = require('../middlewares/auth'); const { isLoggedIn } = require('../middlewares/auth');
router.post('/create-room', chatController.createChatRoom); router.post('/create-room', chatController.createChatRoom);
router.get('/rooms', chatController.getChatRooms);
router.post('/update-status', chatController.updateStatus); router.post('/update-status', chatController.updateStatus);
router.post('/update-read-status', chatController.updateReadStatus); router.post('/update-read-status', chatController.updateReadStatus);
router.get('/unread-messages/:nickname', chatController.getUnreadMessages); router.get('/unread-messages/:nickname', chatController.getUnreadMessages);
...@@ -13,6 +12,7 @@ router.post('/update-status-and-logid', chatController.updateStatusAndLogId); ...@@ -13,6 +12,7 @@ router.post('/update-status-and-logid', chatController.updateStatusAndLogId);
router.post('/update-read-log-id', chatController.updateReadLogId); router.post('/update-read-log-id', chatController.updateReadLogId);
router.use(isLoggedIn); router.use(isLoggedIn);
router.get('/rooms', chatController.getChatRooms);
router.post('/:chatRoomId/notices', chatController.addNotice); router.post('/:chatRoomId/notices', chatController.addNotice);
router.get('/:chatRoomId/notices/latest', chatController.getLatestNotice); router.get('/:chatRoomId/notices/latest', chatController.getLatestNotice);
......
...@@ -28,6 +28,7 @@ router.get('/:meetingId', MeetingController.getMeetingDetail); ...@@ -28,6 +28,7 @@ router.get('/:meetingId', MeetingController.getMeetingDetail);
// 번개 모임 탈퇴 // 번개 모임 탈퇴
router.delete('/:meetingId/leave', MeetingController.leaveMeeting); router.delete('/:meetingId/leave', MeetingController.leaveMeeting);
// 번개 모임 삭제
router.delete('/:meetingId', MeetingController.deleteMeeting);
module.exports = router; module.exports = router;
\ No newline at end of file
...@@ -30,15 +30,18 @@ class ChatService { ...@@ -30,15 +30,18 @@ class ChatService {
} }
// 채팅방 목록 조회 // 채팅방 목록 조회
async getChatRooms() { async getChatRooms(name) {
const rooms = await ChatRooms.find({}, { chatRoomId: 1, chatRoomName: 1, messages: { $slice: -1 } }); const rooms = await ChatRooms.find(
{ "participants.name": name },
{ chatRoomId: 1, chatRoomName: 1, messages: { $slice: -1 } }
);
return rooms.map(room => { return rooms.map(room => {
const lastMessage = room.messages[0] || {}; const lastMessage = room.messages[0] || {};
return { return {
chatRoomId: room.chatRoomId, chatRoomId: room.chatRoomId,
chatRoomName: room.chatRoomName, chatRoomName: room.chatRoomName,
lastMessage: { lastMessage: {
sender: lastMessage.sender || '없음', sender: lastMessage.sender || '알림',
message: lastMessage.message || '메시지 없음', message: lastMessage.message || '메시지 없음',
timestamp: lastMessage.timestamp || null, timestamp: lastMessage.timestamp || null,
}, },
......
...@@ -39,13 +39,22 @@ class MeetingService { ...@@ -39,13 +39,22 @@ class MeetingService {
return totalIdx; 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 = { const event = {
meetingTitle, meetingTitle,
inviterName, inviterName,
inviteeTokens, 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 { ...@@ -67,7 +76,7 @@ class MeetingService {
// 사용자와 FCM 토큰 조회 // 사용자와 FCM 토큰 조회
const user = await this._findUserWithFcmTokens(created_by); const user = await this._findUserWithFcmTokens(created_by);
console.log("user", user); console.log("user", user);
const userFcmTokens = user.fcmTokenList.map((fcmToken) => fcmToken.token); const userFcmTokens = user.fcmTokenList.map((fcmToken) => fcmToken.token);
...@@ -119,8 +128,8 @@ class MeetingService { ...@@ -119,8 +128,8 @@ class MeetingService {
); );
const time_indices = Array.from( const time_indices = Array.from(
{ length: time_idx_end - time_idx_start + 1 }, { length: parseInt(time_idx_end) - parseInt(time_idx_start) + 1 },
(_, i) => time_idx_start + i (_, i) => (parseInt(time_idx_start) + i).toString()
); );
await ScheduleService.createSchedules({ await ScheduleService.createSchedules({
userId: created_by, userId: created_by,
...@@ -147,7 +156,12 @@ class MeetingService { ...@@ -147,7 +156,12 @@ class MeetingService {
// RabbitMQ 메시지 발행 (푸시 알림 요청) // RabbitMQ 메시지 발행 (푸시 알림 요청)
if (inviteeTokens.length > 0) { 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 }); const chatRoom = await ChatRooms.findOne({ chatRoomId: chatRoomId });
...@@ -215,7 +229,7 @@ class MeetingService { ...@@ -215,7 +229,7 @@ class MeetingService {
return availableFriendIds; return availableFriendIds;
} }
async joinMeeting(meetingId, userId) { async joinMeeting(meetingId, userId) {
const meeting = await Meeting.findByPk(meetingId); const meeting = await Meeting.findByPk(meetingId);
console.log(`참여하려는 모임: ${JSON.stringify(meeting)}`); console.log(`참여하려는 모임: ${JSON.stringify(meeting)}`);
...@@ -240,81 +254,104 @@ class MeetingService { ...@@ -240,81 +254,104 @@ class MeetingService {
// 트랜잭션을 사용하여 참가자 추가 및 스케줄 업데이트를 원자적으로 처리 // 트랜잭션을 사용하여 참가자 추가 및 스케줄 업데이트를 원자적으로 처리
await sequelize.transaction(async (transaction) => { await sequelize.transaction(async (transaction) => {
if (meeting.cur_num >= meeting.max_num) { if (meeting.cur_num >= meeting.max_num) {
throw new Error("모임 인원이 모두 찼습니다."); throw new Error("모임 인원이 모두 찼습니다.");
} }
// 스케줄 충돌 확인 // 스케줄 충돌 확인
const hasConflict = await ScheduleService.checkScheduleOverlapByTime( const hasConflict = await ScheduleService.checkScheduleOverlapByTime(
userId, userId,
meeting.time_idx_start, meeting.time_idx_start,
meeting.time_idx_end, meeting.time_idx_end,
transaction transaction
); );
console.log(`스케줄 충돌 결과: ${hasConflict}`); console.log(`스케줄 충돌 결과: ${hasConflict}`);
if (hasConflict) { if (hasConflict) {
throw new Error("스케줄이 겹칩니다. 다른 모임에 참가하세요."); throw new Error("스케줄이 겹칩니다. 다른 모임에 참가하세요.");
} }
await MeetingParticipant.create( await MeetingParticipant.create(
{ meeting_id: meetingId, user_id: userId }, { meeting_id: meetingId, user_id: userId },
{ transaction } { transaction }
); );
const time_indices = Array.from( const time_indices = Array.from(
{ length: meeting.time_idx_end - meeting.time_idx_start + 1 }, { length: meeting.time_idx_end - meeting.time_idx_start + 1 },
(_, i) => meeting.time_idx_start + i (_, i) => meeting.time_idx_start + i
); );
await ScheduleService.createSchedules({ await ScheduleService.createSchedules({
userId: userId, userId: userId,
title: `번개 모임: ${meeting.title}`, title: `번개 모임: ${meeting.title}`,
is_fixed: false, is_fixed: false,
time_indices: time_indices, time_indices: time_indices,
}, transaction); }, transaction);
// 채팅방 참가 (MongoDB) // 채팅방 참가 (MongoDB)
const user = await User.findOne({ const user = await User.findOne({
where: { id: userId }, where: { id: userId },
include: [ include: [
{ {
model: FcmToken, model: FcmToken,
as: 'fcmTokenList', // FCM 토큰 가져오기 as: 'fcmTokenList', // FCM 토큰 가져오기
attributes: ['token'], attributes: ['token'],
}, },
], ],
transaction 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, true);
chatRoom.lastReadAt.set(user.name, new Date());
chatRoom.lastReadLogId.set(user.name, null);
await chatRoom.save();
}
// 현재 인원 수 증가 const userFcmTokens = user.fcmTokenList.map((token) => token.token);
await meeting.increment("cur_num", { by: 1, transaction });
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) { async getMeetings(userId, pagination) {
const { limit = 20, offset = 0 } = pagination; const { limit = 20, offset = 0 } = pagination;
try { try {
const meetings = await Meeting.findAll({ const meetings = await Meeting.findAll({
attributes: [ attributes: [
...@@ -337,7 +374,7 @@ class MeetingService { ...@@ -337,7 +374,7 @@ class MeetingService {
offset, offset,
distinct: true distinct: true
}); });
const hasNext = meetings.length > limit; const hasNext = meetings.length > limit;
const content = await Promise.all( const content = await Promise.all(
meetings.slice(0, limit).map(async (meeting) => { meetings.slice(0, limit).map(async (meeting) => {
...@@ -347,13 +384,13 @@ class MeetingService { ...@@ -347,13 +384,13 @@ class MeetingService {
user_id: userId user_id: userId
} }
}); });
const hasConflict = await ScheduleService.checkScheduleOverlapByTime( const hasConflict = await ScheduleService.checkScheduleOverlapByTime(
userId, userId,
meeting.time_idx_start, meeting.time_idx_start,
meeting.time_idx_end meeting.time_idx_end
); );
return new MeetingResponseDTO( return new MeetingResponseDTO(
meeting, meeting,
!!isParticipant, !!isParticipant,
...@@ -362,7 +399,7 @@ class MeetingService { ...@@ -362,7 +399,7 @@ class MeetingService {
); );
}) })
); );
return { content, hasNext }; return { content, hasNext };
} catch (error) { } catch (error) {
console.error('getMeetings error:', error); console.error('getMeetings error:', error);
...@@ -372,7 +409,7 @@ class MeetingService { ...@@ -372,7 +409,7 @@ class MeetingService {
async getMyMeetings(userId, pagination) { async getMyMeetings(userId, pagination) {
const { limit = 20, offset = 0 } = pagination; const { limit = 20, offset = 0 } = pagination;
try { try {
const meetings = await Meeting.findAll({ const meetings = await Meeting.findAll({
attributes: [ attributes: [
...@@ -401,7 +438,7 @@ class MeetingService { ...@@ -401,7 +438,7 @@ class MeetingService {
offset, offset,
distinct: true distinct: true
}); });
const hasNext = meetings.length > limit; const hasNext = meetings.length > limit;
const content = meetings.slice(0, limit).map(meeting => { const content = meetings.slice(0, limit).map(meeting => {
return new MeetingResponseDTO( return new MeetingResponseDTO(
...@@ -411,14 +448,14 @@ class MeetingService { ...@@ -411,14 +448,14 @@ class MeetingService {
meeting.creator?.name || 'Unknown' meeting.creator?.name || 'Unknown'
); );
}); });
return { content, hasNext }; return { content, hasNext };
} catch (error) { } catch (error) {
console.error('getMyMeetings error:', error); console.error('getMyMeetings error:', error);
throw new Error('Failed to fetch my meetings'); throw new Error('Failed to fetch my meetings');
} }
} }
async getMeetingDetail(meetingId, userId) { async getMeetingDetail(meetingId, userId) {
const meeting = await Meeting.findByPk(meetingId, { const meeting = await Meeting.findByPk(meetingId, {
include: [ include: [
...@@ -440,17 +477,17 @@ class MeetingService { ...@@ -440,17 +477,17 @@ class MeetingService {
} }
] ]
}); });
if (!meeting) { if (!meeting) {
throw new Error("모임을 찾을 수 없습니다."); throw new Error("모임을 찾을 수 없습니다.");
} }
const hasConflict = await ScheduleService.checkScheduleOverlapByTime( const hasConflict = await ScheduleService.checkScheduleOverlapByTime(
userId, userId,
meeting.time_idx_start, meeting.time_idx_start,
meeting.time_idx_end meeting.time_idx_end
); );
return new MeetingDetailResponseDTO(meeting, hasConflict); return new MeetingDetailResponseDTO(meeting, hasConflict);
} }
...@@ -526,13 +563,13 @@ class MeetingService { ...@@ -526,13 +563,13 @@ class MeetingService {
// 저장 // 저장
chatRoom.save(); chatRoom.save();
} }
async leaveMeeting(meetingId, userId) { async leaveMeeting(meetingId, userId) {
const meeting = await Meeting.findByPk(meetingId); const meeting = await Meeting.findByPk(meetingId);
if (!meeting) { if (!meeting) {
throw new Error('모임을 찾을 수 없습니다.'); throw new Error('모임을 찾을 수 없습니다.');
} }
await sequelize.transaction(async (transaction) => { await sequelize.transaction(async (transaction) => {
// 참가자 확인 // 참가자 확인
const participant = await MeetingParticipant.findOne({ const participant = await MeetingParticipant.findOne({
...@@ -542,16 +579,16 @@ class MeetingService { ...@@ -542,16 +579,16 @@ class MeetingService {
}, },
transaction transaction
}); });
if (!participant) { if (!participant) {
throw new Error('참가하지 않은 모임입니다.'); throw new Error('참가하지 않은 모임입니다.');
} }
// 생성자는 탈퇴할 수 없음 // 생성자는 탈퇴할 수 없음
if (meeting.created_by === userId) { if (meeting.created_by === userId) {
throw new Error('모임 생성자는 탈퇴할 수 없습니다.'); throw new Error('모임 생성자는 탈퇴할 수 없습니다.');
} }
// 참가자 제거 // 참가자 제거
await MeetingParticipant.destroy({ await MeetingParticipant.destroy({
where: { where: {
...@@ -560,7 +597,7 @@ class MeetingService { ...@@ -560,7 +597,7 @@ class MeetingService {
}, },
transaction transaction
}); });
// 관련 스케줄 삭제 // 관련 스케줄 삭제
await Schedule.destroy({ await Schedule.destroy({
where: { where: {
...@@ -572,7 +609,7 @@ class MeetingService { ...@@ -572,7 +609,7 @@ class MeetingService {
}, },
transaction transaction
}); });
// 채팅방에서 제거 // 채팅방에서 제거
const chatRoom = await ChatRooms.findOne({ const chatRoom = await ChatRooms.findOne({
chatRoomId: meeting.chatRoomId chatRoomId: meeting.chatRoomId
...@@ -583,13 +620,61 @@ class MeetingService { ...@@ -583,13 +620,61 @@ class MeetingService {
chatRoom.isOnline.delete(user.name); chatRoom.isOnline.delete(user.name);
chatRoom.lastReadAt.delete(user.name); chatRoom.lastReadAt.delete(user.name);
chatRoom.lastReadLogId.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 chatRoom.save();
} }
// 현재 인원 수 감소 // 현재 인원 수 감소
await meeting.decrement('cur_num', { by: 1, transaction }); 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(); module.exports = new MeetingService();
\ No newline at end of file
...@@ -10,11 +10,17 @@ class ScheduleService { ...@@ -10,11 +10,17 @@ class ScheduleService {
* @param {object} [transaction] - Sequelize 트랜잭션 객체 -> 미팅방에서 쓰기위해 트랜잭션을 넘겨받는걸 추가 * @param {object} [transaction] - Sequelize 트랜잭션 객체 -> 미팅방에서 쓰기위해 트랜잭션을 넘겨받는걸 추가
*/ */
async createSchedules({ userId, title, is_fixed, time_indices }, transaction = null) { 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({ const overlaps = await Schedule.findAll({
where: { where: {
user_id: userId, user_id: userId,
time_idx: { time_idx: {
[Op.in]: time_indices [Op.in]: parsedTimeIndices
} }
}, },
transaction transaction
...@@ -24,7 +30,7 @@ class ScheduleService { ...@@ -24,7 +30,7 @@ class ScheduleService {
throw new Error(`Schedule overlaps at time_idx ${overlaps[0].time_idx}`); 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, user_id: userId,
title, title,
time_idx, time_idx,
...@@ -43,7 +49,7 @@ class ScheduleService { ...@@ -43,7 +49,7 @@ class ScheduleService {
user_id: userId, user_id: userId,
title, title,
is_fixed, is_fixed,
time_indices, time_indices: parsedTimeIndices,
createdAt: createdSchedules[0].createdAt, createdAt: createdSchedules[0].createdAt,
updatedAt: createdSchedules[0].updatedAt updatedAt: createdSchedules[0].updatedAt
}; };
......
...@@ -7,16 +7,15 @@ async function syncRdb() { ...@@ -7,16 +7,15 @@ async function syncRdb() {
try { try {
// 데이터베이스 연결 테스트 // 데이터베이스 연결 테스트
await sequelize.authenticate(); await sequelize.authenticate();
console.log('Rdb데이터베이스 연결 성공.'); console.log('Rdb 데이터베이스 연결 성공.');
// 모든 모델 동기화 await sequelize.sync({alter :true});
await sequelize.sync({ force: true }); console.log('모든 모델이 성공적으로 동기화됨.');
console.log('모든 모델이 성공적으로 동기화되었습니다.');
} catch (error) { } catch (error) {
console.error('Rdb데이터베이스 연결 실패:', error); console.error('Rdb 데이터베이스 연결 실패:', error);
} }
} }
module.exports = syncRdb; module.exports = syncRdb;
\ No newline at end of file
...@@ -94,6 +94,7 @@ function startWebSocketServer() { ...@@ -94,6 +94,7 @@ function startWebSocketServer() {
}); });
server.on('upgrade', (req, socket, head) => { server.on('upgrade', (req, socket, head) => {
console.log('WebSocket 업그레이드 요청 수신:', req.headers);
handleWebSocketUpgrade(req, socket); handleWebSocketUpgrade(req, socket);
}); });
...@@ -103,19 +104,22 @@ function startWebSocketServer() { ...@@ -103,19 +104,22 @@ function startWebSocketServer() {
} }
function handleWebSocketUpgrade(req, socket) { function handleWebSocketUpgrade(req, socket) {
const key = req.headers['sec-websocket-key']; try {
const acceptKey = generateAcceptValue(key); const key = req.headers['sec-websocket-key'];
const responseHeaders = [ const acceptKey = generateAcceptValue(key);
'HTTP/1.1 101 Switching Protocols', const responseHeaders = [
'Upgrade: websocket', 'HTTP/1.1 101 Switching Protocols',
'Connection: Upgrade', 'Upgrade: websocket',
`Sec-WebSocket-Accept: ${acceptKey}` 'Connection: Upgrade',
]; `Sec-WebSocket-Accept: ${acceptKey}`,
`Access-Control-Allow-Origin: ${process.env.FRONT_URL}`, // 환경변수에서 가져옴
socket.write(responseHeaders.join('\r\n') + '\r\n\r\n'); 'Access-Control-Allow-Credentials: true'
];
// 클라이언트를 clients 배열에 추가 socket.write(responseHeaders.join('\r\n') + '\r\n\r\n');
clients.push(socket); clients.push(socket);
} catch (error) {
console.error('WebSocket 업그레이드 실패:', error);
}
socket.on('data', async buffer => { socket.on('data', async buffer => {
try { try {
......