paricafe/src/services/stats.ts

914 lines
17 KiB
TypeScript
Raw Normal View History

2018-10-20 20:35:37 -05:00
/**
*
*/
2018-10-20 20:05:15 -05:00
const nestedProperty = require('nested-property');
2018-10-20 22:37:00 -05:00
import autobind from 'autobind-decorator';
2018-10-20 17:10:35 -05:00
import * as mongo from 'mongodb';
import db from '../db/mongodb';
2018-10-21 00:08:05 -05:00
import Note, { INote } from '../models/note';
import User, { isLocalUser, IUser } from '../models/user';
import DriveFile, { IDriveFile } from '../models/drive-file';
2018-10-20 17:10:35 -05:00
import { ICollection } from 'monk';
2018-10-21 03:51:35 -05:00
import Following from '../models/following';
2018-10-20 17:10:35 -05:00
type Obj = { [key: string]: any };
type Partial<T> = {
[P in keyof T]?: Partial<T[P]>;
};
2018-10-20 20:05:15 -05:00
type ArrayValue<T> = {
[P in keyof T]: T[P] extends number ? Array<T[P]> : ArrayValue<T[P]>;
};
2018-10-20 17:10:35 -05:00
type Span = 'day' | 'hour';
2018-10-20 20:05:15 -05:00
//#region Chart Core
2018-10-21 01:08:07 -05:00
type Log<T extends Obj> = {
2018-10-20 17:10:35 -05:00
_id: mongo.ObjectID;
2018-10-20 19:20:11 -05:00
/**
*
*/
group?: any;
2018-10-20 17:10:35 -05:00
/**
*
*/
date: Date;
/**
*
*/
span: Span;
/**
*
*/
data: T;
2018-10-20 22:37:00 -05:00
/**
*
*/
unique?: Obj;
2018-10-20 17:10:35 -05:00
};
2018-10-20 20:35:37 -05:00
/**
*
*/
2018-10-21 01:08:07 -05:00
abstract class Stats<T> {
protected collection: ICollection<Log<T>>;
2018-10-21 13:30:45 -05:00
protected abstract async getTemplate(init: boolean, latest?: T, group?: any): Promise<T>;
2018-10-20 17:10:35 -05:00
2018-10-21 13:41:50 -05:00
constructor(name: string, grouped = false) {
2018-10-21 01:08:07 -05:00
this.collection = db.get<Log<T>>(`stats.${name}`);
2018-10-21 13:41:50 -05:00
if (grouped) {
this.collection.createIndex({ span: -1, date: -1, group: -1 }, { unique: true });
} else {
this.collection.createIndex({ span: -1, date: -1 }, { unique: true });
}
2018-10-20 17:10:35 -05:00
}
2018-10-20 22:37:00 -05:00
@autobind
private convertQuery(x: Obj, path: string): Obj {
const query: Obj = {};
const dive = (x: Obj, path: string) => {
Object.entries(x).forEach(([k, v]) => {
const p = path ? `${path}.${k}` : k;
if (typeof v === 'number') {
query[p] = v;
} else {
dive(v, p);
}
});
};
dive(x, path);
return query;
}
@autobind
2018-10-21 02:54:07 -05:00
private async getCurrentLog(span: Span, group?: any): Promise<Log<T>> {
2018-10-20 17:10:35 -05:00
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
const h = now.getHours();
const current =
span == 'day' ? new Date(y, m, d) :
span == 'hour' ? new Date(y, m, d, h) :
null;
// 現在(今日または今のHour)の統計
2018-10-21 01:08:07 -05:00
const currentLog = await this.collection.findOne({
2018-10-20 19:20:11 -05:00
group: group,
2018-10-20 17:10:35 -05:00
span: span,
date: current
2018-10-20 19:20:11 -05:00
});
2018-10-20 17:10:35 -05:00
2018-10-21 01:08:07 -05:00
if (currentLog) {
return currentLog;
2018-10-21 00:44:37 -05:00
}
// 集計期間が変わってから、初めてのチャート更新なら
// 最も最近の統計を持ってくる
// * 例えば集計期間が「日」である場合で考えると、
// * 昨日何もチャートを更新するような出来事がなかった場合は、
// * 統計がそもそも作られずドキュメントが存在しないということがあり得るため、
// * 「昨日の」と決め打ちせずに「もっとも最近の」とします
2018-10-21 13:30:45 -05:00
const latest = await this.collection.findOne({
2018-10-21 00:44:37 -05:00
group: group,
span: span
}, {
sort: {
date: -1
}
});
2018-10-21 13:30:45 -05:00
if (latest) {
2018-10-21 00:44:37 -05:00
// 現在の統計を初期挿入
2018-10-21 13:30:45 -05:00
const data = await this.getTemplate(false, latest.data);
2018-10-21 00:44:37 -05:00
2018-10-21 01:08:07 -05:00
const log = await this.collection.insert({
2018-10-21 00:44:37 -05:00
group: group,
span: span,
date: current,
data: data
});
2018-10-21 01:08:07 -05:00
return log;
2018-10-20 17:10:35 -05:00
} else {
2018-10-21 00:44:37 -05:00
// 統計が存在しなかったら
// * Misskeyインスタンスを建てて初めてのチャート更新時など
// 空の統計を作成
2018-10-21 03:51:35 -05:00
const data = await this.getTemplate(true, null, group);
2018-10-21 00:44:37 -05:00
2018-10-21 01:08:07 -05:00
const log = await this.collection.insert({
2018-10-20 19:20:11 -05:00
group: group,
2018-10-21 00:44:37 -05:00
span: span,
date: current,
data: data
2018-10-20 17:10:35 -05:00
});
2018-10-21 01:08:07 -05:00
return log;
2018-10-20 17:10:35 -05:00
}
}
2018-10-20 22:37:00 -05:00
@autobind
2018-10-21 02:54:07 -05:00
protected commit(query: Obj, group?: any, uniqueKey?: string, uniqueValue?: string): void {
2018-10-21 01:08:07 -05:00
const update = (log: Log<T>) => {
2018-10-20 22:37:00 -05:00
// ユニークインクリメントの場合、指定のキーに指定の値が既に存在していたら弾く
2018-10-21 00:47:44 -05:00
if (
uniqueKey &&
2018-10-21 01:08:07 -05:00
log.unique &&
log.unique[uniqueKey] &&
log.unique[uniqueKey].includes(uniqueValue)
2018-10-21 00:47:44 -05:00
) return;
// ユニークインクリメントの指定のキーに値を追加
2018-10-20 22:37:00 -05:00
if (uniqueKey) {
query['$push'] = {
[`unique.${uniqueKey}`]: uniqueValue
};
}
this.collection.update({
2018-10-21 01:08:07 -05:00
_id: log._id
2018-10-20 22:37:00 -05:00
}, query);
2018-10-20 17:10:35 -05:00
};
2018-10-21 01:08:07 -05:00
this.getCurrentLog('day', group).then(log => update(log));
this.getCurrentLog('hour', group).then(log => update(log));
2018-10-20 22:37:00 -05:00
}
2018-10-20 17:10:35 -05:00
2018-10-20 22:37:00 -05:00
@autobind
2018-10-21 02:54:07 -05:00
protected inc(inc: Partial<T>, group?: any): void {
2018-10-20 22:37:00 -05:00
this.commit({
$inc: this.convertQuery(inc, 'data')
}, group);
}
2018-10-20 17:10:35 -05:00
2018-10-20 22:37:00 -05:00
@autobind
2018-10-21 02:54:07 -05:00
protected incIfUnique(inc: Partial<T>, key: string, value: string, group?: any): void {
2018-10-20 22:37:00 -05:00
this.commit({
$inc: this.convertQuery(inc, 'data')
}, group, key, value);
2018-10-20 17:10:35 -05:00
}
2018-10-20 22:37:00 -05:00
@autobind
2018-10-21 02:54:07 -05:00
public async getChart(span: Span, range: number, group?: any): Promise<ArrayValue<T>> {
2018-10-21 00:08:05 -05:00
const promisedChart: Promise<T>[] = [];
2018-10-20 17:10:35 -05:00
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
const h = now.getHours();
const gt =
span == 'day' ? new Date(y, m, d - range) :
span == 'hour' ? new Date(y, m, d, h - range) : null;
2018-10-21 01:08:07 -05:00
const logs = await this.collection.find({
2018-10-20 19:20:11 -05:00
group: group,
2018-10-20 17:10:35 -05:00
span: span,
date: {
$gt: gt
}
2018-10-20 19:20:11 -05:00
}, {
2018-10-20 17:10:35 -05:00
sort: {
date: -1
},
fields: {
_id: 0
}
});
for (let i = (range - 1); i >= 0; i--) {
const current =
span == 'day' ? new Date(y, m, d - i) :
span == 'hour' ? new Date(y, m, d, h - i) :
null;
2018-10-21 01:08:07 -05:00
const log = logs.find(l => l.date.getTime() == current.getTime());
2018-10-20 17:10:35 -05:00
2018-10-21 01:08:07 -05:00
if (log) {
promisedChart.unshift(Promise.resolve(log.data));
2018-10-20 17:10:35 -05:00
} else { // 隙間埋め
2018-10-21 01:08:07 -05:00
const latest = logs.find(l => l.date.getTime() < current.getTime());
2018-10-21 03:51:35 -05:00
promisedChart.unshift(this.getTemplate(false, latest ? latest.data : null));
2018-10-20 17:10:35 -05:00
}
}
2018-10-21 00:08:05 -05:00
const chart = await Promise.all(promisedChart);
2018-10-20 20:05:15 -05:00
const res: ArrayValue<T> = {} as any;
/**
* [{
2018-10-21 00:47:44 -05:00
* xxxxx: 1,
* yyyyy: 5
2018-10-20 20:05:15 -05:00
* }, {
2018-10-21 00:47:44 -05:00
* xxxxx: 2,
* yyyyy: 6
2018-10-20 20:05:15 -05:00
* }, {
2018-10-21 00:47:44 -05:00
* xxxxx: 3,
* yyyyy: 7
2018-10-20 20:05:15 -05:00
* }]
*
*
*
* {
2018-10-21 00:47:44 -05:00
* xxxxx: [1, 2, 3],
* yyyyy: [5, 6, 7]
2018-10-20 20:05:15 -05:00
* }
*
*
*/
const dive = (x: Obj, path?: string) => {
Object.entries(x).forEach(([k, v]) => {
2018-10-20 20:24:56 -05:00
const p = path ? `${path}.${k}` : k;
2018-10-20 20:05:15 -05:00
if (typeof v == 'object') {
dive(v, p);
} else {
nestedProperty.set(res, p, chart.map(s => nestedProperty.get(s, p)));
}
});
};
dive(chart[0]);
return res;
2018-10-20 17:10:35 -05:00
}
}
2018-10-20 20:05:15 -05:00
//#endregion
2018-10-20 17:10:35 -05:00
2018-10-20 19:20:11 -05:00
//#region Users stats
/**
*
*/
2018-10-21 01:08:07 -05:00
type UsersLog = {
2018-10-20 19:20:11 -05:00
local: {
/**
2018-10-21 03:28:27 -05:00
*
2018-10-20 19:20:11 -05:00
*/
total: number;
2018-10-20 17:10:35 -05:00
2018-10-20 19:20:11 -05:00
/**
2018-10-21 03:28:27 -05:00
*
2018-10-20 19:20:11 -05:00
*/
inc: number;
2018-10-20 17:10:35 -05:00
2018-10-20 19:20:11 -05:00
/**
2018-10-21 03:28:27 -05:00
*
2018-10-20 19:20:11 -05:00
*/
dec: number;
};
2018-10-20 17:10:35 -05:00
2018-10-21 03:28:27 -05:00
remote: UsersLog['local'];
2018-10-20 19:20:11 -05:00
};
2018-10-20 17:10:35 -05:00
2018-10-21 01:08:07 -05:00
class UsersStats extends Stats<UsersLog> {
2018-10-20 19:20:11 -05:00
constructor() {
2018-10-21 00:08:05 -05:00
super('users');
2018-10-20 19:20:11 -05:00
}
2018-10-20 17:10:35 -05:00
2018-10-20 22:37:00 -05:00
@autobind
2018-10-21 13:30:45 -05:00
protected async getTemplate(init: boolean, latest?: UsersLog): Promise<UsersLog> {
2018-10-21 01:08:07 -05:00
const [localCount, remoteCount] = init ? await Promise.all([
2018-10-21 00:08:05 -05:00
User.count({ host: null }),
User.count({ host: { $ne: null } })
]) : [
2018-10-21 13:30:45 -05:00
latest ? latest.local.total : 0,
latest ? latest.remote.total : 0
2018-10-21 00:08:05 -05:00
];
2018-10-20 17:10:35 -05:00
2018-10-20 19:20:11 -05:00
return {
local: {
2018-10-21 00:08:05 -05:00
total: localCount,
2018-10-20 19:20:11 -05:00
inc: 0,
dec: 0
},
remote: {
2018-10-21 00:08:05 -05:00
total: remoteCount,
2018-10-20 19:20:11 -05:00
inc: 0,
dec: 0
}
2018-10-20 17:10:35 -05:00
};
2018-10-20 19:20:11 -05:00
}
2018-10-20 17:10:35 -05:00
2018-10-20 22:37:00 -05:00
@autobind
2018-10-20 19:20:11 -05:00
public async update(user: IUser, isAdditional: boolean) {
const update: Obj = {};
2018-10-20 17:10:35 -05:00
2018-10-20 19:20:11 -05:00
update.total = isAdditional ? 1 : -1;
if (isAdditional) {
update.inc = 1;
} else {
update.dec = 1;
}
2018-10-20 17:10:35 -05:00
2018-10-20 19:20:11 -05:00
await this.inc({
[isLocalUser(user) ? 'local' : 'remote']: update
});
}
}
2018-10-20 17:10:35 -05:00
2018-10-21 01:08:07 -05:00
export const usersStats = new UsersStats();
2018-10-20 19:20:11 -05:00
//#endregion
2018-10-20 17:10:35 -05:00
2018-10-20 19:20:11 -05:00
//#region Notes stats
/**
* 稿
*/
2018-10-21 01:08:07 -05:00
type NotesLog = {
2018-10-20 19:20:11 -05:00
local: {
/**
2018-10-21 03:28:27 -05:00
* 稿
2018-10-20 19:20:11 -05:00
*/
total: number;
2018-10-20 17:10:35 -05:00
2018-10-20 19:20:11 -05:00
/**
2018-10-21 03:28:27 -05:00
* 稿
2018-10-20 19:20:11 -05:00
*/
inc: number;
2018-10-20 17:10:35 -05:00
2018-10-20 19:20:11 -05:00
/**
2018-10-21 03:28:27 -05:00
* 稿
2018-10-20 19:20:11 -05:00
*/
dec: number;
2018-10-20 17:10:35 -05:00
2018-10-20 19:20:11 -05:00
diffs: {
2018-10-20 17:10:35 -05:00
/**
2018-10-21 03:28:27 -05:00
* 稿
2018-10-20 17:10:35 -05:00
*/
2018-10-20 19:20:11 -05:00
normal: number;
2018-10-20 17:10:35 -05:00
/**
2018-10-21 03:28:27 -05:00
* 稿
2018-10-20 17:10:35 -05:00
*/
2018-10-20 19:20:11 -05:00
reply: number;
2018-10-20 17:10:35 -05:00
/**
2018-10-21 03:28:27 -05:00
* Renoteの投稿数の差分
2018-10-20 17:10:35 -05:00
*/
2018-10-20 19:20:11 -05:00
renote: number;
2018-10-20 17:10:35 -05:00
};
};
2018-10-21 03:28:27 -05:00
remote: NotesLog['local'];
2018-10-20 17:10:35 -05:00
};
2018-10-21 01:08:07 -05:00
class NotesStats extends Stats<NotesLog> {
2018-10-20 17:10:35 -05:00
constructor() {
2018-10-21 00:08:05 -05:00
super('notes');
2018-10-20 17:10:35 -05:00
}
2018-10-20 22:37:00 -05:00
@autobind
2018-10-21 13:30:45 -05:00
protected async getTemplate(init: boolean, latest?: NotesLog): Promise<NotesLog> {
2018-10-21 01:08:07 -05:00
const [localCount, remoteCount] = init ? await Promise.all([
2018-10-21 00:08:05 -05:00
Note.count({ '_user.host': null }),
Note.count({ '_user.host': { $ne: null } })
]) : [
2018-10-21 13:30:45 -05:00
latest ? latest.local.total : 0,
latest ? latest.remote.total : 0
2018-10-21 00:08:05 -05:00
];
2018-10-20 17:10:35 -05:00
return {
2018-10-20 19:20:11 -05:00
local: {
2018-10-21 00:08:05 -05:00
total: localCount,
2018-10-20 19:20:11 -05:00
inc: 0,
dec: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
2018-10-20 17:10:35 -05:00
}
},
2018-10-20 19:20:11 -05:00
remote: {
2018-10-21 00:08:05 -05:00
total: remoteCount,
2018-10-20 19:20:11 -05:00
inc: 0,
dec: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
2018-10-20 17:10:35 -05:00
}
}
};
}
2018-10-20 22:37:00 -05:00
@autobind
2018-10-20 19:20:11 -05:00
public async update(note: INote, isAdditional: boolean) {
2018-10-20 22:37:00 -05:00
const update: Obj = {
diffs: {}
};
2018-10-20 17:10:35 -05:00
update.total = isAdditional ? 1 : -1;
if (isAdditional) {
update.inc = 1;
} else {
update.dec = 1;
}
if (note.replyId != null) {
update.diffs.reply = isAdditional ? 1 : -1;
} else if (note.renoteId != null) {
update.diffs.renote = isAdditional ? 1 : -1;
} else {
update.diffs.normal = isAdditional ? 1 : -1;
}
2018-10-20 19:20:11 -05:00
await this.inc({
[isLocalUser(note._user) ? 'local' : 'remote']: update
});
}
}
2018-10-21 01:08:07 -05:00
export const notesStats = new NotesStats();
2018-10-20 19:20:11 -05:00
//#endregion
//#region Drive stats
/**
*
*/
2018-10-21 01:08:07 -05:00
type DriveLog = {
2018-10-20 19:20:11 -05:00
local: {
/**
2018-10-21 03:28:27 -05:00
*
2018-10-20 19:20:11 -05:00
*/
totalCount: number;
/**
2018-10-21 03:28:27 -05:00
*
2018-10-20 19:20:11 -05:00
*/
totalSize: number;
/**
2018-10-21 03:28:27 -05:00
*
2018-10-20 19:20:11 -05:00
*/
incCount: number;
/**
2018-10-21 03:28:27 -05:00
* 使
2018-10-20 19:20:11 -05:00
*/
incSize: number;
/**
2018-10-21 03:28:27 -05:00
*
2018-10-20 19:20:11 -05:00
*/
decCount: number;
/**
2018-10-21 03:28:27 -05:00
* 使
2018-10-20 19:20:11 -05:00
*/
decSize: number;
};
2018-10-20 17:10:35 -05:00
2018-10-21 03:28:27 -05:00
remote: DriveLog['local'];
2018-10-20 19:20:11 -05:00
};
2018-10-21 01:08:07 -05:00
class DriveStats extends Stats<DriveLog> {
2018-10-20 19:20:11 -05:00
constructor() {
2018-10-21 00:08:05 -05:00
super('drive');
2018-10-20 17:10:35 -05:00
}
2018-10-20 22:37:00 -05:00
@autobind
2018-10-21 13:30:45 -05:00
protected async getTemplate(init: boolean, latest?: DriveLog): Promise<DriveLog> {
2018-10-21 00:08:05 -05:00
const calcSize = (local: boolean) => DriveFile
.aggregate([{
$match: {
'metadata._user.host': local ? null : { $ne: null },
'metadata.deletedAt': { $exists: false }
}
}, {
$project: {
length: true
}
}, {
$group: {
_id: null,
usage: { $sum: '$length' }
}
}])
.then(res => res.length > 0 ? res[0].usage : 0);
2018-10-21 01:08:07 -05:00
const [localCount, remoteCount, localSize, remoteSize] = init ? await Promise.all([
2018-10-21 00:08:05 -05:00
DriveFile.count({ 'metadata._user.host': null }),
DriveFile.count({ 'metadata._user.host': { $ne: null } }),
calcSize(true),
calcSize(false)
]) : [
2018-10-21 13:30:45 -05:00
latest ? latest.local.totalCount : 0,
latest ? latest.remote.totalCount : 0,
latest ? latest.local.totalSize : 0,
latest ? latest.remote.totalSize : 0
2018-10-21 00:08:05 -05:00
];
2018-10-20 19:20:11 -05:00
return {
local: {
2018-10-21 00:08:05 -05:00
totalCount: localCount,
totalSize: localSize,
2018-10-20 19:20:11 -05:00
incCount: 0,
incSize: 0,
decCount: 0,
decSize: 0
},
remote: {
2018-10-21 00:08:05 -05:00
totalCount: remoteCount,
totalSize: remoteSize,
2018-10-20 19:20:11 -05:00
incCount: 0,
incSize: 0,
decCount: 0,
decSize: 0
}
};
}
2018-10-20 17:10:35 -05:00
2018-10-20 22:37:00 -05:00
@autobind
2018-10-20 19:20:11 -05:00
public async update(file: IDriveFile, isAdditional: boolean) {
2018-10-20 17:10:35 -05:00
const update: Obj = {};
update.totalCount = isAdditional ? 1 : -1;
update.totalSize = isAdditional ? file.length : -file.length;
if (isAdditional) {
update.incCount = 1;
update.incSize = file.length;
} else {
update.decCount = 1;
update.decSize = file.length;
}
2018-10-20 19:20:11 -05:00
await this.inc({
[isLocalUser(file.metadata._user) ? 'local' : 'remote']: update
});
}
}
2018-10-21 01:08:07 -05:00
export const driveStats = new DriveStats();
2018-10-20 19:20:11 -05:00
//#endregion
//#region Network stats
/**
*
*/
2018-10-21 01:08:07 -05:00
type NetworkLog = {
2018-10-20 19:20:11 -05:00
/**
*
*/
incomingRequests: number;
/**
*
*/
outgoingRequests: number;
2018-10-20 17:10:35 -05:00
2018-10-20 19:20:11 -05:00
/**
*
* TIP: (totalTime / incomingRequests)
*/
totalTime: number;
2018-10-20 17:10:35 -05:00
2018-10-20 19:20:11 -05:00
/**
*
*/
incomingBytes: number;
/**
*
*/
outgoingBytes: number;
};
2018-10-21 01:08:07 -05:00
class NetworkStats extends Stats<NetworkLog> {
2018-10-20 19:20:11 -05:00
constructor() {
2018-10-21 00:08:05 -05:00
super('network');
2018-10-20 19:20:11 -05:00
}
2018-10-20 22:37:00 -05:00
@autobind
2018-10-21 13:30:45 -05:00
protected async getTemplate(init: boolean, latest?: NetworkLog): Promise<NetworkLog> {
2018-10-20 19:20:11 -05:00
return {
incomingRequests: 0,
outgoingRequests: 0,
totalTime: 0,
incomingBytes: 0,
outgoingBytes: 0
};
}
2018-10-20 22:37:00 -05:00
@autobind
2018-10-20 19:20:11 -05:00
public async update(incomingRequests: number, time: number, incomingBytes: number, outgoingBytes: number) {
2018-10-21 01:08:07 -05:00
const inc: Partial<NetworkLog> = {
2018-10-20 19:20:11 -05:00
incomingRequests: incomingRequests,
totalTime: time,
incomingBytes: incomingBytes,
outgoingBytes: outgoingBytes
2018-10-20 17:10:35 -05:00
};
2018-10-20 19:20:11 -05:00
await this.inc(inc);
2018-10-20 17:10:35 -05:00
}
}
2018-10-21 01:08:07 -05:00
export const networkStats = new NetworkStats();
2018-10-20 19:20:11 -05:00
//#endregion
2018-10-21 02:54:07 -05:00
//#region Hashtag stats
/**
*
*/
type HashtagLog = {
/**
* 稿
*/
count: number;
};
class HashtagStats extends Stats<HashtagLog> {
constructor() {
2018-10-21 13:41:50 -05:00
super('hashtag', true);
2018-10-21 02:54:07 -05:00
}
@autobind
2018-10-21 13:30:45 -05:00
protected async getTemplate(init: boolean, latest?: HashtagLog): Promise<HashtagLog> {
2018-10-21 02:54:07 -05:00
return {
count: 0
};
}
@autobind
public async update(hashtag: string, userId: mongo.ObjectId) {
const inc: Partial<HashtagLog> = {
count: 1
};
await this.incIfUnique(inc, 'users', userId.toHexString(), hashtag);
}
}
export const hashtagStats = new HashtagStats();
//#endregion
2018-10-21 03:51:35 -05:00
//#region Following stats
/**
*
*/
type FollowingLog = {
local: {
/**
*
*/
followings: {
/**
*
*/
total: number;
/**
*
*/
inc: number;
/**
*
*/
dec: number;
};
/**
*
*/
followers: {
/**
*
*/
total: number;
/**
*
*/
inc: number;
/**
*
*/
dec: number;
};
};
remote: FollowingLog['local'];
};
class FollowingStats extends Stats<FollowingLog> {
constructor() {
2018-10-21 13:41:50 -05:00
super('following', true);
2018-10-21 03:51:35 -05:00
}
@autobind
2018-10-21 13:30:45 -05:00
protected async getTemplate(init: boolean, latest?: FollowingLog, group?: any): Promise<FollowingLog> {
2018-10-21 03:58:02 -05:00
const [
localFollowingsCount,
localFollowersCount,
remoteFollowingsCount,
remoteFollowersCount
] = init ? await Promise.all([
2018-10-21 03:51:35 -05:00
Following.count({ followerId: group, '_followee.host': null }),
2018-10-21 04:43:45 -05:00
Following.count({ followeeId: group, '_follower.host': null }),
2018-10-21 03:51:35 -05:00
Following.count({ followerId: group, '_followee.host': { $ne: null } }),
2018-10-21 04:43:45 -05:00
Following.count({ followeeId: group, '_follower.host': { $ne: null } })
2018-10-21 03:51:35 -05:00
]) : [
2018-10-21 13:30:45 -05:00
latest ? latest.local.followings.total : 0,
latest ? latest.local.followers.total : 0,
latest ? latest.remote.followings.total : 0,
latest ? latest.remote.followers.total : 0
2018-10-21 03:51:35 -05:00
];
return {
local: {
followings: {
2018-10-21 03:58:02 -05:00
total: localFollowingsCount,
2018-10-21 03:51:35 -05:00
inc: 0,
dec: 0
},
followers: {
2018-10-21 03:58:02 -05:00
total: localFollowersCount,
2018-10-21 03:51:35 -05:00
inc: 0,
dec: 0
}
},
remote: {
followings: {
2018-10-21 03:58:02 -05:00
total: remoteFollowingsCount,
2018-10-21 03:51:35 -05:00
inc: 0,
dec: 0
},
followers: {
2018-10-21 03:58:02 -05:00
total: remoteFollowersCount,
2018-10-21 03:51:35 -05:00
inc: 0,
dec: 0
}
}
};
}
@autobind
public async update(follower: IUser, followee: IUser, isFollow: boolean) {
const update: Obj = {};
update.total = isFollow ? 1 : -1;
if (isFollow) {
update.inc = 1;
} else {
update.dec = 1;
}
this.inc({
[isLocalUser(follower) ? 'local' : 'remote']: { followings: update }
2018-10-21 13:34:56 -05:00
}, follower._id);
2018-10-21 03:51:35 -05:00
this.inc({
[isLocalUser(followee) ? 'local' : 'remote']: { followers: update }
2018-10-21 13:34:56 -05:00
}, followee._id);
2018-10-21 03:51:35 -05:00
}
}
export const followingStats = new FollowingStats();
2018-10-21 03:28:27 -05:00
//#endregion
2018-10-21 14:30:27 -05:00
//#region Per user notes stats
/**
* 稿
*/
type PerUserNotesLog = {
/**
* 稿
*/
total: number;
/**
* 稿
*/
inc: number;
/**
* 稿
*/
dec: number;
diffs: {
/**
* 稿
*/
normal: number;
/**
* 稿
*/
reply: number;
/**
* Renoteの投稿数の差分
*/
renote: number;
};
};
class PerUserNotesStats extends Stats<PerUserNotesLog> {
constructor() {
2018-10-21 14:31:45 -05:00
super('perUserNotes');
2018-10-21 14:30:27 -05:00
}
@autobind
protected async getTemplate(init: boolean, latest?: PerUserNotesLog, group?: any): Promise<PerUserNotesLog> {
const [count] = init ? await Promise.all([
Note.count({ userId: group, deletedAt: null }),
]) : [
latest ? latest.total : 0
];
return {
total: count,
inc: 0,
dec: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
};
}
@autobind
public async update(user: IUser, note: INote, isAdditional: boolean) {
const update: Obj = {
diffs: {}
};
update.total = isAdditional ? 1 : -1;
if (isAdditional) {
update.inc = 1;
} else {
update.dec = 1;
}
if (note.replyId != null) {
update.diffs.reply = isAdditional ? 1 : -1;
} else if (note.renoteId != null) {
update.diffs.renote = isAdditional ? 1 : -1;
} else {
update.diffs.normal = isAdditional ? 1 : -1;
}
await this.inc(update, user._id);
}
}
export const perUserNotesStats = new PerUserNotesStats();
//#endregion