16 changed files with 411 additions and 42 deletions
@ -0,0 +1,276 @@ |
|||||
|
// 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); }, |
||||
|
}; |
@ -0,0 +1,5 @@ |
|||||
|
extends ../layouts/main |
||||
|
block content |
||||
|
|
||||
|
h1 Core Connect Response |
||||
|
pre= JSON.stringify(txConnect, null, 2) |
After Width: | Height: | Size: 502 KiB |
Loading…
Reference in new issue