// user-notification.js // Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; const path = require('path'); const mongoose = require('mongoose'); const UserNotification = mongoose.model('UserNotification'); const pug = require('pug'); const striptags = require('striptags'); const { SiteService, SiteError } = require('../../lib/site-lib'); class UserNotificationService extends SiteService { constructor (dtp) { super(dtp, module.exports); this.populateUserNotification = [ { path: 'user', select: '_id username username_lc displayName picture', }, { path: 'attachment', }, ]; } async start ( ) { this.templates = { }; this.templates.notification = pug.compileFile(path.join(this.dtp.config.root, 'app', 'views', 'notification', 'components', 'notification-standalone.pug')); } async create (user, notificationDefinition) { const NOW = new Date(); /* * Validate general notification data */ if (!notificationDefinition.action) { throw new SiteError(406, 'Missing action'); } if (!notificationDefinition.content) { throw new SiteError(406, 'Missing content'); } if (!notificationDefinition.href) { throw new SiteError(406, 'Missing href'); } /* * Validate source data */ if (!notificationDefinition.source) { throw new SiteError(406, 'Missing source information'); } /* * Validate source site */ if (!notificationDefinition.source.site) { throw new SiteError(406, 'Missing source site information'); } if (!notificationDefinition.source.site.name) { throw new SiteError(406, 'Missing source site name'); } if (!notificationDefinition.source.site.description) { throw new SiteError(406, 'Missing source site description'); } if (!notificationDefinition.source.site.domain) { throw new SiteError(406, 'Missing source site domain'); } if (!notificationDefinition.source.site.domainKey) { throw new SiteError(406, 'Missing source site domain key'); } if (!notificationDefinition.source.site.company) { throw new SiteError(406, 'Missing source site company name'); } if (!notificationDefinition.source.site.coreAuth || !notificationDefinition.source.site.coreAuth.scopes || !Array.isArray(notificationDefinition.source.site.coreAuth.scopes) || (notificationDefinition.source.site.coreAuth.scopes.length === 0)) { throw new SiteError(406, 'Missing source site Core auth or scope information'); } /* * Validate source package */ if (!notificationDefinition.source.pkg) { throw new SiteError(406, 'Missing source package information'); } if (!notificationDefinition.source.pkg.name) { throw new SiteError(406, 'Missing source package name'); } if (!notificationDefinition.source.pkg.version) { throw new SiteError(406, 'Missing source package version'); } /* * Validate source emitter */ if (!notificationDefinition.source.emitter) { throw new SiteError(406, 'Missing source emitter information'); } if (!notificationDefinition.source.emitter.emitterId) { throw new SiteError(406, 'Missing source emitter ID'); } if (!notificationDefinition.source.emitter.username) { throw new SiteError(406, 'Missing source emitter username'); } if (!notificationDefinition.source.emitter.href) { throw new SiteError(406, 'Missing source emitter href'); } const notification = new UserNotification(); notification.created = NOW; notification.user = user._id; notification.status = 'new'; notification.action = striptags(notificationDefinition.action.trim().toLowerCase()); if (notificationDefinition.label) { notification.label = striptags(notificationDefinition.label.trim()); } notification.content = striptags(notificationDefinition.content.trim()); notification.href = striptags(notificationDefinition.href.trim()); notification.source = { pkg: { name: striptags(notificationDefinition.source.pkt.name.trim().toLowerCase()), version: striptags(notificationDefinition.source.pkt.version.trim()), }, site: { name: striptags(notificationDefinition.source.site.name.trim()), domain: striptags(notificationDefinition.source.site.domain.trim().toLowerCase()), domainKey: striptags(notificationDefinition.source.site.domainKey.trim().toLowerCase()), coreAuth: { scopes: notificationDefinition.source.site.coreAuth.scopes.map((scope) => scope.trim().toLowerCase()), }, }, emitter: { emitterId: mongoose.Types.ObjectId(notificationDefinition.source.emitter.emitterId), username: striptags(notificationDefinition.source.emitter.username.trim()), href: striptags(notificationDefinition.source.emitter.href.trim()), }, }; if (notificationDefinition.source.site.company) { notification.source.site.company = striptags(notificationDefinition.source.site.company.trim()); } if (notificationDefinition.source.site.description) { notification.source.site.description = striptags(notificationDefinition.source.site.description.trim()); } if (notificationDefinition.source.emitter.displayName) { notification.source.emitter.displayName = striptags(notificationDefinition.source.emitter.displayName); } notification.attachmentType = notificationDefinition.attachmentType; notification.attachment = notificationDefinition.attachment; await notification.save(); return notification.toObject(); } async getNewCountForUser (user) { const result = await UserNotification.aggregate([ { $match: { user: user._id, status: 'new', }, }, { $group: { _id: { user: 1 }, count: { $sum: 1 }, }, }, { $project: { _id: -1, count: '$count', }, }, ]); this.log('getNewCountForUser', { result }); return result[0].count; } async getForUser (user, pagination) { const notifications = await UserNotification .find({ user: user._id }) .sort({ created: -1 }) .skip(pagination.skip) .limit(pagination.cpp) .populate(this.populateUserNotification) .lean(); const newNotifications = notifications.map((notif) => notif.status === 'new'); if (newNotifications.length > 0) { await UserNotification.updateMany( { _id: { $in: newNotifications.map((notif) => notif._id) } }, { $set: { stats: 'seen' } }, ); } return notifications; } } module.exports = { slug: 'user-notification', name: 'userNotification', create: (dtp) => { return new UserNotificationService(dtp); }, };