Skip to content
Snippets Groups Projects
Commit 8f1a7211 authored by 심재엽's avatar 심재엽
Browse files

[#17] 채팅 푸시알림 추가

parents 40950690 2a507ad5
No related branches found
No related tags found
2 merge requests!31Develop,!25[#17] 채팅 푸시알림 추가
......@@ -57,9 +57,12 @@ app.use('/api/friend', friendRoutes);
const meetingRoutes = require('./routes/meetingRoute');
app.use('/api/meeting', meetingRoutes);
//const chatRoutes = require('./routes/chatRoute');
const chatRoutes = require('./routes/chatRoute');
app.use('/api/chat', chatRoutes);
const memberRoutes = require('./routes/memberRoute');
app.use('/api/member', memberRoutes);
// 스케줄 클리너 초기화
initScheduleCleaner();
......
const chatService = require('../services/chatService');
// 내부용 채팅방 생성
exports.createChatRoomInternal = async (params) => {
exports.createChatRoom = async (params) => {
try {
return await chatService.createChatRoom(params);
const chatRoomId = await chatService.createChatRoom(params);
res.json(chatRoomId);
} catch (err) {
console.error('Error in createChatRoomInternal:', err);
return { success: false, error: err.message };
}
};
// 새 채팅방 생성
exports.createChatRoom = async (req, res) => {
try {
const chatRoomId = await chatService.createChatRoom();
res.json({ chatRoomId });
} catch (err) {
console.error('Error creating room:', err);
console.error('Error in createChatRoom:', err);
res.status(500).json({ error: 'Failed to create room' });
}
};
......
const MemberService = require('../services/memberService');
class MemberController {
async registerToken(req, res) {
const { email, fcmToken } = req.body;
try {
const result = await MemberService.registerToken(email, fcmToken);
res.status(200).json(result);
} catch (error) {
console.error('Error registering FCM token:', error);
res.status(500).json({ message: error.message || 'Internal server error' });
}
}
}
module.exports = new MemberController();
\ No newline at end of file
const mongoose = require('mongoose');
// MongoDB 채팅방 스키마 수정 (현재 참가 중인 유저 목록 추가)
// 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' 가능
type: { type: String, default: 'message' }, // 기본값은 'message', 다른 값으로 'join', 'leave' 가능
}],
participants: [{ type: String }],
lastReadAt: { type: Map, of: Date }, // 각 참가자의 마지막 읽은 메시지 시간 기록
lastReadLogId: { type: Map, of: String }, // 각 참가자의 마지막으로 읽은 logID 기록
isOnline: { type: Map, of: Boolean } // 각 참가자의 온라인 상태
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 ChatRooms = mongoose.models.ChatRooms || mongoose.model('ChatRooms', chatRoomsSchema);
module.exports = ChatRooms;
\ No newline at end of file
const ChatRoom = mongoose.model('ChatRooms', chatRoomsSchema);
module.exports = ChatRoom;
\ No newline at end of file
const { DataTypes } = require('sequelize');
const sequelize = require('../config/sequelize');
const User = require('./User'); // 올바른 경로 확인
const FcmToken = sequelize.define('FcmToken', {
userId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: User, // 문자열 대신 모델 객체를 참조
key: 'id',
},
},
token: {
type: DataTypes.STRING,
allowNull: false,
},
}, {
tableName: 'FcmTokens',
timestamps: true,
});
// 관계 설정
FcmToken.belongsTo(User, { foreignKey: 'userId', as: 'user' });
User.hasMany(FcmToken, { foreignKey: 'userId', as: 'fcmTokenList' });
module.exports = FcmToken;
\ No newline at end of file
......@@ -5,6 +5,9 @@ const User = require('./user');
const Friend = require('./Friend');
const Schedule = require('./Schedule');
const Meeting = require('./Meeting');
const MeetingParticipant = require('./MeetingParticipant'); //폴더명수정
const Friend = require('./Friend');
const FcmToken = require('./fcmToken');
const MeetingParticipant = require('./MeetingParticipant');
const ChatRooms = require('./ChatRooms');
......@@ -35,4 +38,11 @@ module.exports = {
Meeting,
MeetingParticipant,
ChatRooms,
sequelize,
User,
Schedule,
Meeting,
MeetingParticipant,
Friend,
FcmToken,
};
This diff is collapsed.
......@@ -15,9 +15,12 @@
"dependencies": {
"connect-flash": "^0.1.1",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.21.1",
"express-session": "^1.18.1",
"firebase-admin": "^13.0.1",
"jest": "^29.7.0",
"joi": "^17.13.3",
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
......
const express = require('express');
const router = express.Router();
const MemberController = require('../controllers/memberController');
// FCM 토큰 저장
router.post('/register-token', MemberController.registerToken);
module.exports = router;
\ No newline at end of file
......@@ -2,58 +2,46 @@ const ChatRoom = require('../models/ChatRooms');
const { v4: uuidv4 } = require('uuid');
class ChatService {
// 채팅방 생성
async createChatRoom({ meeting_id, participants }) {
try {
const chatRoomId = uuidv4();
const newRoom = new ChatRoom({
chatRoomId: chatRoomId,
meeting_id,
participants,
messages: [],
lastReadAt: participants.reduce((acc, user) => {
acc[user] = new Date();
return acc;
}, {}),
lastReadLogId: participants.reduce((acc, user) => {
acc[user] = null;
return acc;
}, {}),
isOnline: participants.reduce((acc, user) => {
acc[user] = true;
return acc;
}, {}),
});
const joinMessage = {
message: `${participants[0]}님이 번개 모임을 생성했습니다.`,
timestamp: new Date(),
type: 'join',
};
async createChatRoom({ meeting_id, participants, chatRoomName }) {
try {
const chatRoomId = uuidv4();
const newRoom = new ChatRoom({
chatRoomId,
chatRoomName,
meeting_id,
messages: [],
});
newRoom.messages.push(joinMessage);
await newRoom.save();
const joinMessage = {
message: `${participants[0].name}님이 번개 모임을 생성했습니다.`,
timestamp: new Date(),
type: 'join',
};
return { success: true, chatRoomId };
} catch (err) {
console.error('Error creating chat room:', err);
throw new Error('Failed to create chat room');
}
newRoom.messages.push(joinMessage);
await newRoom.save();
return { success: true, chatRoomId };
} catch (err) {
console.error('Error creating chat room:', err);
throw new Error('Failed to create chat room');
}
}
// 채팅방 목록 조회
async getChatRooms() {
const rooms = await ChatRoom.find({}, { chatRoomId: 1, messages: { $slice: -1 } });
const rooms = await ChatRoom.find({}, { chatRoomId: 1, chatRoomName: 1, messages: { $slice: -1 } });
return rooms.map(room => {
const lastMessage = room.messages[0] || {};
return {
chatRoomId: room.chatRoomId,
chatRoomName: room.chatRoomName,
lastMessage: {
sender: lastMessage.sender || '없음',
message: lastMessage.message || '메시지 없음',
timestamp: lastMessage.timestamp || null,
}
},
};
});
}
......@@ -61,7 +49,7 @@ class ChatService {
// 사용자 상태 업데이트
async updateStatus(chatRoomId, nickname, isOnline) {
await ChatRoom.updateOne(
{ chatRoomId },
{ chatRoomId, "participants.name": nickname },
{ $set: { [`isOnline.${nickname}`]: isOnline } }
);
}
......@@ -70,14 +58,14 @@ class ChatService {
async updateReadStatus(chatRoomId, nickname) {
const now = new Date();
await ChatRoom.updateOne(
{ chatRoomId },
{ chatRoomId, "participants.name": nickname },
{ $set: { [`lastReadAt.${nickname}`]: now } }
);
}
// 읽지 않은 메시지 조회
async getUnreadMessages(nickname) {
const chatRooms = await ChatRoom.find({ participants: nickname });
const chatRooms = await ChatRoom.find({ "participants.name": nickname });
return await Promise.all(chatRooms.map(async (chatRoom) => {
const lastReadAt = chatRoom.lastReadAt.get(nickname) || new Date(0);
const unreadMessagesCount = chatRoom.messages.filter(message =>
......@@ -98,31 +86,49 @@ class ChatService {
}
const unreadCounts = chatRoom.participants
.filter(user => chatRoom.lastReadLogId.get(user))
.map(user => chatRoom.lastReadLogId.get(user))
.filter(participant => chatRoom.lastReadLogId.has(participant.name)) // Map에 존재하는 키만 처리
.map(participant => chatRoom.lastReadLogId.get(participant.name)) // lastReadLogId 값 추출
.reduce((acc, logId) => {
acc[logId] = (acc[logId] || 0) + 1;
acc[logId] = (acc[logId] || 0) + 1; // logId 기준으로 등장 횟수 누적
return acc;
}, {});
let count = 0;
return Object.entries(unreadCounts)
.sort(([logId1], [logId2]) => logId1.localeCompare(logId2))
const sortedUnreadCounts = Object.entries(unreadCounts)
.sort(([logId1], [logId2]) => logId1.localeCompare(logId2)) // logId 기준 오름차순 정렬
.reduce((acc, [logId, value]) => {
count += value;
acc[count] = logId;
count += value; // 누적 합계
acc[count] = logId; // 누적 합계를 키로 저장
return acc;
}, {});
return sortedUnreadCounts;
}
// 읽은 메시지 로그 ID 업데이트
async updateReadLogId(chatRoomId, nickname, logId) {
await ChatRoom.updateOne(
{ chatRoomId },
{ chatRoomId, "participants.name": nickname },
{ $set: { [`lastReadLogId.${nickname}`]: logId } }
);
}
// FCM 토큰 업데이트
async updateFcmToken(chatRoomId, nickname, fcmToken) {
const chatRoom = await ChatRoom.findOne({ chatRoomId, "participants.name": nickname });
if (!chatRoom) {
throw new Error('Chat room or participant not found');
}
const participant = chatRoom.participants.find(p => p.name === nickname);
if (participant) {
if (!participant.fcmTokens.includes(fcmToken)) {
participant.fcmTokens.push(fcmToken);
await chatRoom.save();
}
}
}
// 상태와 로그 ID 동시 업데이트
async updateStatusAndLogId(chatRoomId, nickname, isOnline, logId) {
let finalLogId = logId;
......@@ -135,7 +141,7 @@ class ChatService {
}
await ChatRoom.updateOne(
{ chatRoomId },
{ chatRoomId, "participants.name": nickname },
{
$set: {
[`isOnline.${nickname}`]: isOnline,
......@@ -144,6 +150,65 @@ class ChatService {
}
);
}
// 메시지 전송
async sendMessage(chatRoomId, sender, messageContent) {
try {
// 채팅방 조회
const chatRoom = await ChatRoom.findOne({ chatRoomId });
if (!chatRoom) {
throw new Error('Chat room not found');
}
// 메시지 추가
const newMessage = {
sender,
message: messageContent,
timestamp: new Date(),
type: 'message',
};
chatRoom.messages.push(newMessage);
await chatRoom.save();
// 오프라인 사용자 찾기
const offlineParticipants = chatRoom.participants.filter(
participant => !chatRoom.isOnline[participant.name]
);
// 오프라인 사용자들에게 FCM 푸시 알림 전송
for (const participant of offlineParticipants) {
const tokens = participant.fcmTokens || [];
if (tokens.length > 0) {
const message = {
notification: {
title: `새 메시지: ${chatRoom.chatRoomName}`,
body: `${sender}: ${messageContent}`,
},
tokens,
};
try {
const response = await admin.messaging().sendMulticast(message);
console.log(
`푸시 알림 전송 성공 (${participant.name}):`,
response.successCount
);
} catch (error) {
console.error(
`푸시 알림 전송 실패 (${participant.name}):`,
error
);
}
}
}
return newMessage;
} catch (error) {
console.error('Error sending message:', error);
throw new Error('Failed to send message');
}
}
}
module.exports = new ChatService();
module.exports = new ChatService();
\ No newline at end of file
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');
......@@ -7,7 +11,8 @@ const ChatRooms = require('../models/ChatRooms');
const MeetingResponseDTO = require('../dtos/MeetingResponseDTO');
const MeetingDetailResponseDTO = require('../dtos/MeetingDetailResponseDTO');
const CreateMeetingRequestDTO = require('../dtos/CreateMeetingRequestDTO');
const ScheduleService = require('./scheduleService');
const ScheduleService = require('./scheduleService'); // ScheduleService 임포트
const chatService = require('./chatService');
class MeetingService {
/**
......@@ -27,7 +32,6 @@ class MeetingService {
}
async createMeeting(meetingData) {
// DTO를 사용하여 요청 데이터 검증
const createMeetingDTO = new CreateMeetingRequestDTO(meetingData);
createMeetingDTO.validate();
......@@ -43,26 +47,31 @@ class MeetingService {
max_num,
} = meetingData;
// 사용자 존재 여부 확인
const user = await User.findOne({ where: { id: created_by } });
if (!user) {
throw new Error('사용자를 찾을 수 없습니다.');
// 사용자와 FCM 토큰 조회
const user = await this._findUserWithFcmTokens(created_by);
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 result = await sequelize.transaction(async (transaction) => {
// 채팅방 생성 (MongoDB)
const chatRoomId = uuidv4(); // 고유한 채팅방 ID 생성
const chatRoomData = {
chatRoomId,
participants: [user.name],
messages: [],
lastReadAt: {},
lastReadLogId: {},
isOnline: {},
};
const chatRoom = new ChatRooms(chatRoomData);
await chatRoom.save();
return await Meeting.sequelize.transaction(async (transaction) => {
const chatRoomData = this._constructChatRoomData(title, user, userFcmTokens);
const chatRoomResponse = await chatService.createChatRoom(chatRoomData);
if (!chatRoomResponse.success) {
throw new Error('채팅방 생성 실패');
}
const chatRoomId = chatRoomResponse.chatRoomId;
// 모임 생성
const newMeeting = await Meeting.create(
......@@ -113,11 +122,23 @@ class MeetingService {
time_idx_start,
time_idx_end,
}, transaction);
await ScheduleService.createSchedule({
userId: created_by,
title: `번개 모임: ${title}`,
start_time: new Date(start_time),
end_time: new Date(end_time),
is_fixed: true,
});
const chatRoom = await ChatRoom.findOne({ chatRoomId: chatRoomId });
if (chatRoom) {
console.log("채팅방 찾음");
this._addParticipantToChatRoom(chatRoom, user, userFcmTokens);
}
return { meeting_id: newMeeting.id, chatRoomId, invitedFriendIds };
});
return result;
}
async sendInvites({ meetingId, creatorId, time_idx_start, time_idx_end }, transaction) {
......@@ -394,8 +415,40 @@ class MeetingService {
// 현재 인원 수 증가
await meeting.increment("cur_num", { by: 1, transaction });
await Meeting.sequelize.transaction(async (transaction) => {
const hasConflict = await ScheduleService.checkScheduleOverlap(
userId,
new Date(meeting.start_time),
new Date(meeting.end_time)
);
if (hasConflict) {
throw new Error('스케줄이 겹칩니다. 다른 모임에 참가하세요.');
}
await MeetingParticipant.create({ meeting_id: meetingId, user_id: userId }, { transaction });
await ScheduleService.createSchedule({
userId,
title: `번개 모임: ${meeting.title}`,
start_time: new Date(meeting.start_time),
end_time: new Date(meeting.end_time),
is_fixed: true,
});
// 사용자와 FCM 토큰 조회
const user = await this._findUserWithFcmTokens(userId);
const userFcmTokens = user.fcmTokenList.map((fcmToken) => fcmToken.token);
const chatRoom = await ChatRoom.findOne({ chatRoomId: meeting.chatRoomId });
if (chatRoom) {
console.log("채팅방 찾음");
this._addParticipantToChatRoom(chatRoom, user, userFcmTokens);
}
});
});
}
async getMeetingDetail(meetingId) {
......@@ -426,6 +479,79 @@ class MeetingService {
return new MeetingDetailResponseDTO(meeting);
}
/**
* 번개 모임 마감
*/
async closeMeeting(meetingId) {
const meeting = await Meeting.findByPk(meetingId);
if (!meeting) {
throw new Error('모임을 찾을 수 없습니다.');
}
if (meeting.type === 'CLOSE') {
throw new Error('이미 마감된 모임입니다.');
}
meeting.type = 'CLOSE';
await meeting.save();
return meeting;
}
// Helper functions
async _findUserWithFcmTokens(userId) {
const user = await User.findOne({
where: { id: userId },
include: [
{
model: FcmToken,
as: 'fcmTokenList',
attributes: ['token'],
},
],
});
if (!user) {
throw new Error('사용자를 찾을 수 없습니다.');
}
return user;
}
_constructChatRoomData(title, user, userFcmTokens) {
return {
meeting_id: null,
participants: [
{
name: user.name,
fcmTokens: userFcmTokens || [],
isOnline: true,
lastReadAt: new Date(),
lastReadLogId: null,
},
],
chatRoomName: title,
};
}
_addParticipantToChatRoom(chatRoom, user, userFcmTokens) {
// Map 필드가 초기화되지 않은 경우 기본값 설정
if (!chatRoom.isOnline) chatRoom.isOnline = new Map();
if (!chatRoom.lastReadAt) chatRoom.lastReadAt = new Map();
if (!chatRoom.lastReadLogId) chatRoom.lastReadLogId = new Map();
// 참가자 추가 로직
if (!chatRoom.participants.some(participant => participant.name === user.name)) {
chatRoom.participants.push({ name: user.name, fcmTokens: userFcmTokens });
chatRoom.isOnline.set(user.name, true);
chatRoom.lastReadAt.set(user.name, new Date());
chatRoom.lastReadLogId.set(user.name, null);
}
// 저장
chatRoom.save();
}
}
module.exports = new MeetingService();
module.exports = new MeetingService();
\ No newline at end of file
const User = require('../models/User');
const FcmToken = require('../models/fcmToken');
const ChatRoom = require('../models/chatRooms');
class MemberService {
async registerToken(email, fcmToken) {
console.log(`Registering FCM token for email: ${email}, token: ${fcmToken}`);
// 1. RDB에서 사용자 검색
const user = await User.findOne({ where: { email } });
if (!user) throw new Error('User not found');
console.log(`User found: ${user.name}`);
// 2. RDB의 FcmTokens 테이블에 저장
const existingToken = await FcmToken.findOne({
where: { userId: user.id, token: fcmToken },
});
if (!existingToken) {
await FcmToken.create({ userId: user.id, token: fcmToken });
console.log(`FCM token ${fcmToken} saved to FcmTokens table`);
} else {
console.log(`FCM token ${fcmToken} already exists for user ${user.name}`);
}
// 3. MongoDB에서 관련 채팅방의 FCM 토큰 업데이트
const existingChatRooms = await ChatRoom.find({ "participants.name": user.name });
for (const room of existingChatRooms) {
room.participants = room.participants.map((participant) => {
if (participant.name === user.name) {
const currentFcmTokens = participant.fcmTokens || [];
if (!currentFcmTokens.includes(fcmToken)) {
participant.fcmTokens = Array.from(new Set([...currentFcmTokens, fcmToken]));
}
}
return participant;
});
await room.save();
}
console.log(`FCM token registration process completed for email: ${email}`);
return { message: 'FCM token registered successfully' };
}
}
module.exports = new MemberService();
\ No newline at end of file
......@@ -2,8 +2,21 @@ const http = require('http');
const crypto = require('crypto');
// const ChatRoom = require('./models/chatRoom.js');
const mongoose = require('mongoose');
const admin = require('firebase-admin');
const dotenv = require('dotenv');
const ChatRoom = require('./models/chatRooms');
// .env 파일 로드
dotenv.config();
// 서비스 계정 키 파일 경로를 환경 변수에서 가져오기
const serviceAccountPath = process.env.FIREBASE_CREDENTIAL_PATH;
// Firebase Admin SDK 초기화
admin.initializeApp({
credential: admin.credential.cert(require(serviceAccountPath)),
});
// WebSocket 관련 데이터
let clients = [];
let chatRooms = {};
......@@ -85,8 +98,6 @@ function startWebSocketServer() {
if (type === 'join') {
chatRoomId = clientChatRoomId;
nickname = clientNickname;
console.log("join시 chatRoomId", chatRoomId);
console.log("join시 nickname", nickname);
await ChatRoom.updateOne(
{ chatRoomId },
......@@ -103,32 +114,42 @@ function startWebSocketServer() {
}
const chatRoom = await ChatRoom.findOne({ chatRoomId });
console.log("join시 chatRoom", chatRoom);
if (!chatRoom) {
console.error(`ChatRoom을 찾을 수 없습니다: chatRoomId = ${chatRoomId}`);
} else {
console.log(`ChatRoom 조회 성공: ${chatRoom}`);
}
const isAlreadyParticipant = chatRoom.participants.includes(nickname);
if (!isAlreadyParticipant) {
// 참가자 확인
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'
};
chatRooms[chatRoomId].push(joinMessage);
await ChatRoom.updateOne({ chatRoomId }, {
$push: { messages: joinMessage, participants: nickname }
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)));
});
} else {
console.log(`${nickname}은 이미 채팅방에 참가 중입니다.`);
console.log(`${nickname} 새 참가자로 추가`);
}
try {
......@@ -177,12 +198,63 @@ function startWebSocketServer() {
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인 사용자만 필터링
});
for (const participant of offlineParticipants) {
const tokens = participant.fcmTokens || [];
// console("푸시 알림 보내는 토큰", tokens);
if (tokens.length > 0) {
const message = {
tokens, // FCM 토큰 배열
notification: {
title: `${chatRoom.chatRoomName}`,
body: `${nickname}: ${text}`,
},
data: {
key1: 'value1',
key2: 'value2',
},
android: {
priority: 'high',
},
apns: {
payload: {
aps: {
sound: 'default',
},
},
},
};
try {
console.log(`푸시 알림 전송 중 (${participant.name}):`, message); // 디버깅 로그 추가
const response = await admin.messaging().sendEachForMulticast(message);
console.log(`푸시 알림 전송 성공 (${participant.name}):`, response.successCount);
} catch (error) {
console.error(`푸시 알림 전송 실패 (${participant.name}):`, error);
}
} else {
console.log(`사용자 ${participant.name}의 FCM 토큰이 없습니다.`);
}
}
} catch (err) {
console.error('MongoDB 채팅 메시지 저장 오류:', err);
}
} else if (type === 'leave') {
const leaveMessage = { message: `${nickname}님이 퇴장했습니다.`, timestamp: new Date() };
const leaveMessage = {
message: `${nickname}님이 퇴장했습니다.`,
timestamp: new Date(),
type: 'leave'
};
chatRooms[chatRoomId].push(leaveMessage);
await ChatRoom.updateOne(
......
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