@tmlmobilidade/interfaces
Version:
This package provides SDK-style connectors for interacting with databases (e.g., stops, plans, rides, alerts) and external providers (e.g., authentication, storage). It simplifies data access and integration across projects.
109 lines (108 loc) • 4.24 kB
JavaScript
/* * */
import { MongoCollectionClass } from '../../common/mongo-collection.js';
import { roles } from '../auth/roles.js';
import { users } from '../auth/users.js';
import { getModuleConfig } from '@tmlmobilidade/consts';
import { CreateNotificationSchema, UpdateNotificationSchema } from '@tmlmobilidade/types';
import { asyncSingletonProxy, mergeObjects } from '@tmlmobilidade/utils';
/* * */
class NotificationsClass extends MongoCollectionClass {
static _instance;
createSchema = CreateNotificationSchema;
updateSchema = UpdateNotificationSchema;
constructor() {
super();
}
static async getInstance() {
if (!NotificationsClass._instance) {
const instance = new NotificationsClass();
await instance.connect();
NotificationsClass._instance = instance;
}
return NotificationsClass._instance;
}
async sendNotification(scope, topic, user, id, title, description) {
// Fetch roles and users that have access to this topic
const rolesWithTopic = await roles.findMany({ 'permissions.action': topic });
const roleIdsWithTopic = rolesWithTopic.map(r => r._id);
const usersWithTopic = await users.findMany({
$or: [
{ 'permissions.action': topic },
{ role_ids: { $in: roleIdsWithTopic } },
],
});
if (usersWithTopic.length === 0)
return;
// Base notification template
const baseNotification = {
created_by: user?._id,
is_read: false,
payload: {
body: description,
href: `${getModuleConfig(scope, 'frontend_url')}/${scope}/${id}`,
icon: scope,
title,
},
priority: 'normal',
scope,
topic,
updated_by: user?._id,
};
// Iterate over eligible users (excluding creator)
for (const recipient of usersWithTopic.filter(u => u._id !== baseNotification.created_by)) {
const permissions = this.collectUserPermissions(recipient, rolesWithTopic);
const canReceiveEmail = this.getNotificationPermission(permissions, topic);
const newNotification = { ...baseNotification, user_id: recipient._id };
const result = await notifications.insertOne(newNotification);
// // Send email if permission allows
// if (canReceiveEmail) {
// await sendGenericNotificationEmail({
// data: {
// body: result.payload.body,
// notificationId: result._id,
// notificationUrl: result.payload.href ?? '',
// title: result.payload.title,
// },
// to: recipient.email,
// });
// }
}
}
getCollectionIndexes() {
return [
{ background: true, key: { name: 1 }, unique: true },
];
}
getCollectionName() {
return 'notifications';
}
getEnvName() {
return 'DATABASE_URI';
}
/**
* Collects all effective permissions of a user (direct + via roles),
* merging duplicates by (scope:action).
*/
collectUserPermissions(user, rolesWithTopic) {
const rolePermissions = rolesWithTopic
.filter(role => user.role_ids?.includes(role._id))
.flatMap(role => role.permissions ?? []);
const allPermissions = [...rolePermissions, ...(user.permissions ?? [])];
const map = new Map();
for (const permission of allPermissions) {
const key = `${permission.scope}:${permission.action}`;
const existing = map.get(key);
map.set(key, existing ? mergeObjects(existing, permission) : permission);
}
return map;
}
/**
* Determines whether a user can receive email notifications for a topic.
*/
getNotificationPermission(permissions, topic) {
const permission = permissions.get(`notifications:${topic}`);
return permission['resource']?.send_mail ?? false;
}
}
/* * */
export const notifications = asyncSingletonProxy(NotificationsClass);