diff --git a/.gitignore b/.gitignore index 241c0f6e54e4da30c80a69c553d9d43e5b04f2fd..c95fa60cf8457ba1592eea59de29eea125ba3f81 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ node_modules/ .env config.json/ +resources/ +app.js +output.log +weblog.log + diff --git a/app.js b/app.js index 952e375ab1eefe8e4640271668cb1ca136c36754..7fa87fe075aff37349a506b093ba73a52d104798 100644 --- a/app.js +++ b/app.js @@ -16,45 +16,54 @@ const app = express(); app.use(morgan('dev')); //濡쒓퉭�� -// CORS �ㅼ젙 + +// CORS �ㅼ젙 (濡쒖뺄 �섍꼍��) app.use( cors({ - origin: 'http://localhost:3000', + origin:[ process.env.FROENT_URL,'https://yanawa.shop'], methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'], - credentials: true, + credentials: true, }) ); - - -// 誘몃뱾�⑥뼱 �ㅼ젙 -app.use(express.json()); -app.use(express.urlencoded({ extended: false })); - -// �몄뀡 �ㅼ젙 +// app.use( session({ - secret: 'your_session_secret', + secret: 'your-secret-key', resave: false, saveUninitialized: false, + rolling: true, + cookie: { + httpOnly: true, + secure: true, + maxAge: 60 * 60 * 1000, // 1�쒓컙 + sameSite: 'none', + }, }) ); +// 誘몃뱾�⑥뼱 �ㅼ젙 +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); + // Passport 珥덇린�� 諛� �몄뀡 �곌껐 app.use(passport.initialize()); app.use(passport.session()); app.use(flash()); + + +app.set('trust proxy', 1); console.log('MongoDB URI:', process.env.MONGO_URI); //�쇱슦�� �깅줉 -const authRoutes = require('./routes/auth'); -app.use('/auth', authRoutes); +const authRoutes = require('./routes/authRoute'); +app.use('/api/auth', authRoutes); -const scheduleRoutes = require('./routes/schedule'); +const scheduleRoutes = require('./routes/scheduleRoute'); app.use('/api/schedule', scheduleRoutes); -const friendRoutes = require('./routes/friend'); +const friendRoutes = require('./routes/friendRoute'); app.use('/api/friend', friendRoutes); const meetingRoutes = require('./routes/meetingRoute'); @@ -66,6 +75,9 @@ app.use('/api/chat', chatRoutes); const memberRoutes = require('./routes/memberRoute'); app.use('/api/member', memberRoutes); +const sessionRouter = require('./routes/sessionRoute'); +app.use('/api/session', sessionRouter); + // �ㅼ�以� �대━�� 珥덇린�� initScheduleCleaner(); @@ -86,4 +98,4 @@ const PORT = process.env.PORT || 3000; console.error('�� �쒕쾭 �쒖옉 以� �ㅻ쪟 諛쒖깮:', error); process.exit(1); } -})(); \ No newline at end of file +})(); diff --git a/controllers/chatController.js b/controllers/chatController.js index 41a2d3c6f05d4f021964785d98414452322050ec..bfcca38df3a7ab0b513d3c4bee2c46533226b09b 100644 --- a/controllers/chatController.js +++ b/controllers/chatController.js @@ -91,4 +91,67 @@ exports.updateStatusAndLogId = async (req, res) => { console.error('Error updating user status and lastReadLogId:', err); res.status(500).json({ error: 'Failed to update user status and lastReadLogId' }); } +}; + +// 怨듭� �깅줉 +exports.addNotice = async (req, res) => { + const { chatRoomId } = req.params; + const { sender, message } = req.body; + + try { + const notice = await chatService.addNotice(chatRoomId, sender, message); + res.status(200).json(notice); + } catch (error) { + console.error('Error adding notice:', error.message); + res.status(500).json({ error: 'Failed to add notice' }); + } +}; + +// 理쒖떊 怨듭� 議고쉶 +exports.getLatestNotice = async (req, res) => { + const { chatRoomId } = req.params; + + try { + const latestNotice = await chatService.getLatestNotice(chatRoomId); + if (latestNotice) { + res.status(200).json(latestNotice); + } else { + res.status(404).json({ message: 'No notices found' }); + } + } catch (error) { + console.error('Error fetching latest notice:', error.message); + res.status(500).json({ error: 'Failed to fetch latest notice' }); + } +}; + +// 怨듭� �꾩껜 議고쉶 +exports.getAllNotices = async (req, res) => { + const { chatRoomId } = req.params; + + try { + const notices = await chatService.getAllNotices(chatRoomId); + console.log(`[getAllNotices] Notices for chatRoomId ${chatRoomId}:`, notices); // 濡쒓렇 異붽� + res.status(200).json(notices); + } catch (error) { + console.error('Error fetching all notices:', error.message); + res.status(500).json({ error: 'Failed to fetch all notices' }); + } +}; + +// 怨듭��ы빆 �곸꽭 議고쉶 +exports.getNoticeById = async (req, res) => { + const { chatRoomId, noticeId } = req.params; + + try { + const notice = await chatService.getNoticeById(chatRoomId, noticeId); + + if (!notice) { + return res.status(404).json({ error: 'Notice not found' }); + } + + res.status(200).json(notice); + } catch (error) { + console.error('Error fetching notice by ID:', error.message); + res.status(500).json({ error: 'Failed to fetch notice by ID' }); + } }; \ No newline at end of file diff --git a/controllers/friendController.js b/controllers/friendController.js index ce50caf5463a7095f3516a27fc14c7a0c114f7f4..09cbf577aa6ce94f6e9bc4ac52d902c5d97e6ae5 100644 --- a/controllers/friendController.js +++ b/controllers/friendController.js @@ -1,53 +1,59 @@ const FriendService = require('../services/friendService'); const { User } = require('../models'); +const performanceMonitor = require('../utils/performanceMonitor'); class friendController { - /** - * 移쒓뎄 �붿껌 蹂대궡湲� - * �대씪�댁뼵�몃뒗 userId�� �붿껌�� 蹂대궪 �ъ슜�먯쓽 email�� �꾩넚 - */ - async sendFriendRequest(req, res, next) { - const { userId, email } = req.body; - - try { + /** + * 移쒓뎄 �붿껌 蹂대궡湲� + * �대씪�댁뼵�몃뒗 userId�� �붿껌�� 蹂대궪 �ъ슜�먯쓽 email�� �꾩넚 + * POST /api/friend/request + * + */ + async sendFriendRequest(req, res) { + try { + return await performanceMonitor.measureAsync('sendFriendRequest', async () => { + const email = req.body; + const userId = req.user.id; if (!userId || !email) { - return res.status(400).json({ message: 'userId�� email�� �꾩닔 �낅젰 ��ぉ�낅땲��.' }); + throw new Error('userId�� email�� �꾩닔 �낅젰 ��ぉ�낅땲��.'); } - // 移쒓뎄 �붿껌�� 諛쏆쓣 �ъ슜�먯쓽 �뺣낫 議고쉶 (�쒕퉬�ㅻ줈 遺꾨━�좎� �앷컖) - const receiver = await User.findOne({ where: { email: email } }); + + const receiver = await User.findOne({ where: { email } }); if (!receiver) { - return res.status(404).json({ message: '�붿껌�� 諛쏆쓣 �ъ슜�먮� 李얠쓣 �� �놁뒿�덈떎.' }); + throw new Error('�붿껌�� 諛쏆쓣 �ъ슜�먮� 李얠쓣 �� �놁뒿�덈떎.'); } - const friendId = receiver.id; - // 移쒓뎄 �붿껌 蹂대궡湲� �쒕퉬�� �몄텧 - const friendRequest = await FriendService.sendFriendRequest(userId, friendId); + + const friendRequest = await FriendService.sendFriendRequest(userId, receiver.id); return res.status(201).json({ - success:true, - data:friendRequest + success: true, + data: friendRequest }); - } catch (error) { - // �좊땲�� �쒖빟議곌굔 �ㅻ쪟 泥섎━ - if (error.message === 'Friend request already exists') { - return res.status(409).json({ message: error.message }); + }); + } catch (error) { + return res.status(error.status || 500).json({ + success: false, + error: { + message: error.message, + code: 'FRIEND_REQUEST_ERROR' } - - // �쇰컲 �ㅻ쪟 泥섎━ - return res.status(500).json({ message: '�쒕쾭 �ㅻ쪟媛� 諛쒖깮�덉뒿�덈떎.', error: error.message }); - } + }); } + } + /** * 諛쏆� 移쒓뎄 �붿껌 紐⑸줉 議고쉶 * GET /api/friend/requests/received */ async getReceivedRequests(req, res) { try { - const userId = req.user.id; - const requests = await FriendService.getReceivedRequests(userId); - - return res.status(200).json({ - success: true, - data: requests + return await performanceMonitor.measureAsync('getReceivedRequests', async () => { + const userId = req.user.id; + const requests = await FriendService.getReceivedRequests(userId); + return res.status(200).json({ + success: true, + data: requests + }); }); } catch (error) { return res.status(500).json({ @@ -66,12 +72,13 @@ class friendController { */ async getSentRequests(req, res) { try { - const userId = req.user.id; - const requests = await FriendService.getSentRequests(userId); - - return res.status(200).json({ - success: true, - data: requests + return await performanceMonitor.measureAsync('getSentRequests', async () => { + const userId = req.user.id; + const requests = await FriendService.getSentRequests(userId); + return res.status(200).json({ + success: true, + data: requests + }); }); } catch (error) { return res.status(500).json({ @@ -83,21 +90,21 @@ class friendController { }); } } - /** * 移쒓뎄 �붿껌 �섎씫 * POST /api/friend/request/:friendId/accept */ async acceptRequest(req, res) { try { - const userId = req.user.id; - const { friendId } = req.params; - - const result = await FriendService.acceptFriendRequest(userId, friendId); - - return res.status(200).json({ - success: true, - data: result + return await performanceMonitor.measureAsync('acceptFriendRequest', async () => { + const userId = req.user.id; + const { friendId } = req.params; + + const result = await FriendService.acceptFriendRequest(userId, friendId); + return res.status(200).json({ + success: true, + data: result + }); }); } catch (error) { return res.status(400).json({ @@ -116,14 +123,15 @@ class friendController { */ async rejectRequest(req, res) { try { - const userId = req.user.id; - const { friendId } = req.params; - - const result = await FriendService.rejectFriendRequest(userId, friendId); - - return res.status(200).json({ - success: true, - data: result + return await performanceMonitor.measureAsync('rejectFriendRequest', async () => { + const userId = req.user.id; + const { friendId } = req.params; + + const result = await FriendService.rejectFriendRequest(userId, friendId); + return res.status(200).json({ + success: true, + data: result + }); }); } catch (error) { return res.status(400).json({ @@ -142,23 +150,20 @@ class friendController { */ async getFriendList(req, res) { try { - const userId = req.user.id; - const page = parseInt(req.query.page) || 0; - const size = parseInt(req.query.size) || 20; - - const friends = await FriendService.getFriendList(userId, { - limit: size, - offset: page * size - }); + return await performanceMonitor.measureAsync('getFriendList', async () => { + const userId = req.user.id; + const page = parseInt(req.query.page) || 0; + const size = parseInt(req.query.size) || 20; + + const friends = await FriendService.getFriendList(userId, { + limit: size, + offset: page * size + }); - return res.status(200).json({ - success: true, - data: { - content: friends, - page: page, - size: size, - hasNext: friends.length === size - } + return res.status(200).json({ + success: true, + data: friends + }); }); } catch (error) { return res.status(500).json({ @@ -177,17 +182,18 @@ class friendController { */ async deleteFriend(req, res) { try { - const userId = req.user.id; - const { friendId } = req.params; - - const result = await FriendService.deleteFriend(userId, friendId); - - return res.status(200).json({ - success: true, - data: { - message: 'Friend deleted successfully', - result: result - } + return await performanceMonitor.measureAsync('deleteFriend', async () => { + const userId = req.user.id; + const { friendId } = req.params; + + const result = await FriendService.deleteFriend(userId, friendId); + return res.status(200).json({ + success: true, + data: { + message: 'Friend deleted successfully', + result: result + } + }); }); } catch (error) { return res.status(400).json({ diff --git a/controllers/meetingController.js b/controllers/meetingController.js index 12ee66a740228e3ee825b171ef6df7a5d1060ccc..78f4464ff4d7bebb4c3a425ce3154bb4c1e5c042 100644 --- a/controllers/meetingController.js +++ b/controllers/meetingController.js @@ -14,7 +14,7 @@ class MeetingController { * "time_idx_start": 40, // ��: 10:00 AM * "time_idx_end": 42, // ��: 10:30 AM * "location": "�뚯쓽�� A", - * "deadline": "2024-04-25T23:59:59Z", + * "deadline": "43", * "type": "OPEN" // "OPEN" �먮뒗 "CLOSE" * "max_num": * } @@ -116,6 +116,55 @@ class MeetingController { res.status(500).json({ error: err.message || '紐⑥엫 �곸꽭 議고쉶 �ㅽ뙣' }); } } + + /** + * �닿� 李몄뿬�� 紐⑥엫 紐⑸줉 議고쉶 + * GET /api/meetings/my + */ + async getMyMeetings(req, res) { + try { + const userId = req.user.id; + const page = parseInt(req.query.page) || 0; + const size = parseInt(req.query.size) || 20; + + const meetings = await MeetingService.getMyMeetings(userId, { + limit: size, + offset: page * size + }); + + res.status(200).json({ + success: true, + data: { + content: meetings.content, + page: page, + size: size, + hasNext: meetings.hasNext + } + }); + } catch (err) { + console.error('�� 紐⑥엫 紐⑸줉 議고쉶 �ㅻ쪟:', err); + res.status(500).json({ error: err.message || '�� 紐⑥엫 紐⑸줉 議고쉶 �ㅽ뙣' }); + } + } + + + /** + * 踰덇컻 紐⑥엫 �덊눜 + * DELETE /api/meeting/:meetingId/leave + */ + async leaveMeeting(req, res) { + const { meetingId } = req.params; + const userId = req.user.id; + + try { + await MeetingService.leaveMeeting(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/controllers/scheduleController.js b/controllers/scheduleController.js index 4d43ee118a980c6ddf536eff25592f0cf734a66c..0855a84620a9c725abb490437de0f1a88b0d16e9 100644 --- a/controllers/scheduleController.js +++ b/controllers/scheduleController.js @@ -1,6 +1,7 @@ // controllers/scheduleController.js const ScheduleService = require('../services/scheduleService'); const ScheduleRequestDTO = require('../dtos/ScheduleRequestDTO'); +const performanceMonitor = require('../utils/performanceMonitor'); class scheduleController { /** @@ -21,24 +22,20 @@ class scheduleController { */ async createSchedule(req, res) { try { - const userId = req.user.id; - const scheduleRequestDTO = new ScheduleRequestDTO(req.body); - const validatedData = scheduleRequestDTO.validate('create'); // 'create' ���� 寃�利� - - const { title, is_fixed, events } = validatedData; - - const schedules = await ScheduleService.createSchedules({ - userId, - title, - is_fixed, - events - }); + return await performanceMonitor.measureAsync('createSchedule', async () => { + const userId = req.user.id; + const scheduleRequestDTO = new ScheduleRequestDTO(req.body); + const validatedData = scheduleRequestDTO.validate('create'); + + const schedule = await ScheduleService.createSchedules({ + userId, + ...validatedData + }); - return res.status(201).json({ - success: true, - data: { - schedules - } + return res.status(201).json({ + success: true, + data: { schedule } + }); }); } catch (error) { return res.status(400).json({ @@ -49,7 +46,7 @@ class scheduleController { } }); } - } + } /** * �ㅼ�以� �섏젙 @@ -57,31 +54,28 @@ class scheduleController { * Bulk update 吏��� * �붿껌 蹂몃Ц �덉떆: * { - * updates: [ - * { time_idx: 36, title: 'New Title', is_fixed: true }, - * { time_idx: 44, title: 'Another Title' }, - * // ... - * ] + * "originalTitle": "�뚭퀬由ъ쬁 �ㅽ꽣��", // 湲곗〈 �ㅼ�以꾩쓽 �쒕ぉ + * "title": "�뚭퀬由ъ쬁 �ㅽ꽣�� 2.0", // 蹂�寃쏀븷 �쒕ぉ (�쒕ぉ 蹂�寃� �덊븷嫄곕㈃ 湲곗〈 �쒕ぉ�� �l뼱�쇳븿 * -> title濡� �숈씪 �ㅼ�以꾩쓣 李얠븘��) + * "is_fixed": true, + * "time_indices": [36, 37, 38, 40] // 蹂�寃쏀븷 time_indices 諛곗뿴 * } */ async updateSchedules(req, res) { try { - const userId = req.user.id; - const scheduleRequestDTO = new ScheduleRequestDTO(req.body); - const validatedData = scheduleRequestDTO.validate('bulk_update'); // 'bulk_update' ���� 寃�利� + return await performanceMonitor.measureAsync('updateSchedules', async () => { + const userId = req.user.id; + const scheduleRequestDTO = new ScheduleRequestDTO(req.body); + const validatedData = scheduleRequestDTO.validate('bulk_update'); - const { updates } = validatedData; + const updatedSchedule = await ScheduleService.updateSchedules(userId, validatedData); - const updatedSchedules = await ScheduleService.updateSchedules(userId, updates); - - return res.status(200).json({ - success: true, - data: { - schedules: updatedSchedules - } + return res.status(200).json({ + success: true, + data: { schedule: updatedSchedule } + }); }); } catch (error) { - if (error.code === 'SCHEDULE_NOT_FOUND') { + if (error.message === 'Schedule not found') { return res.status(404).json({ success: false, error: { @@ -106,25 +100,25 @@ class scheduleController { * Bulk delete 吏��� * �붿껌 蹂몃Ц �덉떆: * { - * time_idxs: [36, 44, ...] + * "title": "�뚭퀬由ъ쬁 �ㅽ꽣��" * } */ async deleteSchedules(req, res) { try { - const userId = req.user.id; - const scheduleRequestDTO = new ScheduleRequestDTO(req.body); - const validatedData = scheduleRequestDTO.validate('bulk_delete'); // 'bulk_delete' ���� 寃�利� - - const { time_idxs } = validatedData; - - const result = await ScheduleService.deleteSchedules(userId, time_idxs); - - return res.status(200).json({ - success: true, - data: { - message: 'Schedules successfully deleted', - deleted_time_idxs: result.deleted_time_idxs - } + return await performanceMonitor.measureAsync('deleteSchedules', async () => { + const userId = req.user.id; + const scheduleRequestDTO = new ScheduleRequestDTO(req.body); + const validatedData = scheduleRequestDTO.validate('bulk_delete'); + + const result = await ScheduleService.deleteSchedules(userId, validatedData.title); + + return res.status(200).json({ + success: true, + data: { + message: 'Schedule successfully deleted', + deletedCount: result.deletedCount + } + }); }); } catch (error) { return res.status(404).json({ @@ -136,19 +130,20 @@ class scheduleController { }); } } - /** * �대떦 �ъ슜�� �꾩껜 �ㅼ�以� 議고쉶 * GET /api/schedule/all */ async getAllSchedules(req, res) { try { - const userId = req.user.id; - const schedules = await ScheduleService.getAllSchedules(userId); + return await performanceMonitor.measureAsync('getAllSchedules', async () => { + const userId = req.user.id; + const schedules = await ScheduleService.getAllSchedules(userId); - return res.status(200).json({ - success: true, - data: schedules + return res.status(200).json({ + success: true, + data: { schedules } + }); }); } catch (error) { return res.status(500).json({ @@ -168,14 +163,18 @@ class scheduleController { */ async getScheduleByTimeIdx(req, res) { try { - const { time_idx } = req.params; - const userId = req.user.id; + return await performanceMonitor.measureAsync('getScheduleByTimeIdx', async () => { + const { time_idx } = req.params; + const userId = req.user.id; + const scheduleRequestDTO = new ScheduleRequestDTO({ time_idx: parseInt(time_idx, 10) }); + const validatedData = scheduleRequestDTO.validate('get_by_time_idx'); - const schedule = await ScheduleService.getScheduleByTimeIdx(userId, parseInt(time_idx, 10)); + const schedule = await ScheduleService.getScheduleByTimeIdx(userId, validatedData.time_idx); - return res.status(200).json({ - success: true, - data: schedule + return res.status(200).json({ + success: true, + data: { schedule } + }); }); } catch (error) { if (error.message === 'Schedule not found') { diff --git a/dtos/ScheduleRequestDTO.js b/dtos/ScheduleRequestDTO.js index 854bd3f124ebe293fe781924683ed0d9b0a4c168..218f9e040e224a1b7b00f144e510a26e640cb4e3 100644 --- a/dtos/ScheduleRequestDTO.js +++ b/dtos/ScheduleRequestDTO.js @@ -13,27 +13,28 @@ class ScheduleRequestDTO { schema = Joi.object({ title: Joi.string().min(1).max(255).required(), is_fixed: Joi.boolean().required(), - events: Joi.array().items( - Joi.object({ - time_idx: Joi.number().integer().min(0).max(671).required(), - }) - ).min(1).required() + time_indices: Joi.array() + .items(Joi.number().integer().min(0).max(671)) + .min(1) + .required() }); } else if (type === 'bulk_update') { schema = Joi.object({ - updates: Joi.array().items( - Joi.object({ - time_idx: Joi.number().integer().min(0).max(671).required(), - title: Joi.string().min(1).max(255).optional(), - is_fixed: Joi.boolean().optional(), - }) - ).min(1).required() + originalTitle: Joi.string().min(1).max(255).required(), + title: Joi.string().min(1).max(255).required(), + is_fixed: Joi.boolean().required(), + time_indices: Joi.array() + .items(Joi.number().integer().min(0).max(671)) + .min(1) + .required() }); } else if (type === 'bulk_delete') { schema = Joi.object({ - time_idxs: Joi.array().items( - Joi.number().integer().min(0).max(671).required() - ).min(1).required() + title: Joi.string().min(1).max(255).required() + }); + } else if (type === 'get_by_time_idx') { + schema = Joi.object({ + time_idx: Joi.number().integer().min(0).max(671).required() }); } diff --git a/dtos/ScheduleResponseDTO.js b/dtos/ScheduleResponseDTO.js index a75316e65e13999800b70b4e23e9c40203dccee8..75276216f17143fd685160c0a47e72dd8b61608e 100644 --- a/dtos/ScheduleResponseDTO.js +++ b/dtos/ScheduleResponseDTO.js @@ -1,14 +1,25 @@ // dtos/ScheduleResponseDTO.js class ScheduleResponseDTO { - constructor(schedule) { - this.id = schedule.id; - this.user_id = schedule.user_id; - this.title = schedule.title; - this.time_idx = schedule.time_idx; // �덈줈�� time_idx �꾨뱶 異붽� - this.is_fixed = schedule.is_fixed; - this.createdAt = schedule.createdAt; - this.updatedAt = schedule.updatedAt; + static groupSchedules(schedules) { + const grouped = schedules.reduce((acc, schedule) => { + const key = `${schedule.title}-${schedule.is_fixed}`; + if (!acc[key]) { + acc[key] = { + id: schedule.id, + user_id: schedule.user_id, + title: schedule.title, + is_fixed: schedule.is_fixed, + time_indices: [], + createdAt: schedule.createdAt, + updatedAt: schedule.updatedAt + }; + } + acc[key].time_indices.push(schedule.time_idx); + return acc; + }, {}); + + return Object.values(grouped); } } diff --git a/middlewares/auth.js b/middlewares/auth.js index afc74eaad5520ace4dbb2b36a9a644cec88e8387..52eb397476e16fa8c98e7d2e29a15973d76215bc 100644 --- a/middlewares/auth.js +++ b/middlewares/auth.js @@ -1,15 +1,16 @@ // middlewares/auth.js - -exports.isLoggedIn = (req, res, next) => { //濡쒓렇�몃맂 �ъ슜�먯옄留� �묎렐�덉슜 +exports.isLoggedIn = (req, res, next) => { // 濡쒓렇�몃맂 �ъ슜�먮쭔 �묎렐 �덉슜 if (req.isAuthenticated()) { return next(); } - res.redirect('/auth/login'); + // 由щ떎�대젆�� ���� 401 Unauthorized �곹깭 諛섑솚 + res.status(401).json({ error: '濡쒓렇�� �섏��딆� �ъ슜��' }); }; -exports.isNotLoggedIn = (req, res, next) => { //濡쒓렇�� �덈릺硫� 由щ떎�대젆�� +exports.isNotLoggedIn = (req, res, next) => { // 濡쒓렇�� �덈맂 �ъ슜�먮쭔 �묎렐 �덉슜 if (!req.isAuthenticated()) { return next(); } - res.redirect('/'); + // 由щ떎�대젆�� ���� 400 Bad Request �곹깭 諛섑솚 (�꾩슂�� �곕씪 蹂�寃� 媛���) + res.status(400).json({ error: '�대� 濡쒓렇�몃맂' }); }; diff --git a/models/fcmToken.js b/models/fcmToken.js index f9fa9207dcf3adaf53875e9c78fd040ee78f4eec..855b833ed49fe0de3b033f59676ce3fbe17c1077 100644 --- a/models/fcmToken.js +++ b/models/fcmToken.js @@ -1,3 +1,4 @@ +//models/friend.js const { DataTypes } = require('sequelize'); const sequelize = require('../config/sequelize'); const User = require('./user'); // �щ컮瑜� 寃쎈줈 �뺤씤 diff --git a/models/Friend.js b/models/friend.js similarity index 100% rename from models/Friend.js rename to models/friend.js diff --git a/models/index.js b/models/index.js index 2998026a2b9ad6d4f59a58d48c0eca3b23229c43..7caa67277dc86e3f1dcc043a15e8897cbc7ee641 100644 --- a/models/index.js +++ b/models/index.js @@ -10,32 +10,106 @@ const Invite =require('./invite') const MeetingParticipant = require('./meetingParticipant'); // const ChatRooms = require('./ChatRooms'); -// 愿�怨� �ㅼ젙 -Friend.belongsTo(User, { foreignKey: 'requester_id', as: 'requester' }); // 移쒓뎄 �붿껌�� 蹂대궦 �ъ슜�� -Friend.belongsTo(User, { foreignKey: 'receiver_id', as: 'receiver' }); // 移쒓뎄 �붿껌�� 諛쏆� �ъ슜�� +// Friend 愿�怨� �ㅼ젙 +Friend.belongsTo(User, { + foreignKey: 'requester_id', + as: 'requester', + onDelete: 'CASCADE', +}); +Friend.belongsTo(User, { + foreignKey: 'receiver_id', + as: 'receiver', + onDelete: 'CASCADE', +}); +User.hasMany(Friend, { + foreignKey: 'requester_id', + as: 'sentRequests', + onDelete: 'CASCADE', +}); +User.hasMany(Friend, { + foreignKey: 'receiver_id', + as: 'receivedRequests', + onDelete: 'CASCADE', +}); -User.hasMany(Friend, { foreignKey: 'requester_id', as: 'sentRequests' }); // 移쒓뎄 �붿껌�� 蹂대궦 紐⑸줉 -User.hasMany(Friend, { foreignKey: 'receiver_id', as: 'receivedRequests' }); // 移쒓뎄 �붿껌�� 諛쏆� 紐⑸줉 -// �곌� 愿�怨� �ㅼ젙 -Meeting.belongsTo(User, { foreignKey: 'created_by', as: 'creator' }); -User.hasMany(Meeting, { foreignKey: 'created_by', as: 'meetings' }); +// Meeting 愿�怨� �ㅼ젙 +Meeting.belongsTo(User, { + foreignKey: 'created_by', + as: 'creator', + onDelete: 'SET NULL', +}); +User.hasMany(Meeting, { + foreignKey: 'created_by', + as: 'meetings', + onDelete: 'SET NULL', +}); -MeetingParticipant.belongsTo(Meeting, { foreignKey: 'meeting_id', as: 'meeting' }); -Meeting.hasMany(MeetingParticipant, { foreignKey: 'meeting_id', as: 'participants' }); +// MeetingParticipant 愿�怨� �ㅼ젙 +MeetingParticipant.belongsTo(Meeting, { + foreignKey: 'meeting_id', + as: 'meeting', + onDelete: 'CASCADE', +}); +Meeting.hasMany(MeetingParticipant, { + foreignKey: 'meeting_id', + as: 'participants', + onDelete: 'CASCADE', +}); +MeetingParticipant.belongsTo(User, { + foreignKey: 'user_id', + as: 'user', + onDelete: 'CASCADE', +}); +User.hasMany(MeetingParticipant, { + foreignKey: 'user_id', + as: 'meetingParticipations', + onDelete: 'CASCADE', +}); -MeetingParticipant.belongsTo(User, { foreignKey: 'user_id', as: 'user' }); -User.hasMany(MeetingParticipant, { foreignKey: 'user_id', as: 'meetingParticipations' }); +// Schedule 愿�怨� �ㅼ젙 +Schedule.belongsTo(User, { + foreignKey: 'user_id', + as: 'user', + onDelete: 'CASCADE', +}); +User.hasMany(Schedule, { + foreignKey: 'user_id', + as: 'schedules', + onDelete: 'CASCADE', +}); -Schedule.belongsTo(User, { foreignKey: 'user_id', as: 'user' }); -User.hasMany(Schedule, { foreignKey: 'user_id', as: 'schedules' }); +// Invite 愿�怨� �ㅼ젙 +Invite.belongsTo(Meeting, { + foreignKey: 'meeting_id', + as: 'meeting', + onDelete: 'CASCADE', +}); +Invite.belongsTo(User, { + foreignKey: 'inviter_id', + as: 'inviter', + onDelete: 'CASCADE', +}); +Invite.belongsTo(User, { + foreignKey: 'invitee_id', + as: 'invitee', + onDelete: 'CASCADE', +}); +User.hasMany(Invite, { + foreignKey: 'inviter_id', + as: 'sentInvites', + onDelete: 'CASCADE', +}); +User.hasMany(Invite, { + foreignKey: 'invitee_id', + as: 'receivedInvites', + onDelete: 'CASCADE', +}); +Meeting.hasMany(Invite, { + foreignKey: 'meeting_id', + as: 'invites', + onDelete: 'CASCADE', +}); -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 = { sequelize, @@ -46,5 +120,4 @@ module.exports = { MeetingParticipant, Friend, FcmToken, - Invite, }; diff --git a/models/Invite.js b/models/invite.js similarity index 86% rename from models/Invite.js rename to models/invite.js index d4e6105550ab73c611ed17055684c2358645a51c..621344c2d6d6994ea705737129b0e28752f3df35 100644 --- a/models/Invite.js +++ b/models/invite.js @@ -1,7 +1,8 @@ -//models/invite.js - +// 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: { diff --git a/output.log b/output.log new file mode 100644 index 0000000000000000000000000000000000000000..f50856d2ff07cf69b2737346ecb7af37efe6c5b0 --- /dev/null +++ b/output.log @@ -0,0 +1,92 @@ +MongoDB URI: mongodb+srv://admin:lim1234!!@goodmeeting.vkniz.mongodb.net/ +(node:237546) [MONGODB DRIVER] Warning: useNewUrlParser is a deprecated option: useNewUrlParser has no effect since Node.js Driver version 4.0.0 and will be removed in the next major version +(Use `node --trace-warnings ...` to show where the warning was created) +(node:237546) [MONGODB DRIVER] Warning: useUnifiedTopology is a deprecated option: useUnifiedTopology has no effect since Node.js Driver version 4.0.0 and will be removed in the next major version +�� MongoDB �곌껐 �깃났 +Rdb�곗씠�곕쿋�댁뒪 �곌껐 �깃났. +紐⑤뱺 紐⑤뜽�� �깃났�곸쑝濡� �숆린�붾릺�덉뒿�덈떎. +Server is running on 8080 +[0mGET /api/session/info [33m401[0m 9.962 ms - 76[0m +[0mGET /api/chat/unread-messages/%EC%9E%84%EC%84%B8%ED%98%84 [32m200[0m 18.575 ms - 2[0m +[0mGET /api/chat/rooms [36m304[0m 71.212 ms - -[0m +[0mGET /api/session/info [33m401[0m 1.954 ms - 76[0m +[0mGET /api/auth/login [36m302[0m 3.293 ms - 0[0m +[0mGET /api/auth/google/callback?code=4%2F0AeanS0bo56K9bp52E_K4k1_MsLy3ISx6PmunHrrE1MlEYKczIMg2thRYYgY7Fk-YzLgDLA&scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+openid&authuser=1&hd=ajou.ac.kr&prompt=none [36m302[0m 453.008 ms - 48[0m +[0mGET /api/session/info [32m200[0m 6.112 ms - 51[0m +[0mGET /api/session/info [32m200[0m 16.234 ms - 51[0m +[0mGET /api/chat/unread-messages/%EC%9E%84%EC%84%B8%ED%98%84 [36m304[0m 22.679 ms - -[0m +[0mGET /api/chat/rooms [36m304[0m 23.630 ms - -[0m +[0mGET /api/session/info [32m200[0m 6.990 ms - 51[0m +[0mGET /api/session/info [32m200[0m 7.645 ms - 51[0m +[0mGET /api/chat/rooms [36m304[0m 16.225 ms - -[0m +[0mGET /api/chat/unread-messages/%EC%9E%84%EC%84%B8%ED%98%84 [36m304[0m 11.814 ms - -[0m +[0mGET /api/schedule/all [36m304[0m 9.932 ms - -[0m +Performance Measurement - getAllSchedules: 5.537642002105713ms +[0mGET /api/session/info [32m200[0m 7.587 ms - 51[0m +[0mGET /api/chat/rooms [36m304[0m 16.365 ms - -[0m +[0mGET /api/chat/unread-messages/%EC%9E%84%EC%84%B8%ED%98%84 [36m304[0m 11.954 ms - -[0m +[0mGET /api/schedule/all [36m304[0m 19.858 ms - -[0m +Performance Measurement - getAllSchedules: 4.0661240220069885ms +[0mPOST /api/schedule [32m201[0m 57.019 ms - 254[0m +Performance Measurement - createSchedule: 32.22604298591614ms +[0mPOST /api/schedule [32m201[0m 18.985 ms - 209[0m +Performance Measurement - createSchedule: 14.478051960468292ms +[0mGET /api/session/info [33m401[0m 1.989 ms - 76[0m +[0mGET /api/session/info [32m200[0m 18.484 ms - 51[0m +[0mGET /api/meeting/my?page=0&size=20 [32m200[0m 27.169 ms - 73[0m +[0mGET /api/friend/all?page=0&size=10 [32m200[0m 12.508 ms - 73[0m +Performance Measurement - getFriendList: 8.078101992607117ms +[0mGET /api/friend/requests/received [32m200[0m 21.379 ms - 26[0m +Performance Measurement - getReceivedRequests: 12.82328200340271ms +[0mGET /api/session/info [32m200[0m 5.489 ms - 51[0m +[0mGET /api/session/info [32m200[0m 8.371 ms - 51[0m +[0mGET /api/meeting/my?page=0&size=20 [36m304[0m 16.133 ms - -[0m +[0mGET /api/friend/all?page=0&size=10 [36m304[0m 29.121 ms - -[0m +Performance Measurement - getFriendList: 6.826422989368439ms +[0mGET /api/friend/requests/received [36m304[0m 32.139 ms - -[0m +Performance Measurement - getReceivedRequests: 5.952132999897003ms +[0mGET /api/session/info [32m200[0m 22.840 ms - 51[0m +[0mGET /api/session/info [32m200[0m 9.178 ms - 51[0m +[0mGET /api/meeting/my?page=0&size=20 [36m304[0m 14.669 ms - -[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.464 ms - 0[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.141 ms - 0[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.210 ms - 0[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.142 ms - 0[0m +[0mGET /api/session/info [33m401[0m 0.925 ms - 76[0m +[0mGET /api/session/info [33m401[0m 1.223 ms - 76[0m +[0mGET /api/chat/rooms [36m304[0m 8.083 ms - -[0m +[0mGET /api/chat/unread-messages/%EC%8B%AC%EC%9E%AC%EC%97%BD [36m304[0m 4.635 ms - -[0m +[0mGET /api/session/info [33m401[0m 0.874 ms - 76[0m +[0mGET /api/auth/login? [36m302[0m 0.917 ms - 0[0m +[0mGET /api/auth/google/callback?code=4%2F0AeanS0bOw-YtdxF3OtlXlNJwNPikLk99RTj2hA5DEUqDh90OVHztLhrKtkMXUSWpI-uqyQ&scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+openid&authuser=0&hd=ajou.ac.kr&prompt=none [36m302[0m 474.318 ms - 48[0m +[0mGET /api/session/info [32m200[0m 3.726 ms - 51[0m +[0mGET /api/session/info [32m200[0m 14.662 ms - 51[0m +[0mGET /api/chat/rooms [36m304[0m 21.626 ms - -[0m +[0mGET /api/chat/unread-messages/%EC%8B%AC%EC%9E%AC%EC%97%BD [36m304[0m 22.687 ms - -[0m +[0mGET /api/session/info [32m200[0m 6.551 ms - 51[0m +[0mGET /api/meeting/my?page=0&size=20 [36m304[0m 8.614 ms - -[0m +[0mGET /api/session/info [32m200[0m 5.132 ms - 51[0m +[0mGET /api/chat/rooms [36m304[0m 13.220 ms - -[0m +[0mGET /api/chat/unread-messages/%EC%8B%AC%EC%9E%AC%EC%97%BD [36m304[0m 8.728 ms - -[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.184 ms - 0[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.134 ms - 0[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.192 ms - 0[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.235 ms - 0[0m +[0mGET /api/friend/requests/received [36m304[0m 52.485 ms - -[0m +Performance Measurement - getReceivedRequests: 6.246300995349884ms +[0mGET /api/friend/all?page=0&size=10 [36m304[0m 49.049 ms - -[0m +Performance Measurement - getFriendList: 4.844668984413147ms +[0mGET /api/session/info [32m200[0m 3.957 ms - 51[0m +[0mGET /api/session/info [32m200[0m 5.007 ms - 51[0m +[0mGET /api/meeting/my?page=0&size=20 [36m304[0m 7.817 ms - -[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.228 ms - 0[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.145 ms - 0[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.194 ms - 0[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.158 ms - 0[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.198 ms - 0[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.135 ms - 0[0m +[0mGET /api/session/info [33m401[0m 0.856 ms - 76[0m +[0mGET /api/session/info [33m401[0m 0.662 ms - 76[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.377 ms - 0[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.478 ms - 0[0m +[0mOPTIONS /api/schedule/all [32m204[0m 0.194 ms - 0[0m diff --git a/package-lock.json b/package-lock.json index 606fda2ae8a59791cd06433516eb3868de7badc8..802c64f2397dd75331fc18d1b6245657d4a973bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "amqplib": "^0.10.5", "connect-flash": "^0.1.1", + "connect-mongo": "^5.1.0", "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.5", @@ -253,7 +254,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", @@ -269,7 +270,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -282,7 +283,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", @@ -296,7 +297,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", @@ -310,7 +311,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", @@ -325,7 +326,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -335,7 +336,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.222.0", @@ -347,7 +348,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -360,7 +361,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", @@ -374,7 +375,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", @@ -443,7 +444,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.693.0.tgz", "integrity": "sha512-WfycTcylmrSOnCN8x/xeIjHa4gIV4UhG85LWLZ3M4US8+HJQ8l4c4WUf+pUoTaSxN86vhbXlz0iRvA89nF854Q==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -496,7 +497,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -546,7 +547,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -600,7 +601,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -652,7 +653,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -675,7 +676,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.693.0.tgz", "integrity": "sha512-hlpV3tkOhpFl87aToH6Q6k7JBNNuARBPk+irPMtgE8ZqpYRP9tJ/RXftirzZ7CqSzc7NEWe/mnbJzRXw7DfgVQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-cognito-identity": "3.693.0", @@ -692,7 +693,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.693.0.tgz", "integrity": "sha512-hMUZaRSF7+iBKZfBHNLihFs9zvpM1CB8MBOTnTp5NGCVkRYF3SB2LH+Kcippe0ats4qCyB1eEoyQX99rERp2iQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", @@ -709,7 +710,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", @@ -731,7 +732,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", @@ -758,7 +759,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/credential-provider-env": "3.693.0", @@ -782,7 +783,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.693.0.tgz", "integrity": "sha512-cvxQkrTWHHjeHrPlj7EWXPnFSq8x7vMx+Zn1oTsMpCY445N9KuzjfJTkmNGwU2GT6rSZI9/0MM02aQvl5bBBTQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", @@ -800,7 +801,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-sso": "3.693.0", @@ -820,7 +821,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", @@ -840,7 +841,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.693.0.tgz", "integrity": "sha512-0CCH8GuH1E41Kpq52NujErbUIRewDWLkdbYO8UJGybDbUQ8KC5JG1tP7K20tKYHmVgJGXDHo+XUIG7ogHD6/JA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-cognito-identity": "3.693.0", @@ -869,7 +870,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -885,7 +886,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -900,7 +901,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -916,7 +917,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", @@ -935,7 +936,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -953,7 +954,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -973,7 +974,7 @@ "version": "3.692.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.692.0.tgz", "integrity": "sha512-RpNvzD7zMEhiKgmlxGzyXaEcg2khvM7wd5sSHVapOcrde1awQSOMGI4zKBQ+wy5TnDfrm170ROz/ERLYtrjPZA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.0", @@ -987,7 +988,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -1003,7 +1004,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.693.0.tgz", "integrity": "sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1016,7 +1017,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -1029,7 +1030,7 @@ "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/middleware-user-agent": "3.693.0", @@ -3816,7 +3817,6 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", - "dev": true, "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -5616,7 +5616,7 @@ "version": "3.1.8", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.8.tgz", "integrity": "sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.1", @@ -5630,7 +5630,7 @@ "version": "3.0.12", "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.12.tgz", "integrity": "sha512-YAJP9UJFZRZ8N+UruTeq78zkdjUHmzsY62J4qKWZ4SXB4QXJ/+680EfXXgkYA2xj77ooMqtUY9m406zGNqwivQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.11", @@ -5647,7 +5647,7 @@ "version": "2.5.3", "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.3.tgz", "integrity": "sha512-96uW8maifUSmehaeW7uydWn7wBc98NEeNI3zN8vqakGpyCQgzyJaA64Z4FCOUmAdCJkhppd/7SZ798Fo4Xx37g==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^3.0.10", @@ -5667,7 +5667,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.7.tgz", "integrity": "sha512-cEfbau+rrWF8ylkmmVAObOmjbTIzKyUC5TkBL58SbLywD0RCBC4JAUKbmtSm2w5KUJNRPGgpGFMvE2FKnuNlWQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.11", @@ -5684,7 +5684,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.1.tgz", "integrity": "sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^4.1.7", @@ -5698,7 +5698,7 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.10.tgz", "integrity": "sha512-3zWGWCHI+FlJ5WJwx73Mw2llYR8aflVyZN5JhoqLxbdPZi6UyKSdCeXAWJw9ja22m6S6Tzz1KZ+kAaSwvydi0g==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.1", @@ -5714,7 +5714,7 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.10.tgz", "integrity": "sha512-Lp2L65vFi+cj0vFMu2obpPW69DU+6O5g3086lmI4XcnRCG8PxvpWC7XyaVwJCxsZFzueHjXnrOH/E0pl0zikfA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.1", @@ -5725,7 +5725,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -5760,7 +5760,7 @@ "version": "3.0.12", "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.12.tgz", "integrity": "sha512-1mDEXqzM20yywaMDuf5o9ue8OkJ373lSPbaSjyEvkWdqELhFMyNNgKGWL/rCSf4KME8B+HlHKuR8u9kRj8HzEQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^4.1.7", @@ -5775,7 +5775,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.3.tgz", "integrity": "sha512-Hdl9296i/EMptaX7agrSzJZDiz5Y8XPUeBbctTmMtnCguGpqfU3jVsTUan0VLaOhsnquqWLL8Bl5HrlbVGT1og==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/core": "^2.5.3", @@ -5795,7 +5795,7 @@ "version": "3.0.27", "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.27.tgz", "integrity": "sha512-H3J/PjJpLL7Tt+fxDKiOD25sMc94YetlQhCnYeNmina2LZscAdu0ZEZPas/kwePHABaEtqp7hqa5S4UJgMs1Tg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.11", @@ -5816,7 +5816,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, + "devOptional": true, "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -5830,7 +5830,7 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.10.tgz", "integrity": "sha512-MnAuhh+dD14F428ubSJuRnmRsfOpxSzvRhaGVTvd/lrUDE3kxzCCmH8lnVTvoNQnV2BbJ4c15QwZ3UdQBtFNZA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.1", @@ -5844,7 +5844,7 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.10.tgz", "integrity": "sha512-grCHyoiARDBBGPyw2BeicpjgpsDFWZZxptbVKb3CRd/ZA15F/T6rZjCCuBUjJwdck1nwUuIxYtsS4H9DDpbP5w==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.1", @@ -5858,7 +5858,7 @@ "version": "3.1.11", "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.11.tgz", "integrity": "sha512-URq3gT3RpDikh/8MBJUB+QGZzfS7Bm6TQTqoh4CqE8NBuyPkWa5eUXj0XFcFfeZVgg3WMh1u19iaXn8FvvXxZw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^3.1.10", @@ -5874,7 +5874,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.1.tgz", "integrity": "sha512-fr+UAOMGWh6bn4YSEezBCpJn9Ukp9oR4D32sCjCo7U81evE11YePOQ58ogzyfgmjIO79YeOdfXXqr0jyhPQeMg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^3.1.8", @@ -5891,7 +5891,7 @@ "version": "3.1.10", "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.10.tgz", "integrity": "sha512-n1MJZGTorTH2DvyTVj+3wXnd4CzjJxyXeOgnTlgNVFxaaMeT4OteEp4QrzF8p9ee2yg42nvyVK6R/awLCakjeQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.1", @@ -5905,7 +5905,7 @@ "version": "4.1.7", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.1", @@ -5919,7 +5919,7 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.10.tgz", "integrity": "sha512-nT9CQF3EIJtIUepXQuBFb8dxJi3WVZS3XfuDksxSCSn+/CzZowRLdhDn+2acbBv8R6eaJqPupoI/aRFIImNVPQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.1", @@ -5934,7 +5934,7 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.10.tgz", "integrity": "sha512-Oa0XDcpo9SmjhiDD9ua2UyM3uU01ZTuIrNdZvzwUTykW1PM8o2yJvMh1Do1rY5sUQg4NDV70dMi0JhDx4GyxuQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.1", @@ -5948,7 +5948,7 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.10.tgz", "integrity": "sha512-zHe642KCqDxXLuhs6xmHVgRwy078RfqxP2wRDpIyiF8EmsWXptMwnMwbVa50lw+WOGNrYm9zbaEg0oDe3PTtvQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.1" @@ -5961,7 +5961,7 @@ "version": "3.1.11", "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.11.tgz", "integrity": "sha512-AUdrIZHFtUgmfSN4Gq9nHu3IkHMa1YDcN+s061Nfm+6pQ0mJy85YQDB0tZBCmls0Vuj22pLwDPmL92+Hvfwwlg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.1", @@ -5975,7 +5975,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.3.tgz", "integrity": "sha512-pPSQQ2v2vu9vc8iew7sszLd0O09I5TRc5zhY71KA+Ao0xYazIG+uLeHbTJfIWGO3BGVLiXjUr3EEeCcEQLjpWQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", @@ -5995,7 +5995,7 @@ "version": "3.4.4", "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.4.tgz", "integrity": "sha512-dPGoJuSZqvirBq+yROapBcHHvFjChoAQT8YPWJ820aPHHiowBlB3RL1Q4kPT1hx0qKgJuf+HhyzKi5Gbof4fNA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/core": "^2.5.3", @@ -6014,7 +6014,7 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -6027,7 +6027,7 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.10.tgz", "integrity": "sha512-j90NUalTSBR2NaZTuruEgavSdh8MLirf58LoGSk4AtQfyIymogIhgnGUU2Mga2bkMkpSoC9gxb74xBXL5afKAQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/querystring-parser": "^3.0.10", @@ -6039,7 +6039,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^3.0.0", @@ -6054,7 +6054,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -6064,7 +6064,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -6077,7 +6077,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", @@ -6091,7 +6091,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -6104,7 +6104,7 @@ "version": "3.0.27", "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.27.tgz", "integrity": "sha512-GV8NvPy1vAGp7u5iD/xNKUxCorE4nQzlyl057qRac+KwpH5zq8wVq6rE3lPPeuFLyQXofPN6JwxL1N9ojGapiQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^3.1.10", @@ -6121,7 +6121,7 @@ "version": "3.0.27", "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.27.tgz", "integrity": "sha512-7+4wjWfZqZxZVJvDutO+i1GvL6bgOajEkop4FuR6wudFlqBiqwxw3HoH6M9NgeCd37km8ga8NPp2JacQEtAMPg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/config-resolver": "^3.0.12", @@ -6140,7 +6140,7 @@ "version": "2.1.6", "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.6.tgz", "integrity": "sha512-mFV1t3ndBh0yZOJgWxO9J/4cHZVn5UG1D8DeCc6/echfNkeEJWu9LD7mgGH5fHrEdR7LDoWw7PQO6QiGpHXhgA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.11", @@ -6155,7 +6155,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -6168,7 +6168,7 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.10.tgz", "integrity": "sha512-eJO+/+RsrG2RpmY68jZdwQtnfsxjmPxzMlQpnHKjFPwrYqvlcT+fHdT+ZVwcjlWSrByOhGr9Ff2GG17efc192A==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.1", @@ -6182,7 +6182,7 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.10.tgz", "integrity": "sha512-1l4qatFp4PiU6j7UsbasUHL2VU023NRB/gfaa1M0rDqVrRN4g3mCArLRyH3OuktApA4ye+yjWQHjdziunw2eWA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/service-error-classification": "^3.0.10", @@ -6197,7 +6197,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.3.1.tgz", "integrity": "sha512-Ff68R5lJh2zj+AUTvbAU/4yx+6QPRzg7+pI7M1FbtQHcRIp7xvguxVsQBKyB3fwiOwhAKu0lnNyYBaQfSW6TNw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/fetch-http-handler": "^4.1.1", @@ -6217,7 +6217,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -6230,7 +6230,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^3.0.0", @@ -7287,14 +7287,12 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", - "dev": true, "license": "MIT" }, "node_modules/@types/whatwg-url": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/webidl-conversions": "*" @@ -8305,6 +8303,18 @@ "dev": true, "license": "MIT" }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/ast-module-types": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-5.0.0.tgz", @@ -8813,6 +8823,12 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "license": "MIT" }, + "node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -8848,7 +8864,7 @@ "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/brace-expansion": { @@ -8927,7 +8943,6 @@ "version": "6.9.0", "resolved": "https://registry.npmjs.org/bson/-/bson-6.9.0.tgz", "integrity": "sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=16.20.1" @@ -9896,6 +9911,46 @@ "node": ">= 0.4.0" } }, + "node_modules/connect-mongo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-5.1.0.tgz", + "integrity": "sha512-xT0vxQLqyqoUTxPLzlP9a/u+vir0zNkhiy9uAdHjSCcUUf7TS5b55Icw8lVyYFxfemP3Mf9gdwUOgeF3cxCAhw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.1", + "kruptein": "^3.0.0" + }, + "engines": { + "node": ">=12.9.0" + }, + "peerDependencies": { + "express-session": "^1.17.1", + "mongodb": ">= 5.1.0 < 7" + } + }, + "node_modules/connect-mongo/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/connect-mongo/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -12050,7 +12105,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", - "dev": true, "license": "Apache-2.0", "optional": true, "peer": true, @@ -12066,7 +12120,6 @@ "version": "5.1.3", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", - "dev": true, "license": "Apache-2.0", "optional": true, "peer": true, @@ -15677,6 +15730,18 @@ "node": ">=6" } }, + "node_modules/kruptein": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.7.tgz", + "integrity": "sha512-vTftnEjfbqFHLqxDUMQCj6gBo5lKqjV4f0JsM8rk8rM3xmvFZ2eSy4YALdaye7E+cDKnEj7eAjFR3vwh8a4PgQ==", + "license": "MIT", + "dependencies": { + "asn1.js": "^5.4.1" + }, + "engines": { + "node": ">8" + } + }, "node_modules/lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", @@ -16090,7 +16155,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "dev": true, "license": "MIT" }, "node_modules/merge-descriptors": { @@ -16197,6 +16261,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, "node_modules/minimatch": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", @@ -16578,7 +16648,6 @@ "version": "6.10.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz", "integrity": "sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@mongodb-js/saslprep": "^1.1.5", @@ -16625,7 +16694,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@types/whatwg-url": "^11.0.2", @@ -18396,7 +18464,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -19625,7 +19692,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "dev": true, "license": "MIT", "dependencies": { "memory-pager": "^1.0.2" @@ -21095,7 +21161,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", - "dev": true, "license": "MIT", "dependencies": { "punycode": "^2.3.0" @@ -21680,7 +21745,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -21749,7 +21813,6 @@ "version": "13.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", - "dev": true, "license": "MIT", "dependencies": { "tr46": "^4.1.1", diff --git a/package.json b/package.json index 8e51516724be5ca51ee4886fdc9f36b8634bf4ee..aa8c29f37efa405f247a7234f5d985aa7cc0dde9 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dependencies": { "amqplib": "^0.10.5", "connect-flash": "^0.1.1", + "connect-mongo": "^5.1.0", "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.5", diff --git a/passport/googleStrategy.js b/passport/googleStrategy.js index 7acaf173e06eef64c1bf1dc82b22b181154801c6..f6698b36fab68e39e1d5b078802a20e197594797 100644 --- a/passport/googleStrategy.js +++ b/passport/googleStrategy.js @@ -1,15 +1,15 @@ // passport/googleStrategy.js - const { Strategy: GoogleStrategy } = require('passport-google-oauth20'); -const User = require('../models/user'); +const User = require('../models/user'); module.exports = new GoogleStrategy( { - clientID: process.env.GOOGLE_CLIENT_ID, // .env �뚯씪�� �ㅼ젙 + clientID: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, callbackURL: process.env.CALLBACK_URL, + passReqToCallback: true, // req 媛앹껜瑜� 肄쒕갚�� �꾨떖 }, - async (accessToken, refreshToken, profile, done) => { + async (req, accessToken, refreshToken, profile, done) => { try { // �꾨줈�꾩뿉�� �ъ슜�� �뺣낫 異붿텧 const email = profile.emails[0].value; @@ -23,7 +23,7 @@ module.exports = new GoogleStrategy( return done(null, user); } catch (err) { - return done(err); + return done(err, null); } } ); diff --git a/routes/auth.js b/routes/auth.js deleted file mode 100644 index 7eda249d0f05a64d3537bc0462e2da4f7c6fd831..0000000000000000000000000000000000000000 --- a/routes/auth.js +++ /dev/null @@ -1,35 +0,0 @@ -// routes/auth.js - -const express = require('express'); -const passport = require('passport'); - -const router = express.Router(); - -// GET /auth/login -router.get('/login', (req, res) => { - res.send('<a href="/auth/google">Log in with Google</a>'); -}); - -// GET /auth/logout -router.get('/logout', (req, res) => { - req.logout(() => { - res.redirect('/'); - }); -}); - -// GET /auth/google -router.get( - '/google', - passport.authenticate('google', { scope: ['profile', 'email'] }) -); - -// GET /auth/google/callback -router.get( - '/google/callback', - passport.authenticate('google', { failureRedirect: '/auth/login' }), - (req, res) => { - res.redirect('/'); - } -); - -module.exports = router; diff --git a/routes/authRoute.js b/routes/authRoute.js new file mode 100644 index 0000000000000000000000000000000000000000..a83f6d484e2469e47eebbd4d6af82392b619c61f --- /dev/null +++ b/routes/authRoute.js @@ -0,0 +1,109 @@ +const express = require('express'); +const passport = require('passport'); +const MemberService = require('../services/memberService'); + +const router = express.Router(); + +// Google OAuth 濡쒓렇�� �쇱슦�� +router.get( + '/login', + (req, res, next) => { + const { state } = req.query; // �대씪�댁뼵�몄뿉�� �꾨떖�� state(fcmToken) + console.log("State received at /login:", state); + + passport.authenticate("google", { + scope: ["profile", "email"], // �붿껌�� �ъ슜�� �뺣낫 + state, // �꾨떖諛쏆� fcmToken�� state濡� �ㅼ젙 + })(req, res, next); + } +); + +router.get( + '/google/callback', + passport.authenticate('google', { + failureRedirect: `${process.env.FRONT_URL}/login` + }), + async (req, res) => { + // Google OAuth �몄쬆 �깃났 �� state �뚮씪誘명꽣濡� �꾨떖�� fcmToken 媛��몄삤湲� + const fcmToken = req.query.state; + console.log("諛쏆븘�� fcmToken", fcmToken); + const userEmail = req.user.email; // Google 濡쒓렇�몄뿉�� 媛��몄삩 email + const redirectUrl = process.env.FRONT_URL; + req.session.userEmail = userEmail; // �몄뀡�� �ъ슜�� �대찓�� ���� + + try { + if (fcmToken) { + // FCM �좏겙 �깅줉 + await MemberService.registerToken(userEmail, fcmToken); + console.log(`FCM token registered for user: ${userEmail}`); + } else { + console.warn("No FCM token provided during login"); + } + } catch (error) { + console.error("Error registering FCM token during login:", error); + } + + req.session.save((err) => { + if (err) { + console.error('�몄뀡 ���� �ㅻ쪟:', err); + return res.status(500).json({ error: '�쒕쾭 �ㅻ쪟' }); + } + res.redirect(redirectUrl); + + }); + } +); + +// 濡쒓렇�꾩썐 �쇱슦�� +router.get('/logout', (req, res) => { + if (req.session) { + req.session.destroy((err) => { + if (err) { + console.error('�몄뀡 ��젣 �ㅻ쪟:', err); + return res.status(500).json({ error: '�쒕쾭 �ㅻ쪟' }); + } + const redirectUrl = process.env.FRONT_URL; + res.redirect(redirectUrl); + }); + } else { + // �몄뀡�� �녿뒗 寃쎌슦�먮룄 由щ떎�대젆�� + const redirectUrl = process.env.FRONT_URL; + res.redirect(redirectUrl); + } +}); + +// �ъ슜�� ��젣 �쇱슦�� +router.delete('/leave', async (req, res) => { + try { + // �몄쬆�� �ъ슜�� �뺤씤 + if (!req.user) { + return res.status(401).json({ error: '�몄쬆�섏� �딆� �ъ슜�먯엯�덈떎.' }); + } + + const userId = req.user.id; + + // �ъ슜�� ��젣 + const deleted = await User.destroy({ + where: { id: userId } + }); + + if (!deleted) { + return res.status(404).json({ error: '�ъ슜�먮� 李얠쓣 �� �놁뒿�덈떎.' }); + } + + // �몄뀡 ��젣 + req.session.destroy((err) => { + if (err) { + console.error('�몄뀡 ��젣 �ㅻ쪟:', err); + return res.status(500).json({ error: '�쒕쾭 �ㅻ쪟' }); + } + // �깃났 硫붿떆吏� 諛섑솚 (由щ떎�대젆�� ���� JSON �묐떟) + res.status(200).json({ message: '�ъ슜�� 怨꾩젙�� �깃났�곸쑝濡� ��젣�섏뿀�듬땲��.' }); + }); + } catch (error) { + console.error('�ъ슜�� ��젣 �ㅻ쪟:', error); + res.status(500).json({ error: '�쒕쾭 �ㅻ쪟' }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/chatRoute.js b/routes/chatRoute.js index 6ae03f13f5e429e9c245c289d859400f99ac799a..ed55aa560c36105aff3706f702bda26fe0c6f234 100644 --- a/routes/chatRoute.js +++ b/routes/chatRoute.js @@ -1,6 +1,7 @@ const express = require('express'); const router = express.Router(); const chatController = require('../controllers/chatController'); +const { isLoggedIn } = require('../middlewares/auth'); router.post('/create-room', chatController.createChatRoom); router.get('/rooms', chatController.getChatRooms); @@ -11,4 +12,11 @@ router.get('/unread-count/:chatRoomId', chatController.getUnreadCount); router.post('/update-status-and-logid', chatController.updateStatusAndLogId); router.post('/update-read-log-id', chatController.updateReadLogId); +router.use(isLoggedIn); + +router.post('/:chatRoomId/notices', chatController.addNotice); +router.get('/:chatRoomId/notices/latest', chatController.getLatestNotice); +router.get('/:chatRoomId/notices', chatController.getAllNotices); +router.get('/:chatRoomId/notices/:noticeId', chatController.getNoticeById); + module.exports = router; diff --git a/routes/friend.js b/routes/friendRoute.js similarity index 100% rename from routes/friend.js rename to routes/friendRoute.js diff --git a/routes/inviteRoutes.js b/routes/inviteRoute.js similarity index 100% rename from routes/inviteRoutes.js rename to routes/inviteRoute.js diff --git a/routes/meetingRoute.js b/routes/meetingRoute.js index a2788cad0c127f0b8014ffe20ccca7f86b061b9b..a85d20297d3f8b8f51fd5c11645ac829c9a3d680 100644 --- a/routes/meetingRoute.js +++ b/routes/meetingRoute.js @@ -7,6 +7,9 @@ const MeetingController = require('../controllers/meetingController'); router.use(isLoggedIn); +// �닿� 李몄뿬�� 紐⑥엫 紐⑸줉 議고쉶 +router.get('/my', MeetingController.getMyMeetings); + // 踰덇컻 紐⑥엫 �앹꽦 router.post('/', MeetingController.createMeeting); @@ -22,4 +25,9 @@ router.post('/:meetingId/join', MeetingController.joinMeeting); // 踰덇컻 紐⑥엫 �곸꽭 議고쉶 router.get('/:meetingId', MeetingController.getMeetingDetail); +// 踰덇컻 紐⑥엫 �덊눜 +router.delete('/:meetingId/leave', MeetingController.leaveMeeting); + + + module.exports = router; \ No newline at end of file diff --git a/routes/performanceRoute.js b/routes/performanceRoute.js new file mode 100644 index 0000000000000000000000000000000000000000..eeab98cf437121ab8bd3c8904bbc0683e36a8198 --- /dev/null +++ b/routes/performanceRoute.js @@ -0,0 +1,14 @@ +// routes/performanceRoute.js +const express = require('express'); +const router = express.Router(); +const performanceMonitor = require('../utils/performanceMonitor'); + +router.get('/stats', (req, res) => { + const stats = performanceMonitor.getAllStats(); + res.json({ + success: true, + data: { stats } + }); +}); + +module.exports = router; diff --git a/routes/schedule.js b/routes/scheduleRoute.js similarity index 100% rename from routes/schedule.js rename to routes/scheduleRoute.js diff --git a/routes/sessionRoute.js b/routes/sessionRoute.js new file mode 100644 index 0000000000000000000000000000000000000000..7ecd2922a17ef9e4f9fe9e1093090e98d7f4a082 --- /dev/null +++ b/routes/sessionRoute.js @@ -0,0 +1,23 @@ +const express = require('express'); +const router = express.Router(); + +// GET /api/session/info +router.get('/info', (req, res) => { + if (req.user) { + const { email, name } = req.user; + // 罹먯떛 鍮꾪솢�깊솕 + res.set('Cache-Control', 'no-store'); + res.set('Pragma', 'no-cache'); + return res.status(200).json({ + email,name + }); + } + // �몄뀡�� 留뚮즺�섏뿀嫄곕굹 �ъ슜�� �뺣낫媛� �녿뒗 寃쎌슦 + res.set('Cache-Control', 'no-store'); + res.set('Pragma', 'no-cache'); + res.status(401).json({ + message: '�몄뀡�� 留뚮즺�섏뿀嫄곕굹 �ъ슜�� �뺣낫媛� �놁뒿�덈떎.', + }); +}); + +module.exports = router; \ No newline at end of file diff --git a/schemas/ChatRooms.js b/schemas/chatRooms.js similarity index 85% rename from schemas/ChatRooms.js rename to schemas/chatRooms.js index 1c2001cfe54dcc6c3034a23e01b3bc706100b94b..beaba10e774b5036b2206fc0a4152b5fc105f9a6 100644 --- a/schemas/ChatRooms.js +++ b/schemas/chatRooms.js @@ -1,7 +1,5 @@ -// schemas/chatRooms.js const mongoose = require('mongoose'); -// MongoDB 梨꾪똿諛� �ㅽ궎留� �섏젙 (FCM �좏겙�� 諛곗뿴濡� 愿�由�) const chatRoomsSchema = new mongoose.Schema({ chatRoomId: { type: String, required: true, unique: true }, chatRoomName: { type: String, required: true }, @@ -18,6 +16,11 @@ const chatRoomsSchema = new mongoose.Schema({ lastReadAt: { type: Map, of: Date }, lastReadLogId: { type: Map, of: String }, isOnline: { type: Map, of: Boolean }, + notices: [{ + sender: { type: String }, + message: { type: String }, + timestamp: { type: Date, default: Date.now }, + }] }, { collection: 'chatrooms' }); const ChatRooms = mongoose.models.ChatRooms || mongoose.model('ChatRooms', chatRoomsSchema); diff --git a/services/chatService.js b/services/chatService.js index 0d462a5da57a7d0ce940cbafa7dbe7eee295828b..f67ee428d4efc761df9dd467600ec25eb93e4cbc 100644 --- a/services/chatService.js +++ b/services/chatService.js @@ -209,6 +209,87 @@ class ChatService { } } + // 怨듭��ы빆 異붽� + async addNotice(chatRoomId, sender, message) { + try { + const newNotice = { + sender, + message, + timestamp: new Date(), + }; + + const updatedChatRoom = await ChatRooms.findOneAndUpdate( + { chatRoomId }, + { $push: { notices: newNotice } }, // 怨듭��ы빆 諛곗뿴�� 異붽� + { new: true } + ); + + if (!updatedChatRoom) { + throw new Error('Chat room not found'); + } + + return newNotice; + } catch (error) { + console.error('Error adding notice:', error.message); + throw new Error('Failed to add notice'); + } + } + + // 理쒖떊 怨듭��ы빆 議고쉶 + async getLatestNotice(chatRoomId) { + try { + const chatRoom = await ChatRooms.findOne( + { chatRoomId }, + { notices: { $slice: -1 } } // 理쒖떊 怨듭� 1媛쒕쭔 媛��몄삤湲� + ); + + if (!chatRoom || chatRoom.notices.length === 0) { + return null; + } + + return chatRoom.notices[0]; + } catch (error) { + console.error('Error fetching latest notice:', error.message); + throw new Error('Failed to fetch latest notice'); + } + } + + // 怨듭��ы빆 �꾩껜 議고쉶 + async getAllNotices(chatRoomId) { + try { + const chatRoom = await ChatRooms.findOne({ chatRoomId }, { notices: 1 }); + + if (!chatRoom) { + throw new Error('Chat room not found'); + } + + return chatRoom.notices; + } catch (error) { + console.error('Error fetching all notices:', error.message); + throw new Error('Failed to fetch all notices'); + } + } + + // 怨듭��ы빆 �곸꽭 議고쉶 + async getNoticeById(chatRoomId, noticeId) { + try { + const chatRoom = await ChatRooms.findOne({ chatRoomId }); + if (!chatRoom) { + throw new Error('Chat room not found'); + } + + const notice = chatRoom.notices.find(notice => notice._id.toString() === noticeId); + if (!notice) { + throw new Error('Notice not found'); + } + + return notice; + } catch (error) { + console.error('Error in getNoticeById:', error.message); + throw error; + } + } + } module.exports = new ChatService(); \ No newline at end of file diff --git a/services/friendService.js b/services/friendService.js index d35b3c700c9b4b1a1b7a18326eafb2eace829478..5e1652459a2276272c5559d997137e238db0178e 100644 --- a/services/friendService.js +++ b/services/friendService.js @@ -11,9 +11,7 @@ const FriendListDTO = require('../dtos/FriendListDTO'); class FriendService { /** * User 議댁옱 �щ� �좏슚�� 寃��� - * @param {number} userId - 寃��ы븷 �ъ슜�� ID - * @returns {Promise<User>} - �좏슚�� �ъ슜�� 媛앹껜 - * @throws {Error} - �ъ슜�먭� 議댁옱�섏� �딆쓣 寃쎌슦 + * userId - 寃��ы븷 �ъ슜�� ID */ async validUser(userId) { const user = await User.findByPk(userId); @@ -25,10 +23,9 @@ class FriendService { /** * 移쒓뎄 �붿껌 蹂대궡湲� - * @param {number} userId - 移쒓뎄 �붿껌�� 蹂대궡�� �ъ슜�� ID - * @param {number} friendId - 移쒓뎄 �붿껌�� 諛쏅뒗 �ъ슜�� ID - * @returns {Promise<FriendResponseDTO>} - �앹꽦�� 移쒓뎄 �붿껌 DTO - * @throws {Error} - �좏슚�섏� �딆� �붿껌�� 寃쎌슦 + * userId - 移쒓뎄 �붿껌�� 蹂대궡�� �ъ슜�� ID + * friendId - 移쒓뎄 �붿껌�� 諛쏅뒗 �ъ슜�� ID + * returns - �앹꽦�� 移쒓뎄 �붿껌 DTO */ async sendFriendRequest(userId, friendId) { await this.validUser(userId); @@ -81,8 +78,8 @@ class FriendService { /** * 諛쏆� 移쒓뎄 �붿껌 紐⑸줉 議고쉶 - * @param {number} userId - �붿껌�� 諛쏆� �ъ슜�� ID - * @returns {Promise<Array<FriendResponseDTO>>} - 諛쏆� 移쒓뎄 �붿껌 紐⑸줉 DTO 諛곗뿴 + userId - �붿껌�� 諛쏆� �ъ슜�� ID + 諛쏆� 移쒓뎄 �붿껌 紐⑸줉 DTO 諛곗뿴 */ async getReceivedRequests(userId) { const receivedRequests = await Friend.findAll({ @@ -101,8 +98,7 @@ class FriendService { /** * 蹂대궦 移쒓뎄 �붿껌 紐⑸줉 議고쉶 - * @param {number} userId - �붿껌�� 蹂대궦 �ъ슜�� ID - * @returns {Promise<Array<FriendResponseDTO>>} - 蹂대궦 移쒓뎄 �붿껌 紐⑸줉 DTO 諛곗뿴 + * userId - �붿껌�� 蹂대궦 �ъ슜�� ID */ async getSentRequests(userId) { const sentRequests = await Friend.findAll({ @@ -163,10 +159,9 @@ class FriendService { /** * 移쒓뎄 �붿껌 嫄곗젅 - * @param {number} userId - �붿껌�� 嫄곗젅�섎뒗 �ъ슜�� ID - * @param {number} friendId - 移쒓뎄 �붿껌�� 蹂대궦 �ъ슜�� ID - * @returns {Promise<number>} - ��젣�� 移쒓뎄 �붿껌 �� - * @throws {Error} - 移쒓뎄 �붿껌�� 議댁옱�섏� �딆쓣 寃쎌슦 + *userId - �붿껌�� 嫄곗젅�섎뒗 �ъ슜�� ID + * friendId - 移쒓뎄 �붿껌�� 蹂대궦 �ъ슜�� ID + * returns - ��젣�� 移쒓뎄 �붿껌 �� */ async rejectFriendRequest(userId, friendId) { const result = await Friend.destroy({ @@ -186,10 +181,10 @@ class FriendService { /** * 移쒓뎄 紐⑸줉 議고쉶 - * @param {number} userId - 移쒓뎄 紐⑸줉�� 議고쉶�� �ъ슜�� ID - * @param {number} limit - �� �섏씠吏��� �쒖떆�� 移쒓뎄 �� - * @param {number} offset - �섏씠吏� �ㅽ봽�� - * @returns {Promise<Array<FriendListDTO>>} - 移쒓뎄 紐⑸줉 DTO 諛곗뿴 + userId - 移쒓뎄 紐⑸줉�� 議고쉶�� �ъ슜�� ID + limit - �� �섏씠吏��� �쒖떆�� 移쒓뎄 �� + offset - �섏씠吏� �ㅽ봽�� + 移쒓뎄 紐⑸줉 DTO 諛곗뿴 */ async getFriendList(userId, pagination) { const { limit = 20, offset = 0 } = pagination; @@ -215,7 +210,7 @@ class FriendService { } ], order: [['id', 'ASC']], - limit: limit + 1, // �ㅼ쓬 �섏씠吏� 議댁옱 �щ� �뺤씤�� �꾪빐 1媛� �� 議고쉶 + limit: limit + 1, offset }); @@ -232,10 +227,10 @@ class FriendService { /** * 移쒓뎄 ��젣 - * @param {number} userId - 移쒓뎄瑜� ��젣�섎뒗 �ъ슜�� ID - * @param {number} friendId - ��젣�� 移쒓뎄�� �ъ슜�� ID - * @returns {Promise<number>} - ��젣�� 移쒓뎄 愿�怨� �� - * @throws {Error} - 移쒓뎄 愿�怨꾧� 議댁옱�섏� �딆쓣 寃쎌슦 + - 移쒓뎄瑜� ��젣�섎뒗 �ъ슜�� ID + - ��젣�� 移쒓뎄�� �ъ슜�� ID + - ��젣�� 移쒓뎄 愿�怨� �� + -移쒓뎄 愿�怨꾧� 議댁옱�섏� �딆쓣 寃쎌슦 */ async deleteFriend(userId, friendId) { const result = await Friend.destroy({ diff --git a/services/meetingService.js b/services/meetingService.js index 0c04014bb5f628591c57903f37a283c25b9f8217..bc7b7f784f9027f83f0ae5f777e50384c0a82e1d 100644 --- a/services/meetingService.js +++ b/services/meetingService.js @@ -1,7 +1,4 @@ -// const { Meeting, MeetingParticipant, User, Schedule } = require('../models'); -// const ChatRoom = require('../models/chatRooms'); -// const FcmToken = require('../models/fcmToken'); // services/meetingService.js const { v4: uuidv4 } = require('uuid'); const { Op } = require('sequelize'); @@ -121,20 +118,16 @@ class MeetingService { { transaction } ); - // �ㅼ�以� �앹꽦 (紐⑥엫 �쒓컙 踰붿쐞 �� 紐⑤뱺 time_idx�� ���� �앹꽦) - const events = []; - for (let idx = time_idx_start; idx <= time_idx_end; idx++) { - events.push({ time_idx: idx }); - } - await ScheduleService.createSchedules( - { - userId: created_by, - title: `踰덇컻 紐⑥엫: ${title}`, - is_fixed: false, - events: events, - }, - transaction + const time_indices = Array.from( + { length: time_idx_end - time_idx_start + 1 }, + (_, i) => time_idx_start + i ); + await ScheduleService.createSchedules({ + userId: created_by, + title: `踰덇컻 紐⑥엫: ${title}`, + is_fixed: false, + time_indices: time_indices, + }, transaction); // 移쒓뎄 珥덈� 濡쒖쭅 �몄텧 const invitedFriendIds = await this.sendInvites({ @@ -267,24 +260,17 @@ class MeetingService { { 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: false, - events: events, - }, - 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({ @@ -328,130 +314,111 @@ class MeetingService { async getMeetings(userId, pagination) { const { limit = 20, offset = 0 } = pagination; - - 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', - required: false, - attributes: [], - }, - { - model: User, - as: 'creator', - attributes: ['name'], - } - ], - order: [['createdAt', 'DESC']], - offset - }); - const hasNext = meetings.length > limit; - const content = await Promise.all( - meetings.slice(0, limit).map(async (meeting) => { - const isParticipant = await MeetingParticipant.findOne({ - where: { - meeting_id: meeting.id, - user_id: userId + try { + const meetings = await Meeting.findAll({ + attributes: [ + 'id', 'title', 'description', + 'time_idx_start', 'time_idx_end', + 'location', 'time_idx_deadline', + 'type', 'max_num', 'cur_num', + 'created_at' + ], + include: [ + { + model: User, + as: 'creator', + attributes: ['name'], + required: false } - }); + ], + order: [['created_at', 'DESC']], + limit: limit + 1, + offset, + distinct: true + }); - const hasConflict = await ScheduleService.checkScheduleOverlapByTime( - userId, - meeting.time_idx_start, - meeting.time_idx_end - ); + const hasNext = meetings.length > limit; + const content = await Promise.all( + meetings.slice(0, limit).map(async (meeting) => { + const isParticipant = await MeetingParticipant.findOne({ + where: { + meeting_id: meeting.id, + user_id: userId + } + }); - const creatorName = meeting.creator ? meeting.creator.name : 'Unknown'; - return new MeetingResponseDTO(meeting, !!isParticipant, hasConflict, creatorName); - }) - ); + const hasConflict = await ScheduleService.checkScheduleOverlapByTime( + userId, + meeting.time_idx_start, + meeting.time_idx_end + ); - return { - content, - hasNext - }; + return new MeetingResponseDTO( + meeting, + !!isParticipant, + hasConflict, + meeting.creator?.name || 'Unknown' + ); + }) + ); + + return { content, hasNext }; + } catch (error) { + console.error('getMeetings error:', error); + throw new Error('Failed to fetch meetings'); + } } async getMyMeetings(userId, pagination) { const { limit = 20, offset = 0 } = pagination; - 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 }, - attributes: [], - }, - { - model: User, - as: 'creator', - attributes: ['name'], - } - ], - where: { - [Op.or]: [ - { created_by: userId }, - { '$participants.user_id$': userId } - ] - }, - order: [['createdAt', 'DESC']], - offset - }); - - const hasNext = meetings.length > limit; - const content = await Promise.all( - meetings.slice(0, limit).map(async (meeting) => { - const isParticipant = await MeetingParticipant.findOne({ - where: { - meeting_id: meeting.id, - user_id: userId + try { + const meetings = await Meeting.findAll({ + attributes: [ + 'id', 'title', 'description', + 'time_idx_start', 'time_idx_end', + 'location', 'time_idx_deadline', + 'type', 'max_num', 'cur_num', + 'created_at' + ], + include: [ + { + model: MeetingParticipant, + as: 'participants', + where: { user_id: userId }, + required: true + }, + { + model: User, + as: 'creator', + attributes: ['name'], + required: false } - }); + ], + order: [['created_at', 'DESC']], + limit: limit + 1, + offset, + distinct: true + }); - const hasConflict = await ScheduleService.checkScheduleOverlapByTime( - userId, - meeting.time_idx_start, - meeting.time_idx_end + const hasNext = meetings.length > limit; + const content = meetings.slice(0, limit).map(meeting => { + return new MeetingResponseDTO( + meeting, + true, // 李몄뿬�먮줈 議고쉶�덉쑝誘�濡� ��긽 true + false, // �대� 李몄뿬 以묒씤 誘명똿�대�濡� 異⑸룎 泥댄겕 遺덊븘�� + meeting.creator?.name || 'Unknown' ); + }); - const creatorName = meeting.creator ? meeting.creator.name : 'Unknown'; - return new MeetingResponseDTO(meeting, !!isParticipant, hasConflict, creatorName); - }) - ); - - return { - content, - hasNext - }; + 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: [ @@ -612,7 +579,10 @@ class MeetingService { }); if (chatRoom) { const user = await User.findByPk(userId); - chatRoom.participants = chatRoom.participants.filter(p => p !== user.name); + chatRoom.participants = chatRoom.participants.filter(p => p.name !== user.name); + chatRoom.isOnline.delete(user.name); + chatRoom.lastReadAt.delete(user.name); + chatRoom.lastReadLogId.delete(user.name); await chatRoom.save(); } diff --git a/services/memberService.js b/services/memberService.js index 0435672d90a949dd46dcb7bad1de73d7fe764f69..ebe4fe15d7974bcfce53ad99dbc732ed79672bc2 100644 --- a/services/memberService.js +++ b/services/memberService.js @@ -25,7 +25,7 @@ class MemberService { } // 3. MongoDB�먯꽌 愿��� 梨꾪똿諛⑹쓽 FCM �좏겙 �낅뜲�댄듃 - const existingChatRooms = await ChatRoom.find({ "participants.name": user.name }); + const existingChatRooms = await ChatRooms.find({ "participants.name": user.name }); for (const room of existingChatRooms) { room.participants = room.participants.map((participant) => { if (participant.name === user.name) { diff --git a/services/modifyScheduleResponse.test.js b/services/modifyScheduleResponse.test.js new file mode 100644 index 0000000000000000000000000000000000000000..64ab78c260c61a9274df0f3f77fa5d459cc65934 --- /dev/null +++ b/services/modifyScheduleResponse.test.js @@ -0,0 +1,187 @@ +// test/scheduleService.test.js +const sequelize = require('../config/sequelize'); +const { Schedule, User, Meeting, MeetingParticipant, FcmToken } = require('../models'); +const ScheduleService = require('../services/scheduleService'); +const MeetingService = require('../services/meetingService'); +const ChatRooms = require('../schemas/chatRooms'); + +describe('Schedule Service and Meeting Integration Tests', () => { + beforeAll(async () => { + await sequelize.sync({ force: true }); + }); + + beforeEach(async () => { + await MeetingParticipant.destroy({ where: {} }); + await Meeting.destroy({ where: {} }); + await Schedule.destroy({ where: {} }); + await User.destroy({ where: {} }); + await FcmToken.destroy({ where: {} }); + + // �붾� �ъ슜�� �앹꽦 + await User.bulkCreate([ + { id: 1, name: 'Alice', email: 'alice@example.com' }, + { id: 2, name: 'Bob', email: 'bob@example.com' }, + { id: 3, name: 'Charlie', email: 'charlie@example.com' } + ]); + + // ChatRooms Mock �ㅼ젙 + jest.spyOn(ChatRooms.prototype, 'save').mockResolvedValue(undefined); + jest.spyOn(ChatRooms, 'findOne').mockResolvedValue({ + participants: [], + isOnline: new Map(), + lastReadAt: new Map(), + lastReadLogId: new Map(), + save: jest.fn().mockResolvedValue(true) + }); + }); + + afterAll(async () => { + await sequelize.close(); + }); + + describe('Schedule Service Tests', () => { + test('should create schedule with time_indices', async () => { + const scheduleData = { + userId: 1, + title: 'Test Schedule', + is_fixed: true, + time_indices: [36, 37, 38] + }; + + const schedule = await ScheduleService.createSchedules(scheduleData); + + expect(schedule).toBeDefined(); + expect(schedule.title).toBe('Test Schedule'); + expect(schedule.time_indices).toEqual([36, 37, 38]); + + const dbSchedules = await Schedule.findAll({ + where: { user_id: 1, title: 'Test Schedule' } + }); + expect(dbSchedules.length).toBe(3); + }); + + test('should update schedule with new time_indices', async () => { + await ScheduleService.createSchedules({ + userId: 1, + title: 'Original Schedule', + is_fixed: true, + time_indices: [36, 37, 38] + }); + + const updateData = { + originalTitle: 'Original Schedule', + title: 'Updated Schedule', + is_fixed: true, + time_indices: [36, 37, 38, 39] + }; + + const updatedSchedule = await ScheduleService.updateSchedules(1, updateData); + + expect(updatedSchedule.title).toBe('Updated Schedule'); + expect(updatedSchedule.time_indices).toEqual([36, 37, 38, 39]); + }); + + test('should delete schedule by title', async () => { + await ScheduleService.createSchedules({ + userId: 1, + title: 'Schedule to Delete', + is_fixed: true, + time_indices: [40, 41, 42] + }); + + const result = await ScheduleService.deleteSchedules(1, 'Schedule to Delete'); + expect(result.deletedCount).toBe(3); + + const remainingSchedules = await Schedule.findAll({ + where: { user_id: 1, title: 'Schedule to Delete' } + }); + expect(remainingSchedules.length).toBe(0); + }); + }); + + describe('Meeting Integration Tests', () => { + beforeEach(() => { + jest.spyOn(User, 'findOne').mockResolvedValue({ + id: 1, + name: 'Alice', + email: 'alice@example.com', + fcmTokenList: [] + }); + }); + + test('should create meeting with correct schedules', async () => { + const meetingData = { + title: 'Test Meeting', + time_idx_start: 50, + time_idx_end: 52, + created_by: 1, + type: 'OPEN', + max_num: 5 + }; + + const meeting = await MeetingService.createMeeting(meetingData); + + const creatorSchedules = await Schedule.findAll({ + where: { + user_id: 1, + title: `踰덇컻 紐⑥엫: ${meetingData.title}` + } + }); + + expect(creatorSchedules.length).toBe(3); + expect(creatorSchedules.map(s => s.time_idx).sort()).toEqual([50, 51, 52]); + }); + + test('should create correct schedules when joining meeting', async () => { + const meetingData = { + title: 'Join Test Meeting', + time_idx_start: 60, + time_idx_end: 62, + created_by: 1, + type: 'OPEN', + max_num: 5, + time_idx_deadline: 59 + }; + + const meeting = await MeetingService.createMeeting(meetingData); + jest.spyOn(MeetingService, 'getCurrentTimeIdx').mockReturnValue(58); + + await MeetingService.joinMeeting(meeting.meeting_id, 2); + + const participantSchedules = await Schedule.findAll({ + where: { + user_id: 2, + title: `踰덇컻 紐⑥엫: ${meetingData.title}` + } + }); + + expect(participantSchedules.length).toBe(3); + expect(participantSchedules.map(s => s.time_idx).sort()).toEqual([60, 61, 62]); + }); + + test('should handle schedule conflicts correctly', async () => { + await ScheduleService.createSchedules({ + userId: 2, + title: 'Existing Schedule', + is_fixed: true, + time_indices: [70, 71] + }); + + const meetingData = { + title: 'Conflict Test Meeting', + time_idx_start: 70, + time_idx_end: 72, + created_by: 1, + type: 'OPEN', + max_num: 5, + time_idx_deadline: 69 + }; + + const meeting = await MeetingService.createMeeting(meetingData); + + await expect( + MeetingService.joinMeeting(meeting.meeting_id, 2) + ).rejects.toThrow('�ㅼ�以꾩씠 寃뱀묩�덈떎'); + }); + }); +}); \ No newline at end of file diff --git a/services/performance.test.js b/services/performance.test.js new file mode 100644 index 0000000000000000000000000000000000000000..e987e86bb2471becaa10ec51713bdc6a1d4c88d2 --- /dev/null +++ b/services/performance.test.js @@ -0,0 +1,203 @@ +// services/performance.test.js +require('dotenv').config(); +const { Op } = require('sequelize'); +const ScheduleService = require('./scheduleService'); +const sequelize = require('../config/sequelize'); +const Schedule = require('../models/schedule'); + +class PerformanceTester { + constructor() { + this.testUserIds = [1, 2, 3, 4, 5]; // 5紐낆쓽 �뚯뒪�� �좎�留� �ъ슜 + this.results = { + operations: { + createSchedules: [], + getAllSchedules: [], + updateSchedules: [], + deleteSchedules: [] + }, + summary: {} + }; + } + + async setup() { + try { + await sequelize.authenticate(); + console.log('Database connection established successfully.'); + await Schedule.destroy({ where: {}, force: true }); + console.log('Test data cleaned successfully.'); + console.log('Using existing user IDs:', this.testUserIds); + } catch (error) { + console.error('Setup failed:', error); + throw error; + } + } + + async runLoadTest() { + console.log('Starting simplified test...'); + + const testSchedules = this.testUserIds.map((userId, i) => ({ + userId, + title: `Test Schedule ${i}`, + is_fixed: true, + time_indices: [i * 2, i * 2 + 1] + })); + + console.log('Test schedules:', testSchedules); + + const transaction = await sequelize.transaction(); + try { + // Create �뚯뒪�� + console.log('\nTesting createSchedules...'); + const createdSchedules = []; + for (const schedule of testSchedules) { + const result = await this.measureOperation('createSchedules', async () => { + const created = await ScheduleService.createSchedules(schedule, transaction); + console.log(`Created schedule for user ${schedule.userId}`); + return created; + }); + if (result) createdSchedules.push(result); + } + await transaction.commit(); + + // �앹꽦�� �ㅼ�以� �뺤씤 + const verifySchedules = await Schedule.findAll({ + where: { + user_id: { [Op.in]: this.testUserIds } + }, + raw: true + }); + console.log('\nVerified schedules:', verifySchedules); + + // GetAll �뚯뒪�� + console.log('\nTesting getAllSchedules...'); + for (const userId of this.testUserIds) { + await this.measureOperation('getAllSchedules', async () => { + return await ScheduleService.getAllSchedules(userId); + }); + } + + // Update �뚯뒪�� + console.log('\nTesting updateSchedules...'); + for (const schedule of createdSchedules) { + await this.measureOperation('updateSchedules', async () => { + return await ScheduleService.updateSchedules(schedule.user_id, { + originalTitle: schedule.title, + title: `Updated ${schedule.title}`, + is_fixed: schedule.is_fixed, + time_indices: schedule.time_indices + }); + }); + } + + // Delete �뚯뒪�� + console.log('\nTesting deleteSchedules...'); + const deleteTransaction = await sequelize.transaction(); + try { + for (const schedule of createdSchedules) { + await this.measureOperation('deleteSchedules', async () => { + return await ScheduleService.deleteSchedules( + schedule.user_id, + `Updated ${schedule.title}`, + deleteTransaction + ); + }); + } + await deleteTransaction.commit(); + } catch (error) { + await deleteTransaction.rollback(); + throw error; + } + } catch (error) { + await transaction.rollback(); + throw error; + } + + this.analyzePerfResults(); + } + + async measureOperation(name, operation) { + const start = process.hrtime.bigint(); + try { + const result = await operation(); + const end = process.hrtime.bigint(); + const duration = Number(end - start) / 1000000; + this.results.operations[name].push({ success: true, duration }); + return result; + } catch (error) { + const end = process.hrtime.bigint(); + const duration = Number(end - start) / 1000000; + this.results.operations[name].push({ + success: false, + duration, + error: error.message + }); + console.error(`Error in ${name}:`, error.message); + return null; + } + } + + analyzePerfResults() { + Object.entries(this.results.operations).forEach(([operation, results]) => { + const successful = results.filter(r => r.success); + const failed = results.filter(r => !r.success); + if (successful.length > 0) { + const durations = successful.map(r => r.duration); + this.results.summary[operation] = { + totalRequests: results.length, + successCount: successful.length, + failCount: failed.length, + avgDuration: durations.reduce((a, b) => a + b, 0) / successful.length, + minDuration: Math.min(...durations), + maxDuration: Math.max(...durations), + p95: this.calculatePercentile(durations, 95), + p99: this.calculatePercentile(durations, 99) + }; + } + }); + this.printResults(); + } + + calculatePercentile(array, percentile) { + const sorted = array.sort((a, b) => a - b); + const index = Math.ceil((percentile / 100) * sorted.length) - 1; + return sorted[index]; + } + + printResults() { + console.log('\n=== Performance Test Results ==='); + Object.entries(this.results.summary).forEach(([operation, stats]) => { + console.log(`\n${operation}:`); + console.log(`Total Requests: ${stats.totalRequests}`); + console.log(`Success Rate: ${((stats.successCount / stats.totalRequests) * 100).toFixed(2)}%`); + console.log(`Average Duration: ${stats.avgDuration.toFixed(2)}ms`); + console.log(`Min Duration: ${stats.minDuration.toFixed(2)}ms`); + console.log(`Max Duration: ${stats.maxDuration.toFixed(2)}ms`); + console.log(`95th Percentile: ${stats.p95.toFixed(2)}ms`); + console.log(`99th Percentile: ${stats.p99.toFixed(2)}ms`); + }); + } + + async cleanup() { + try { + await Schedule.destroy({ where: {}, force: true }); + console.log('Cleanup completed successfully.'); + } catch (error) { + console.error('Cleanup failed:', error); + } + } +} + +async function runTests() { + const tester = new PerformanceTester(); + try { + await tester.setup(); + console.log('Starting performance tests...'); + await tester.runLoadTest(); + } catch (error) { + console.error('Test failed:', error); + } finally { + await sequelize.close(); + } +} + +runTests(); \ No newline at end of file diff --git a/services/schedule.test.js b/services/schedule.test.js index 373d1e5b67bb78e5e043483ed798a012bbb1f114..1002f0acca632b902c9e6384d6f0f66c00b92fab 100644 --- a/services/schedule.test.js +++ b/services/schedule.test.js @@ -1,16 +1,14 @@ // test/scheduleService.test.js -const sequelize = require('../config/sequelize'); // �ㅼ젣 寃쎈줈�� 留욊쾶 �섏젙 +const sequelize = require('../config/sequelize'); const { Schedule, User } = require('../models'); -const ScheduleService = require('../services/scheduleService'); // Uppercase 'S'濡� 媛��몄삤湲� +const ScheduleService = require('../services/scheduleService'); const ScheduleResponseDTO = require('../dtos/ScheduleResponseDTO'); beforeAll(async () => { - // �뚯뒪�� �ㅼ쐞�멸� �쒖옉�섍린 �꾩뿉 �곗씠�곕쿋�댁뒪瑜� �숆린�뷀빀�덈떎. await sequelize.sync({ force: true }); }); beforeEach(async () => { - // 媛� �뚯뒪�멸� �쒖옉�섍린 �꾩뿉 湲곗〈 �곗씠�곕� ��젣�⑸땲��. await Schedule.destroy({ where: {} }); await User.destroy({ where: {} }); diff --git a/services/scheduleService.js b/services/scheduleService.js index e664a4909abfbca48cbb533bcbcf67f77152382d..6c66d7ff0398f922ddec76baccc00c2d89fb2a40 100644 --- a/services/scheduleService.js +++ b/services/scheduleService.js @@ -9,158 +9,248 @@ class ScheduleService { * �ㅼ�以� �앹꽦 (踰뚰겕) * @param {object} [transaction] - Sequelize �몃옖��뀡 媛앹껜 -> 誘명똿諛⑹뿉�� �곌린�꾪빐 �몃옖��뀡�� �섍꺼諛쏅뒗嫄� 異붽� */ - async createSchedules({ userId, title, is_fixed, events }, transaction = null) { - const scheduleDTOs = []; + async createSchedules({ userId, title, is_fixed, time_indices }, transaction = null) { + const overlaps = await Schedule.findAll({ + where: { + user_id: userId, + time_idx: { + [Op.in]: time_indices + } + }, + transaction + }); + + if (overlaps.length > 0) { + throw new Error(`Schedule overlaps at time_idx ${overlaps[0].time_idx}`); + } - for (const event of events) { - const { time_idx } = event; + const scheduleData = time_indices.map(time_idx => ({ + user_id: userId, + title, + time_idx, + is_fixed + })); - // 以묐났 �ㅼ�以� 寃��� - const overlap = await this.checkScheduleOverlap(userId, time_idx, transaction); - if (overlap) { - throw new Error(`Schedule overlaps with existing schedule at time_idx ${time_idx}`); - } + try { + const createdSchedules = await Schedule.bulkCreate(scheduleData, { + transaction, + returning: true, + validate: true + }); - const scheduleData = { + return { + id: createdSchedules[0].id, user_id: userId, title, - time_idx, is_fixed, + time_indices, + createdAt: createdSchedules[0].createdAt, + updatedAt: createdSchedules[0].updatedAt }; - - const schedule = await Schedule.create(scheduleData, { transaction }); - scheduleDTOs.push(new ScheduleResponseDTO(schedule)); + } catch (error) { + throw new Error(`Failed to bulk create schedules: ${error.message}`); } + } + + async getAllSchedules(userId) { + try { + const schedules = await Schedule.findAll({ + where: { user_id: userId }, + order: [['time_idx', 'ASC']] + }); - return scheduleDTOs; + return ScheduleResponseDTO.groupSchedules(schedules); + } catch (error) { + throw new Error(`Failed to fetch schedules: ${error.message}`); + } } - /** - * �ㅼ�以� �섏젙 (踰뚰겕) - * @param {Array} updates - �섏젙�� �ㅼ�以� 諛곗뿴 - */ async updateSchedules(userId, updates, transaction = null) { - const updatedSchedules = []; - - for (const update of updates) { - const { time_idx, title, is_fixed } = update; + const { originalTitle, title, is_fixed, time_indices } = updates; + const t = transaction || await sequelize.transaction(); - const schedule = await Schedule.findOne({ - where: { user_id: userId, time_idx }, - transaction, - }); + try { + // 湲곗〈 �ㅼ�以� 議고쉶 + const [existingSchedule, existingSchedules] = await Promise.all([ + Schedule.findOne({ + where: { + user_id: userId, + title: originalTitle + }, + transaction: t + }), + Schedule.findAll({ + attributes: ['time_idx'], + where: { + user_id: userId, + title: originalTitle + }, + transaction: t + }) + ]); - if (!schedule) { - throw new Error(`Schedule not found at time_idx ${time_idx}`); + if (!existingSchedule) { + throw new Error('Schedule not found'); } - const updatedData = {}; - if (title !== undefined) updatedData.title = title; - if (is_fixed !== undefined) updatedData.is_fixed = is_fixed; + const existingTimeIndices = existingSchedules.map(s => s.time_idx); + const toDelete = existingTimeIndices.filter(idx => !time_indices.includes(idx)); + const toAdd = time_indices.filter(idx => !existingTimeIndices.includes(idx)); - const updatedSchedule = await schedule.update(updatedData, { transaction }); - updatedSchedules.push(new ScheduleResponseDTO(updatedSchedule)); - } + // 踰뚰겕 �곗궛 + const operations = []; - return updatedSchedules; - } + // ��젣 �곗궛 + if (toDelete.length > 0) { + operations.push( + Schedule.destroy({ + where: { + user_id: userId, + title: originalTitle, + time_idx: { + [Op.in]: toDelete + } + }, + transaction: t + }) + ); + } - /** - * �ㅼ�以� ��젣 (踰뚰겕) - * @param {number} userId - �ъ슜�� ID - * @param {Array<number>} time_idxs - ��젣�� �ㅼ�以꾩쓽 time_idx 諛곗뿴 - * @param {object} [transaction] - Sequelize �몃옖��뀡 媛앹껜 - */ - async deleteSchedules(userId, time_idxs, transaction = null) { - const deleted_time_idxs = []; + // �낅뜲�댄듃 �곗궛 + operations.push( + Schedule.update( + { title, is_fixed }, + { + where: { + user_id: userId, + title: originalTitle + }, + transaction: t + } + ) + ); - for (const time_idx of time_idxs) { - const deletedCount = await Schedule.destroy({ - where: { user_id: userId, time_idx }, - transaction, - }); + // �앹꽦 �곗궛 + if (toAdd.length > 0) { + operations.push( + Schedule.bulkCreate( + toAdd.map(time_idx => ({ + user_id: userId, + title, + time_idx, + is_fixed + })), + { + transaction: t, + validate: true + } + ) + ); + } - if (deletedCount === 0) { - throw new Error(`Schedule not found at time_idx ${time_idx}`); + await Promise.all(operations); // 蹂묐젹 泥섎━ + + if (!transaction) { + await t.commit(); } - deleted_time_idxs.push(time_idx); + return { + id: existingSchedule.id, + user_id: userId, + title, + is_fixed, + time_indices, + createdAt: existingSchedule.createdAt, + updatedAt: new Date() + }; + + } catch (error) { + if (!transaction) { + await t.rollback(); + } + throw error; } + } + + async deleteSchedules(userId, title, transaction = null) { + const deletedSchedules = await Schedule.destroy({ + where: { + user_id: userId, + title + }, + transaction + }); - return { deleted_time_idxs }; + return { deletedCount: deletedSchedules }; } /** * �뱀젙 time_idx濡� �ㅼ�以� 議고쉶 */ async getScheduleByTimeIdx(userId, time_idx) { - const schedule = await Schedule.findOne({ - where: { user_id: userId, time_idx }, + // �대떦 time_idx�� �ㅼ�以� 李얘린 + const schedules = await Schedule.findAll({ + where: { + user_id: userId, + title: { + [Op.in]: sequelize.literal( + `(SELECT title FROM Schedules WHERE user_id = ${userId} AND time_idx = ${time_idx})` + ) + } + }, + order: [['time_idx', 'ASC']] }); - if (!schedule) { - throw new Error('Schedule not found'); - } - - return new ScheduleResponseDTO(schedule); + return ScheduleResponseDTO.groupSchedules(schedules)[0]; } - /** - * 紐⑤뱺 �ㅼ�以� 議고쉶 - */ async getAllSchedules(userId) { try { const schedules = await Schedule.findAll({ where: { user_id: userId }, - order: [['time_idx', 'ASC']], + order: [['time_idx', 'ASC']] }); - return schedules.map((schedule) => new ScheduleResponseDTO(schedule)); + return ScheduleResponseDTO.groupSchedules(schedules); } catch (error) { throw new Error(`Failed to fetch schedules: ${error.message}`); } } - /** - * 以묐났 �ㅼ�以� 寃��� - */ async checkScheduleOverlap(userId, time_idx, transaction = null) { const overlappingSchedule = await Schedule.findOne({ where: { user_id: userId, time_idx }, - transaction, + transaction }); - return !!overlappingSchedule; } async checkScheduleOverlapByTime(userId, time_idx_start, time_idx_end, transaction = null) { - console.log( - `checkScheduleOverlapByTime �몄텧: userId=${userId}, time_idx_start=${time_idx_start}, time_idx_end=${time_idx_end}` - ); - const overlappingSchedule = await Schedule.findOne({ + const overlappingSchedules = await Schedule.findAll({ where: { user_id: userId, time_idx: { - [Op.between]: [time_idx_start, time_idx_end] + [Op.between]: [time_idx_start, time_idx_end] } }, - transaction, + transaction }); - console.log(`以묐났 �ㅼ�以�: ${JSON.stringify(overlappingSchedule)}`); - const result = !!overlappingSchedule; - console.log(`�ㅼ�以� 異⑸룎 寃곌낵: ${result}`); - return result; + + const groupedSchedules = ScheduleResponseDTO.groupSchedules(overlappingSchedules); + const result = groupedSchedules.length > 0; + + console.log(`checkScheduleOverlapByTime �몄텧: userId=${userId}, time_idx_start=${time_idx_start}, time_idx_end=${time_idx_end}`); + console.log(`以묐났 �ㅼ�以�: ${JSON.stringify(groupedSchedules)}`); + console.log(`�ㅼ�以� 異⑸룎 寃곌낵: ${result}`); + + return result; } - - /** - * 留뚮즺�� �ㅼ�以� ��젣 - */ async cleanExpiredSchedules() { try { const deletedCount = await Schedule.destroy({ - where: { is_fixed: false }, + where: { is_fixed: false } }); - //console.log(`Deleted ${deletedCount} flexible schedules.`); + return { deletedCount }; } catch (error) { console.error('Failed to clean expired schedules:', error); throw error; diff --git a/start.sh b/start.sh new file mode 100755 index 0000000000000000000000000000000000000000..00e9bb6ef347484cfd135e68776c215f20ef9957 --- /dev/null +++ b/start.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Define application file names +APP_JS="app.js" +WS_SERVER_JS="wsServer.js" + +# Find and kill processes for app.js +APP_PID=$(pgrep -f "node .*${APP_JS}") +if [ -n "$APP_PID" ]; then + echo "Stopping ${APP_JS} with PID: $APP_PID" + kill -9 "$APP_PID" +else + echo "No running process found for ${APP_JS}" +fi + +# Find and kill processes for wsServer.js +WS_SERVER_PID=$(pgrep -f "node .*${WS_SERVER_JS}") +if [ -n "$WS_SERVER_PID" ]; then + echo "Stopping ${WS_SERVER_JS} with PID: $WS_SERVER_PID" + kill -9 "$WS_SERVER_PID" +else + echo "No running process found for ${WS_SERVER_JS}" +fi + +# Start app.js with nohup +echo "Starting ${APP_JS}..." +nohup node "${APP_JS}" > output.log 2>&1 & +echo "${APP_JS} started with PID: $!" + +# Start wsServer.js with nohup +echo "Starting ${WS_SERVER_JS}..." +nohup node "${WS_SERVER_JS}" > weblog.log 2>&1 & +echo "${WS_SERVER_JS} started with PID: $!" + diff --git a/sync.js b/sync.js index 84cfdef99fa993f2ee41affb5ea24f99dbb36aa7..3890bf2b4eb0b96a4d61b840044ded103e2623a5 100644 --- a/sync.js +++ b/sync.js @@ -1,7 +1,5 @@ // sync.js -//require('dotenv').config(); // �섍꼍 蹂��� 濡쒕뱶 - const sequelize = require('./config/sequelize'); const model=require('./models'); // 紐⑤뜽�ㅼ쓣 媛��몄샂 (�ъ씠�� �댄럺�몃줈 紐⑤뜽�ㅼ씠 �깅줉��) diff --git a/utils/performanceMonitor.js b/utils/performanceMonitor.js new file mode 100644 index 0000000000000000000000000000000000000000..dfba98533a396a4eaa540ef297c68b21526bdc51 --- /dev/null +++ b/utils/performanceMonitor.js @@ -0,0 +1,63 @@ +// utils/performanceMonitor.js +const { performance, PerformanceObserver } = require('perf_hooks'); + +class PerformanceMonitor { + constructor() { + this.measurements = new Map(); + + // �깅뒫 愿�李곗옄 �ㅼ젙 + this.observer = new PerformanceObserver((items) => { + items.getEntries().forEach((entry) => { + const measurements = this.measurements.get(entry.name) || []; + measurements.push(entry.duration); + this.measurements.set(entry.name, measurements); + + console.log(`Performance Measurement - ${entry.name}: ${entry.duration}ms`); + }); + }); + + this.observer.observe({ entryTypes: ['measure'] }); + } + + async measureAsync(name, fn) { + const start = performance.now(); + try { + return await fn(); + } finally { + const duration = performance.now() - start; + performance.measure(name, { + start, + duration, + detail: { timestamp: new Date().toISOString() } + }); + } + } + + getStats(name) { + const measurements = this.measurements.get(name) || []; + if (measurements.length === 0) return null; + + const sum = measurements.reduce((a, b) => a + b, 0); + const avg = sum / measurements.length; + const min = Math.min(...measurements); + const max = Math.max(...measurements); + + return { + count: measurements.length, + average: avg, + min: min, + max: max, + total: sum + }; + } + + getAllStats() { + const stats = {}; + for (const [name, measurements] of this.measurements.entries()) { + stats[name] = this.getStats(name); + } + return stats; + } +} + +module.exports = new PerformanceMonitor(); \ No newline at end of file diff --git a/weblog.log b/weblog.log new file mode 100644 index 0000000000000000000000000000000000000000..dbd9d956fe0863cb20caf8eb3c2078cd7e57aeb7 --- /dev/null +++ b/weblog.log @@ -0,0 +1,6 @@ +(node:237547) [MONGODB DRIVER] Warning: useNewUrlParser is a deprecated option: useNewUrlParser has no effect since Node.js Driver version 4.0.0 and will be removed in the next major version +(Use `node --trace-warnings ...` to show where the warning was created) +(node:237547) [MONGODB DRIVER] Warning: useUnifiedTopology is a deprecated option: useUnifiedTopology has no effect since Node.js Driver version 4.0.0 and will be removed in the next major version +MongoDB�� �깃났�곸쑝濡� �곌껐�섏뿀�듬땲��. +WebSocket 梨꾪똿 �쒕쾭媛� 8081 �ы듃�먯꽌 �ㅽ뻾 以묒엯�덈떎. +RabbitMQ connection established diff --git a/wsServer.js b/wsServer.js index e938ef812029c5d1147f6fc9aca204c69e7dc599..79e7b6d6550cd9baa4269da37be15fe8ea754f1a 100644 --- a/wsServer.js +++ b/wsServer.js @@ -10,22 +10,21 @@ const ChatRoom = require('./schemas/chatRooms'); // .env �뚯씪 濡쒕뱶 dotenv.config(); -// �쒕퉬�� 怨꾩젙 �� �뚯씪 寃쎈줈瑜� �섍꼍 蹂��섏뿉�� 媛��몄삤湲� -const serviceAccountPath = process.env.FIREBASE_CREDENTIAL_PATH; +const HEARTBEAT_TIMEOUT = 10000; // 10珥� �� ���꾩븘�� -// Firebase Admin SDK 珥덇린�� -admin.initializeApp({ - credential: admin.credential.cert(require(serviceAccountPath)), -}); +// RabbitMQ �곌껐 �� �앹꽦 +let amqpConnection, amqpChannel; // WebSocket 愿��� �곗씠�� let clients = []; -let chatRooms = {}; + +// �대씪�댁뼵�� �곹깭瑜� ���ν븯�� Map +const clientHeartbeats = new Map(); // MongoDB �곌껐 �ㅼ젙 async function connectMongoDB() { try { - await mongoose.connect('mongodb://localhost:27017/chat', { + await mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, }); @@ -39,14 +38,35 @@ async function connectMongoDB() { } } -// RabbitMQ 硫붿떆吏� 諛쒗뻾 �⑥닔 +// // RabbitMQ 硫붿떆吏� 諛쒗뻾 �⑥닔 +// async function publishToQueue(queue, message) { +// const connection = await amqp.connect(process.env.RABBITMQ_URL || 'amqp://localhost'); +// const channel = await connection.createChannel(); +// await channel.assertQueue(queue, { durable: true }); +// channel.sendToQueue(queue, Buffer.from(JSON.stringify(message))); +// console.log(`Message sent to queue ${queue}:`, message); +// setTimeout(() => connection.close(), 500); // �곌껐 �リ린 +// } + +async function setupRabbitMQ() { + try { + amqpConnection = await amqp.connect(process.env.RABBITMQ_URL || 'amqp://localhost'); + amqpChannel = await amqpConnection.createChannel(); + console.log('RabbitMQ connection established'); + } catch (err) { + console.error('RabbitMQ Setup', err); + process.exit(1); + } +} + async function publishToQueue(queue, message) { - const connection = await amqp.connect(process.env.RABBITMQ_URL || 'amqp://localhost'); - const channel = await connection.createChannel(); - await channel.assertQueue(queue, { durable: true }); - channel.sendToQueue(queue, Buffer.from(JSON.stringify(message))); - console.log(`Message sent to queue ${queue}:`, message); - setTimeout(() => connection.close(), 500); // �곌껐 �リ린 + try { + await amqpChannel.assertQueue(queue, { durable: true }); + amqpChannel.sendToQueue(queue, Buffer.from(JSON.stringify(message))); + console.log(`Message sent to queue ${queue}:`, message); + } catch (err) { + logError('RabbitMQ Publish', err); + } } // RabbitMQ瑜� �듯빐 �몄떆 �뚮┝ �붿껌�� �꾩넚�섎뒗 �⑥닔 @@ -67,237 +87,355 @@ async function getChatHistory(chatRoomId) { return chatRoom ? chatRoom.messages : []; } -// WebSocket �쒕쾭 �앹꽦 諛� �몃뱶�곗씠�� 泥섎━ function startWebSocketServer() { - const wsServer = http.createServer((req, res) => { + const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('WebSocket server is running'); }); - wsServer.on('upgrade', (req, socket, head) => { - 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); - - let chatRoomId = null; - let nickname = null; - - socket.on('data', async buffer => { - let message; - try { - message = parseMessage(buffer); - const parsedData = JSON.parse(message); - const { type, chatRoomId: clientChatRoomId, nickname: clientNickname, text } = parsedData; - - console.log('�쒕쾭�먯꽌 �섏떊�� 硫붿떆吏�:', { type, clientChatRoomId, clientNickname, text }); - - if (type === 'join' || type === 'leave') { - await ChatRoom.updateOne( - { chatRoomId: clientChatRoomId }, - { $set: { [`isOnline.${clientNickname}`]: type === 'join' } } - ); - - const statusMessage = { - type: 'status', - chatRoomId: clientChatRoomId, - nickname: clientNickname, - isOnline: type === 'join', - }; - - clients.forEach(client => { - client.write(constructReply(JSON.stringify(statusMessage))); - }); - } - - if (type === 'join') { - chatRoomId = clientChatRoomId; - nickname = clientNickname; - - await ChatRoom.updateOne( - { chatRoomId }, - { - $set: { - [`isOnline.${nickname}`]: true, - [`lastReadLogId.${nickname}`]: null, - }, - } - ); - - if (!chatRooms[chatRoomId]) { - chatRooms[chatRoomId] = []; - } - - const chatRoom = await ChatRoom.findOne({ chatRoomId }); - - // 李멸��� �뺤씤 - const participantIndex = chatRoom.participants.findIndex(participant => participant.name === nickname); - if (participantIndex !== -1) { - const existingParticipant = chatRoom.participants[participantIndex]; - - // 李멸��� �곹깭 �낅뜲�댄듃 - existingParticipant.isOnline = true; - existingParticipant.lastReadAt = new Date(); - - await chatRoom.save(); - } else { - // �� 李멸��� 異붽� - const joinMessage = { - message: `${nickname}�섏씠 李멸��덉뒿�덈떎.`, - timestamp: new Date(), - type: 'join' - }; - - chatRoom.participants.push({ - name: nickname, - fcmTokens: parsedData.fcmToken ? [parsedData.fcmToken] : [], - lastReadAt: new Date(), - lastReadLogId: null, - isOnline: true, - }); - - chatRoom.messages.push(joinMessage); - - await chatRoom.save(); - - clients.forEach(client => { - client.write(constructReply(JSON.stringify(joinMessage))); - }); - - console.log(`${nickname} �� 李멸��먮줈 異붽�`); - } - - try { - const previousMessages = await getChatHistory(chatRoomId); - if (previousMessages.length > 0) { - socket.write(constructReply(JSON.stringify({ type: 'previousMessages', messages: previousMessages }))); - console.log(`�댁쟾 硫붿떆吏� �꾩넚: ${previousMessages.length}媛�`); - } - } catch (err) { - console.error('�댁쟾 梨꾪똿 湲곕줉 遺덈윭�ㅺ린 以� �ㅻ쪟 諛쒖깮:', err); - } - - } else if (type === 'message') { - const chatMessage = { - message: text, - timestamp: new Date(), - type: 'message', - sender: nickname - }; - - - chatRooms[chatRoomId].push(chatMessage); - - try { - // �덈줈�� 硫붿떆吏�瑜� messages 諛곗뿴�� 異붽� - const updatedChatRoom = await ChatRoom.findOneAndUpdate( - { chatRoomId }, - { $push: { messages: chatMessage } }, - { new: true, fields: { "messages": { $slice: -1 } } } // 留덉�留� 異붽��� 硫붿떆吏�留� 媛��몄샂 - ); - - // 留덉�留됱뿉 異붽��� 硫붿떆吏��� _id瑜� 媛��몄삤湲� - const savedMessage = updatedChatRoom.messages[updatedChatRoom.messages.length - 1]; - - // �덈줈�� 硫붿떆吏� �꾩넚: �대씪�댁뼵�몃줈 硫붿떆吏� 釉뚮줈�쒖틦�ㅽ듃 - const messageData = { - type: 'message', - chatRoomId, - sender: nickname, - message: text, - timestamp: chatMessage.timestamp, - _id: savedMessage._id // ���λ맂 硫붿떆吏��� _id �ъ슜 - }; - - clients.forEach(client => { - client.write(constructReply(JSON.stringify(messageData))); - console.log('梨꾪똿 硫붿떆吏� �꾩넚:', messageData); - }); - - // �ㅽ봽�쇱씤 �ъ슜�먯뿉寃� FCM �몄떆 �뚮┝ �꾩넚 - const chatRoom = await ChatRoom.findOne({ chatRoomId }); - const offlineParticipants = chatRoom.participants.filter(participant => { - // isOnline �곹깭瑜� Map�먯꽌 媛��몄삤湲� - const isOnline = chatRoom.isOnline.get(participant.name); - return isOnline === false; // �뺥솗�� false�� �ъ슜�먮쭔 �꾪꽣留� - }); - - console.log("offlineParticipants", offlineParticipants); - - // RabbitMQ�� �몄떆 �뚮┝ �붿껌 諛쒗뻾 - await sendPushNotificationRequest(chatRoom.chatRoomName, clientNickname, text, offlineParticipants, chatRoomId); - } catch (err) { - console.error('MongoDB 梨꾪똿 硫붿떆吏� ���� �ㅻ쪟:', err); - } - } else if (type === 'leave') { - const leaveMessage = { - message: `${nickname}�섏씠 �댁옣�덉뒿�덈떎.`, - timestamp: new Date(), - type: 'leave' - }; - - chatRooms[chatRoomId].push(leaveMessage); - - await ChatRoom.updateOne( - { chatRoomId }, - { $set: { [`isOnline.${nickname}`]: false } } - ); - - await ChatRoom.updateOne({ chatRoomId }, { - $push: { messages: leaveMessage }, - $pull: { participants: nickname } - }); - - clients.forEach(client => { - client.write(constructReply(JSON.stringify(leaveMessage))); - }); - - clients = clients.filter(client => client !== socket); - } - } catch (err) { - console.error('硫붿떆吏� 泥섎━ 以� �ㅻ쪟 諛쒖깮:', err); - } - }); + server.on('upgrade', (req, socket, head) => { + handleWebSocketUpgrade(req, socket); + }); - socket.on('close', async () => { - if (nickname && chatRoomId) { - await ChatRoom.updateOne( - { chatRoomId }, - { $set: { [`isOnline.${nickname}`]: false } } - ); - - const statusMessage = { - type: 'status', - chatRoomId, - nickname, - isOnline: false, - }; - - clients.forEach(client => { - client.write(constructReply(JSON.stringify(statusMessage))); - }); - } + server.listen(8081, () => { + console.log('WebSocket 梨꾪똿 �쒕쾭媛� 8081 �ы듃�먯꽌 �ㅽ뻾 以묒엯�덈떎.'); + }); +} + +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); + + socket.on('data', async buffer => { + try { + message = parseMessage(buffer); + if (!message) return; // 硫붿떆吏�媛� 鍮꾩뼱 �덈뒗 寃쎌슦 臾댁떆 + + const parsedData = JSON.parse(message); + const { type, chatRoomId: clientChatRoomId, nickname: clientNickname, text } = parsedData; + await handleClientMessage(socket, parsedData); + } catch (err) { + console.error('Error processing message:', err); + } + }); + + socket.on('close', async () => { + + console.log(`WebSocket �곌껐�� 醫낅즺�섏뿀�듬땲��: ${socket.nickname}, ${socket.chatRoomId}`); + + // �대씪�댁뼵�� Heartbeat 留듭뿉�� �쒓굅 + clientHeartbeats.delete(socket); + + // �대씪�댁뼵�� 紐⑸줉�먯꽌 �쒓굅 + clients = clients.filter((client) => client !== socket); + + + // �뚯폆 醫낅즺 ��, 李� �リ린 or hidden �뚮Ц�� �대� �⑤씪�� �곹깭 false濡� �� (以묐났 濡쒖쭅 二쇱꽍 泥섎━) + // if (socket.nickname && socket.chatRoomId) { + // await ChatRoom.updateOne( + // { chatRoomId: socket.chatRoomId }, + // { $set: { [`isOnline.${socket.nickname}`]: false } } + // ); + // } + }); + + socket.on('error', (err) => { + console.error(`WebSocket error: ${err}`); + clients = clients.filter((client) => client !== socket); + }); + +} + +// 硫붿떆吏� ���� 泥섎━ +async function handleClientMessage(socket, data) { + const { type, chatRoomId, nickname, text, fcmToken } = data; + + // ���꾩븘�껊맂 �뚯폆 李⑤떒 + if (socket.isTimedOut) { + console.log(`���꾩븘�껊맂 �대씪�댁뼵�몄쓽 �ъ뿰寃곗쓣 李⑤떒: ${nickname}`); + return; + } + + switch (type) { + case 'heartbeat': + // console.log(`Heartbeat received from ${nickname} in room ${chatRoomId}`); + clientHeartbeats.set(socket, Date.now()); + break; + case 'join': + // WebSocket�� �ъ슜�� �뺣낫 ���� + // socket.nickname = nickname; + // socket.chatRoomId = chatRoomId; + await handleJoin(socket, chatRoomId, nickname, fcmToken); + break; + case 'message': + await handleMessage(chatRoomId, nickname, text); + break; + case 'leave': + await handleLeave(chatRoomId, nickname); + break; + case 'notice': + await handleSetNotice(chatRoomId, nickname, text); + break; + default: + console.log(`Unknown message type: ${type}`); + } +} + +// join - 李멸� 硫붿떆吏� +async function handleJoin(socket, chatRoomId, nickname) { + if (socket.isTimedOut) { + console.log(`���꾩븘�껊맂 �대씪�댁뼵�몄쓽 �ъ갭�щ� 李⑤떒: ${nickname}`); + return; + } + + // Set client properties + socket.chatRoomId = chatRoomId; + socket.nickname = nickname; + + console.log(`Client joined room: ${chatRoomId}, nickname: ${nickname}`); + + await ChatRoom.updateOne( + { chatRoomId: chatRoomId }, + { $set: { [`isOnline.${nickname}`]:true } } + ); + + const statusMessage = { + type: 'status', + chatRoomId: chatRoomId, + nickname: nickname, + isOnline: true, + }; + + broadcastMessage(chatRoomId, statusMessage); + + await ChatRoom.updateOne( + { chatRoomId }, + { $set: { + [`isOnline.${nickname}`]: true, + [`lastReadLogId.${nickname}`]: null, + }, + } + ); + + const chatRoom = await ChatRoom.findOne({ chatRoomId }); + + // 李멸��� �뺤씤 + const participantIndex = chatRoom.participants.findIndex(participant => participant.name === nickname); + + if (participantIndex !== -1) { + const existingParticipant = chatRoom.participants[participantIndex]; + + // 李멸��� �곹깭 �낅뜲�댄듃 + existingParticipant.isOnline = true; + existingParticipant.lastReadAt = new Date(); + + await chatRoom.save(); + } else { + // �� 李멸��� 異붽� + const joinMessage = { + message: `${nickname}�섏씠 李멸��덉뒿�덈떎.`, + timestamp: new Date(), + type: 'join' + }; + + chatRoom.participants.push({ + name: nickname, + fcmTokens: parsedData.fcmToken ? [parsedData.fcmToken] : [], + lastReadAt: new Date(), + lastReadLogId: null, + isOnline: true, + }); + + chatRoom.messages.push(joinMessage); + + await chatRoom.save(); + + broadcastMessage(chatRoomId, joinMessage); + + console.log(`${nickname} �� 李멸��먮줈 異붽�`); + } + + const previousMessages = await getChatHistory(chatRoomId); + if (previousMessages.length > 0) { + socket.write(constructReply(JSON.stringify({ type: 'previousMessages', messages: previousMessages }))); + } +} + +// meessage - �쇰컲 硫붿떆吏� +async function handleMessage(chatRoomId, nickname, text) { + const chatMessage = { message: text, timestamp: new Date(), type: 'message', sender: nickname }; + + try { + const updatedChatRoom = await ChatRoom.findOneAndUpdate( + { chatRoomId }, + { $push: { messages: chatMessage } }, + { new: true, fields: { messages: { $slice: -1 } } } + ); + + // 留덉�留됱뿉 異붽��� 硫붿떆吏��� _id瑜� 媛��몄삤湲� + const savedMessage = updatedChatRoom.messages[updatedChatRoom.messages.length - 1]; + + // �덈줈�� 硫붿떆吏� �꾩넚: �대씪�댁뼵�몃줈 硫붿떆吏� 釉뚮줈�쒖틦�ㅽ듃 + const messageData = { + type: 'message', + chatRoomId, + sender: nickname, + message: text, + timestamp: chatMessage.timestamp, + _id: savedMessage._id // ���λ맂 硫붿떆吏��� _id �ъ슜 + }; + + console.log('梨꾪똿�먯꽌 Current clients:', clients.map(client => client.chatRoomId)); + + // broadcastMessage(chatRoomId, messageData); + + clients.forEach(client => { + client.write(constructReply(JSON.stringify(messageData))); + console.log('梨꾪똿 硫붿떆吏� �꾩넚:', messageData); }); - socket.on('error', (err) => { - console.error(`WebSocket error: ${err}`); - clients = clients.filter(client => client !== socket); + // �ㅽ봽�쇱씤 �ъ슜�먯뿉寃� FCM �몄떆 �뚮┝ �꾩넚 + const chatRoom = await ChatRoom.findOne({ chatRoomId }); + const offlineParticipants = chatRoom.participants.filter(participant => { + // isOnline �곹깭瑜� Map�먯꽌 媛��몄삤湲� + const isOnline = chatRoom.isOnline.get(participant.name); + return isOnline === false; // �뺥솗�� false�� �ъ슜�먮쭔 �꾪꽣留� }); + + console.log("offlineParticipants", offlineParticipants); + + // RabbitMQ�� �몄떆 �뚮┝ �붿껌 諛쒗뻾 + await sendPushNotificationRequest(chatRoom.chatRoomName, nickname, text, offlineParticipants, chatRoomId); + + } catch (err) { + console.error('Error saving message to MongoDB:', err); + } +} + +// leave - �댁옣 硫붿떆吏� +async function handleLeave(chatRoomId, nickname) { + await ChatRoom.updateOne( + { chatRoomId: clientChatRoomId }, + { $set: { [`isOnline.${clientNickname}`]: type === 'leave' } } + ); + + const statusMessage = { + type: 'status', + chatRoomId: clientChatRoomId, + nickname: clientNickname, + isOnline: type === 'leave', + }; + + clients.forEach(client => { + client.write(constructReply(JSON.stringify(statusMessage))); }); - wsServer.listen(8081, () => { - console.log('WebSocket 梨꾪똿 �쒕쾭媛� 8081 �ы듃�먯꽌 �ㅽ뻾 以묒엯�덈떎.'); + const leaveMessage = { message: `${nickname} �섏씠 �댁옣�덉뒿�덈떎.`, timestamp: new Date(), type: 'leave' }; + await ChatRoom.updateOne({ chatRoomId }, { $push: { messages: leaveMessage } }); + broadcastMessage(chatRoomId, leaveMessage); +} + +async function handleSetNotice(chatRoomId, sender, message) { + const notice = { + sender, + message, + timestamp: new Date(), + }; + + try { + // MongoDB�� 理쒖떊 怨듭� ���� + await ChatRoom.updateOne( + { chatRoomId }, + { $push: { notices: notice } } + ); + + // 紐⑤뱺 �대씪�댁뼵�몄뿉寃� 怨듭��ы빆 �낅뜲�댄듃 硫붿떆吏� �꾩넚 + const noticeMessage = { + type: 'notice', + chatRoomId, + sender, + message, + }; + + clients.forEach(client => { + client.write(constructReply(JSON.stringify(noticeMessage))); + }); + + // broadcastMessage(chatRoomId, noticeMessage); + + console.log('怨듭��ы빆 �낅뜲�댄듃:', noticeMessage); + } catch (error) { + console.error('怨듭��ы빆 �낅뜲�댄듃 �ㅽ뙣:', error); + } +} + +// Broadcast message to clients in the same chat room +function broadcastMessage(chatRoomId, message) { + clients.forEach((client) => { + if (client.chatRoomId === chatRoomId) { + client.write(constructReply(JSON.stringify(message))); + } }); } +// 二쇨린�곸쑝濡� Heartbeat �곹깭 �뺤씤 +setInterval(async () => { + const now = Date.now(); + for (const [socket, lastHeartbeat] of clientHeartbeats.entries()) { + if (now - lastHeartbeat > HEARTBEAT_TIMEOUT) { + console.log('���꾩븘�� ���� �대씪�댁뼵��:', { + nickname: socket.nickname, + chatRoomId: socket.chatRoomId, + lastHeartbeat: new Date(lastHeartbeat).toISOString(), + }); + + // Heartbeat 留듭뿉�� �쒓굅 + clientHeartbeats.delete(socket); + + // �곹깭 �뚮옒洹� �ㅼ젙 + socket.isTimedOut = true; + + // �뚯폆 �곌껐 醫낅즺 + socket.end(); + + // �대씪�댁뼵�� 紐⑸줉�먯꽌 �쒓굅 + clients = clients.filter((client) => client !== socket); + + // �대씪�댁뼵�몃� �ㅽ봽�쇱씤�쇰줈 �ㅼ젙 + console.log("Client timed out �� �ㅽ봽�쇱씤 �ㅼ젙"); + await ChatRoom.updateOne( + { [`isOnline.${socket.nickname}`]: false }, + { [`lastReadAt.${socket.nickname}`]: new Date() } + ); + + // �대씪�댁뼵�몄뿉寃� �곌껐 醫낅즺 硫붿떆吏� �꾩넚 + const timeoutMessage = JSON.stringify({ + type: 'status', + nickname: socket.nickname, + chatRoomId: socket.chatRoomId, + isOnline: false, + }); + + clients.forEach(client => { + client.write(constructReply(timeoutMessage)); + }); + + + } + } +}, 5000); // 5珥덈쭏�� �곹깭 �뺤씤 + // Sec-WebSocket-Accept �ㅻ뜑 媛� �앹꽦 -> env泥섎━ function generateAcceptValue(key) { return crypto.createHash('sha1').update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary').digest('base64'); @@ -305,27 +443,37 @@ function generateAcceptValue(key) { // WebSocket 硫붿떆吏� �뚯떛 �⑥닔 function parseMessage(buffer) { - const byteArray = [...buffer]; - const secondByte = byteArray[1]; - let length = secondByte & 127; - let maskStart = 2; - - if (length === 126) { - length = (byteArray[2] << 8) + byteArray[3]; - maskStart = 4; - } else if (length === 127) { - length = 0; - for (let i = 0; i < 8; i++) { - length = (length << 8) + byteArray[2 + i]; + try { + const byteArray = [...buffer]; + const secondByte = byteArray[1]; + let length = secondByte & 127; + let maskStart = 2; + + if (length === 126) { + length = (byteArray[2] << 8) + byteArray[3]; + maskStart = 4; + } else if (length === 127) { + length = 0; + for (let i = 0; i < 8; i++) { + length = (length << 8) + byteArray[2 + i]; + } + maskStart = 10; } - maskStart = 10; - } - const dataStart = maskStart + 4; - const mask = byteArray.slice(maskStart, dataStart); - const data = byteArray.slice(dataStart, dataStart + length).map((byte, i) => byte ^ mask[i % 4]); + const dataStart = maskStart + 4; + const mask = byteArray.slice(maskStart, dataStart); + const data = byteArray.slice(dataStart, dataStart + length).map((byte, i) => byte ^ mask[i % 4]); + + const decodedMessage = new TextDecoder('utf-8').decode(Uint8Array.from(data)); + + // JSON �좏슚�� 寃��� + JSON.parse(decodedMessage); - return new TextDecoder('utf-8').decode(Uint8Array.from(data)); + return decodedMessage; + } catch (err) { + console.error('Error parsing WebSocket message:', err.message); + return null; // �좏슚�섏� �딆� 硫붿떆吏��� 臾댁떆 + } } // �대씪�댁뼵�� 硫붿떆吏� �묐떟 �앹꽦 �⑥닔 @@ -353,5 +501,8 @@ function constructReply(message) { return Buffer.concat([Buffer.from(reply), messageBuffer]); } +// �쒕쾭 �쒖옉 �� RabbitMQ �ㅼ젙 +setupRabbitMQ(); + // MongoDB �곌껐 �� WebSocket �쒕쾭 �쒖옉 -connectMongoDB(); \ No newline at end of file +connectMongoDB();