const {decodeTime} = require('ulid'); // TODO all the duplication... const buildDict = (fn, ...args) => { const dict = {}; for (let [key, value] of fn(...args)) { dict[key] = value; } return dict; } const zip = (list, reverse) => { return buildDict(function* () { for (let [k, v] of list) { yield reverse ? [v, k] : [k, v]; } }); } const sortByValue = (obj, reverse = false, firstN = -1) => { let list = []; for (let i in obj) { if (obj.hasOwnProperty(i)) { list.push([parseInt(obj[i]), i]); } } list = list.sort((a, b) => reverse ? b[0] - a[0] : a[0] - b[0]); if (firstN >= 0) { list = list.slice(0, firstN); } return zip(list, true); } const formatMonth = d => `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`; const buildChart = (rows) => { const dates = rows.map(row => new Date(decodeTime(row.id))); const chart = {}; let loop = dates[0]; const end = dates[dates.length - 1]; while(loop <= end){ chart[formatMonth(loop)] = 0; loop = new Date(loop.setDate(loop.getDate() + 1)); } chart[formatMonth(loop)] = 0; for (let date of dates) { chart[formatMonth(date)]++; } return chart; } module.exports.statsFile = process.env.STATS_FILE.replace('%projectdir%', __dirname + '/..') module.exports.calculateStats = async (db, allLocales) => { const users = { overall: (await db.get(`SELECT count(*) AS c FROM users`)).c, admins: (await db.get(`SELECT count(*) AS c FROM users WHERE roles!=''`)).c, chart: buildChart(await db.all(`SELECT id FROM users ORDER BY id`)), }; const locales = {}; for (let locale in allLocales) { if (!allLocales.hasOwnProperty(locale)) { continue; } const profiles = await db.all(`SELECT pronouns, flags FROM profiles WHERE locale='${locale}'`); const pronouns = {} const flags = {} for (let profile of profiles) { const pr = JSON.parse(profile.pronouns); for (let pronoun in pr) { if (!pr.hasOwnProperty(pronoun)) { continue; } if (pronoun.includes(',') || pr[pronoun] < 0) { continue; } const p = pronoun.replace(/^.*:\/\//, '').replace(/^\//, '').toLowerCase().replace(/^[a-z]+\.[^/]+\//, ''); if (pronouns[p] === undefined) { pronouns[p] = 0; } pronouns[p] += pr[pronoun] === 1 ? 1 : 0.5; } const fl = JSON.parse(profile.flags); for (let flag of fl) { if (flags[flag] === undefined) { flags[flag] = 0; } flags[flag] += 1; } } locales[locale] = { name: allLocales[locale].name, url: allLocales[locale].url, profiles: profiles.length, pronouns: sortByValue(pronouns, true, 36), flags: sortByValue(flags, true, 36), nouns: { approved: (await db.get(`SELECT count(*) AS c FROM nouns WHERE locale='${locale}' AND approved=1 AND deleted=0`)).c, awaiting: (await db.get(`SELECT count(*) AS c FROM nouns WHERE locale='${locale}' AND approved=0 AND deleted=0`)).c, }, chart: buildChart(await db.all(`SELECT id FROM profiles WHERE locale='${locale}' ORDER BY id`)), }; } const cardsCount = (await db.get(`SELECT 1.0 * (SELECT count(*) FROM profiles WHERE card IS NOT NULL) / (SELECT count(*) FROM profiles) as c`)).c; const cards = Math.round(cardsCount * 1000) / 1000; return { calculatedAt: parseInt(new Date() / 1000), users, locales, cards }; }