feat: Implement in-app notifications with new database model, enhance Firebase initialization to support service account files, and add message read functionalities.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -45,3 +45,4 @@ temp/
|
|||||||
*.pid.lock
|
*.pid.lock
|
||||||
prisma/migrations/
|
prisma/migrations/
|
||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
|
real-estate-2d71e-firebase-adminsdk-fbsvc-2cb74385ab.json
|
||||||
@@ -132,6 +132,9 @@ model User {
|
|||||||
conversations Conversation[]
|
conversations Conversation[]
|
||||||
messages Message[]
|
messages Message[]
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
notifications Notification[]
|
||||||
|
|
||||||
@@index([email])
|
@@index([email])
|
||||||
@@index([role])
|
@@index([role])
|
||||||
@@index([status])
|
@@index([status])
|
||||||
@@ -500,6 +503,31 @@ model Message {
|
|||||||
@@map("messages")
|
@@map("messages")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===========================================
|
||||||
|
// IN-APP NOTIFICATIONS
|
||||||
|
// ===========================================
|
||||||
|
|
||||||
|
model Notification {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
userId String
|
||||||
|
type String // 'connection', 'message', 'system', 'update', 'request'
|
||||||
|
title String
|
||||||
|
description String
|
||||||
|
read Boolean @default(false)
|
||||||
|
actionUrl String?
|
||||||
|
data Json? // Extra metadata (conversationId, connectionRequestId, etc.)
|
||||||
|
|
||||||
|
// Timestamps
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@index([userId, read])
|
||||||
|
@@index([userId, createdAt])
|
||||||
|
@@map("notifications")
|
||||||
|
}
|
||||||
|
|
||||||
// ===========================================
|
// ===========================================
|
||||||
// CMS CONTENT - Dynamic page content management
|
// CMS CONTENT - Dynamic page content management
|
||||||
// ===========================================
|
// ===========================================
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export default () => ({
|
|||||||
|
|
||||||
// Firebase
|
// Firebase
|
||||||
firebase: {
|
firebase: {
|
||||||
|
serviceAccountKeyPath: process.env.FIREBASE_SERVICE_ACCOUNT_KEY_PATH,
|
||||||
projectId: process.env.FIREBASE_PROJECT_ID,
|
projectId: process.env.FIREBASE_PROJECT_ID,
|
||||||
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
|
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
|
||||||
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
|
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
import { Server, Socket } from 'socket.io';
|
import { Server, Socket } from 'socket.io';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
||||||
import { MessagesService } from './messages.service';
|
import { MessagesService } from './messages.service';
|
||||||
import { CreateMessageDto } from './dto';
|
import { CreateMessageDto } from './dto';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
@@ -38,7 +37,6 @@ export class MessagesGateway implements OnGatewayConnection, OnGatewayDisconnect
|
|||||||
private readonly messagesService: MessagesService,
|
private readonly messagesService: MessagesService,
|
||||||
private readonly jwtService: JwtService,
|
private readonly jwtService: JwtService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly eventEmitter: EventEmitter2,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -188,19 +186,6 @@ export class MessagesGateway implements OnGatewayConnection, OnGatewayDisconnect
|
|||||||
? participants.agentUserId
|
? participants.agentUserId
|
||||||
: participants.userId;
|
: participants.userId;
|
||||||
this.sendToUser(otherUserId, 'new_message', message);
|
this.sendToUser(otherUserId, 'new_message', message);
|
||||||
|
|
||||||
// Emit push notification event
|
|
||||||
const senderProfile = message.sender?.agentProfile || message.sender?.userProfile;
|
|
||||||
const senderName = senderProfile
|
|
||||||
? `${senderProfile.firstName || ''} ${senderProfile.lastName || ''}`.trim()
|
|
||||||
: 'Someone';
|
|
||||||
|
|
||||||
this.eventEmitter.emit('notification.message', {
|
|
||||||
recipientUserId: otherUserId,
|
|
||||||
senderName,
|
|
||||||
messagePreview: data.message.content?.substring(0, 100) || '',
|
|
||||||
conversationId: data.conversationId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true, message };
|
return { success: true, message };
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ import {
|
|||||||
ForbiddenException,
|
ForbiddenException,
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import { CreateMessageDto } from './dto';
|
import { CreateMessageDto } from './dto';
|
||||||
import { ConnectionStatus, MessageStatus, MessageType, UserRole } from '@prisma/client';
|
import { ConnectionStatus, MessageStatus, MessageType, UserRole } from '@prisma/client';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MessagesService {
|
export class MessagesService {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if a user can message an agent (must have ACCEPTED connection)
|
* Validate if a user can message an agent (must have ACCEPTED connection)
|
||||||
@@ -223,6 +227,23 @@ export class MessagesService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Emit push notification event for the recipient
|
||||||
|
const recipientUserId = isUser
|
||||||
|
? conversation.agentProfile.userId
|
||||||
|
: conversation.userId;
|
||||||
|
const senderProfile =
|
||||||
|
message.sender?.agentProfile || message.sender?.userProfile;
|
||||||
|
const senderName = senderProfile
|
||||||
|
? `${senderProfile.firstName || ''} ${senderProfile.lastName || ''}`.trim()
|
||||||
|
: 'Someone';
|
||||||
|
|
||||||
|
this.eventEmitter.emit('notification.message', {
|
||||||
|
recipientUserId,
|
||||||
|
senderName,
|
||||||
|
messagePreview: dto.content?.substring(0, 100) || '',
|
||||||
|
conversationId,
|
||||||
|
});
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
|
Get,
|
||||||
Post,
|
Post,
|
||||||
|
Patch,
|
||||||
Delete,
|
Delete,
|
||||||
Body,
|
Body,
|
||||||
|
Param,
|
||||||
|
Query,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
@@ -12,6 +16,7 @@ import {
|
|||||||
ApiOperation,
|
ApiOperation,
|
||||||
ApiResponse,
|
ApiResponse,
|
||||||
ApiBearerAuth,
|
ApiBearerAuth,
|
||||||
|
ApiQuery,
|
||||||
} from '@nestjs/swagger';
|
} from '@nestjs/swagger';
|
||||||
import { JwtAuthGuard } from '../auth/guards';
|
import { JwtAuthGuard } from '../auth/guards';
|
||||||
import { CurrentUser } from '../auth/decorators';
|
import { CurrentUser } from '../auth/decorators';
|
||||||
@@ -27,6 +32,63 @@ export class NotificationsController {
|
|||||||
private readonly notificationsService: NotificationsService,
|
private readonly notificationsService: NotificationsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// IN-APP NOTIFICATIONS
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Get notifications list' })
|
||||||
|
@ApiQuery({ name: 'filter', required: false, enum: ['all', 'unread'] })
|
||||||
|
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||||
|
@ApiQuery({ name: 'limit', required: false, type: Number })
|
||||||
|
@ApiResponse({ status: 200, description: 'Notifications list with pagination' })
|
||||||
|
async getNotifications(
|
||||||
|
@CurrentUser('id') userId: string,
|
||||||
|
@Query('filter') filter?: 'all' | 'unread',
|
||||||
|
@Query('page') page?: string,
|
||||||
|
@Query('limit') limit?: string,
|
||||||
|
) {
|
||||||
|
return this.notificationsService.getNotifications(
|
||||||
|
userId,
|
||||||
|
filter || 'all',
|
||||||
|
page ? parseInt(page, 10) : 1,
|
||||||
|
limit ? parseInt(limit, 10) : 20,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('unread-count')
|
||||||
|
@ApiOperation({ summary: 'Get unread notification count' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Unread count' })
|
||||||
|
async getUnreadCount(@CurrentUser('id') userId: string) {
|
||||||
|
const count = await this.notificationsService.getUnreadCount(userId);
|
||||||
|
return { unreadCount: count };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id/read')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@ApiOperation({ summary: 'Mark a notification as read' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Notification marked as read' })
|
||||||
|
async markAsRead(
|
||||||
|
@CurrentUser('id') userId: string,
|
||||||
|
@Param('id') notificationId: string,
|
||||||
|
) {
|
||||||
|
await this.notificationsService.markAsRead(userId, notificationId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch('read-all')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@ApiOperation({ summary: 'Mark all notifications as read' })
|
||||||
|
@ApiResponse({ status: 200, description: 'All notifications marked as read' })
|
||||||
|
async markAllAsRead(@CurrentUser('id') userId: string) {
|
||||||
|
await this.notificationsService.markAllAsRead(userId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// FCM TOKEN MANAGEMENT
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
@Post('fcm-token')
|
@Post('fcm-token')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@ApiOperation({ summary: 'Register FCM token for push notifications' })
|
@ApiOperation({ summary: 'Register FCM token for push notifications' })
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { ConfigService } from '@nestjs/config';
|
|||||||
import { OnEvent } from '@nestjs/event-emitter';
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import * as admin from 'firebase-admin';
|
import * as admin from 'firebase-admin';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
|
||||||
interface FcmToken {
|
interface FcmToken {
|
||||||
@@ -28,6 +30,38 @@ export class NotificationsService implements OnModuleInit {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
onModuleInit() {
|
onModuleInit() {
|
||||||
|
try {
|
||||||
|
if (admin.apps.length > 0) {
|
||||||
|
this.firebaseInitialized = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try loading from service account JSON file first
|
||||||
|
const serviceAccountPath = this.configService.get<string>(
|
||||||
|
'firebase.serviceAccountKeyPath',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (serviceAccountPath) {
|
||||||
|
const resolvedPath = path.isAbsolute(serviceAccountPath)
|
||||||
|
? serviceAccountPath
|
||||||
|
: path.resolve(process.cwd(), serviceAccountPath);
|
||||||
|
|
||||||
|
if (fs.existsSync(resolvedPath)) {
|
||||||
|
const serviceAccount = JSON.parse(
|
||||||
|
fs.readFileSync(resolvedPath, 'utf8'),
|
||||||
|
);
|
||||||
|
admin.initializeApp({
|
||||||
|
credential: admin.credential.cert(serviceAccount),
|
||||||
|
});
|
||||||
|
this.firebaseInitialized = true;
|
||||||
|
this.logger.log(
|
||||||
|
'Firebase Admin initialized from service account file',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to individual env vars
|
||||||
const projectId = this.configService.get<string>('firebase.projectId');
|
const projectId = this.configService.get<string>('firebase.projectId');
|
||||||
const clientEmail = this.configService.get<string>('firebase.clientEmail');
|
const clientEmail = this.configService.get<string>('firebase.clientEmail');
|
||||||
const privateKey = this.configService.get<string>('firebase.privateKey');
|
const privateKey = this.configService.get<string>('firebase.privateKey');
|
||||||
@@ -39,7 +73,6 @@ export class NotificationsService implements OnModuleInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (admin.apps.length === 0) {
|
|
||||||
admin.initializeApp({
|
admin.initializeApp({
|
||||||
credential: admin.credential.cert({
|
credential: admin.credential.cert({
|
||||||
projectId,
|
projectId,
|
||||||
@@ -47,11 +80,18 @@ export class NotificationsService implements OnModuleInit {
|
|||||||
privateKey,
|
privateKey,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
this.firebaseInitialized = true;
|
this.firebaseInitialized = true;
|
||||||
this.logger.log('Firebase Admin initialized for push notifications');
|
this.logger.log('Firebase Admin initialized for push notifications');
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to initialize Firebase Admin: ${error.message}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// FCM TOKEN MANAGEMENT
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
private parseFcmTokens(raw: Prisma.JsonValue | null | undefined): FcmToken[] {
|
private parseFcmTokens(raw: Prisma.JsonValue | null | undefined): FcmToken[] {
|
||||||
if (!raw || !Array.isArray(raw)) return [];
|
if (!raw || !Array.isArray(raw)) return [];
|
||||||
@@ -69,8 +109,6 @@ export class NotificationsService implements OnModuleInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const existingTokens = this.parseFcmTokens(user?.fcmTokens);
|
const existingTokens = this.parseFcmTokens(user?.fcmTokens);
|
||||||
|
|
||||||
// Remove existing entry for this token (if re-registering)
|
|
||||||
const filtered = existingTokens.filter((t) => t.token !== token);
|
const filtered = existingTokens.filter((t) => t.token !== token);
|
||||||
|
|
||||||
filtered.push({
|
filtered.push({
|
||||||
@@ -109,17 +147,81 @@ export class NotificationsService implements OnModuleInit {
|
|||||||
this.logger.log(`FCM token removed for user ${userId}`);
|
this.logger.log(`FCM token removed for user ${userId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendPushNotification(
|
// ==========================================
|
||||||
|
// IN-APP NOTIFICATION CRUD
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
async getNotifications(
|
||||||
userId: string,
|
userId: string,
|
||||||
category: string,
|
filter: 'all' | 'unread' = 'all',
|
||||||
payload: NotificationPayload,
|
page = 1,
|
||||||
): Promise<void> {
|
limit = 20,
|
||||||
if (!this.firebaseInitialized) {
|
) {
|
||||||
this.logger.debug('Firebase not initialized, skipping push notification');
|
const where: Prisma.NotificationWhereInput = { userId };
|
||||||
return;
|
if (filter === 'unread') {
|
||||||
|
where.read = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check user notification preferences
|
const [notifications, total] = await Promise.all([
|
||||||
|
this.prisma.notification.findMany({
|
||||||
|
where,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
skip: (page - 1) * limit,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.notification.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const unreadCount = await this.prisma.notification.count({
|
||||||
|
where: { userId, read: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
notifications,
|
||||||
|
unreadCount,
|
||||||
|
pagination: {
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
total,
|
||||||
|
pages: Math.ceil(total / limit),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUnreadCount(userId: string): Promise<number> {
|
||||||
|
return this.prisma.notification.count({
|
||||||
|
where: { userId, read: false },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async markAsRead(userId: string, notificationId: string) {
|
||||||
|
return this.prisma.notification.updateMany({
|
||||||
|
where: { id: notificationId, userId },
|
||||||
|
data: { read: true },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async markAllAsRead(userId: string) {
|
||||||
|
return this.prisma.notification.updateMany({
|
||||||
|
where: { userId, read: false },
|
||||||
|
data: { read: true },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// CREATE & SEND NOTIFICATION
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
private async createAndSendNotification(
|
||||||
|
userId: string,
|
||||||
|
type: string,
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
category: string,
|
||||||
|
actionUrl?: string,
|
||||||
|
data?: Record<string, string>,
|
||||||
|
) {
|
||||||
|
// Check user notification preferences for inApp
|
||||||
const user = await this.prisma.user.findUnique({
|
const user = await this.prisma.user.findUnique({
|
||||||
where: { id: userId },
|
where: { id: userId },
|
||||||
select: { notificationPreferences: true, fcmTokens: true },
|
select: { notificationPreferences: true, fcmTokens: true },
|
||||||
@@ -127,20 +229,42 @@ export class NotificationsService implements OnModuleInit {
|
|||||||
|
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
// Check if inApp notifications are enabled for this category
|
|
||||||
const prefs = user.notificationPreferences as Record<string, any> | null;
|
const prefs = user.notificationPreferences as Record<string, any> | null;
|
||||||
if (prefs?.notifications?.[category]?.inApp === false) {
|
const inAppEnabled = prefs?.notifications?.[category]?.inApp !== false;
|
||||||
this.logger.debug(
|
|
||||||
`Push notification skipped for user ${userId}: ${category} inApp disabled`,
|
// Always persist the in-app notification (if enabled)
|
||||||
);
|
if (inAppEnabled) {
|
||||||
return;
|
await this.prisma.notification.create({
|
||||||
|
data: {
|
||||||
|
userId,
|
||||||
|
type,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
actionUrl,
|
||||||
|
data: data ? (data as unknown as Prisma.InputJsonValue) : undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokens = this.parseFcmTokens(user.fcmTokens);
|
// Send push notification (if Firebase configured and user has tokens)
|
||||||
if (tokens.length === 0) {
|
if (inAppEnabled) {
|
||||||
this.logger.debug(`No FCM tokens for user ${userId}`);
|
await this.sendPushNotification(userId, user, {
|
||||||
return;
|
title,
|
||||||
|
body: description,
|
||||||
|
data,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendPushNotification(
|
||||||
|
userId: string,
|
||||||
|
user: { fcmTokens: Prisma.JsonValue | null },
|
||||||
|
payload: NotificationPayload,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!this.firebaseInitialized) return;
|
||||||
|
|
||||||
|
const tokens = this.parseFcmTokens(user.fcmTokens);
|
||||||
|
if (tokens.length === 0) return;
|
||||||
|
|
||||||
const tokenStrings = tokens.map((t) => t.token);
|
const tokenStrings = tokens.map((t) => t.token);
|
||||||
|
|
||||||
@@ -191,11 +315,11 @@ export class NotificationsService implements OnModuleInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Push notification sent to user ${userId}: ${response.successCount} success, ${response.failureCount} failed`,
|
`Push sent to ${userId}: ${response.successCount} ok, ${response.failureCount} fail`,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Failed to send push notification to user ${userId}: ${error.message}`,
|
`Push failed for ${userId}: ${error.message}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,15 +335,19 @@ export class NotificationsService implements OnModuleInit {
|
|||||||
messagePreview: string;
|
messagePreview: string;
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
}) {
|
}) {
|
||||||
await this.sendPushNotification(event.recipientUserId, 'message_updates', {
|
await this.createAndSendNotification(
|
||||||
title: `New message from ${event.senderName}`,
|
event.recipientUserId,
|
||||||
body: event.messagePreview || 'You have a new message',
|
'message',
|
||||||
data: {
|
`New message from ${event.senderName}`,
|
||||||
|
event.messagePreview || 'You have a new message',
|
||||||
|
'message_updates',
|
||||||
|
`/messages?conversation=${event.conversationId}`,
|
||||||
|
{
|
||||||
type: 'message',
|
type: 'message',
|
||||||
conversationId: event.conversationId,
|
conversationId: event.conversationId,
|
||||||
link: `/messages?conversation=${event.conversationId}`,
|
link: `/messages?conversation=${event.conversationId}`,
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent('notification.connection_request')
|
@OnEvent('notification.connection_request')
|
||||||
@@ -228,18 +356,18 @@ export class NotificationsService implements OnModuleInit {
|
|||||||
senderName: string;
|
senderName: string;
|
||||||
connectionRequestId: string;
|
connectionRequestId: string;
|
||||||
}) {
|
}) {
|
||||||
await this.sendPushNotification(
|
await this.createAndSendNotification(
|
||||||
event.recipientUserId,
|
event.recipientUserId,
|
||||||
|
'request',
|
||||||
|
'New Connection Request',
|
||||||
|
`${event.senderName} wants to connect with you`,
|
||||||
'new_lead_alerts',
|
'new_lead_alerts',
|
||||||
|
'/dashboard/connections',
|
||||||
{
|
{
|
||||||
title: 'New Connection Request',
|
|
||||||
body: `${event.senderName} wants to connect with you`,
|
|
||||||
data: {
|
|
||||||
type: 'connection_request',
|
type: 'connection_request',
|
||||||
connectionRequestId: event.connectionRequestId,
|
connectionRequestId: event.connectionRequestId,
|
||||||
link: '/dashboard/connections',
|
link: '/dashboard/connections',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user