notification

This commit is contained in:
2026-02-23 10:47:23 +03:30
parent 1e7ad2f72e
commit 8303a4c108
14 changed files with 245 additions and 466 deletions
+1 -6
View File
@@ -1,6 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, IsMobilePhone, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { NotifEvent } from 'src/modules/notification/interfaces/notification.interface';
import { IsMobilePhone, IsNotEmpty, IsOptional, IsString } from 'class-validator';
export class CreateAdminDto {
@ApiProperty({ description: 'Mobile phone number' })
@@ -23,8 +22,4 @@ export class CreateAdminDto {
@IsNotEmpty()
@IsString()
roleId!: string;
@ApiProperty({})
@IsArray()
notificationPrefrences:NotifEvent[]
}
+1 -8
View File
@@ -1,9 +1,7 @@
import { Cascade, Entity, ManyToOne, OneToOne, OptionalProps, PrimaryKey, Property } from '@mikro-orm/core';
import { Cascade, Entity, ManyToOne, OptionalProps, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity';
import { normalizePhone } from '../../util/phone.util';
import { Role } from 'src/modules/roles/entities/role.entity';
import { ulid } from 'ulid';
import { NotificationPreference } from 'src/modules/notification/entities/notification-preference.entity';
@Entity({ tableName: 'admins' })
export class Admin extends BaseEntity {
@@ -28,9 +26,4 @@ export class Admin extends BaseEntity {
@ManyToOne(() => Role)
role: Role
// @OneToOne(() => NotificationPreference, pref => pref.admin, {
// })
// notificationPreference!: NotificationPreference;
}
+13 -39
View File
@@ -7,7 +7,6 @@ import { normalizePhone } from '../../util/phone.util';
import { CreateAdminDto } from '../dto/create-admin.dto';
import { AdminRepository } from '../repositories/admin.repository';
import { RoleRepository } from 'src/modules/roles/respository/role.repository';
import { NotificationPreference } from 'src/modules/notification/entities/notification-preference.entity';
import { PermissionEnum } from 'src/common/enums/permission.enum';
@Injectable()
@@ -19,7 +18,7 @@ export class AdminService {
) { }
async create(dto: CreateAdminDto) {
const { phone, firstName, lastName, roleId, notificationPrefrences } = dto;
const { phone, firstName, lastName, roleId } = dto;
const normalizedPhone = normalizePhone(phone);
@@ -31,39 +30,25 @@ export class AdminService {
throw new BadRequestException('This phone number is already used');
}
// retrieve deleted admin
if (currentAdmin && currentAdmin.deletedAt) {
currentAdmin.deletedAt = undefined
await this.em.flush()
return currentAdmin
currentAdmin.deletedAt = undefined;
await this.em.flush();
return currentAdmin;
}
const role = await this.roleRepository.findOne({ id: roleId })
const role = await this.roleRepository.findOne({ id: roleId });
if (!role) {
throw new BadRequestException('Role not found');
}
return this.em.transactional(async em => {
const admin = em.create(Admin, {
phone: normalizedPhone,
firstName,
lastName,
role
})
const notificationPreference = em.create(NotificationPreference, {
admin,
events: notificationPrefrences,
})
await this.em.persistAndFlush([admin, notificationPreference]);
return admin
})
const admin = this.em.create(Admin, {
phone: normalizedPhone,
firstName,
lastName,
role,
});
await this.em.persistAndFlush(admin);
return admin;
}
async findByPhone(phone: string): Promise<Admin | null> {
@@ -103,17 +88,6 @@ export class AdminService {
admin.lastName = rest.lastName;
}
if (rest.notificationPrefrences) {
//
const nf = await this.em.findOne(NotificationPreference, {
admin
})
if (!nf) {
throw new BadRequestException('Notification prefrences not found!')
}
nf.events = rest.notificationPrefrences
await this.em.flush()
}
if (rest.phone !== undefined && rest.phone !== admin.phone) {
const normalizedPhone = normalizePhone(rest.phone);
const exists = await this.adminRepository.findOne({ phone: normalizedPhone });
@@ -1,12 +1,9 @@
import { Controller, Get, Body, Param, UseGuards, Query, Patch, Put, Post } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiQuery, ApiHeader } from '@nestjs/swagger';
import { NotificationService } from '../services/notification.service';
import { NotificationPreferenceService } from '../services/notification-preference.service';
import { AuthGuard } from '../../auth/guards/auth.guard';
import { UserId } from '../../../common/decorators/user-id.decorator';
import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard';
import { UpdatePreferenceDto } from '../dto/update-preference.dto';
import { CreatePreferenceDto } from '../dto/create-preference.dto';
import { AdminId } from 'src/common/decorators/admin-id.decorator';
import { NotificationMessage } from 'src/common/enums/message.enum';
import { Permissions } from 'src/common/decorators/permissions.decorator';
@@ -17,7 +14,6 @@ import { PermissionEnum } from 'src/common/enums/permission.enum';
export class NotificationsController {
constructor(
private readonly notificationService: NotificationService,
private readonly preferenceService: NotificationPreferenceService,
) { }
// @UseGuards(AuthGuard)
@@ -1,24 +0,0 @@
import { IsNotEmpty, IsEnum, IsArray } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { NotifEvent } from '../interfaces/notification.interface';
import { NotifChannelEnum } from '../interfaces/notification.interface';
export class CreatePreferenceDto {
@ApiProperty({
description: 'Notification title/type (e.g., order.created, review.created)',
enum: NotifEvent,
example: NotifEvent.NEW_ORDER,
})
@IsEnum(NotifEvent)
@IsNotEmpty()
title!: NotifEvent;
@ApiProperty({
description: 'Notification type (SMS, PUSH, BOTH, or NONE)',
enum: NotifChannelEnum,
example: NotifChannelEnum.SMS,
})
@IsArray()
@IsEnum(NotifChannelEnum, { each: true })
channels!: NotifChannelEnum[];
}
@@ -1,15 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, IsEnum } from 'class-validator';
import { NotifChannelEnum } from '../interfaces/notification.interface';
export class UpdatePreferenceDto {
@ApiProperty({
description: 'Notification channels (can be empty or contain any enum values)',
enum: NotifChannelEnum,
isArray: true,
example: ['sms', 'push'],
})
@IsArray()
@IsEnum(NotifChannelEnum, { each: true })
channels!: NotifChannelEnum[];
}
@@ -1,15 +0,0 @@
import { Entity, Property, ManyToOne, Unique, PrimaryKey, OneToOne, Cascade, OptionalProps } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity';
import { NotifEvent } from '../interfaces/notification.interface';
import { Admin } from 'src/modules/admin/entities/admin.entity';
@Entity({ tableName: 'notification_preferences' })
export class NotificationPreference extends BaseEntity {
[OptionalProps]?: 'createdAt' | 'deletedAt'
@OneToOne(() => Admin, { owner: true, unique: true, cascade: [Cascade.PERSIST, Cascade.REMOVE] })
admin!: Admin;
@Property({ type: 'json', nullable: true })
events: NotifEvent[] = [];
}
@@ -3,11 +3,7 @@ export enum NotifChannelEnum {
SMS = 'sms',
PUSH = 'push',
}
export enum NotifTypeEnum {
TRANSACTIONAL = 'transactional',
PROMOTIONAL = 'promotional',
SYSTEM = 'system',
}
export enum NotifEvent {
NEW_REQUEST = 'newRequest',
INVOICED = 'invoiced',
@@ -25,28 +21,13 @@ export interface NotifRequestMessage {
templateId: string;
parameters?: Record<string, string>;
};
pushNotif: {
title: string;
content: string;
icon: string;
action: {
type: string; //view order
url: string;
};
};
}
export interface NotifRequest {
// requestId: string;
// timestamp: Date;
// notifType: NotifTypeEnum;
// channels: NotifChannelEnum[];
channels: NotifChannelEnum[];
recipients: recipientType[];
message: NotifRequestMessage;
metadata: {
priority: number;
// retries: number;
};
}
//************************************************ */
interface INotifySmsPayload {
@@ -3,9 +3,7 @@ import { MikroOrmModule } from '@mikro-orm/nestjs';
import { BullModule } from '@nestjs/bullmq';
import { JwtModule } from '@nestjs/jwt';
import { Notification } from './entities/notification.entity';
import { NotificationPreference } from './entities/notification-preference.entity';
import { NotificationService } from './services/notification.service';
import { NotificationPreferenceService } from './services/notification-preference.service';
import { NotificationQueueService } from './services/notification-queue.service';
import { PushNotificationService } from './services/push-notification.service';
import { SmsProcessor } from './processors/sms.processor';
@@ -31,7 +29,7 @@ import { SmsListeners } from './listeners/sms.listeners';
imports: [
forwardRef(() => AuthModule),
JwtModule,
MikroOrmModule.forFeature([Notification, NotificationPreference, User, SmsLog]),
MikroOrmModule.forFeature([Notification, User, SmsLog]),
BullModule.registerQueue(
{ name: NotificationQueueNameEnum.SMS },
{ name: NotificationQueueNameEnum.PUSH },
@@ -54,7 +52,6 @@ import { SmsListeners } from './listeners/sms.listeners';
controllers: [NotificationsController],
providers: [
NotificationService,
NotificationPreferenceService,
NotificationQueueService,
PushNotificationService,
PushProcessor,
@@ -67,7 +64,6 @@ import { SmsListeners } from './listeners/sms.listeners';
SmsLogRepository,
SmsListeners,
],
exports: [NotificationService, NotificationPreferenceService,
NotificationQueueService, PushNotificationService, SmsService],
exports: [NotificationService, NotificationQueueService, PushNotificationService, SmsService],
})
export class NotificationsModule { }
@@ -9,5 +9,48 @@ export class SmsLogRepository extends EntityRepository<SmsLog> {
super(em, SmsLog);
}
async getSmsCountByPhone(
page: number = 1,
limit: number = 10,
): Promise<PaginatedResult<{ phone: string; smsCount: number }>> {
const offset = (page - 1) * limit;
const knex = this.em.getConnection().getKnex();
const countResult = await knex('sms_logs')
.countDistinct('phone as total')
.first();
const total = Number(countResult?.total ?? 0);
const rows = await knex('sms_logs')
.select('phone')
.count('* as smsCount')
.groupBy('phone')
.orderBy('smsCount', 'desc')
.limit(limit)
.offset(offset);
const data = rows.map((row: Record<string, unknown>) => ({
phone: String(row.phone ?? ''),
smsCount: Number(row.smsCount ?? row.sms_count ?? 0),
}));
return {
data,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit) || 1,
},
};
}
async getTotalSmsCount(): Promise<number> {
const result = await this.em
.createQueryBuilder(SmsLog, 's')
.select('count(*)', true)
.execute('get');
return Number(result ?? 0);
}
}
@@ -1,84 +0,0 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql';
import { NotificationPreference } from '../entities/notification-preference.entity';
import { CreatePreferenceDto } from '../dto/create-preference.dto';
import { UpdatePreferenceDto } from '../dto/update-preference.dto';
import { NotifEvent } from '../interfaces/notification.interface';
@Injectable()
export class NotificationPreferenceService {
constructor(private readonly em: EntityManager) { }
// async create( dto: CreatePreferenceDto): Promise<NotificationPreference> {
// const restaurant = await this.em.findOne(Restaurant, { id: });
// if (!restaurant) {
// throw new NotFoundException('Restaurant not found');
// }
// const preference = this.em.create(NotificationPreference, {
// restaurant,
// channels: dto.channels,
// title: dto.title,
// });
// await this.em.persistAndFlush(preference);
// return preference;
// }
// async findByRestaurant(: string): Promise<NotificationPreference[]> {
// return this.em.find(NotificationPreference, {
// restaurant: { id: },
// });
// }
// async findByRestaurantAndType(: string, title: NotifTitleEnum): Promise<NotificationPreference | null> {
// return this.em.findOne(NotificationPreference, {
// restaurant: { id: },
// title,
// });
// }
// async updateEnabled(
// : string,
// notificationType: string,
// enabled: boolean,
// ): Promise<NotificationPreference> {
// const preference = await this.findByRestaurantAndType(, notificationType);
// if (!preference) {
// throw new NotFoundException('Notification preference not found');
// }
// preference.enabled = enabled;
// await this.em.persistAndFlush(preference);
// return preference;
// }
// async updatePreference(
// preferenceId: string,
// : string,
// dto: UpdatePreferenceDto,
// ): Promise<NotificationPreference> {
// const preference = await this.em.findOne(NotificationPreference, {
// id: preferenceId,
// restaurant: { id: },
// });
// if (!preference) {
// throw new NotFoundException('Notification preference not found');
// }
// this.em.assign(preference, dto);
// await this.em.persistAndFlush(preference);
// return preference;
// }
// async delete(: string, preferenceId: string): Promise<void> {
// const preference = await this.em.findOne(NotificationPreference, {
// restaurant: { id: },
// id: preferenceId,
// });
// if (!preference) {
// throw new NotFoundException('Notification preference not found');
// }
// await this.em.removeAndFlush(preference);
// }
}
@@ -1,13 +1,12 @@
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { EntityManager, FilterQuery } from '@mikro-orm/postgresql';
import { Notification } from '../entities/notification.entity';
import { NotificationPreferenceService } from './notification-preference.service';
import { NotificationQueueService } from './notification-queue.service';
import { NotificationsGateway } from '../notifications.gateway';
import { NotifChannelEnum, NotifRequest, NotifEvent } from '../interfaces/notification.interface';
import { SmsLog } from '../entities/smsLogs.entity';
import { SmsLogRepository } from '../repositories/sms-log.repository';
import { PaginatedResult } from '../../../common/interfaces/pagination.interface';
import { Admin } from '../../admin/entities/admin.entity';
import { User } from '../../user/entities/user.entity';
@Injectable()
export class NotificationService {
@@ -15,214 +14,189 @@ export class NotificationService {
constructor(
private readonly em: EntityManager,
private readonly preferenceService: NotificationPreferenceService,
private readonly queueService: NotificationQueueService,
private readonly notificationGateway: NotificationsGateway,
private readonly smsLogRepository: SmsLogRepository,
) { }
// async sendNotification(params: NotifRequest): Promise<Notification[]> {
// const { recipients, message, metadata, } = params;
async sendNotification(params: NotifRequest): Promise<Notification[]> {
const { recipients, message, channels } = params;
// // create Database notifications
// const notifications = await this.createAdminBulkNotifications(
// recipients.map(recipient => ({
// title: message.title,
// content: message.content,
// adminId: 'adminId' in recipient ? recipient.adminId : null,
// userId: 'userId' in recipient ? recipient.userId : null,
// })),
// );
// Create database notifications
const notifData = recipients.map(recipient => ({
title: message.title,
content: message.content,
adminId: 'adminId' in recipient ? recipient.adminId : null,
userId: 'userId' in recipient ? recipient.userId : null,
}))
// // get admin prefrences
// const preference = await this.preferenceService.findByRestaurantAndType(, message.title);
const notifications = await this.createAdminBulkNotifications(notifData);
// if(preference?.channels?.length === 0) {
// this.logger.warn(`Notification type is NONE for restaurant ${}, title ${message.title}`);
// return notifications;
// }
if (channels.length === 0) {
this.logger.warn(`No notification channels for title ${message.title}`);
return notifications;
}
// // send in app notification
// if (preference?.channels?.includes(NotifChannelEnum.IN_APP)) {
// await this.queueService.addBulkInAppNotifications(
// notifications.map(notification => ({
// recipient: { adminId: notification.admin?.id || '', },
// subject: message.title,
// body: message.content,
// notificationId: notification.id,
// })),
// );
// }
// Send in-app notifications (only for admin recipients)
if (channels.includes(NotifChannelEnum.IN_APP)) {
// // add sms notifications to queue
// if (preference?.channels?.includes(NotifChannelEnum.SMS)) {
// await this.queueService.addBulkSmsNotifications(
// recipients.map(recipient => ({
// recipient,
// templateId: message.sms.templateId,
// parameters: message.sms.parameters,
// })),
// );
// }
const adminNotifications = notifications
.filter(n => n.admin?.id)
.map(notification => ({
recipient: { adminId: notification.admin!.id },
subject: message.title,
body: message.content,
notificationId: notification.id,
}))
// this.logger.log(`Queued notification for restaurant ${}, title ${message.title}`);
if (adminNotifications.length > 0) {
await this.queueService.addBulkInAppNotifications(adminNotifications);
}
}
// return notifications;
// }
// Add SMS notifications to queue
if (channels.includes(NotifChannelEnum.SMS) && message.sms?.templateId) {
await this.queueService.addBulkSmsNotifications(
recipients.map(recipient => ({
recipient,
templateId: message.sms.templateId,
parameters: message.sms.parameters,
})),
);
}
// async createAdminBulkNotifications(
// params: {
this.logger.log(`Queued notification for title ${message.title}`);
return notifications;
}
// title: NotifTitleEnum;
// content: string;
// adminId: string | null;
// userId: string | null;
// }[],
// ): Promise < Notification[] > {
// const notifications = params.map(param => {
// return this.em.create(Notification, {
// admin: param.adminId,
// user: param.userId && param.userId.trim() !== '' ? param.userId : null,
// title: param.title,
// content: param.content,
// });
// });
// await this.em.persistAndFlush(notifications);
// return notifications;
// }
async createAdminBulkNotifications(
params: {
title: NotifEvent;
content: string;
adminId: string | null;
userId: string | null;
}[],
): Promise<Notification[]> {
const notifications = params.map(param => {
return this.em.create(Notification, {
admin: param.adminId ? this.em.getReference(Admin, param.adminId) : undefined,
user: param.userId && param.userId.trim() !== '' ? this.em.getReference(User, param.userId) : undefined,
title: param.title,
content: param.content,
});
});
await this.em.persistAndFlush(notifications);
return notifications;
}
// async findOne(id: string): Promise < Notification > {
// const notification = await this.em.findOne(Notification, { id }, { populate: ['restaurant', 'user'] });
// if(!notification) {
// throw new NotFoundException('Notification not found');
// }
// return notification;
// }
async findAdminNotifications(
adminId: string,
limit = 50,
cursor?: string,
status?: 'seen' | 'unseen',
): Promise<{ data: Notification[]; nextCursor: string | null }> {
const where: FilterQuery<Notification> = {
admin: { id: adminId },
};
// async findByRestaurant(
// : string,
// adminId: string,
// limit = 50,
// cursor ?: string,
// status ?: 'seen' | 'unseen',
// ): Promise < { data: Notification[]; nextCursor: string | null } > {
// const where: FilterQuery<Notification> = {
// restaurant: { id: },
// admin: { id: adminId },
// };
if (status === 'seen') {
where.seenAt = { $ne: null };
} else if (status === 'unseen') {
where.seenAt = null;
}
// // Filter by status (seen/unseen)
// if (status === 'seen') {
// where.seenAt = { $ne: null };
// } else if (status === 'unseen') {
// where.seenAt = null;
// }
if (cursor) {
where.id = { $lt: cursor };
}
// // Cursor-based pagination: if cursor is provided, get items with id < cursor
// if (cursor) {
// where.id = { $lt: cursor };
// }
const notifications = await this.em.find(Notification, where, {
orderBy: { createdAt: 'DESC', id: 'DESC' },
limit: limit + 1,
populate: ['user'],
});
// const notifications = await this.em.find(Notification, where, {
// orderBy: { createdAt: 'DESC', id: 'DESC' },
// limit: limit + 1, // fetch one extra to determine next page
// populate: ['user'],
// });
const hasNextPage = notifications.length > limit;
const data = hasNextPage ? notifications.slice(0, limit) : notifications;
const nextCursor = hasNextPage && data.length > 0 ? data[data.length - 1].id : null;
// const hasNextPage = notifications.length > limit;
// const data = hasNextPage ? notifications.slice(0, limit) : notifications;
// const nextCursor = hasNextPage && data.length > 0 ? data[data.length - 1].id : null;
return { data, nextCursor: nextCursor ?? null };
}
// return {
// data,
// nextCursor: nextCursor ?? null,
// };
// }
// async findByUserAndRestaurant(
// userId: string,
// : string,
// limit = 50,
// cursor ?: string,
// status ?: 'seen' | 'unseen',
// ): Promise < { data: Notification[]; nextCursor: string | null } > {
// const where: FilterQuery<Notification> = {
// user: { id: userId },
// restaurant: { id: },
// };
async findUserNotifications(
userId: string,
limit = 50,
cursor?: string,
status?: 'seen' | 'unseen',
): Promise<{ data: Notification[]; nextCursor: string | null }> {
const where: FilterQuery<Notification> = {
user: { id: userId },
};
// // Filter by status (seen/unseen)
// if (status === 'seen') {
// where.seenAt = { $ne: null };
// } else if (status === 'unseen') {
// where.seenAt = null;
// }
if (status === 'seen') {
where.seenAt = { $ne: null };
} else if (status === 'unseen') {
where.seenAt = null;
}
// // Cursor-based pagination: if cursor is provided, get items with id < cursor (since ULIDs are time-ordered)
// if (cursor) {
// where.id = { $lt: cursor };
// }
if (cursor) {
where.id = { $lt: cursor };
}
// const notifications = await this.em.find(Notification, where, {
// orderBy: { createdAt: 'DESC', id: 'DESC' },
// limit: limit + 1, // Fetch one extra to determine if there's a next page
// });
const notifications = await this.em.find(Notification, where, {
orderBy: { createdAt: 'DESC', id: 'DESC' },
limit: limit + 1,
});
// // Check if there's a next page
// const hasNextPage = notifications.length > limit;
// const data = hasNextPage ? notifications.slice(0, limit) : notifications;
// const nextCursor = hasNextPage && data.length > 0 ? data[data.length - 1].id : null;
const hasNextPage = notifications.length > limit;
const data = hasNextPage ? notifications.slice(0, limit) : notifications;
const nextCursor = hasNextPage && data.length > 0 ? data[data.length - 1].id : null;
// return {
// data,
// nextCursor: nextCursor ?? null,
// };
// }
return { data, nextCursor: nextCursor ?? null };
}
// async readNotificationAdmin(id: string, adminId: string, : string): Promise < void> {
// const notification = await this.em.findOne(Notification, {
// id,
// admin: { id: adminId },
// restaurant: { id: },
// });
// if(!notification) {
// throw new NotFoundException('Notification not found');
// }
// notification.seenAt = new Date();
// await this.em.persistAndFlush(notification);
// }
// async readNotificationAsUser(id: string, userId: string, : string): Promise < void> {
// const notification = await this.em.findOne(Notification, {
// id,
// user: { id: userId },
// restaurant: { id: },
// });
// if(!notification) {
// throw new NotFoundException('Notification not found');
// }
// notification.seenAt = new Date();
// await this.em.persistAndFlush(notification);
// }
// async findByRestaurantAndType(: string, title: NotifTitleEnum, limit = 50): Promise < Notification[] > {
async readNotificationAdmin(id: string, adminId: string): Promise<void> {
const notification = await this.em.findOne(Notification, {
id,
admin: { id: adminId },
});
if (!notification) {
throw new NotFoundException('Notification not found');
}
notification.seenAt = new Date();
await this.em.persistAndFlush(notification);
}
async readNotificationAsUser(id: string, userId: string): Promise<void> {
const notification = await this.em.findOne(Notification, {
id,
user: { id: userId },
});
if (!notification) {
throw new NotFoundException('Notification not found');
}
notification.seenAt = new Date();
await this.em.persistAndFlush(notification);
}
// async findByTitle(title: NotifEvent, limit = 50): Promise<Notification[]> {
// return this.em.find(
// Notification,
// {
// restaurant: { id: },
// title,
// },
// { title },
// {
// orderBy: { createdAt: 'DESC' },
// limit,
// populate: ['user'],
// populate: ['user', 'admin'],
// },
// );
// }
// async findByAdminAndRestaurant(adminId: string, : string, limit = 50): Promise < Notification[] > {
// async findByAdminAndTitle(adminId: string, title: NotifEvent, limit = 50): Promise<Notification[]> {
// return this.em.find(
// Notification,
// { admin: { id: adminId }, restaurant: { id: } },
// { admin: { id: adminId }, title },
// {
// orderBy: { createdAt: 'DESC' },
// limit,
@@ -230,50 +204,45 @@ export class NotificationService {
// );
// }
// async countUnseenByUserAndRestaurant(userId: string, : string): Promise < number > {
// const where: FilterQuery<Notification> = {
// user: { id: userId },
// restaurant: { id: },
// seenAt: null,
// };
// return this.em.count(Notification, where);
// }
// async countUnseenByRestaurant(adminId: string, : string): Promise < number > {
// const where: FilterQuery<Notification> = {
// admin: { id: adminId },
// restaurant: { id: },
// seenAt: null,
// };
// return this.em.count(Notification, where);
// }
async countUnseenByUser(userId: string): Promise<number> {
return this.em.count(Notification, {
user: { id: userId },
seenAt: null,
});
}
// readAllNotifsAsUser(userId: string, : string): Promise < number > {
// const where: FilterQuery<Notification> = {
// user: { id: userId },
// restaurant: { id: },
// seenAt: null,
// };
// return this.em.nativeUpdate(Notification, where, { seenAt: new Date() });
// }
// readAllNotifsAsAdmin(adminId: string, : string): Promise < number > {
// const where: FilterQuery<Notification> = {
// admin: { id: adminId },
// restaurant: { id: },
// seenAt: null,
// };
// return this.em.nativeUpdate(Notification, where, { seenAt: new Date() });
// }
async countUnseenByAdmin(adminId: string): Promise<number> {
return this.em.count(Notification, {
admin: { id: adminId },
seenAt: null,
});
}
async readAllNotifsAsUser(userId: string): Promise<number> {
const notifications = await this.em.find(Notification, {
user: { id: userId },
seenAt: null,
});
const now = new Date();
notifications.forEach(n => { n.seenAt = now; });
await this.em.persistAndFlush(notifications);
return notifications.length;
}
async readAllNotifsAsAdmin(adminId: string): Promise<number> {
const notifications = await this.em.find(Notification, {
admin: { id: adminId },
seenAt: null,
});
const now = new Date();
notifications.forEach(n => { n.seenAt = now; });
await this.em.persistAndFlush(notifications);
return notifications.length;
}
// async getSmsCountByRestaurant(
// page: number = 1,
// limit: number = 10,
// ): Promise < PaginatedResult < { : string; restaurantName: string; smsCount: number } >> {
// return this.smsLogRepository.getSmsCountByRestaurant(page, limit);
// }
// async getSmsCountBy(: string): Promise < number > {
// return this.smsLogRepository.getSmsCountBy();
// }
}
@@ -5,7 +5,7 @@ import * as admin from 'firebase-admin';
export interface PushNotificationPayload {
title: string;
body: string;
data?: Record<string, any>;
data?: Record<string, unknown>;
token?: string;
tokens?: string[];
}
@@ -34,7 +34,6 @@ export class PushNotificationService {
return;
}
// Parse the service account JSON
const serviceAccount = JSON.parse(firebaseConfig);
if (!this.firebaseApp) {
@@ -134,7 +133,7 @@ export class PushNotificationService {
}
}
private convertDataToString(data: Record<string, any>): Record<string, string> {
private convertDataToString(data: Record<string, unknown>): Record<string, string> {
const result: Record<string, string> = {};
for (const [key, value] of Object.entries(data)) {
result[key] = typeof value === 'string' ? value : JSON.stringify(value);