Skip to content
Snippets Groups Projects

Compare revisions

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

Source

Select target project
No results found
Select Git revision
  • deploy
  • develop
  • master
3 results

Target

Select target project
  • websystem1/webback
1 result
Select Git revision
  • deploy
  • develop
  • master
3 results
Show changes
Commits on Source (27)
......@@ -5,4 +5,6 @@ resources/
app.js
output.log
weblog.log
start.sh
start2.sh
......@@ -13,7 +13,12 @@ exports.createChatRoom = async (params) => {
// 채팅방 목록 조회
exports.getChatRooms = async (req, res) => {
try {
const roomData = await chatService.getChatRooms();
const name = req.user.name; // Google 로그인에서 가져온 email
console.log("name", name);
// 본인이 참가자로 포함된 채팅방만 가져오기
const roomData = await chatService.getChatRooms(name);
res.json(roomData);
} catch (err) {
console.error('Error fetching rooms:', err);
......
......@@ -12,7 +12,7 @@ class friendController {
async sendFriendRequest(req, res) {
try {
return await performanceMonitor.measureAsync('sendFriendRequest', async () => {
const email = req.body;
const { email } = req.body;
const userId = req.user.id;
if (!userId || !email) {
......@@ -97,8 +97,28 @@ class friendController {
async acceptRequest(req, res) {
try {
return await performanceMonitor.measureAsync('acceptFriendRequest', async () => {
if (!req.user || !req.user.id) {
return res.status(401).json({
success: false,
error: {
message: '인증되지 않은 사용자입니다.',
code: 'UNAUTHORIZED'
}
});
}
const userId = req.user.id;
const { friendId } = req.params;
const friendId = parseInt(req.params.friendId, 10);
if (!friendId || isNaN(friendId)) {
return res.status(400).json({
success: false,
error: {
message: '유효하지 않은 친구 ID입니다.',
code: 'INVALID_FRIEND_ID'
}
});
}
const result = await FriendService.acceptFriendRequest(userId, friendId);
return res.status(200).json({
......@@ -107,6 +127,7 @@ class friendController {
});
});
} catch (error) {
console.error('Friend request accept error:', error);
return res.status(400).json({
success: false,
error: {
......@@ -162,7 +183,9 @@ class friendController {
return res.status(200).json({
success: true,
data: friends
data: {
...friends
}
});
});
} catch (error) {
......
......@@ -108,8 +108,9 @@ class MeetingController {
*/
async getMeetingDetail(req, res) {
const { meetingId } = req.params;
const userId=req.user.id;
try {
const meetingDetail = await MeetingService.getMeetingDetail(meetingId);
const meetingDetail = await MeetingService.getMeetingDetail(meetingId,userId);
res.status(200).json(meetingDetail);
} catch (err) {
console.error('모임 상세 조회 오류:', err);
......@@ -165,6 +166,24 @@ class MeetingController {
}
}
/**
* 번개 모임 삭제
* DELETE /api/meeting/:meetingId
*/
// controllers/meetingController.js
async deleteMeeting(req, res) {
const { meetingId } = req.params;
const userId = req.user.id;
try {
await MeetingService.deleteMeeting(meetingId, userId);
res.status(200).json({ message: '모임이 삭제되었습니다.' });
} catch (err) {
console.error('모임 삭제 오류:', err);
res.status(500).json({ error: err.message || '모임 삭제 실패' });
}
}
}
module.exports = new MeetingController();
......@@ -22,7 +22,7 @@ class CreateMeetingRequestDTO {
time_idx_start: Joi.number().integer().min(0).required(),
time_idx_end: Joi.number().integer().greater(Joi.ref('time_idx_start')).required(),
location: Joi.string().allow('', null).optional(),
time_idx_deadline: Joi.number().integer().min(0).less(Joi.ref('time_idx_start')).optional(),
time_idx_deadline: Joi.number().integer().min(0).max(Joi.ref('time_idx_start')).optional(),
type: Joi.string().valid('OPEN', 'CLOSE').required(),
created_by: Joi.number().integer().positive().required(),
});
......
......@@ -13,8 +13,8 @@ class MeetingDetailResponseDTO {
this.isScheduleConflict = isScheduleConflict;
this.participants = meeting.participants.map(participant => ({
userId: participant.user_id,
name: participant.participantUser ? participant.participantUser.name : 'Unknown',
email: participant.participantUser ? participant.participantUser.email : 'Unknown'
name: participant.user ? participant.user.name : 'Unknown',
email: participant.user ? participant.user.email : 'Unknown'
}));
}
}
......
......@@ -78,6 +78,7 @@ User.hasMany(Schedule, {
onDelete: 'CASCADE',
});
// Invite 관계 설정
Invite.belongsTo(Meeting, {
foreignKey: 'meeting_id',
......@@ -109,7 +110,17 @@ Meeting.hasMany(Invite, {
as: 'invites',
onDelete: 'CASCADE',
});
FcmToken.belongsTo(User,{
foreignKey:'userId',
as:'user',
onDelete:'CASCADE',
});
User.hasMany(FcmToken,
{
foreignKey:'userId',
as:'fcmTokenList',
onDelete:'CASCADE',
});
module.exports = {
sequelize,
......@@ -119,5 +130,6 @@ module.exports = {
Meeting,
MeetingParticipant,
Friend,
Invite,
FcmToken,
};
......@@ -24,6 +24,7 @@ const Meeting = sequelize.define('Meeting', {
},
time_idx_deadline: {
type: DataTypes.INTEGER,
defaultValue:671,
},
type: {
type: DataTypes.ENUM('OPEN', 'CLOSE'),
......@@ -37,7 +38,7 @@ const Meeting = sequelize.define('Meeting', {
max_num: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 10, // 기본값 설정 (필요에 따라 조정)
defaultValue: 20, // 기본값 설정 (필요에 따라 조정)
},
cur_num: {
type: DataTypes.INTEGER,
......
......@@ -42,7 +42,7 @@ async function startPushServer() {
body: `${sender}: ${messageContent}`,
},
data: {
click_action: `http://localhost:3000/chat/chatRoom/${chatRoomId}`, // 클릭 시 이동할 URL
click_action: `${process.env.FRONT_URL}/mypage`, // 클릭 시 이동할 URL
},
android: { priority: 'high' },
apns: { payload: { aps: { sound: 'default' } } },
......@@ -71,24 +71,42 @@ async function startPushServer() {
channel.consume(meetingQueue, async (msg) => {
if (msg !== null) {
const event = JSON.parse(msg.content.toString());
const { meetingTitle, inviterName, inviteeTokens } = event;
const { meetingTitle, inviterName, inviteeTokens, type } = event;
console.log('Meeting 푸시 알림 요청 수신:', event);
console.log("푸시 알림 보내는 fcmToken", inviteeTokens);
if (inviteeTokens.length > 0) {
const message = {
let message;
// 이벤트 타입에 따라 알림 내용 구성
if (type === 'invite') {
message = {
tokens: inviteeTokens,
notification: {
title: '번개 모임 초대',
body: `${inviterName}님이 ${meetingTitle} 모임에 초대했습니다.`,
body: `${inviterName}님이 ${meetingTitle} 번개모임에 초대했습니다.`,
},
data: {
click_action: `http://localhost:3000`, // 클릭 시 이동할 URL
click_action: `${process.env.FRONT_URL}/meeting`, // 클릭 시 이동할 URL
},
android: { priority: 'high' },
apns: { payload: { aps: { sound: 'default' } } },
};
} else if (type === 'join') {
message = {
tokens: inviteeTokens,
notification: {
title: `${meetingTitle}`,
body: `${inviterName}님이 ${meetingTitle} 모임에 참가했습니다.`,
},
data: {
click_action: `${process.env.FRONT_URL}/meeting`, // 클릭 시 이동할 URL
},
android: { priority: 'high' },
apns: { payload: { aps: { sound: 'default' } } },
};
}
try {
const response = await admin.messaging().sendEachForMulticast(message);
......
......@@ -4,7 +4,6 @@ const chatController = require('../controllers/chatController');
const { isLoggedIn } = require('../middlewares/auth');
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);
......@@ -13,6 +12,7 @@ router.post('/update-status-and-logid', chatController.updateStatusAndLogId);
router.post('/update-read-log-id', chatController.updateReadLogId);
router.use(isLoggedIn);
router.get('/rooms', chatController.getChatRooms);
router.post('/:chatRoomId/notices', chatController.addNotice);
router.get('/:chatRoomId/notices/latest', chatController.getLatestNotice);
......
......@@ -28,6 +28,7 @@ router.get('/:meetingId', MeetingController.getMeetingDetail);
// 번개 모임 탈퇴
router.delete('/:meetingId/leave', MeetingController.leaveMeeting);
// 번개 모임 삭제
router.delete('/:meetingId', MeetingController.deleteMeeting);
module.exports = router;
\ No newline at end of file
......@@ -30,15 +30,18 @@ class ChatService {
}
// 채팅방 목록 조회
async getChatRooms() {
const rooms = await ChatRooms.find({}, { chatRoomId: 1, chatRoomName: 1, messages: { $slice: -1 } });
async getChatRooms(name) {
const rooms = await ChatRooms.find(
{ "participants.name": name },
{ 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 || '없음',
sender: lastMessage.sender || '알림',
message: lastMessage.message || '메시지 없음',
timestamp: lastMessage.timestamp || null,
},
......
......@@ -39,13 +39,22 @@ class MeetingService {
return totalIdx;
}
async sendMeetingPushNotificationRequest(meetingTitle, inviterName, inviteeTokens) {
// async sendMeetingPushNotificationRequest(meetingTitle, inviterName, inviteeTokens) {
// const event = {
// meetingTitle,
// inviterName,
// inviteeTokens,
// };
// await this.publishToQueue('meeting_push_notifications', event); // meeting_push_notifications 큐에 메시지 발행
// }
async sendMeetingPushNotificationRequest(meetingTitle, inviterName, inviteeTokens, type) {
const event = {
meetingTitle,
inviterName,
inviteeTokens,
type, // 이벤트 타입 ('invite' 또는 'join')
};
await this.publishToQueue('meeting_push_notifications', event); // meeting_push_notifications 큐에 메시지 발행
await this.publishToQueue('meeting_push_notifications', event); // 큐에 메시지 발행
}
......@@ -119,8 +128,8 @@ class MeetingService {
);
const time_indices = Array.from(
{ length: time_idx_end - time_idx_start + 1 },
(_, i) => time_idx_start + i
{ length: parseInt(time_idx_end) - parseInt(time_idx_start) + 1 },
(_, i) => (parseInt(time_idx_start) + i).toString()
);
await ScheduleService.createSchedules({
userId: created_by,
......@@ -147,7 +156,12 @@ class MeetingService {
// RabbitMQ 메시지 발행 (푸시 알림 요청)
if (inviteeTokens.length > 0) {
await this.sendMeetingPushNotificationRequest(title, user.name, inviteeTokens);
await this.sendMeetingPushNotificationRequest(
title,
user.name,
inviteeTokens,
'invite'
);
}
const chatRoom = await ChatRooms.findOne({ chatRoomId: chatRoomId });
......@@ -300,9 +314,32 @@ class MeetingService {
name: user.name,
fcmTokens: userFcmTokens, // FCM 토큰 추가
});
chatRoom.isOnline.set(user.name, true);
chatRoom.isOnline.set(user.name, false);
chatRoom.lastReadAt.set(user.name, new Date());
chatRoom.lastReadLogId.set(user.name, null);
const joinMessage = {
message: `${user.name}님이 참가했습니다.`,
timestamp: new Date(),
type: 'join'
};
chatRoom.messages.push(joinMessage);
// 기존 참가자 FCM 토큰 가져오기
const otherParticipants = chatRoom.participants.filter(participant => participant.name !== user.name);
const otherParticipantTokens = otherParticipants.flatMap(participant => participant.fcmTokens);
if (otherParticipantTokens.length > 0) {
// RabbitMQ 메시지 발행
await this.sendMeetingPushNotificationRequest(
meeting.title,
user.name,
otherParticipantTokens,
'join'
);
}
await chatRoom.save();
}
......@@ -583,6 +620,15 @@ class MeetingService {
chatRoom.isOnline.delete(user.name);
chatRoom.lastReadAt.delete(user.name);
chatRoom.lastReadLogId.delete(user.name);
const leaveMessage = {
message: `${user.name}님이 퇴장했습니다.`,
timestamp: new Date(),
type: 'leave'
};
chatRoom.messages.push(leaveMessage);
await chatRoom.save();
}
......@@ -590,6 +636,45 @@ class MeetingService {
await meeting.decrement('cur_num', { by: 1, transaction });
});
}
async deleteMeeting(meetingId, userId) {
const meeting = await Meeting.findByPk(meetingId);
if (!meeting) {
throw new Error('모임을 찾을 수 없습니다.');
}
if (meeting.created_by !== userId) {
throw new Error('모임 생성자만 삭제할 수 있습니다.');
}
return await sequelize.transaction(async (transaction) => {
const participants = await MeetingParticipant.findAll({
where: { meeting_id: meetingId },
attributes: ['user_id'],
transaction
});
const participantIds = participants.map(p => p.user_id);
// 모든 참가자의 스케줄 삭제
await Schedule.destroy({
where: {
user_id: { [Op.in]: participantIds },
title: `번개 모임: ${meeting.title}`,
time_idx: {
[Op.between]: [meeting.time_idx_start, meeting.time_idx_end]
}
},
transaction
});
await ChatRooms.deleteOne({ chatRoomId: meeting.chatRoomId });
// 모임 관련 데이터 삭제
await meeting.destroy({ transaction });
});
}
}
module.exports = new MeetingService();
\ No newline at end of file
......@@ -10,11 +10,17 @@ class ScheduleService {
* @param {object} [transaction] - Sequelize 트랜잭션 객체 -> 미팅방에서 쓰기위해 트랜잭션을 넘겨받는걸 추가
*/
async createSchedules({ userId, title, is_fixed, time_indices }, transaction = null) {
const parsedTimeIndices = time_indices.map(idx => parseInt(idx, 10));
if (!userId || !title || !parsedTimeIndices.length) {
throw new Error('Required parameters missing');
}
const overlaps = await Schedule.findAll({
where: {
user_id: userId,
time_idx: {
[Op.in]: time_indices
[Op.in]: parsedTimeIndices
}
},
transaction
......@@ -24,7 +30,7 @@ class ScheduleService {
throw new Error(`Schedule overlaps at time_idx ${overlaps[0].time_idx}`);
}
const scheduleData = time_indices.map(time_idx => ({
const scheduleData = parsedTimeIndices.map(time_idx => ({
user_id: userId,
title,
time_idx,
......@@ -43,7 +49,7 @@ class ScheduleService {
user_id: userId,
title,
is_fixed,
time_indices,
time_indices: parsedTimeIndices,
createdAt: createdSchedules[0].createdAt,
updatedAt: createdSchedules[0].updatedAt
};
......
......@@ -9,9 +9,8 @@ async function syncRdb() {
await sequelize.authenticate();
console.log('Rdb 데이터베이스 연결 성공.');
// 모든 모델 동기화
await sequelize.sync({ force: true });
console.log('모든 모델이 성공적으로 동기화되었습니다.');
await sequelize.sync({alter :true});
console.log('모든 모델이 성공적으로 동기화됨.');
} catch (error) {
console.error('Rdb 데이터베이스 연결 실패:', error);
}
......
......@@ -94,6 +94,7 @@ function startWebSocketServer() {
});
server.on('upgrade', (req, socket, head) => {
console.log('WebSocket 업그레이드 요청 수신:', req.headers);
handleWebSocketUpgrade(req, socket);
});
......@@ -103,19 +104,22 @@ function startWebSocketServer() {
}
function handleWebSocketUpgrade(req, socket) {
try {
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}`
`Sec-WebSocket-Accept: ${acceptKey}`,
`Access-Control-Allow-Origin: ${process.env.FRONT_URL}`, // 환경변수에서 가져옴
'Access-Control-Allow-Credentials: true'
];
socket.write(responseHeaders.join('\r\n') + '\r\n\r\n');
// 클라이언트를 clients 배열에 추가
clients.push(socket);
} catch (error) {
console.error('WebSocket 업그레이드 실패:', error);
}
socket.on('data', async buffer => {
try {
......