Skip to content
Snippets Groups Projects
Commit 167c0c40 authored by 세현 임's avatar 세현 임
Browse files

Revert "[#%{issuses}] {taskTitle}"

This reverts merge request !11
parent 5f0d6b6d
No related branches found
No related tags found
No related merge requests found
// app.js
require('dotenv').config();
......@@ -6,23 +7,10 @@ const express = require('express');
const session = require('express-session');
const passport = require('./passport'); // 변경된 경로
const flash = require('connect-flash');
const { initScheduleCleaner } = require('./utils/scheduler');
const connectMongoDB = require('./config/mongoose'); // MongoDB 연결
const { sequelize } = require('./config/sequelize'); // Sequelize 연결
const cors = require('cors');
const { initScheduleCleaner } = require('./utils/scheduler'); // 유동 스케줄 자동 삭제 유틸
const app = express();
// CORS 설정
app.use(
cors({
origin: 'http://localhost:3000', // 허용할 도메인 설정 (예: 프론트엔드 주소)
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // 쿠키와 인증 정보를 허용하려면 true로 설정
})
);
// 미들웨어 설정
app.use(express.json());
......@@ -47,43 +35,28 @@ app.use(flash());
/**
* 라우터 등록
*/
// 로그인 라우터
const authRoutes = require('./routes/auth');
app.use('/auth', authRoutes);
// Schedule 라우터
const scheduleRoutes = require('./routes/schedule');
app.use('/api/schedule', scheduleRoutes);
// Friend 라우터
const friendRoutes = require('./routes/friend');
app.use('/api/friend', friendRoutes);
const meetingRoutes = require('./routes/meetingRoute');
app.use('/api/meeting', meetingRoutes);
const chatRoutes = require('./routes/chatRoute');
app.use('/api/chat', chatRoutes);
// 스케줄 클리너 초기화
initScheduleCleaner();
const PORT = process.env.PORT || 3000;
// MongoDB 및 MySQL 연결 후 서버 시작
(async () => {
try {
// MongoDB 연결
await connectMongoDB();
console.log('✅ MongoDB 연결 성공');
// MySQL 연결 확인
await sequelize.authenticate();
console.log('✅ MySQL 연결 성공');
// 서버 시작
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
} catch (error) {
console.error('❌ 서버 시작 중 오류 발생:', error);
process.exit(1);
}
})();
\ No newline at end of file
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
......@@ -2,17 +2,17 @@
const mongoose = require('mongoose');
const connectMongoDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('✅ MongoDB 연결 성공');
} catch (error) {
console.error('❌ MongoDB 연결 실패:', error);
throw error;
}
};
mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
module.exports = connectMongoDB;
\ No newline at end of file
mongoose.connection.on('connected', () => {
console.log('Mongoose connected.');
});
mongoose.connection.on('error', (err) => {
console.error('Mongoose connection error:', err);
});
module.exports = mongoose;
const chatService = require('../services/chatService');
// 내부용 채팅방 생성
exports.createChatRoomInternal = async (params) => {
try {
return await chatService.createChatRoom(params);
} 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);
res.status(500).json({ error: 'Failed to create room' });
}
};
// 채팅방 목록 조회
exports.getChatRooms = async (req, res) => {
try {
const roomData = await chatService.getChatRooms();
res.json(roomData);
} catch (err) {
console.error('Error fetching rooms:', err);
res.status(500).json({ error: 'Failed to fetch rooms' });
}
};
// 사용자 상태 업데이트
exports.updateStatus = async (req, res) => {
const { chatRoomId, nickname, isOnline } = req.body;
try {
await chatService.updateStatus(chatRoomId, nickname, isOnline);
res.status(200).json({ message: 'User status updated successfully' });
} catch (err) {
console.error('Error updating user status:', err);
res.status(500).json({ error: 'Failed to update user status' });
}
};
// 읽음 상태 업데이트
exports.updateReadStatus = async (req, res) => {
const { chatRoomId, nickname } = req.body;
try {
await chatService.updateReadStatus(chatRoomId, nickname);
res.status(200).json({ message: 'Read status updated' });
} catch (err) {
console.error('Error updating read status:', err);
res.status(500).json({ error: 'Failed to update read status' });
}
};
// 읽지 않은 메시지 조회
exports.getUnreadMessages = async (req, res) => {
const { nickname } = req.params;
try {
const unreadMessages = await chatService.getUnreadMessages(nickname);
res.status(200).json(unreadMessages);
} catch (err) {
console.error('Error fetching unread messages:', err);
res.status(500).json({ error: 'Failed to fetch unread messages' });
}
};
// 읽지 않은 메시지 수 조회
exports.getUnreadCount = async (req, res) => {
const { chatRoomId } = req.params;
try {
const unreadCountMap = await chatService.getUnreadCount(chatRoomId);
res.status(200).json(unreadCountMap);
} catch (err) {
console.error('Error fetching unread counts:', err);
res.status(500).json({ error: 'Failed to fetch unread counts' });
}
};
// 읽은 로그 ID 업데이트
exports.updateReadLogId = async (req, res) => {
const { chatRoomId, nickname, logId } = req.body;
try {
await chatService.updateReadLogId(chatRoomId, nickname, logId);
res.status(200).json({ message: 'Last read logID updated' });
} catch (err) {
console.error('Error updating last read logID:', err);
res.status(500).json({ error: 'Failed to update last read logID' });
}
};
// 상태와 로그 ID 동시 업데이트
exports.updateStatusAndLogId = async (req, res) => {
const { chatRoomId, nickname, isOnline, logId } = req.body;
try {
await chatService.updateStatusAndLogId(chatRoomId, nickname, isOnline, logId);
res.status(200).json({ message: 'User status and lastReadLogId updated successfully' });
} catch (err) {
console.error('Error updating user status and lastReadLogId:', err);
res.status(500).json({ error: 'Failed to update user status and lastReadLogId' });
}
};
\ No newline at end of file
const MeetingService = require('../services/meetingService');
class MeetingController {
async createMeeting(req, res) {
try {
const result = await MeetingService.createMeeting(req.body);
res.status(201).json(result);
} catch (err) {
console.error('번개 모임 생성 오류:', err);
res.status(500).json({ error: err.message || '번개 모임 생성 실패' });
}
}
async getMeetings(req, res) {
const { userId } = req.query;
if (!userId) {
return res.status(400).json({ error: '사용자 ID가 필요합니다.' });
}
try {
const meetings = await MeetingService.getMeetings(userId);
res.status(200).json(meetings);
} catch (err) {
console.error('모임 목록 조회 오류:', err);
res.status(500).json({ error: err.message || '모임 목록 조회 실패' });
}
}
async closeMeeting(req, res) {
const { meetingId } = req.params;
try {
const meeting = await MeetingService.closeMeeting(meetingId);
res.status(200).json({ message: '모임이 마감되었습니다.', meeting });
} catch (err) {
console.error('모임 마감 오류:', err);
res.status(500).json({ error: err.message || '모임 마감 실패' });
}
}
async joinMeeting(req, res) {
const { meetingId } = req.params;
const { user_id } = req.body;
try {
await MeetingService.joinMeeting(meetingId, user_id);
res.status(200).json({ message: '모임 및 채팅방 참가 완료' });
} catch (err) {
console.error('모임 참가 오류:', err);
res.status(500).json({ error: err.message || '모임 참가 실패' });
}
}
async getMeetingDetail(req, res) {
const { meetingId } = req.params;
try {
const meetingDetail = await MeetingService.getMeetingDetail(meetingId);
res.status(200).json(meetingDetail);
} catch (err) {
console.error('모임 상세 조회 오류:', err);
res.status(500).json({ error: err.message || '모임 상세 조회 실패' });
}
}
}
module.exports = new MeetingController();
\ No newline at end of file
class MeetingDetailResponse {
constructor(meeting) {
this.id = meeting.id;
this.title = meeting.title;
this.description = meeting.description;
this.startTime = meeting.start_time;
this.endTime = meeting.end_time;
this.location = meeting.location;
this.deadline = meeting.deadline;
this.type = meeting.type;
this.creatorName = meeting.creator ? meeting.creator.name : 'Unknown';
this.participants = meeting.participants.map(participant => ({
userId: participant.user_id,
name: participant.participantUser ? participant.participantUser.name : 'Unknown',
email: participant.participantUser ? participant.participantUser.email : 'Unknown'
}));
}
}
module.exports = MeetingDetailResponse;
\ No newline at end of file
// dtos/MeetingResponse.js
class MeetingResponse {
constructor(meeting, isParticipant, isScheduleConflict, creatorName) {
this.id = meeting.id;
this.title = meeting.title;
this.description = meeting.description;
this.startTime = meeting.start_time;
this.endTime = meeting.end_time;
this.location = meeting.location;
this.deadline = meeting.deadline;
this.type = meeting.type;
this.creatorName = creatorName;
this.isParticipant = isParticipant;
this.isScheduleConflict = isScheduleConflict;
}
}
module.exports = MeetingResponse;
\ No newline at end of file
const mongoose = require('mongoose');
// MongoDB 채팅방 스키마 수정 (현재 참가 중인 유저 목록 추가)
const chatRoomsSchema = new mongoose.Schema({
chatRoomId: { type: String, required: true, unique: true },
messages: [{
sender: String,
message: String,
timestamp: Date,
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 } // 각 참가자의 온라인 상태
}, { collection: 'chatrooms' });
// 모델이 이미 정의되어 있는 경우 재정의하지 않음
const ChatRooms = mongoose.models.ChatRooms || mongoose.model('ChatRooms', chatRoomsSchema);
module.exports = ChatRooms;
\ No newline at end of file
// models/Meeting.js
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/sequelize');
const sequelize = require('../config/sequelize');
const User = require('./User');
const Meeting = sequelize.define('Meeting', {
id: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true,
},
title: {
type: DataTypes.STRING,
allowNull: false,
......@@ -35,34 +30,12 @@ const Meeting = sequelize.define('Meeting', {
type: DataTypes.ENUM('OPEN', 'CLOSE'),
allowNull: false,
},
created_by: {
type: DataTypes.BIGINT,
allowNull: false,
references: {
model: 'Users',
key: 'id',
},
},
chatRoomId: { // 새로운 필드 추가
type: DataTypes.STRING,
allowNull: false,
},
}, {
tableName: 'Meetings',
timestamps: false,
});
Meeting.belongsTo(User, { foreignKey: 'created_by', as: 'creator' });
User.hasMany(Meeting, { foreignKey: 'created_by', as: 'meetings' });
// 연관 관계 설정
Meeting.associate = (models) => {
Meeting.belongsTo(models.User, {
foreignKey: 'created_by', // FK 설정
as: 'creator', // 별칭
});
Meeting.hasMany(models.MeetingParticipant, {
foreignKey: 'meeting_id',
as: 'participants',
});
};
module.exports = Meeting;
\ No newline at end of file
module.exports = Meeting;
// models/MeetingParticipant.js
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/sequelize');
const sequelize = require('../config/sequelize');
const Meeting = require('./Meeting');
const User = require('./User');
const MeetingParticipant = sequelize.define('MeetingParticipant', {
meeting_id: {
type: DataTypes.BIGINT,
allowNull: false
},
user_id: {
type: DataTypes.BIGINT,
allowNull: false
}
}, {
const MeetingParticipant = sequelize.define('MeetingParticipant', {}, {
tableName: 'MeetingParticipants',
timestamps: false,
});
MeetingParticipant.associate = (models) => {
MeetingParticipant.belongsTo(models.Meeting, {
foreignKey: 'meeting_id',
as: 'meeting'
});
MeetingParticipant.belongsTo(Meeting, { foreignKey: 'meeting_id', as: 'meeting' });
Meeting.hasMany(MeetingParticipant, { foreignKey: 'meeting_id', as: 'participants' });
MeetingParticipant.belongsTo(models.User, {
foreignKey: 'user_id',
as: 'participantUser'
});
};
MeetingParticipant.belongsTo(User, { foreignKey: 'user_id', as: 'user' });
User.hasMany(MeetingParticipant, { foreignKey: 'user_id', as: 'meetingParticipations' });
module.exports = MeetingParticipant;
// models/User.js
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/sequelize');
const sequelize = require('../config/sequelize'); // sequelize 인스턴스 경로에 맞게 수정하세요.
const User = sequelize.define('User', {
id: {
type: DataTypes.BIGINT, // 수정: id 필드를 BIGINT로 설정
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING, // VARCHAR
allowNull: false,
......@@ -26,17 +21,4 @@ const User = sequelize.define('User', {
timestamps: true, // createdAt과 updatedAt 자동 관리
});
User.associate = (models) => {
User.hasMany(models.Meeting, {
foreignKey: 'created_by',
as: 'createdMeetings',
});
User.hasMany(models.MeetingParticipant, {
foreignKey: 'user_id',
as: 'userMeetingParticipations',
});
};
module.exports = User;
const express = require('express');
const router = express.Router();
const chatController = require('../controllers/chatController');
router.post('/create-room', chatController.createChatRoom);
router.get('/rooms', chatController.getChatRooms);
router.post('/update-status', chatController.updateStatus);
router.post('/update-read-status', chatController.updateReadStatus);
router.get('/unread-messages/:nickname', chatController.getUnreadMessages);
router.get('/unread-count/:chatRoomId', chatController.getUnreadCount);
router.post('/update-status-and-logid', chatController.updateStatusAndLogId);
router.post('/update-read-log-id', chatController.updateReadLogId);
module.exports = router;
// routes/meetingRoutes.js
const express = require('express');
const router = express.Router();
const { isLoggedIn } = require('../middlewares/auth');
const MeetingController = require('../controllers/meetingController');
router.use(isLoggedIn);
// 번개 모임 생성
router.post('/', MeetingController.createMeeting);
// 번개 모임 목록 조회
router.get('/', MeetingController.getMeetings);
// 번개 모임 마감
router.put('/:meetingId/close', MeetingController.closeMeeting);
// 번개 모임 참가
router.post('/:meetingId/join', MeetingController.joinMeeting);
// 번개 모임 상세 조회
router.get('/:meetingId', MeetingController.getMeetingDetail);
module.exports = router;
\ No newline at end of file
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',
};
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 } });
return rooms.map(room => {
const lastMessage = room.messages[0] || {};
return {
chatRoomId: room.chatRoomId,
lastMessage: {
sender: lastMessage.sender || '없음',
message: lastMessage.message || '메시지 없음',
timestamp: lastMessage.timestamp || null,
}
};
});
}
// 사용자 상태 업데이트
async updateStatus(chatRoomId, nickname, isOnline) {
await ChatRoom.updateOne(
{ chatRoomId },
{ $set: { [`isOnline.${nickname}`]: isOnline } }
);
}
// 읽음 상태 업데이트
async updateReadStatus(chatRoomId, nickname) {
const now = new Date();
await ChatRoom.updateOne(
{ chatRoomId },
{ $set: { [`lastReadAt.${nickname}`]: now } }
);
}
// 읽지 않은 메시지 조회
async getUnreadMessages(nickname) {
const chatRooms = await ChatRoom.find({ participants: nickname });
return await Promise.all(chatRooms.map(async (chatRoom) => {
const lastReadAt = chatRoom.lastReadAt.get(nickname) || new Date(0);
const unreadMessagesCount = chatRoom.messages.filter(message =>
message.timestamp > lastReadAt
).length;
return {
chatRoomId: chatRoom.chatRoomId,
unreadCount: unreadMessagesCount,
};
}));
}
// 읽지 않은 메시지 수 조회
async getUnreadCount(chatRoomId) {
const chatRoom = await ChatRoom.findOne({ chatRoomId });
if (!chatRoom) {
throw new Error('Chat room not found');
}
const unreadCounts = chatRoom.participants
.filter(user => chatRoom.lastReadLogId.get(user))
.map(user => chatRoom.lastReadLogId.get(user))
.reduce((acc, logId) => {
acc[logId] = (acc[logId] || 0) + 1;
return acc;
}, {});
let count = 0;
return Object.entries(unreadCounts)
.sort(([logId1], [logId2]) => logId1.localeCompare(logId2))
.reduce((acc, [logId, value]) => {
count += value;
acc[count] = logId;
return acc;
}, {});
}
// 읽은 메시지 로그 ID 업데이트
async updateReadLogId(chatRoomId, nickname, logId) {
await ChatRoom.updateOne(
{ chatRoomId },
{ $set: { [`lastReadLogId.${nickname}`]: logId } }
);
}
// 상태와 로그 ID 동시 업데이트
async updateStatusAndLogId(chatRoomId, nickname, isOnline, logId) {
let finalLogId = logId;
if (!isOnline && logId === null) {
const chatRoom = await ChatRoom.findOne({ chatRoomId });
if (chatRoom && chatRoom.messages.length > 0) {
finalLogId = chatRoom.messages[chatRoom.messages.length - 1]._id;
}
}
await ChatRoom.updateOne(
{ chatRoomId },
{
$set: {
[`isOnline.${nickname}`]: isOnline,
[`lastReadLogId.${nickname}`]: isOnline ? null : finalLogId,
},
}
);
}
}
module.exports = new ChatService();
const { v4: uuidv4 } = require('uuid');
const { Meeting, MeetingParticipant, User } = require('../models');
const ChatRoom = require('../models/chatRooms');
const chatController = require('../controllers/chatController');
const MeetingResponse = require('../dtos/MeetingResponse');
const MeetingDetailResponse = require('../dtos/MeetingDetailResponse');
class MeetingService {
async createMeeting(meetingData) {
const { title, description, start_time, end_time, location, deadline, type, created_by } = meetingData;
const user = await User.findOne({ where: { id: created_by } });
if (!user) {
throw new Error('사용자를 찾을 수 없습니다.');
}
const chatRoomData = {
participants: [user.name],
};
const chatRoomResponse = await chatController.createChatRoomInternal(chatRoomData);
if (!chatRoomResponse.success) {
throw new Error('채팅방 생성 실패');
}
const chatRoomId = chatRoomResponse.chatRoomId;
const newMeeting = await Meeting.create({
title,
description,
start_time,
end_time,
location,
deadline,
type,
created_by,
chatRoomId,
});
await MeetingParticipant.create({
meeting_id: newMeeting.id,
user_id: created_by,
});
return { meeting_id: newMeeting.id, chatRoomId: chatRoomResponse.chatRoomId };
}
async getMeetings(userId) {
const meetings = await Meeting.findAll({
attributes: ['id', 'title', 'description', 'start_time', 'end_time', 'location', 'deadline', 'type'],
include: [
{
model: User,
as: 'creator',
attributes: ['name'],
},
{
model: MeetingParticipant,
as: 'participants',
attributes: ['user_id'],
},
],
});
return meetings.map((meeting) => {
const creatorName = meeting.creator ? meeting.creator.name : 'Unknown';
const isParticipant = meeting.participants.some(participant => participant.user_id === parseInt(userId, 10));
return new MeetingResponse(
meeting,
isParticipant,
false,
creatorName
);
});
}
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;
}
async joinMeeting(meetingId, userId) {
const meeting = await Meeting.findByPk(meetingId);
if (!meeting) {
throw new Error('모임을 찾을 수 없습니다.');
}
if(meeting.type === 'CLOSE') {
throw new Error('이미 마감된 모임입니다.');
}
if (new Date() > new Date(meeting.deadline)) {
throw new Error('참가 신청이 마감되었습니다.');
}
const existingParticipant = await MeetingParticipant.findOne({
where: { meeting_id: meetingId, user_id: userId }
});
if (existingParticipant) {
throw new Error('이미 참가한 사용자입니다.');
}
await MeetingParticipant.create({ meeting_id: meetingId, user_id: userId });
const user = await User.findOne({ where: { id: userId } });
const chatRoom = await ChatRoom.findOne({ meeting_id: meetingId });
if (chatRoom && !chatRoom.participants.includes(user.name)) {
chatRoom.participants.push(user.name);
chatRoom.isOnline.set(user.name, true);
chatRoom.lastReadAt.set(user.name, new Date());
chatRoom.lastReadLogId.set(user.name, null);
await chatRoom.save();
}
}
async getMeetingDetail(meetingId) {
const meeting = await Meeting.findByPk(meetingId, {
include: [
{
model: User,
as: 'creator',
attributes: ['name']
},
{
model: MeetingParticipant,
as: 'participants',
include: [
{
model: User,
as: 'participantUser',
attributes: ['name', 'email']
}
]
}
]
});
if (!meeting) {
throw new Error('모임을 찾을 수 없습니다.');
}
return new MeetingDetailResponse(meeting);
}
}
module.exports = new MeetingService();
const http = require('http');
const crypto = require('crypto');
// const ChatRoom = require('./models/chatRoom.js');
const mongoose = require('mongoose');
const ChatRoom = require('./models/chatRooms');
// WebSocket 관련 데이터
let clients = [];
let chatRooms = {};
// MongoDB 연결 설정
async function connectMongoDB() {
try {
await mongoose.connect('mongodb://localhost:27017/chat', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB에 성공적으로 연결되었습니다.');
// MongoDB 연결 성공 후 WebSocket 서버 시작
startWebSocketServer();
} catch (err) {
console.error('MongoDB 연결 실패:', err);
process.exit(1);
}
}
// 채팅방 기록 불러오기 함수
async function getChatHistory(chatRoomId) {
const chatRoom = await ChatRoom.findOne({ chatRoomId });
return chatRoom ? chatRoom.messages : [];
}
// WebSocket 서버 생성 및 핸드셰이크 처리
function startWebSocketServer() {
const wsServer = 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;
console.log("join시 chatRoomId", chatRoomId);
console.log("join시 nickname", nickname);
await ChatRoom.updateOne(
{ chatRoomId },
{
$set: {
[`isOnline.${nickname}`]: true,
[`lastReadLogId.${nickname}`]: null,
},
}
);
if (!chatRooms[chatRoomId]) {
chatRooms[chatRoomId] = [];
}
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 joinMessage = {
message: `${nickname}님이 참가했습니다.`,
timestamp: new Date(),
type: 'join'
};
chatRooms[chatRoomId].push(joinMessage);
await ChatRoom.updateOne({ chatRoomId }, {
$push: { messages: joinMessage, participants: nickname }
});
clients.forEach(client => {
client.write(constructReply(JSON.stringify(joinMessage)));
});
} else {
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);
});
} catch (err) {
console.error('MongoDB 채팅 메시지 저장 오류:', err);
}
} else if (type === 'leave') {
const leaveMessage = { message: `${nickname}님이 퇴장했습니다.`, timestamp: new Date() };
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);
}
});
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)));
});
}
});
socket.on('error', (err) => {
console.error(`WebSocket error: ${err}`);
clients = clients.filter(client => client !== socket);
});
});
wsServer.listen(8081, () => {
console.log('WebSocket 채팅 서버가 8081 포트에서 실행 중입니다.');
});
}
// Sec-WebSocket-Accept 헤더 값 생성 -> env처리
function generateAcceptValue(key) {
return crypto.createHash('sha1').update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary').digest('base64');
}
// 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];
}
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]);
return new TextDecoder('utf-8').decode(Uint8Array.from(data));
}
// 클라이언트 메시지 응답 생성 함수
function constructReply(message) {
const messageBuffer = Buffer.from(message, 'utf-8');
const length = messageBuffer.length;
const reply = [0x81];
if (length < 126) {
reply.push(length);
} else if (length < 65536) {
reply.push(126, (length >> 8) & 255, length & 255);
} else {
reply.push(
127,
(length >> 56) & 255,
(length >> 48) & 255,
(length >> 40) & 255,
(length >> 32) & 255,
(length >> 24) & 255,
(length >> 16) & 255,
(length >> 8) & 255,
length & 255
);
}
return Buffer.concat([Buffer.from(reply), messageBuffer]);
}
// MongoDB 연결 후 WebSocket 서버 시작
connectMongoDB();
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment