Implemented notifications.
Some checks failed
farmcontrol/farmcontrol-api/pipeline/head There was a failure building this commit

This commit is contained in:
Tom Butcher 2026-03-01 01:42:30 +00:00
parent 838e48ade6
commit 3e47cb131b
11 changed files with 727 additions and 29 deletions

View File

@ -1,6 +1,7 @@
import config from '../config.js';
import { fileModel } from './schemas/management/file.schema.js';
import _ from 'lodash';
import log4js from 'log4js';
import {
deleteAuditLog,
distributeDelete,
@ -8,9 +9,6 @@ import {
modelHasRef,
getFieldsByRef,
getQueryToCacheKey,
} from '../utils.js';
import log4js from 'log4js';
import {
editAuditLog,
distributeUpdate,
newAuditLog,
@ -19,6 +17,8 @@ import {
distributeChildDelete,
distributeChildNew,
distributeStats,
editNotification,
deleteNotification,
} from '../utils.js';
import { getAllModels } from '../services/misc/model.js';
import { redisServer } from './redis.js';
@ -802,6 +802,20 @@ export const editObject = async ({ model, id, updateData, user, populate, recalc
parentType,
user
);
if (
parentType !== 'notification' &&
parentType !== 'auditLog' &&
parentType !== 'userNotifier'
) {
await editNotification(
previousExpandedObject,
{ ...previousExpandedObject, ...updateData },
id,
parentType,
user
);
}
// Distribute update
await distributeUpdate(updateData, id, parentType);
// Call childUpdate event for any child objects
@ -878,6 +892,7 @@ export const newObject = async ({ model, newData, user = null }, distributeChang
const created = expandObjectIds(result.toObject());
await newAuditLog(newData, created._id, parentType, user);
if (distributeChanges == true) {
await distributeNew(created, parentType);
}
@ -922,7 +937,15 @@ export const deleteObject = async ({ model, id, user = null }, distributeChanges
const deleted = expandObjectIds(result.toObject());
// Audit log the deletion
await deleteAuditLog(deleted, id, parentType, user, 'delete');
await deleteAuditLog(deleted, id, parentType, user);
if (
parentType !== 'notification' &&
parentType !== 'auditLog' &&
parentType !== 'userNotifier'
) {
await deleteNotification(deleted, id, parentType, user);
}
if (distributeChanges == true) {
await distributeDelete(deleted, parentType);

View File

@ -0,0 +1,51 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
const notificationSchema = new mongoose.Schema({
_reference: { type: String, default: () => generateId()() },
user: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true,
},
title: {
type: String,
required: true,
},
message: {
type: String,
required: true,
},
type: {
type: String,
required: true,
default: 'info',
},
read: {
type: Boolean,
default: false,
},
metadata: {
type: Object,
required: false,
},
createdAt: {
type: Date,
required: true,
default: Date.now,
},
updatedAt: {
type: Date,
required: true,
default: Date.now,
},
});
notificationSchema.virtual('id').get(function () {
return this._id;
});
notificationSchema.set('toJSON', { virtuals: true });
export const notificationModel = mongoose.model('notification', notificationSchema);

View File

@ -0,0 +1,44 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
const userNotifierSchema = new mongoose.Schema({
_reference: { type: String, default: () => generateId()() },
user: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true,
},
email: {
type: Boolean,
required: true,
default: false,
},
object: {
type: Schema.Types.ObjectId,
refPath: 'objectType',
required: true,
},
objectType: {
type: String,
required: true,
},
createdAt: {
type: Date,
required: true,
default: Date.now,
},
updatedAt: {
type: Date,
required: true,
default: Date.now,
},
});
userNotifierSchema.virtual('id').get(function () {
return this._id;
});
userNotifierSchema.set('toJSON', { virtuals: true });
export const userNotifierModel = mongoose.model('userNotifier', userNotifierSchema);

View File

@ -16,6 +16,8 @@ import { auditLogModel } from './management/auditlog.schema.js';
import { userModel } from './management/user.schema.js';
import { noteTypeModel } from './management/notetype.schema.js';
import { noteModel } from './misc/note.schema.js';
import { notificationModel } from './misc/notification.schema.js';
import { userNotifierModel } from './misc/usernotifier.schema.js';
import { documentSizeModel } from './management/documentsize.schema.js';
import { documentTemplateModel } from './management/documenttemplate.schema.js';
import { hostModel } from './management/host.schema.js';
@ -33,77 +35,243 @@ import { salesOrderModel } from './sales/salesorder.schema.js';
// Map prefixes to models and id fields
export const models = {
PRN: { model: printerModel, idField: '_id', type: 'printer', referenceField: '_reference' },
FIL: { model: filamentModel, idField: '_id', type: 'filament', referenceField: '_reference' },
GCF: { model: gcodeFileModel, idField: '_id', type: 'gcodeFile', referenceField: '_reference' },
JOB: { model: jobModel, idField: '_id', type: 'job', referenceField: '_reference' },
PRT: { model: partModel, idField: '_id', type: 'part', referenceField: '_reference' },
PRD: { model: productModel, idField: '_id', type: 'product', referenceField: '_reference' },
VEN: { model: vendorModel, idField: '_id', type: 'vendor', referenceField: '_reference' },
SJB: { model: subJobModel, idField: '_id', type: 'subJob', referenceField: '_reference' },
PRN: {
model: printerModel,
idField: '_id',
type: 'printer',
referenceField: '_reference',
label: 'Printer',
},
FIL: {
model: filamentModel,
idField: '_id',
type: 'filament',
referenceField: '_reference',
label: 'Filament',
},
GCF: {
model: gcodeFileModel,
idField: '_id',
type: 'gcodeFile',
referenceField: '_reference',
label: 'G-Code File',
},
JOB: { model: jobModel, idField: '_id', type: 'job', referenceField: '_reference', label: 'Job' },
PRT: {
model: partModel,
idField: '_id',
type: 'part',
referenceField: '_reference',
label: 'Part',
},
PRD: {
model: productModel,
idField: '_id',
type: 'product',
referenceField: '_reference',
label: 'Product',
},
VEN: {
model: vendorModel,
idField: '_id',
type: 'vendor',
referenceField: '_reference',
label: 'Vendor',
},
SJB: {
model: subJobModel,
idField: '_id',
type: 'subJob',
referenceField: '_reference',
label: 'Sub Job',
},
FLS: {
model: filamentStockModel,
idField: '_id',
type: 'filamentStock',
referenceField: '_reference',
label: 'Filament Stock',
},
SEV: {
model: stockEventModel,
idField: '_id',
type: 'stockEvent',
referenceField: '_reference',
label: 'Stock Event',
},
SAU: {
model: stockAuditModel,
idField: '_id',
type: 'stockAudit',
referenceField: '_reference',
label: 'Stock Audit',
},
PTS: {
model: partStockModel,
idField: '_id',
type: 'partStock',
referenceField: '_reference',
label: 'Part Stock',
},
PDS: {
model: null,
idField: '_id',
type: 'productStock',
referenceField: '_reference',
label: 'Product Stock',
}, // No productStockModel found
ADL: {
model: auditLogModel,
idField: '_id',
type: 'auditLog',
referenceField: '_reference',
label: 'Audit Log',
},
USR: {
model: userModel,
idField: '_id',
type: 'user',
referenceField: '_reference',
label: 'User',
},
NTY: {
model: noteTypeModel,
idField: '_id',
type: 'noteType',
referenceField: '_reference',
label: 'Note Type',
},
NTE: {
model: noteModel,
idField: '_id',
type: 'note',
referenceField: '_reference',
label: 'Note',
},
NTF: {
model: notificationModel,
idField: '_id',
type: 'notification',
label: 'Notification',
referenceField: '_reference',
},
ONF: {
model: userNotifierModel,
idField: '_id',
type: 'userNotifier',
label: 'User Notifier',
referenceField: '_reference',
},
SEV: { model: stockEventModel, idField: '_id', type: 'stockEvent', referenceField: '_reference' },
SAU: { model: stockAuditModel, idField: '_id', type: 'stockAudit', referenceField: '_reference' },
PTS: { model: partStockModel, idField: '_id', type: 'partStock', referenceField: '_reference' },
PDS: { model: null, idField: '_id', type: 'productStock', referenceField: '_reference' }, // No productStockModel found
ADL: { model: auditLogModel, idField: '_id', type: 'auditLog', referenceField: '_reference' },
USR: { model: userModel, idField: '_id', type: 'user', referenceField: '_reference' },
NTY: { model: noteTypeModel, idField: '_id', type: 'noteType', referenceField: '_reference' },
NTE: { model: noteModel, idField: '_id', type: 'note', referenceField: '_reference' },
DSZ: {
model: documentSizeModel,
idField: '_id',
type: 'documentSize',
label: 'Document Size',
referenceField: '_reference',
},
DTP: {
model: documentTemplateModel,
idField: '_id',
type: 'documentTemplate',
label: 'Document Template',
referenceField: '_reference',
},
DPR: {
model: documentPrinterModel,
idField: '_id',
type: 'documentPrinter',
label: 'Document Printer',
referenceField: '_reference',
},
DJB: {
model: documentJobModel,
idField: '_id',
type: 'documentJob',
label: 'Document Job',
referenceField: '_reference',
},
HST: { model: hostModel, idField: '_id', type: 'host', referenceField: '_reference' },
FLE: { model: fileModel, idField: '_id', type: 'file', referenceField: '_reference' },
HST: {
model: hostModel,
idField: '_id',
type: 'host',
referenceField: '_reference',
label: 'Host',
},
FLE: {
model: fileModel,
idField: '_id',
type: 'file',
referenceField: '_reference',
label: 'File',
},
POR: {
model: purchaseOrderModel,
idField: '_id',
type: 'purchaseOrder',
label: 'Purchase Order',
referenceField: '_reference',
},
ODI: {
model: orderItemModel,
idField: '_id',
type: 'orderItem',
label: 'Order Item',
referenceField: '_reference',
},
COS: {
model: courierServiceModel,
idField: '_id',
type: 'courierService',
label: 'Courier Service',
referenceField: '_reference',
},
COR: {
model: courierModel,
idField: '_id',
type: 'courier',
label: 'Courier',
referenceField: '_reference',
},
TXR: {
model: taxRateModel,
idField: '_id',
type: 'taxRate',
label: 'Tax Rate',
referenceField: '_reference',
},
TXD: {
model: taxRecordModel,
idField: '_id',
type: 'taxRecord',
label: 'Tax Record',
referenceField: '_reference',
},
SHP: {
model: shipmentModel,
idField: '_id',
type: 'shipment',
label: 'Shipment',
referenceField: '_reference',
},
INV: {
model: invoiceModel,
idField: '_id',
type: 'invoice',
label: 'Invoice',
referenceField: '_reference',
},
CLI: {
model: clientModel,
idField: '_id',
type: 'client',
label: 'Client',
referenceField: '_reference',
},
SOR: {
model: salesOrderModel,
idField: '_id',
type: 'salesOrder',
label: 'Sales Order',
referenceField: '_reference',
},
COR: { model: courierModel, idField: '_id', type: 'courier', referenceField: '_reference' },
TXR: { model: taxRateModel, idField: '_id', type: 'taxRate', referenceField: '_reference' },
TXD: { model: taxRecordModel, idField: '_id', type: 'taxRecord', referenceField: '_reference' },
SHP: { model: shipmentModel, idField: '_id', type: 'shipment', referenceField: '_reference' },
INV: { model: invoiceModel, idField: '_id', type: 'invoice', referenceField: '_reference' },
CLI: { model: clientModel, idField: '_id', type: 'client', referenceField: '_reference' },
SOR: { model: salesOrderModel, idField: '_id', type: 'salesOrder', referenceField: '_reference' },
};

View File

@ -41,6 +41,8 @@ import {
paymentRoutes,
clientRoutes,
salesOrderRoutes,
userNotifierRoutes,
notificationRoutes,
} from './routes/index.js';
import path from 'path';
import * as fs from 'fs';
@ -148,6 +150,8 @@ app.use('/payments', paymentRoutes);
app.use('/clients', clientRoutes);
app.use('/salesorders', salesOrderRoutes);
app.use('/notes', noteRoutes);
app.use('/usernotifiers', userNotifierRoutes);
app.use('/notifications', notificationRoutes);
// Start the application
if (process.env.NODE_ENV !== 'test') {

View File

@ -34,6 +34,8 @@ import paymentRoutes from './finance/payments.js';
import clientRoutes from './sales/clients.js';
import salesOrderRoutes from './sales/salesorders.js';
import noteRoutes from './misc/notes.js';
import userNotifierRoutes from './misc/usernotifiers.js';
import notificationRoutes from './misc/notifications.js';
export {
userRoutes,
@ -72,4 +74,6 @@ export {
paymentRoutes,
clientRoutes,
salesOrderRoutes,
userNotifierRoutes,
notificationRoutes,
};

View File

@ -0,0 +1,37 @@
import express from 'express';
import { isAuthenticated } from '../../keycloak.js';
import {
listNotificationsRouteHandler,
markNotificationAsReadRouteHandler,
markAllNotificationsAsReadRouteHandler,
deleteNotificationRouteHandler,
deleteAllNotificationsRouteHandler,
} from '../../services/misc/notifications.js';
import { getFilter } from '../../utils.js';
const router = express.Router();
router.get('/', isAuthenticated, (req, res) => {
const { page = 1, limit = 50, sort = 'createdAt', order = 'descend' } = req.query;
const allowedFilters = ['user'];
const filter = getFilter(req.query, allowedFilters);
listNotificationsRouteHandler(req, res, page, limit, filter, sort, order);
});
router.put('/read-all', isAuthenticated, (req, res) => {
markAllNotificationsAsReadRouteHandler(req, res);
});
router.put('/:id/read', isAuthenticated, (req, res) => {
markNotificationAsReadRouteHandler(req, res);
});
router.delete('/', isAuthenticated, (req, res) => {
deleteAllNotificationsRouteHandler(req, res);
});
router.delete('/:id', isAuthenticated, (req, res) => {
deleteNotificationRouteHandler(req, res);
});
export default router;

View File

@ -0,0 +1,37 @@
import express from 'express';
import { isAuthenticated } from '../../keycloak.js';
import {
listUserNotifiersRouteHandler,
getUserNotifierRouteHandler,
newUserNotifierRouteHandler,
deleteUserNotifierRouteHandler,
editUserNotifierRouteHandler,
} from '../../services/misc/usernotifiers.js';
import { getFilter } from '../../utils.js';
const router = express.Router();
router.get('/', isAuthenticated, (req, res) => {
const { page, limit, property, search, sort, order } = req.query;
const allowedFilters = ['user', 'object', 'objectType'];
const filter = getFilter(req.query, allowedFilters);
listUserNotifiersRouteHandler(req, res, page, limit, property, filter, search, sort, order);
});
router.post('/', isAuthenticated, (req, res) => {
newUserNotifierRouteHandler(req, res);
});
router.get('/:id', isAuthenticated, (req, res) => {
getUserNotifierRouteHandler(req, res);
});
router.put('/:id', isAuthenticated, (req, res) => {
editUserNotifierRouteHandler(req, res);
});
router.delete('/:id', isAuthenticated, (req, res) => {
deleteUserNotifierRouteHandler(req, res);
});
export default router;

View File

@ -0,0 +1,105 @@
import config from '../../config.js';
import { notificationModel } from '../../database/schemas/misc/notification.schema.js';
import log4js from 'log4js';
import { listObjects, editObject } from '../../database/database.js';
const logger = log4js.getLogger('Notifications');
logger.level = config.server.logLevel;
export const listNotificationsRouteHandler = async (
req,
res,
page = 1,
limit = 50,
filter = {},
sort = 'createdAt',
order = 'descend'
) => {
const userFilter = { ...filter, user: req.user._id };
const result = await listObjects({
model: notificationModel,
page,
limit,
filter: userFilter,
sort,
order,
populate: [],
});
if (result?.error) {
logger.error('Error listing notifications.');
res.status(result.code || 500).send(result);
return;
}
logger.debug(`List of notifications (Page ${page}, Limit ${limit}). Count: ${result.length}`);
res.send(result);
};
export const markNotificationAsReadRouteHandler = async (req, res) => {
const id = req.params.id;
const existing = await notificationModel.findById(id).lean();
if (!existing) {
return res.status(404).send({ error: 'Notification not found' });
}
if (String(existing.user) !== String(req.user._id)) {
return res.status(403).send({ error: 'Forbidden: you can only update your own notifications' });
}
const result = await editObject({
model: notificationModel,
id,
updateData: { read: true, updatedAt: new Date() },
user: req.user,
});
if (result?.error) {
logger.error('Error marking notification as read:', result.error);
return res.status(result.code || 500).send(result);
}
logger.debug(`Marked notification as read: ${id}`);
res.send(result);
};
export const markAllNotificationsAsReadRouteHandler = async (req, res) => {
try {
await notificationModel.updateMany(
{ user: req.user._id, read: false },
{ $set: { read: true, updatedAt: new Date() } }
);
logger.debug('Marked all notifications as read');
res.send({ status: 'ok' });
} catch (err) {
logger.error('Error marking all notifications as read:', err);
res.status(500).send({ error: 'Failed to mark all as read' });
}
};
export const deleteNotificationRouteHandler = async (req, res) => {
const id = req.params.id;
const existing = await notificationModel.findById(id).lean();
if (!existing) {
return res.status(404).send({ error: 'Notification not found' });
}
if (String(existing.user._id) !== String(req.user._id)) {
return res.status(403).send({ error: 'Forbidden: you can only delete your own notifications' });
}
await notificationModel.findByIdAndDelete(id);
logger.debug('Deleted notification:', id);
res.send({ status: 'ok' });
};
export const deleteAllNotificationsRouteHandler = async (req, res) => {
try {
await notificationModel.deleteMany({ user: req.user._id });
logger.debug('Deleted all notifications');
res.send({ status: 'ok' });
} catch (err) {
logger.error('Error deleting all notifications:', err);
res.status(500).send({ error: 'Failed to delete all notifications' });
}
};

View File

@ -0,0 +1,146 @@
import config from '../../config.js';
import { userNotifierModel } from '../../database/schemas/misc/usernotifier.schema.js';
import log4js from 'log4js';
import {
deleteObject,
editObject,
getObject,
listObjects,
newObject,
} from '../../database/database.js';
const logger = log4js.getLogger('UserNotifiers');
logger.level = config.server.logLevel;
export const listUserNotifiersRouteHandler = async (
req,
res,
page = 1,
limit = 25,
property = '',
filter = {},
search = '',
sort = '',
order = 'ascend'
) => {
const result = await listObjects({
model: userNotifierModel,
page,
limit,
property,
filter,
search,
sort,
order,
populate: ['user', 'object'],
});
if (result?.error) {
logger.error('Error listing user notifiers.');
res.status(result.code).send(result);
return;
}
logger.debug(`List of user notifiers (Page ${page}, Limit ${limit}). Count: ${result.length}`);
res.send(result);
};
export const getUserNotifierRouteHandler = async (req, res) => {
const id = req.params.id;
const result = await getObject({
model: userNotifierModel,
id,
populate: ['user', 'object'],
});
if (result?.error) {
logger.warn(`User notifier not found with supplied id.`);
return res.status(result.code).send(result);
}
logger.debug(`Retrieved user notifier with ID: ${id}`);
res.send(result);
};
export const newUserNotifierRouteHandler = async (req, res) => {
const newData = {
user: req.user._id,
object: req.body.object,
objectType: req.body.objectType,
};
const result = await newObject({
model: userNotifierModel,
newData,
user: req.user,
});
if (result.error) {
logger.error('No user notifier created:', result.error);
return res.status(result.code).send(result);
}
logger.debug(`New user notifier with ID: ${result._id}`);
res.send(result);
};
export const editUserNotifierRouteHandler = async (req, res) => {
const id = req.params.id;
const existing = await getObject({
model: userNotifierModel,
id,
});
if (existing?.error) {
return res.status(existing.code).send(existing);
}
if (String(existing.user._id) !== String(req.user._id)) {
return res.status(403).send({ error: 'Forbidden: you can only edit your own notifiers' });
}
const updateData = {
updatedAt: new Date(),
email: req.body.email,
};
const result = await editObject({
model: userNotifierModel,
id,
updateData,
user: req.user,
});
if (result.error) {
logger.error('Error editing user notifier:', result.error);
return res.status(result.code).send(result);
}
logger.debug(`Edited user notifier with ID: ${id}`);
res.send(result);
};
export const deleteUserNotifierRouteHandler = async (req, res) => {
const id = req.params.id;
const existing = await getObject({
model: userNotifierModel,
id,
});
if (existing?.error) {
return res.status(existing.code).send(existing);
}
if (String(existing.user._id) !== String(req.user._id)) {
return res.status(403).send({ error: 'Forbidden: you can only delete your own notifiers' });
}
const result = await deleteObject({
model: userNotifierModel,
id,
user: req.user,
});
if (result?.error) {
logger.error('No user notifier deleted:', result.error);
return res.status(result.code).send(result);
}
logger.info(`Successfully deleted user notifier ${id}`);
res.send({
status: 'ok',
});
};

View File

@ -1,11 +1,14 @@
import { mongoose } from 'mongoose';
import { auditLogModel } from './database/schemas/management/auditlog.schema.js';
import { notificationModel } from './database/schemas/misc/notification.schema.js';
import { userNotifierModel } from './database/schemas/misc/usernotifier.schema.js';
import exifr from 'exifr';
import { natsServer } from './database/nats.js';
import log4js from 'log4js';
import config from './config.js';
import crypto from 'crypto';
import canonicalize from 'canonical-json';
import { getModelByName } from './services/misc/model.js';
const logger = log4js.getLogger('Utils');
logger.level = config.server.logLevel;
@ -350,7 +353,11 @@ function getChangedValues(oldObj, newObj, old = false) {
return changes;
}
const AUDIT_EXCLUDED_MODELS = ['notification', 'userNotifier'];
async function newAuditLog(newValue, parentId, parentType, user) {
if (AUDIT_EXCLUDED_MODELS.includes(parentType)) return;
// Filter out createdAt and updatedAt from newValue
const filteredNewValue = { ...newValue };
delete filteredNewValue.createdAt;
@ -372,6 +379,8 @@ async function newAuditLog(newValue, parentId, parentType, user) {
}
async function editAuditLog(oldValue, newValue, parentId, parentType, user) {
if (AUDIT_EXCLUDED_MODELS.includes(parentType)) return;
// Get only the changed values
const changedOldValues = getChangedValues(oldValue, newValue, true);
const changedNewValues = getChangedValues(oldValue, newValue, false);
@ -398,7 +407,35 @@ async function editAuditLog(oldValue, newValue, parentId, parentType, user) {
await distributeNew(auditLog._id, 'auditLog');
}
async function editNotification(oldValue, newValue, parentId, parentType, user) {
const model = getModelByName(parentType);
const objectName = oldValue?.name ?? newValue?.name ?? model?.label ?? parentType;
const changedOldValues = getChangedValues(oldValue, newValue, true);
const changedNewValues = getChangedValues(oldValue, newValue, false);
if (Object.keys(changedOldValues).length === 0 || Object.keys(changedNewValues).length === 0) {
return;
}
await notfiyObjectUserNotifiers(
parentId,
parentType,
`${objectName} edited by ${user?.firstName ?? 'unknown'} ${user?.lastName ?? ''}`,
`The ${parentType} ${parentId} has been updated.`,
'editObject',
{
old: changedOldValues,
new: changedNewValues,
objectType: parentType,
object: { _id: parentId },
user: { _id: user._id, firstName: user.firstName, lastName: user.lastName },
}
);
}
async function deleteAuditLog(deleteValue, parentId, parentType, user) {
if (AUDIT_EXCLUDED_MODELS.includes(parentType)) return;
const auditLog = new auditLogModel({
changes: {
old: deleteValue,
@ -415,6 +452,24 @@ async function deleteAuditLog(deleteValue, parentId, parentType, user) {
await distributeNew(auditLog._id, 'auditLog');
}
async function deleteNotification(object, parentId, parentType, user) {
const model = getModelByName(parentType);
const objectName = oldValue?.name ?? newValue?.name ?? model?.label ?? parentType;
await notfiyObjectUserNotifiers(
parentId,
parentType,
`${objectName} deleted by ${user?.firstName ?? 'unknown'} ${user?.lastName ?? ''}`,
`The ${parentType} ${parentId} has been deleted.`,
'deleteObject',
{
object: object,
objectType: parentType,
object: { _id: parentId },
user: { _id: user._id, firstName: user.firstName, lastName: user.lastName },
}
);
}
async function getAuditLogs(idOrIds) {
if (Array.isArray(idOrIds)) {
return auditLogModel.find({ parent: { $in: idOrIds } }).populate('owner');
@ -493,6 +548,27 @@ async function distributeChildNew(value, id, model) {
}
}
async function notfiyObjectUserNotifiers(id, objectType, title, message, type = 'info', metadata) {
const userNotifiers = await userNotifierModel.find({ object: id, objectType: objectType });
for (const userNotifier of userNotifiers) {
await createNotification(userNotifier.user._id, title, message, type, metadata);
}
}
async function createNotification(user, title, message, type = 'info', metadata) {
const notification = new notificationModel({
user,
title,
message,
type,
metadata,
});
await notification.save();
const value = notification.toJSON ? notification.toJSON() : notification;
await natsServer.publish(`notifications.${user._id}`, value);
return notification;
}
function flatternObjectIds(object) {
if (!object || typeof object !== 'object') {
return object;
@ -707,6 +783,8 @@ export {
parseFilter,
convertToCamelCase,
newAuditLog,
editNotification,
deleteNotification,
editAuditLog,
deleteAuditLog,
getAuditLogs,
@ -719,6 +797,7 @@ export {
distributeChildUpdate,
distributeChildDelete,
distributeChildNew,
notfiyObjectUserNotifiers,
getFilter, // <-- add here
convertPropertiesString,
getFileMeta,