Skip to content

Commit 32a2e9c

Browse files
Merge pull request #19 from NIPA-AWS-Developer-2nd/fix/web-push-chat-notification
[FIX] Web-Push 채팅 알림
2 parents bacb072 + f1b8ecb commit 32a2e9c

3 files changed

Lines changed: 69 additions & 53 deletions

File tree

src/modules/chat/chat.gateway.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,16 +116,17 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
116116
.to(`meeting:${data.meetingId}`)
117117
.emit('new_message', chatMessage);
118118

119+
this.logger.log(`📤 Message sent by ${userId} to meeting ${data.meetingId}: "${data.message}"`);
120+
119121
// 채팅 알림 발송 (비동기)
120122
setImmediate(async () => {
121123
try {
124+
this.logger.log(`🔔 Starting chat notification process for meeting ${data.meetingId}`);
122125
await this.sendChatNotification(data.meetingId, userId, data.message);
123126
} catch (error) {
124127
this.logger.error('Failed to send chat notification:', error);
125128
}
126129
});
127-
128-
this.logger.log(`Message sent by ${userId} to meeting ${data.meetingId}`);
129130
} catch (error) {
130131
this.logger.error('Send message error:', error);
131132
client.emit('error', {
@@ -230,18 +231,25 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
230231
return;
231232
}
232233

234+
this.logger.log(`🔍 Meeting participants: ${meeting?.participantList?.length || 0}`);
235+
this.logger.log(`🔍 Participant list: ${JSON.stringify(meeting?.participantList?.map(p => ({ userId: p.userId, nickname: p.user?.profile?.nickname })) || [])}`);
236+
233237
// 발송자 정보 조회
234238
const senderInfo = await this.chatService.getUserProfile(senderId);
235239
const senderName = senderInfo?.nickname || '알 수 없는 사용자';
240+
this.logger.log(`🔍 Sender: ${senderId} (${senderName})`);
236241

237-
// 현재 온라인인 사용자 제외 (실시간으로 메시지를 받는 사용자들)
242+
// 현재 채팅 소켓에 연결된 사용자 제외 (실시간으로 메시지를 받는 사용자들)
238243
const connectedUserIds = this.getConnectedUsers(meetingId);
244+
this.logger.log(`🔍 Connected users: ${connectedUserIds.length} - ${JSON.stringify(connectedUserIds)}`);
239245

240-
// 오프라인 사용자들에게만 푸시 알림 발송
246+
// 채팅 소켓 오프라인 사용자들에게만 푸시 알림 발송
241247
const offlineParticipants = meeting.participantList
242248
?.filter(p => p.userId !== senderId && !connectedUserIds.includes(p.userId))
243249
.map(p => p.userId) || [];
244250

251+
this.logger.log(`🔍 Offline participants: ${offlineParticipants.length} - ${JSON.stringify(offlineParticipants)}`);
252+
245253
if (offlineParticipants.length > 0) {
246254
await this.meetingNotificationHelper.notifyNewChatMessage(
247255
offlineParticipants,
@@ -252,7 +260,9 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
252260
message
253261
);
254262

255-
this.logger.log(`Chat notification sent to ${offlineParticipants.length} offline users`);
263+
this.logger.log(`✅ Chat notification sent to ${offlineParticipants.length} offline users`);
264+
} else {
265+
this.logger.log(`ℹ️ No offline participants to notify for meeting ${meetingId} (all users are online)`);
256266
}
257267
} catch (error) {
258268
this.logger.error('Failed to send chat notification:', error);

src/modules/chat/chat.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ export class ChatService {
244244
// 사용자 프로필 조회 (채팅 알림용)
245245
async getUserProfile(userId: string) {
246246
const result = await this.meetingRepository.query(
247-
'SELECT nickname FROM user_profile WHERE user_id = $1',
247+
'SELECT nickname FROM user_profiles WHERE "userId" = $1',
248248
[userId]
249249
);
250250
return result[0] || null;

src/modules/notification/templates/meeting-notification.templates.ts

Lines changed: 53 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ export interface MeetingNotificationData {
2020
export class MeetingNotificationTemplates {
2121
static participantJoined(data: MeetingNotificationData): NotificationPayload {
2222
return {
23-
title: `${data.meetingTitle} 모임에 새로운 참가자가 합류했어요!`,
24-
body: `${data.participantName}님이 모임에 참여했습니다. (${data.participantCount}/${data.maxParticipants}명)`,
25-
icon: '/icons/meeting-join.png',
23+
title: `${data.meetingTitle} 번개모임 알림`,
24+
body: `${data.participantName}님이 모임에 참여했습니다 (${data.participantCount}/${data.maxParticipants}명)`,
25+
icon: '/icons/icon-192x192.png',
2626
url: `/meeting/${data.meetingId}`,
2727
data: {
2828
type: 'meeting_participant_joined',
@@ -35,9 +35,9 @@ export class MeetingNotificationTemplates {
3535

3636
static meetingFull(data: MeetingNotificationData): NotificationPayload {
3737
return {
38-
title: `🎉 ${data.meetingTitle} 모임이 가득 찼어요!`,
39-
body: `모든 인원이 모집되었습니다. 곧 만나요! (${data.maxParticipants}/${data.maxParticipants}명)`,
40-
icon: '/icons/meeting-full.png',
38+
title: `${data.meetingTitle} 번개모임 알림`,
39+
body: `모든 인원이 모집되었습니다 (${data.maxParticipants}/${data.maxParticipants}명)`,
40+
icon: '/icons/icon-192x192.png',
4141
url: `/meeting/${data.meetingId}`,
4242
data: {
4343
type: 'meeting_full',
@@ -47,11 +47,13 @@ export class MeetingNotificationTemplates {
4747
};
4848
}
4949

50-
static recruitmentDeadlineWarning(data: MeetingNotificationData): NotificationPayload {
50+
static recruitmentDeadlineWarning(
51+
data: MeetingNotificationData,
52+
): NotificationPayload {
5153
return {
52-
title: `${data.meetingTitle} 모집 마감 임박!`,
53-
body: `${data.timeRemaining} 후 모집이 마감됩니다. 서둘러 참여해보세요!`,
54-
icon: '/icons/meeting-deadline.png',
54+
title: `${data.meetingTitle} 번개모임 알림`,
55+
body: `${data.timeRemaining} 후 모집이 마감됩니다`,
56+
icon: '/icons/icon-192x192.png',
5557
url: `/meeting/${data.meetingId}`,
5658
data: {
5759
type: 'recruitment_deadline_warning',
@@ -63,9 +65,9 @@ export class MeetingNotificationTemplates {
6365

6466
static recruitmentClosed(data: MeetingNotificationData): NotificationPayload {
6567
return {
66-
title: `${data.meetingTitle} 모집이 마감되었습니다`,
67-
body: `모집 시간이 종료되어 더 이상 참여할 수 없습니다.`,
68-
icon: '/icons/meeting-closed.png',
68+
title: `${data.meetingTitle} 번개모임 알림`,
69+
body: `모집 시간이 종료되어 더 이상 참여할 수 없습니다`,
70+
icon: '/icons/icon-192x192.png',
6971
url: `/meeting/${data.meetingId}`,
7072
data: {
7173
type: 'recruitment_closed',
@@ -74,11 +76,13 @@ export class MeetingNotificationTemplates {
7476
};
7577
}
7678

77-
static activityStartReminder(data: MeetingNotificationData): NotificationPayload {
79+
static activityStartReminder(
80+
data: MeetingNotificationData,
81+
): NotificationPayload {
7882
return {
79-
title: `🔔 ${data.meetingTitle} 활동 시작 예정!`,
80-
body: `${data.timeRemaining} 후 활동이 시작됩니다. 준비해주세요!`,
81-
icon: '/icons/meeting-reminder.png',
83+
title: `${data.meetingTitle} 번개모임 알림`,
84+
body: `${data.timeRemaining} 후 활동이 시작됩니다`,
85+
icon: '/icons/icon-192x192.png',
8286
url: `/meeting/${data.meetingId}`,
8387
data: {
8488
type: 'activity_start_reminder',
@@ -90,9 +94,9 @@ export class MeetingNotificationTemplates {
9094

9195
static activityStarted(data: MeetingNotificationData): NotificationPayload {
9296
return {
93-
title: `🚀 ${data.meetingTitle} 활동이 시작되었습니다!`,
94-
body: `출석 체크와 채팅, 미션 인증 기능이 활성화되었어요. 지금 참여해보세요!`,
95-
icon: '/icons/meeting-start.png',
97+
title: `${data.meetingTitle} 번개모임 알림`,
98+
body: `활동이 시작되었습니다 출석 체크를 진행해주세요`,
99+
icon: '/icons/icon-192x192.png',
96100
url: `/meeting/${data.meetingId}/channel`,
97101
data: {
98102
type: 'activity_started',
@@ -106,9 +110,9 @@ export class MeetingNotificationTemplates {
106110

107111
static noShowWarning(data: MeetingNotificationData): NotificationPayload {
108112
return {
109-
title: `⚠️ ${data.meetingTitle} 노쇼 처리`,
110-
body: `출석 체크 시간이 종료되어 노쇼로 처리되었습니다. 다음에는 꼭 참여해주세요.`,
111-
icon: '/icons/no-show.png',
113+
title: `${data.meetingTitle} 번개모임 알림`,
114+
body: `출석 체크 시간이 종료되어 노쇼 처리되었습니다\n다음에는 꼭 참여해주세요`,
115+
icon: '/icons/icon-192x192.png',
112116
url: `/meeting/${data.meetingId}`,
113117
data: {
114118
type: 'no_show_warning',
@@ -121,9 +125,9 @@ export class MeetingNotificationTemplates {
121125
static pointEarned(data: MeetingNotificationData): NotificationPayload {
122126
const missionInfo = data.meetingTitle ? ` (${data.meetingTitle})` : '';
123127
return {
124-
title: `💰 포인트 획득!`,
125-
body: `${data.pointReason}${missionInfo}으로 ${data.pointAmount}P를 획득했습니다!`,
126-
icon: '/icons/point-earned.png',
128+
title: `포인트 획득 알림`,
129+
body: `${data.pointReason}${missionInfo}으로 ${data.pointAmount}P를 획득했습니다`,
130+
icon: '/icons/icon-192x192.png',
127131
url: `/point/history`,
128132
data: {
129133
type: 'point_earned',
@@ -137,9 +141,9 @@ export class MeetingNotificationTemplates {
137141

138142
static pointDeducted(data: MeetingNotificationData): NotificationPayload {
139143
return {
140-
title: `💸 포인트 차감`,
141-
body: `${data.pointReason}으로 ${data.pointAmount}P가 차감되었습니다.`,
142-
icon: '/icons/point-deducted.png',
144+
title: `포인트 차감 알림`,
145+
body: `${data.pointReason}으로 ${data.pointAmount}P가 차감되었습니다`,
146+
icon: '/icons/icon-192x192.png',
143147
url: `/point/history`,
144148
data: {
145149
type: 'point_deducted',
@@ -152,9 +156,9 @@ export class MeetingNotificationTemplates {
152156

153157
static newChatMessage(data: MeetingNotificationData): NotificationPayload {
154158
return {
155-
title: `💬 ${data.meetingTitle}`,
159+
title: `${data.meetingTitle}`,
156160
body: `${data.senderName}: ${data.chatMessage}`,
157-
icon: '/icons/chat-message.png',
161+
icon: '/icons/icon-192x192.png',
158162
url: `/meeting/${data.meetingId}/channel`,
159163
data: {
160164
type: 'new_chat_message',
@@ -166,9 +170,9 @@ export class MeetingNotificationTemplates {
166170

167171
static meetingLiked(data: MeetingNotificationData): NotificationPayload {
168172
return {
169-
title: `❤️ ${data.meetingTitle} 모임을 좋아해요!`,
170-
body: `${data.participantName}님이 회원님의 모임에 좋아요를 눌렀습니다.`,
171-
icon: '/icons/heart.png',
173+
title: `${data.meetingTitle} 번개모임 알림`,
174+
body: `${data.participantName}님이 회원님의 모임에 좋아요를 눌렀습니다`,
175+
icon: '/icons/icon-192x192.png',
172176
url: `/meeting/${data.meetingId}`,
173177
data: {
174178
type: 'meeting_liked',
@@ -179,20 +183,22 @@ export class MeetingNotificationTemplates {
179183
}
180184
}
181185

182-
export const getMeetingNotificationType = (templateType: string): NotificationType => {
186+
export const getMeetingNotificationType = (
187+
templateType: string,
188+
): NotificationType => {
183189
const typeMap: Record<string, NotificationType> = {
184-
'meeting_participant_joined': NotificationType.MEETING_REMINDER,
185-
'meeting_full': NotificationType.MEETING_REMINDER,
186-
'recruitment_deadline_warning': NotificationType.MEETING_REMINDER,
187-
'recruitment_closed': NotificationType.MEETING_REMINDER,
188-
'activity_start_reminder': NotificationType.MEETING_REMINDER,
189-
'activity_started': NotificationType.MEETING_REMINDER,
190-
'no_show_warning': NotificationType.SYSTEM_NOTICE,
191-
'point_earned': NotificationType.SYSTEM_NOTICE,
192-
'point_deducted': NotificationType.SYSTEM_NOTICE,
193-
'new_chat_message': NotificationType.MEETING_REMINDER,
194-
'meeting_liked': NotificationType.FRIEND_REQUEST,
190+
meeting_participant_joined: NotificationType.MEETING_REMINDER,
191+
meeting_full: NotificationType.MEETING_REMINDER,
192+
recruitment_deadline_warning: NotificationType.MEETING_REMINDER,
193+
recruitment_closed: NotificationType.MEETING_REMINDER,
194+
activity_start_reminder: NotificationType.MEETING_REMINDER,
195+
activity_started: NotificationType.MEETING_REMINDER,
196+
no_show_warning: NotificationType.SYSTEM_NOTICE,
197+
point_earned: NotificationType.SYSTEM_NOTICE,
198+
point_deducted: NotificationType.SYSTEM_NOTICE,
199+
new_chat_message: NotificationType.MEETING_REMINDER,
200+
meeting_liked: NotificationType.FRIEND_REQUEST,
195201
};
196202

197203
return typeMap[templateType] || NotificationType.SYSTEM_NOTICE;
198-
};
204+
};

0 commit comments

Comments
 (0)