Files
shields/core/server/analytics.js
2019-01-16 16:30:18 -05:00

149 lines
4.1 KiB
JavaScript

'use strict'
const fs = require('fs')
const { URL } = require('url')
// We can either use a process-wide object regularly saved to a JSON file,
// or a Redis equivalent (for multi-process / when the filesystem is unreliable.
let redis
let useRedis = false
if (process.env.REDISTOGO_URL) {
const { port, hostname, password } = new URL(process.env.REDISTOGO_URL)
redis = require('redis').createClient(port, hostname)
redis.auth(password)
useRedis = true
}
let analytics = {}
let autosaveIntervalId
const analyticsPath = process.env.SHIELDS_ANALYTICS_FILE || './analytics.json'
function performAutosave() {
const contents = JSON.stringify(analytics)
if (useRedis) {
redis.set(analyticsPath, contents)
} else {
fs.writeFileSync(analyticsPath, contents)
}
}
function scheduleAutosaving() {
const analyticsAutoSavePeriod = 10000
autosaveIntervalId = setInterval(performAutosave, analyticsAutoSavePeriod)
}
// For a clean shutdown.
function cancelAutosaving() {
if (autosaveIntervalId) {
clearInterval(autosaveIntervalId)
autosaveIntervalId = null
}
performAutosave()
}
function defaultAnalytics() {
const analytics = Object.create(null)
// In case something happens on the 36th.
analytics.vendorMonthly = new Array(36)
resetMonthlyAnalytics(analytics.vendorMonthly)
analytics.rawMonthly = new Array(36)
resetMonthlyAnalytics(analytics.rawMonthly)
analytics.vendorFlatMonthly = new Array(36)
resetMonthlyAnalytics(analytics.vendorFlatMonthly)
analytics.rawFlatMonthly = new Array(36)
resetMonthlyAnalytics(analytics.rawFlatMonthly)
analytics.vendorFlatSquareMonthly = new Array(36)
resetMonthlyAnalytics(analytics.vendorFlatSquareMonthly)
analytics.rawFlatSquareMonthly = new Array(36)
resetMonthlyAnalytics(analytics.rawFlatSquareMonthly)
return analytics
}
function load() {
const defaultAnalyticsObject = defaultAnalytics()
if (useRedis) {
redis.get(analyticsPath, (err, value) => {
if (err == null && value != null) {
// if/try/return trick:
// if error, then the rest of the function is run.
try {
analytics = JSON.parse(value)
// Extend analytics with a new value.
for (const key in defaultAnalyticsObject) {
if (!(key in analytics)) {
analytics[key] = defaultAnalyticsObject[key]
}
}
return
} catch (e) {
console.error('Invalid Redis analytics, resetting.')
console.error(e)
}
}
analytics = defaultAnalyticsObject
})
} else {
// Not using Redis.
try {
analytics = JSON.parse(fs.readFileSync(analyticsPath))
// Extend analytics with a new value.
for (const key in defaultAnalyticsObject) {
if (!(key in analytics)) {
analytics[key] = defaultAnalyticsObject[key]
}
}
} catch (e) {
if (e.code !== 'ENOENT') {
console.error('Invalid JSON file for analytics, resetting.')
console.error(e)
}
analytics = defaultAnalyticsObject
}
}
}
let lastDay = new Date().getDate()
function resetMonthlyAnalytics(monthlyAnalytics) {
for (let i = 0; i < monthlyAnalytics.length; i++) {
monthlyAnalytics[i] = 0
}
}
function incrMonthlyAnalytics(monthlyAnalytics) {
try {
const currentDay = new Date().getDate()
// If we changed month, reset empty days.
while (lastDay !== currentDay) {
// Assumption: at least a hit a month.
lastDay = (lastDay + 1) % monthlyAnalytics.length
monthlyAnalytics[lastDay] = 0
}
monthlyAnalytics[currentDay]++
} catch (e) {
console.error(e.stack)
}
}
function noteRequest(queryParams, match) {
incrMonthlyAnalytics(analytics.vendorMonthly)
if (queryParams.style === 'flat') {
incrMonthlyAnalytics(analytics.vendorFlatMonthly)
} else if (queryParams.style === 'flat-square') {
incrMonthlyAnalytics(analytics.vendorFlatSquareMonthly)
}
}
function setRoutes(server) {
server.ajax.on('analytics/v1', (json, end) => {
end(analytics)
})
}
module.exports = {
load,
scheduleAutosaving,
cancelAutosaving,
noteRequest,
setRoutes,
}