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

bugfix: chatroom 관련 오류해결(#21)

parent 74962fc2
No related branches found
No related tags found
2 merge requests!31Develop,!28[#21] bugfix: chatroom 관련 오류해결
const mongoose = require('mongoose');
// MongoDB 채팅방 스키마 수정 (FCM 토큰을 배열로 관리)
const chatRoomsSchema = new mongoose.Schema({
chatRoomId: { type: String, required: true, unique: true },
chatRoomName: { type: String, required: true },
messages: [{
sender: String,
message: String,
timestamp: Date,
type: { type: String, default: 'message' }, // 기본값은 'message', 다른 값으로 'join', 'leave' 가능
}],
participants: [{
name: { type: String, required: true },
fcmTokens: { type: [String], default: [] }, // FCM 토큰 배열
}],
lastReadAt: { type: Map, of: Date },
lastReadLogId: { type: Map, of: String },
isOnline: { type: Map, of: Boolean },
}, { collection: 'chatrooms' });
const ChatRoom = mongoose.model('ChatRooms', chatRoomsSchema);
module.exports = ChatRoom;
\ No newline at end of file
...@@ -7,7 +7,7 @@ const Meeting = require('./Meeting'); ...@@ -7,7 +7,7 @@ const Meeting = require('./Meeting');
const Friend = require('./Friend'); const Friend = require('./Friend');
const FcmToken = require('./fcmToken'); const FcmToken = require('./fcmToken');
const MeetingParticipant = require('./MeetingParticipant'); const MeetingParticipant = require('./MeetingParticipant');
const ChatRooms = require('./ChatRooms'); // const ChatRooms = require('./ChatRooms');
// 관계 설정 // 관계 설정
Friend.belongsTo(User, { foreignKey: 'requester_id', as: 'requester' }); // 친구 요청을 보낸 사용자 Friend.belongsTo(User, { foreignKey: 'requester_id', as: 'requester' }); // 친구 요청을 보낸 사용자
...@@ -35,12 +35,6 @@ module.exports = { ...@@ -35,12 +35,6 @@ module.exports = {
Schedule, Schedule,
Meeting, Meeting,
MeetingParticipant, MeetingParticipant,
ChatRooms,
sequelize,
User,
Schedule,
Meeting,
MeetingParticipant,
Friend, Friend,
FcmToken, FcmToken,
}; };
...@@ -11447,7 +11447,7 @@ ...@@ -11447,7 +11447,7 @@
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz",
"integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==",
"dev": true, "devOptional": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
...@@ -11986,6 +11986,7 @@ ...@@ -11986,6 +11986,7 @@
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
"integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"peer": true, "peer": true,
...@@ -12001,6 +12002,7 @@ ...@@ -12001,6 +12002,7 @@
"version": "5.1.3", "version": "5.1.3",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz",
"integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"peer": true, "peer": true,
...@@ -13270,7 +13272,6 @@ ...@@ -13270,7 +13272,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
...@@ -17498,7 +17499,7 @@ ...@@ -17498,7 +17499,7 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"yocto-queue": "^0.1.0" "yocto-queue": "^0.1.0"
...@@ -20299,7 +20300,7 @@ ...@@ -20299,7 +20300,7 @@
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
"integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==",
"dev": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/stubs": { "node_modules/stubs": {
...@@ -21140,7 +21141,6 @@ ...@@ -21140,7 +21141,6 @@
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/tsutils": { "node_modules/tsutils": {
...@@ -22140,7 +22140,7 @@ ...@@ -22140,7 +22140,7 @@
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=10" "node": ">=10"
......
...@@ -20,7 +20,7 @@ router.get('/all', ScheduleController.getAllSchedules); ...@@ -20,7 +20,7 @@ router.get('/all', ScheduleController.getAllSchedules);
* 개별 스케줄 조회 * 개별 스케줄 조회
* Get /api/schedule/:id * Get /api/schedule/:id
*/ */
router.get('/:id', ScheduleController.getScheduleById); router.get('/:id', ScheduleController.getScheduleByTimeIdx);
/** /**
* 스케줄 생성 * 스케줄 생성
...@@ -32,13 +32,13 @@ router.post('/', ScheduleController.createSchedule); ...@@ -32,13 +32,13 @@ router.post('/', ScheduleController.createSchedule);
* 스케줄 수정 * 스케줄 수정
* PUT /api/schedule/:id * PUT /api/schedule/:id
*/ */
router.put('/:id', ScheduleController.updateSchedule); router.put('/:id', ScheduleController.updateSchedules);
/** /**
* 스케줄 삭제 * 스케줄 삭제
* DELETE /api/schedule/:id * DELETE /api/schedule/:id
*/ */
router.delete('/:id', ScheduleController.deleteSchedule); router.delete('/:id', ScheduleController.deleteSchedules);
module.exports = router; module.exports = router;
\ No newline at end of file
// schemas/ChatRoom.js
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const ChatRoomSchema = new mongoose.Schema({ // MongoDB 채팅방 스키마 수정 (FCM 토큰을 배열로 관리)
name: { const chatRoomsSchema = new mongoose.Schema({
type: String, chatRoomId: { type: String, required: true, unique: true },
required: true, chatRoomName: { type: String, required: true },
}, messages: [{
meeting_id: { sender: String,
type: Number, // SQL의 Meetings 테이블 ID 참조 message: String,
default: null, timestamp: Date,
}, type: { type: String, default: 'message' }, // 기본값은 'message', 다른 값으로 'join', 'leave' 가능
type: { }],
type: String, participants: [{
enum: ['OPEN', 'CLOSE'], name: { type: String, required: true },
required: true, fcmTokens: { type: [String], default: [] }, // FCM 토큰 배열
}, }],
created_by: { lastReadAt: { type: Map, of: Date },
type: Number, // SQL의 Users 테이블 ID 참조 lastReadLogId: { type: Map, of: String },
required: true, isOnline: { type: Map, of: Boolean },
}, }, { collection: 'chatrooms' });
}, {
timestamps: true, // createdAt, updatedAt 자동 관리
});
module.exports = mongoose.model('ChatRoom', ChatRoomSchema); const ChatRooms = mongoose.model('ChatRooms', chatRoomsSchema);
module.exports = ChatRooms;
\ No newline at end of file
const ChatRoom = require('../models/ChatRooms'); const ChatRooms = require('../schemas/ChatRooms');
const { v4: uuidv4 } = require('uuid'); const { v4: uuidv4 } = require('uuid');
class ChatService { class ChatService {
...@@ -6,7 +6,7 @@ class ChatService { ...@@ -6,7 +6,7 @@ class ChatService {
async createChatRoom({ meeting_id, participants, chatRoomName }) { async createChatRoom({ meeting_id, participants, chatRoomName }) {
try { try {
const chatRoomId = uuidv4(); const chatRoomId = uuidv4();
const newRoom = new ChatRoom({ const newRoom = new ChatRooms({
chatRoomId, chatRoomId,
chatRoomName, chatRoomName,
meeting_id, meeting_id,
...@@ -31,7 +31,7 @@ class ChatService { ...@@ -31,7 +31,7 @@ class ChatService {
// 채팅방 목록 조회 // 채팅방 목록 조회
async getChatRooms() { async getChatRooms() {
const rooms = await ChatRoom.find({}, { chatRoomId: 1, chatRoomName: 1, messages: { $slice: -1 } }); const rooms = await ChatRooms.find({}, { chatRoomId: 1, chatRoomName: 1, messages: { $slice: -1 } });
return rooms.map(room => { return rooms.map(room => {
const lastMessage = room.messages[0] || {}; const lastMessage = room.messages[0] || {};
return { return {
...@@ -48,7 +48,7 @@ class ChatService { ...@@ -48,7 +48,7 @@ class ChatService {
// 사용자 상태 업데이트 // 사용자 상태 업데이트
async updateStatus(chatRoomId, nickname, isOnline) { async updateStatus(chatRoomId, nickname, isOnline) {
await ChatRoom.updateOne( await ChatRooms.updateOne(
{ chatRoomId, "participants.name": nickname }, { chatRoomId, "participants.name": nickname },
{ $set: { [`isOnline.${nickname}`]: isOnline } } { $set: { [`isOnline.${nickname}`]: isOnline } }
); );
...@@ -57,7 +57,7 @@ class ChatService { ...@@ -57,7 +57,7 @@ class ChatService {
// 읽음 상태 업데이트 // 읽음 상태 업데이트
async updateReadStatus(chatRoomId, nickname) { async updateReadStatus(chatRoomId, nickname) {
const now = new Date(); const now = new Date();
await ChatRoom.updateOne( await ChatRooms.updateOne(
{ chatRoomId, "participants.name": nickname }, { chatRoomId, "participants.name": nickname },
{ $set: { [`lastReadAt.${nickname}`]: now } } { $set: { [`lastReadAt.${nickname}`]: now } }
); );
...@@ -65,14 +65,14 @@ class ChatService { ...@@ -65,14 +65,14 @@ class ChatService {
// 읽지 않은 메시지 조회 // 읽지 않은 메시지 조회
async getUnreadMessages(nickname) { async getUnreadMessages(nickname) {
const chatRooms = await ChatRoom.find({ "participants.name": nickname }); const chatRooms = await ChatRooms.find({ "participants.name": nickname });
return await Promise.all(chatRooms.map(async (chatRoom) => { return await Promise.all(chatRooms.map(async (chatRooms) => {
const lastReadAt = chatRoom.lastReadAt.get(nickname) || new Date(0); const lastReadAt = chatRooms.lastReadAt.get(nickname) || new Date(0);
const unreadMessagesCount = chatRoom.messages.filter(message => const unreadMessagesCount = chatRooms.messages.filter(message =>
message.timestamp > lastReadAt message.timestamp > lastReadAt
).length; ).length;
return { return {
chatRoomId: chatRoom.chatRoomId, chatRoomId: chatRooms.chatRoomId,
unreadCount: unreadMessagesCount, unreadCount: unreadMessagesCount,
}; };
})); }));
...@@ -80,14 +80,14 @@ class ChatService { ...@@ -80,14 +80,14 @@ class ChatService {
// 읽지 않은 메시지 수 조회 // 읽지 않은 메시지 수 조회
async getUnreadCount(chatRoomId) { async getUnreadCount(chatRoomId) {
const chatRoom = await ChatRoom.findOne({ chatRoomId }); const chatRooms = await ChatRooms.findOne({ chatRoomId });
if (!chatRoom) { if (!chatRooms) {
throw new Error('Chat room not found'); throw new Error('Chat room not found');
} }
const unreadCounts = chatRoom.participants const unreadCounts = chatRooms.participants
.filter(participant => chatRoom.lastReadLogId.has(participant.name)) // Map에 존재하는 키만 처리 .filter(participant => chatRooms.lastReadLogId.has(participant.name)) // Map에 존재하는 키만 처리
.map(participant => chatRoom.lastReadLogId.get(participant.name)) // lastReadLogId 값 추출 .map(participant => chatRooms.lastReadLogId.get(participant.name)) // lastReadLogId 값 추출
.reduce((acc, logId) => { .reduce((acc, logId) => {
acc[logId] = (acc[logId] || 0) + 1; // logId 기준으로 등장 횟수 누적 acc[logId] = (acc[logId] || 0) + 1; // logId 기준으로 등장 횟수 누적
return acc; return acc;
...@@ -107,7 +107,7 @@ class ChatService { ...@@ -107,7 +107,7 @@ class ChatService {
// 읽은 메시지 로그 ID 업데이트 // 읽은 메시지 로그 ID 업데이트
async updateReadLogId(chatRoomId, nickname, logId) { async updateReadLogId(chatRoomId, nickname, logId) {
await ChatRoom.updateOne( await ChatRooms.updateOne(
{ chatRoomId, "participants.name": nickname }, { chatRoomId, "participants.name": nickname },
{ $set: { [`lastReadLogId.${nickname}`]: logId } } { $set: { [`lastReadLogId.${nickname}`]: logId } }
); );
...@@ -115,16 +115,16 @@ class ChatService { ...@@ -115,16 +115,16 @@ class ChatService {
// FCM 토큰 업데이트 // FCM 토큰 업데이트
async updateFcmToken(chatRoomId, nickname, fcmToken) { async updateFcmToken(chatRoomId, nickname, fcmToken) {
const chatRoom = await ChatRoom.findOne({ chatRoomId, "participants.name": nickname }); const chatRooms = await ChatRooms.findOne({ chatRoomId, "participants.name": nickname });
if (!chatRoom) { if (!chatRooms) {
throw new Error('Chat room or participant not found'); throw new Error('Chat room or participant not found');
} }
const participant = chatRoom.participants.find(p => p.name === nickname); const participant = chatRooms.participants.find(p => p.name === nickname);
if (participant) { if (participant) {
if (!participant.fcmTokens.includes(fcmToken)) { if (!participant.fcmTokens.includes(fcmToken)) {
participant.fcmTokens.push(fcmToken); participant.fcmTokens.push(fcmToken);
await chatRoom.save(); await chatRooms.save();
} }
} }
} }
...@@ -134,13 +134,13 @@ class ChatService { ...@@ -134,13 +134,13 @@ class ChatService {
let finalLogId = logId; let finalLogId = logId;
if (!isOnline && logId === null) { if (!isOnline && logId === null) {
const chatRoom = await ChatRoom.findOne({ chatRoomId }); const chatRooms = await ChatRooms.findOne({ chatRoomId });
if (chatRoom && chatRoom.messages.length > 0) { if (chatRooms && chatRooms.messages.length > 0) {
finalLogId = chatRoom.messages[chatRoom.messages.length - 1]._id; finalLogId = chatRooms.messages[chatRooms.messages.length - 1]._id;
} }
} }
await ChatRoom.updateOne( await ChatRooms.updateOne(
{ chatRoomId, "participants.name": nickname }, { chatRoomId, "participants.name": nickname },
{ {
$set: { $set: {
...@@ -155,8 +155,8 @@ class ChatService { ...@@ -155,8 +155,8 @@ class ChatService {
async sendMessage(chatRoomId, sender, messageContent) { async sendMessage(chatRoomId, sender, messageContent) {
try { try {
// 채팅방 조회 // 채팅방 조회
const chatRoom = await ChatRoom.findOne({ chatRoomId }); const chatRooms = await ChatRooms.findOne({ chatRoomId });
if (!chatRoom) { if (!chatRooms) {
throw new Error('Chat room not found'); throw new Error('Chat room not found');
} }
...@@ -167,12 +167,12 @@ class ChatService { ...@@ -167,12 +167,12 @@ class ChatService {
timestamp: new Date(), timestamp: new Date(),
type: 'message', type: 'message',
}; };
chatRoom.messages.push(newMessage); chatRooms.messages.push(newMessage);
await chatRoom.save(); await chatRooms.save();
// 오프라인 사용자 찾기 // 오프라인 사용자 찾기
const offlineParticipants = chatRoom.participants.filter( const offlineParticipants = chatRooms.participants.filter(
participant => !chatRoom.isOnline[participant.name] participant => !chatRooms.isOnline[participant.name]
); );
// 오프라인 사용자들에게 FCM 푸시 알림 전송 // 오프라인 사용자들에게 FCM 푸시 알림 전송
...@@ -181,7 +181,7 @@ class ChatService { ...@@ -181,7 +181,7 @@ class ChatService {
if (tokens.length > 0) { if (tokens.length > 0) {
const message = { const message = {
notification: { notification: {
title: `새 메시지: ${chatRoom.chatRoomName}`, title: `새 메시지: ${chatRooms.chatRoomName}`,
body: `${sender}: ${messageContent}`, body: `${sender}: ${messageContent}`,
}, },
tokens, tokens,
......
...@@ -7,7 +7,7 @@ const { v4: uuidv4 } = require('uuid'); ...@@ -7,7 +7,7 @@ const { v4: uuidv4 } = require('uuid');
const { Op } = require('sequelize'); const { Op } = require('sequelize');
const sequelize = require('../config/sequelize'); // 트랜잭션 관리를 위해 sequelize 인스턴스 필요 const sequelize = require('../config/sequelize'); // 트랜잭션 관리를 위해 sequelize 인스턴스 필요
const { Meeting, MeetingParticipant, User, Schedule, Invite, Friend } = require('../models'); const { Meeting, MeetingParticipant, User, Schedule, Invite, Friend } = require('../models');
const ChatRooms = require('../models/ChatRooms'); const ChatRooms = require('../schemas/ChatRooms');
const MeetingResponseDTO = require('../dtos/MeetingResponseDTO'); const MeetingResponseDTO = require('../dtos/MeetingResponseDTO');
const MeetingDetailResponseDTO = require('../dtos/MeetingDetailResponseDTO'); const MeetingDetailResponseDTO = require('../dtos/MeetingDetailResponseDTO');
const CreateMeetingRequestDTO = require('../dtos/CreateMeetingRequestDTO'); const CreateMeetingRequestDTO = require('../dtos/CreateMeetingRequestDTO');
...@@ -51,16 +51,6 @@ class MeetingService { ...@@ -51,16 +51,6 @@ class MeetingService {
const user = await this._findUserWithFcmTokens(created_by); const user = await this._findUserWithFcmTokens(created_by);
const userFcmTokens = user.fcmTokenList.map((fcmToken) => fcmToken.token); const userFcmTokens = user.fcmTokenList.map((fcmToken) => fcmToken.token);
// 스케줄 충돌 확인
// const hasConflict = await ScheduleService.checkScheduleOverlap(
// created_by,
// new Date(start_time),
// new Date(end_time)
// );
// if (hasConflict) {
// throw new Error('스케줄이 겹칩니다. 다른 시간을 선택해주세요.');
// }
const hasConflict = await ScheduleService.checkScheduleOverlapByTime( const hasConflict = await ScheduleService.checkScheduleOverlapByTime(
created_by, created_by,
......
const User = require('../models/User'); const User = require('../models/User');
const FcmToken = require('../models/fcmToken'); const FcmToken = require('../models/fcmToken');
const ChatRoom = require('../models/chatRooms'); const ChatRooms = require('../schemas/ChatRooms');
class MemberService { class MemberService {
async registerToken(email, fcmToken) { async registerToken(email, fcmToken) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment