This commit is contained in:
2026-01-14 11:29:24 +03:30
parent 1075dbc44f
commit 4ab9de447a
26 changed files with 1130 additions and 1710 deletions
@@ -21,259 +21,259 @@ export class NotificationService {
private readonly smsLogRepository: SmsLogRepository,
) { }
async sendNotification(params: NotifRequest): Promise<Notification[]> {
const { recipients, message, metadata, } = params;
// async sendNotification(params: NotifRequest): Promise<Notification[]> {
// const { recipients, message, metadata, } = 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 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,
// })),
// );
// get admin prefrences
const preference = await this.preferenceService.findByRestaurantAndType(, message.title);
// // get admin prefrences
// const preference = await this.preferenceService.findByRestaurantAndType(, message.title);
if(preference?.channels?.length === 0) {
this.logger.warn(`Notification type is NONE for restaurant ${}, title ${message.title}`);
return notifications;
}
// if(preference?.channels?.length === 0) {
// this.logger.warn(`Notification type is NONE for restaurant ${}, 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 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,
// })),
// );
// }
// 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,
})),
);
}
// // 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,
// })),
// );
// }
this.logger.log(`Queued notification for restaurant ${}, title ${message.title}`);
// this.logger.log(`Queued notification for restaurant ${}, title ${message.title}`);
return notifications;
}
// return notifications;
// }
async createAdminBulkNotifications(
params: {
// async createAdminBulkNotifications(
// params: {
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 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 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 },
};
// Filter by status (seen/unseen)
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
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 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;
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: },
};
// Filter by status (seen/unseen)
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 };
}
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
});
// 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;
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[] > {
return this.em.find(
Notification,
{
restaurant: { id: },
title,
},
{
orderBy: { createdAt: 'DESC' },
limit,
populate: ['user'],
},
);
}
async findByAdminAndRestaurant(adminId: string, : string, limit = 50): Promise < Notification[] > {
return this.em.find(
Notification,
{ admin: { id: adminId }, restaurant: { id: } },
{
orderBy: { createdAt: 'DESC' },
limit,
},
);
}
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);
}
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 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();
}
// 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 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 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 },
// };
// // Filter by status (seen/unseen)
// 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
// 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 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;
// 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: },
// };
// // Filter by status (seen/unseen)
// 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 };
// }
// 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
// });
// // 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;
// 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[] > {
// return this.em.find(
// Notification,
// {
// restaurant: { id: },
// title,
// },
// {
// orderBy: { createdAt: 'DESC' },
// limit,
// populate: ['user'],
// },
// );
// }
// async findByAdminAndRestaurant(adminId: string, : string, limit = 50): Promise < Notification[] > {
// return this.em.find(
// Notification,
// { admin: { id: adminId }, restaurant: { id: } },
// {
// orderBy: { createdAt: 'DESC' },
// limit,
// },
// );
// }
// 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);
// }
// 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 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();
// }
}