// dashboard.js // Copyright (C) 2021 Digital Telepresence, LLC // License: Apache-2.0 'use strict'; const mongoose = require('mongoose'); const Link = mongoose.model('Link'); const LinkVisit = mongoose.model('LinkVisit'); const moment = require('moment'); const { SiteService } = require('../../lib/site-lib'); const { link } = require('../../config/limiter'); class DashboardService extends SiteService { static get CACHE_ENABLED ( ) { return process.env.LINKS_DASHBOARD_CACHE === 'enabled'; } constructor (dtp) { super(dtp, module.exports); } /* * * USER VISIT STATS * */ async getUserVisitStats (user) { const { cache: cacheService } = this.dtp.services; let stats; const cacheKey = `stats:user:${user._id}:visit`; if (DashboardService.CACHE_ENABLED) { stats = await cacheService.getObject(cacheKey); if (stats) { return stats; } } this.log.info('getUserVisitStats', 'generating user visit stats report', { userId: user._id }); const END_DATE = new Date(); const START_DATE = moment(END_DATE).subtract(7, 'day').toDate(); const links = await Link.find({ user: user._id }).lean(); const linkIds = links.map((link) => link._id); stats = await LinkVisit.aggregate([ { $match: { link: { $in: linkIds }, $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; } /* * * LINK VISIT STATS * */ async getLinkVisitStats (link) { const { cache: cacheService } = this.dtp.services; let stats; const cacheKey = `stats:link:${link._id}:visit`; if (DashboardService.CACHE_ENABLED) { stats = await cacheService.getObject(cacheKey); if (stats) { return stats; } } this.log.info('getLinkVisitStats', 'generating link visit stats report', { linkId: link._id }); const END_DATE = new Date(); const START_DATE = moment(END_DATE).subtract(7, 'day').toDate(); stats = await LinkVisit.aggregate([ { $match: { link: link._id, $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; } /* * * USER COUNTRY STATS * */ async getUserCountryStats (user) { const { cache: cacheService } = this.dtp.services; let stats; const cacheKey = `stats:user:${user._id}:country`; if (DashboardService.CACHE_ENABLED) { stats = await cacheService.getObject(cacheKey); if (stats) { return stats; } } this.log.info('getUserCountryStats', 'generating user country stats report', { userId: user._id }); const END_DATE = new Date(); const START_DATE = moment(END_DATE).subtract(7, 'day').toDate(); const links = await Link.find({ user: user._id }).lean(); const linkIds = links.map((link) => link._id); stats = await LinkVisit.aggregate([ { $match: { link: { $in: linkIds }, $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; } /* * * LINK COUNTRY STATS * */ async getLinkCountryStats (link) { const { cache: cacheService } = this.dtp.services; let stats; const cacheKey = `stats:link:${link._id}:country`; if (DashboardService.CACHE_ENABLED) { stats = await cacheService.getObject(cacheKey); if (stats) { return stats; } } this.log.info('getLinkCountryStats', 'generating link country stats report', { linkId: link._id }); const END_DATE = new Date(); const START_DATE = moment(END_DATE).subtract(7, 'day').toDate(); stats = await LinkVisit.aggregate([ { $match: { link: link._id, $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; } /* * * USER CITY STATS * */ async getUserCityStats (user) { const { cache: cacheService } = this.dtp.services; let stats; const cacheKey = `stats:user:${user._id}:city`; if (DashboardService.CACHE_ENABLED) { stats = await cacheService.getObject(cacheKey); if (stats) { return stats; } } this.log.info('getUserCityStats', 'generating user city stats report', { userId: user._id }); const END_DATE = new Date(); const START_DATE = moment(END_DATE).subtract(7, 'day').toDate(); const links = await Link.find({ user: user._id }).lean(); const linkIds = links.map((link) => link._id); stats = await LinkVisit.aggregate([ { $match: { link: { $in: linkIds }, $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; } /* * * LINK CITY STATS * */ async getLinkCityStats (link) { const { cache: cacheService } = this.dtp.services; let stats; const cacheKey = `stats:link:${link._id}:city`; if (DashboardService.CACHE_ENABLED) { stats = await cacheService.getObject(cacheKey); if (stats) { return stats; } } this.log.info('getLinkCityStats', 'generating link city stats report', { linkId: link._id }); const END_DATE = new Date(); const START_DATE = moment(END_DATE).subtract(7, 'day').toDate(); stats = await LinkVisit.aggregate([ { $match: { link: link._id, $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; } } module.exports = { slug: 'dashboard', name: 'dashboard', create: (dtp) => { return new DashboardService(dtp); }, };