cryptpad/scripts/user-statistics.js

133 lines
4.5 KiB
JavaScript

// SPDX-FileCopyrightText: 2025 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
const nThen = require("nthen");
const Semaphore = require("saferphore");
const Logger = require("../lib/log");
const Pins = require("../lib/pins");
const config = require("../lib/load-config");
const BlobStorage = require("../lib/storage/blob");
const Store = require("../lib/storage/file");
const Fs = require('node:fs');
const Quota = require("../lib/commands/quota");
const Environment = require('../lib/env');
const Env = Environment.create(config);
const CSV = true;
config.logPath = false;
config.logToStdout = true;
const start = () => {
let time = +new Date();
let Log = {};
let all = {};
let blobStore, store;
nThen(w => {
Logger.create(config, w(function (_log) {
Env.Log = Log = _log;
}));
}).nThen(w => {
config.getSession = function () {};
Store.create(config, w(function (err, _store) {
if (err) {
w.abort();
return void Log.error("ERR_PAD_STORE", err);
}
store = _store;
}));
BlobStorage.create(config, w(function (err, _store) {
if (err) {
w.abort();
return void Log.error("ERR_BLOB_STORE", err);
}
blobStore = _store;
}));
}).nThen(w => {
Quota.updateCachedLimits(Env, w((err) => {
if (err) {
return Env.Log.warn('UPDATE_QUOTA_ERR', err);
}
Env.Log.info('QUOTA_UPDATED', {});
}));
}).nThen(w => {
Env.Log.info('START_LOADING_PINS');
const handlePinLog = (content, id, next) => {
const sema = Semaphore.create(20);
const data = all[id] = {
size: 0,
n_pads: 0,
n_blobs: 0,
n_total: 0,
first: content.first,
last: content.latest
};
if (!content.user) {
data.maybeTeam = true;
}
nThen(ww => {
Object.keys(content.pins).forEach(id => {
sema.take(give => {
let addSize = ww(give((e, s) => {
if (typeof(s) !== "number") {
return; // XXX
}
data.size += s;
data.n_total++;
if (id.length === 32) {
data.n_pads++;
} else {
data.n_blobs++;
}
}));
if (id.length === 32) { // PAD
return store.getChannelSize(id, addSize);
}
blobStore.size(id, addSize);
});
});
}).nThen(() => {
let key = id.replace(/-/g, '/');
if (Env.limits[key]) {
let sub = Env.limits[key];
data.premium = sub?.plan;
}
Env.Log.info('PIN_LOG_HANDLED', key);
next();
});
};
Pins.load(w(() => {
let duration = +new Date() - time;
Env.Log.info('ALL_PINS_LOADED', duration);
}), {
pinPath: config.pinPath,
handler: handlePinLog,
});
}).nThen(() => {
if (!CSV) { return console.log(all); }
let csv = `"User key","Premium plan","Bytes","Number pads","Number blobs","First activity","Last activity","May be a team"\n`;
Object.keys(all).sort((a,b) => {
return all[b].size - all[a].size;
}).forEach(k => {
const data = all[k];
k = k.replace(/-/g, '/');
let first = new Date(data.first).toISOString().slice(0,10);
let last = new Date(data.last).toISOString().slice(0,10);
let plan = data.premium || '';
let t = String(!!data.maybeTeam);
csv += `"${k}","${plan}","${data.size}","${data.n_pads}","${data.n_blobs}","${first}","${last}","${t}"\n`;
});
let filename = `../${new Date().toISOString().slice(0,10)}-stats.csv`;
Fs.writeFile(filename, csv, err => {
if (err) {
console.error(err);
} else {
console.log('CSV available at', filename);
}
});
});
};
start();