// dashboard.js // Copyright (C) 2021 Digital Telepresence, LLC // License: Apache-2.0 'use strict'; const mongoose = require('mongoose'); const User = mongoose.model('User'); const ResourceVisit = mongoose.model('ResourceVisit'); const moment = require('moment'); const { SiteService } = require('../../lib/site-lib'); class DashboardService extends SiteService { static get CACHE_ENABLED ( ) { return process.env.LINKS_DASHBOARD_CACHE === 'enabled'; } constructor (dtp) { super(dtp, module.exports); } async getResourceVisitStats (resourceType, resourceId) { if (!resourceId) { throw new Error('Invalid resource'); } // this will throw if not a valid ObjectId (or able to become one) resourceId = mongoose.Types.ObjectId(resourceId); const { cache: cacheService } = this.dtp.services; let stats; const cacheKey = `stats:${resourceType.toLowerCase()}:${resourceId.toString()}:visit`; if (DashboardService.CACHE_ENABLED) { stats = await cacheService.getObject(cacheKey); if (stats) { return stats; } } this.log.info('generating resource visit stats report', { resourceId }); const END_DATE = new Date(); const START_DATE = moment(END_DATE).subtract(3, 'day').toDate(); stats = await ResourceVisit.aggregate([ { $match: { resource: resourceId, $and: [ { created: { $gt: START_DATE } }, { created: { $lt: END_DATE } }, ], }, }, { $group: { _id: { year: { $year: '$created' }, month: { $month: '$created' }, day: { $dayOfMonth: '$created' }, hour: { $hour: '$created' }, }, count: { $sum: 1 }, }, }, { $project: { _id: false, date: { $dateFromParts: { year: '$_id.year', month: '$_id.month', day: '$_id.day', hour: '$_id.hour', }, }, count: '$count', }, }, { $sort: { date: 1 }, }, ]); const response = { start: START_DATE, end: END_DATE, stats }; await cacheService.setObjectEx(cacheKey, 60 * 5, response); return response; } /* * * RESOURCE COUNTRY STATS * */ async getResourceCountryStats (resourceType, resourceId) { const { cache: cacheService } = this.dtp.services; let stats; // this will throw if not a valid ObjectId (or able to become one) resourceId = mongoose.Types.ObjectId(resourceId); const cacheKey = `stats:${resourceType.toLowerCase()}:${resourceId.toString()}:country`; if (DashboardService.CACHE_ENABLED) { stats = await cacheService.getObject(cacheKey); if (stats) { return stats; } } this.log.info('generating resource country stats report', { resourceId }); const END_DATE = new Date(); const START_DATE = moment(END_DATE).subtract(3, 'day').toDate(); stats = await ResourceVisit.aggregate([ { $match: { resource: resourceId, $and: [ { created: { $gt: START_DATE } }, { created: { $lt: END_DATE } }, ], }, }, { $group: { _id: { country: '$geoip.country', }, count: { $sum: 1 }, }, }, { $project: { _id: false, country: '$_id.country', count: '$count', }, }, { $sort: { count: -1, country: 1 }, }, { $limit: 10, }, ]); const response = { start: START_DATE, end: END_DATE, stats }; await cacheService.setObjectEx(cacheKey, 60 * 5, response); return response; } /* * * RESOURCE CITY STATS * */ async getResourceCityStats (resourceType, resourceId) { const { cache: cacheService } = this.dtp.services; let stats; // this will throw if not a valid ObjectId (or able to become one) resourceId = mongoose.Types.ObjectId(resourceId); const cacheKey = `stats:${resourceType.toLowerCase()}:${resourceId.toString()}:city`; if (DashboardService.CACHE_ENABLED) { stats = await cacheService.getObject(cacheKey); if (stats) { return stats; } } this.log.info('generating resource city stats report', { resourceId }); const END_DATE = new Date(); const START_DATE = moment(END_DATE).subtract(3, 'day').toDate(); stats = await ResourceVisit.aggregate([ { $match: { resource: resourceId, $and: [ { created: { $gt: START_DATE } }, { created: { $lt: END_DATE } }, ], }, }, { $group: { _id: { country: '$geoip.country', region: '$geoip.region', city: '$geoip.city', }, count: { $sum: 1 }, }, }, { $project: { _id: false, country: '$_id.country', region: '$_id.region', city: '$_id.city', count: '$count', }, }, { $sort: { count: -1, city: 1, region: 1, country: 1 }, }, { $limit: 10, }, ]); const response = { start: START_DATE, end: END_DATE, stats }; await cacheService.setObjectEx(cacheKey, 60 * 5, response); return response; } async getUserSignupsPerHour ( ) { this.log.info('generating user signup stats report'); const start = moment().subtract(7, 'day').toDate(); start.setHours(0, 0, 0, 0); const stats = await User.aggregate([ { $match: { created: { $gt: start }, }, }, { $group: { _id: { year: { $year: '$created' }, month: { $month: '$created' }, day: { $dayOfMonth: '$created' }, hour: { $hour: '$created' }, }, count: { $sum: 1 }, }, }, { $project: { _id: false, date: { $dateFromParts: { year: '$_id.year', month: '$_id.month', day: '$_id.day', hour: '$_id.hour', }, }, count: '$count', }, }, { $sort: { date: 1 }, }, ]); return stats; } } module.exports = { slug: 'dashboard', name: 'dashboard', create: (dtp) => { return new DashboardService(dtp); }, };