// 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 { SiteService } = 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: 'event', populate: [ { path: 'attachment', }, ], }, ]; } async start ( ) { await super.start(); this.templates = { }; this.templates.notification = pug.compileFile(path.join(this.dtp.config.root, 'app', 'views', 'notification', 'components', 'notification-standalone.pug')); } middleware (options) { options = Object.assign({ withNotifications: false, }, options || { }); return async (req, res, next) => { res.locals.middleware = res.locals.middleware || { }; const data = res.locals.middleware.notifications = { }; if (!req.user) { // requests with no user don't matter to this middleware return next(); } try { data.newCount = await this.getNewCountForUser(req.user); if (options.withNotifications) { data.new = await this.getForUser(req.user, { skip: 0, cpp: 10 }); } return next(); } catch (error) { this.log.error('failed to populate route with notifications data', { error }); return next(error); } }; } async create (user, event) { const notification = new UserNotification(); notification.created = event.created; notification.user = user._id; notification.status = 'new'; notification.event = event._id; await notification.save(); await this.dtp.redis.hincrby(`user:${user._id}:notification`, 'newCount', 1); return notification.toObject(); } async getNewCountForUser (user) { const userKey = `user:${user._id}:notification`; let count; const value = await this.dtp.redis.hget(userKey, 'newCount'); if (!value) { count = await UserNotification.countDocuments({ user: user._id, status: 'new' }); await this.dtp.redis.hset(userKey, 'newCount', count); return count; } count = parseInt(value, 10); if (count < 0) { count = await UserNotification.countDocuments({ user: user._id, status: 'new' }); await this.dtp.redis.hset(userKey, 'newCount', count); } return 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.filter((notif) => notif.status === 'new'); if (newNotifications.length > 0) { await UserNotification.updateMany( { _id: { $in: newNotifications.map((notif) => notif._id) } }, { $set: { status: 'seen' } }, ); await this.dtp.redis.hincrby( `user:${user._id}:notification`, 'newCount', -(newNotifications.length), ); } return notifications; } async getById (notificationId) { const notification = await UserNotification .findById(notificationId) .populate(this.populateUserNotification) .lean(); return notification; } } module.exports = { name: 'userNotification', slug: 'user-notification', create: (dtp) => { return new UserNotificationService(dtp); }, };