Refactoring
This commit is contained in:
parent
c8b6b6e44f
commit
7c7f32d9a6
122
src/chart/drive.ts
Normal file
122
src/chart/drive.ts
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import Chart, { Obj } from './';
|
||||||
|
import DriveFile, { IDriveFile } from '../models/drive-file';
|
||||||
|
import { isLocalUser } from '../models/user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ドライブに関するチャート
|
||||||
|
*/
|
||||||
|
type DriveLog = {
|
||||||
|
local: {
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全ドライブファイル数
|
||||||
|
*/
|
||||||
|
totalCount: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全ドライブファイルの合計サイズ
|
||||||
|
*/
|
||||||
|
totalSize: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加したドライブファイル数
|
||||||
|
*/
|
||||||
|
incCount: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加したドライブ使用量
|
||||||
|
*/
|
||||||
|
incSize: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 減少したドライブファイル数
|
||||||
|
*/
|
||||||
|
decCount: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 減少したドライブ使用量
|
||||||
|
*/
|
||||||
|
decSize: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
remote: DriveLog['local'];
|
||||||
|
};
|
||||||
|
|
||||||
|
class DriveChart extends Chart<DriveLog> {
|
||||||
|
constructor() {
|
||||||
|
super('drive');
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
protected async getTemplate(init: boolean, latest?: DriveLog): Promise<DriveLog> {
|
||||||
|
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);
|
||||||
|
|
||||||
|
const [localCount, remoteCount, localSize, remoteSize] = init ? await Promise.all([
|
||||||
|
DriveFile.count({ 'metadata._user.host': null }),
|
||||||
|
DriveFile.count({ 'metadata._user.host': { $ne: null } }),
|
||||||
|
calcSize(true),
|
||||||
|
calcSize(false)
|
||||||
|
]) : [
|
||||||
|
latest ? latest.local.totalCount : 0,
|
||||||
|
latest ? latest.remote.totalCount : 0,
|
||||||
|
latest ? latest.local.totalSize : 0,
|
||||||
|
latest ? latest.remote.totalSize : 0
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
local: {
|
||||||
|
totalCount: localCount,
|
||||||
|
totalSize: localSize,
|
||||||
|
incCount: 0,
|
||||||
|
incSize: 0,
|
||||||
|
decCount: 0,
|
||||||
|
decSize: 0
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
totalCount: remoteCount,
|
||||||
|
totalSize: remoteSize,
|
||||||
|
incCount: 0,
|
||||||
|
incSize: 0,
|
||||||
|
decCount: 0,
|
||||||
|
decSize: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async update(file: IDriveFile, isAdditional: boolean) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.inc({
|
||||||
|
[isLocalUser(file.metadata._user) ? 'local' : 'remote']: update
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new DriveChart();
|
37
src/chart/hashtag.ts
Normal file
37
src/chart/hashtag.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import * as mongo from 'mongodb';
|
||||||
|
import Chart, { Partial } from './';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ハッシュタグに関するチャート
|
||||||
|
*/
|
||||||
|
type HashtagLog = {
|
||||||
|
/**
|
||||||
|
* 投稿された数
|
||||||
|
*/
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HashtagChart extends Chart<HashtagLog> {
|
||||||
|
constructor() {
|
||||||
|
super('hashtag', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
protected async getTemplate(init: boolean, latest?: HashtagLog): Promise<HashtagLog> {
|
||||||
|
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 default new HashtagChart();
|
285
src/chart/index.ts
Normal file
285
src/chart/index.ts
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
/**
|
||||||
|
* チャートエンジン
|
||||||
|
*/
|
||||||
|
|
||||||
|
const nestedProperty = require('nested-property');
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import * as mongo from 'mongodb';
|
||||||
|
import db from '../db/mongodb';
|
||||||
|
import { ICollection } from 'monk';
|
||||||
|
|
||||||
|
export type Obj = { [key: string]: any };
|
||||||
|
|
||||||
|
export type Partial<T> = {
|
||||||
|
[P in keyof T]?: Partial<T[P]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ArrayValue<T> = {
|
||||||
|
[P in keyof T]: T[P] extends number ? Array<T[P]> : ArrayValue<T[P]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Span = 'day' | 'hour';
|
||||||
|
|
||||||
|
//#region Chart Core
|
||||||
|
type Log<T extends Obj> = {
|
||||||
|
_id: mongo.ObjectID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集計のグループ
|
||||||
|
*/
|
||||||
|
group?: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集計日時
|
||||||
|
*/
|
||||||
|
date: Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集計期間
|
||||||
|
*/
|
||||||
|
span: Span;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* データ
|
||||||
|
*/
|
||||||
|
data: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ユニークインクリメント用
|
||||||
|
*/
|
||||||
|
unique?: Obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 様々なチャートの管理を司るクラス
|
||||||
|
*/
|
||||||
|
export default abstract class Chart<T> {
|
||||||
|
protected collection: ICollection<Log<T>>;
|
||||||
|
protected abstract async getTemplate(init: boolean, latest?: T, group?: any): Promise<T>;
|
||||||
|
|
||||||
|
constructor(name: string, grouped = false) {
|
||||||
|
this.collection = db.get<Log<T>>(`chart.${name}`);
|
||||||
|
if (grouped) {
|
||||||
|
this.collection.createIndex({ span: -1, date: -1, group: -1 }, { unique: true });
|
||||||
|
} else {
|
||||||
|
this.collection.createIndex({ span: -1, date: -1 }, { unique: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
private async getCurrentLog(span: Span, group?: any): Promise<Log<T>> {
|
||||||
|
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)のログ
|
||||||
|
const currentLog = await this.collection.findOne({
|
||||||
|
group: group,
|
||||||
|
span: span,
|
||||||
|
date: current
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentLog) {
|
||||||
|
return currentLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 集計期間が変わってから、初めてのチャート更新なら
|
||||||
|
// 最も最近のログを持ってくる
|
||||||
|
// * 例えば集計期間が「日」である場合で考えると、
|
||||||
|
// * 昨日何もチャートを更新するような出来事がなかった場合は、
|
||||||
|
// * ログがそもそも作られずドキュメントが存在しないということがあり得るため、
|
||||||
|
// * 「昨日の」と決め打ちせずに「もっとも最近の」とします
|
||||||
|
const latest = await this.collection.findOne({
|
||||||
|
group: group,
|
||||||
|
span: span
|
||||||
|
}, {
|
||||||
|
sort: {
|
||||||
|
date: -1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (latest) {
|
||||||
|
// 現在のログを初期挿入
|
||||||
|
const data = await this.getTemplate(false, latest.data);
|
||||||
|
|
||||||
|
const log = await this.collection.insert({
|
||||||
|
group: group,
|
||||||
|
span: span,
|
||||||
|
date: current,
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
|
||||||
|
return log;
|
||||||
|
} else {
|
||||||
|
// ログが存在しなかったら
|
||||||
|
// * Misskeyインスタンスを建てて初めてのチャート更新時など
|
||||||
|
|
||||||
|
// 空のログを作成
|
||||||
|
const data = await this.getTemplate(true, null, group);
|
||||||
|
|
||||||
|
const log = await this.collection.insert({
|
||||||
|
group: group,
|
||||||
|
span: span,
|
||||||
|
date: current,
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
protected commit(query: Obj, group?: any, uniqueKey?: string, uniqueValue?: string): void {
|
||||||
|
const update = (log: Log<T>) => {
|
||||||
|
// ユニークインクリメントの場合、指定のキーに指定の値が既に存在していたら弾く
|
||||||
|
if (
|
||||||
|
uniqueKey &&
|
||||||
|
log.unique &&
|
||||||
|
log.unique[uniqueKey] &&
|
||||||
|
log.unique[uniqueKey].includes(uniqueValue)
|
||||||
|
) return;
|
||||||
|
|
||||||
|
// ユニークインクリメントの指定のキーに値を追加
|
||||||
|
if (uniqueKey) {
|
||||||
|
query['$push'] = {
|
||||||
|
[`unique.${uniqueKey}`]: uniqueValue
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.collection.update({
|
||||||
|
_id: log._id
|
||||||
|
}, query);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getCurrentLog('day', group).then(log => update(log));
|
||||||
|
this.getCurrentLog('hour', group).then(log => update(log));
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
protected inc(inc: Partial<T>, group?: any): void {
|
||||||
|
this.commit({
|
||||||
|
$inc: this.convertQuery(inc, 'data')
|
||||||
|
}, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
protected incIfUnique(inc: Partial<T>, key: string, value: string, group?: any): void {
|
||||||
|
this.commit({
|
||||||
|
$inc: this.convertQuery(inc, 'data')
|
||||||
|
}, group, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async getChart(span: Span, range: number, group?: any): Promise<ArrayValue<T>> {
|
||||||
|
const promisedChart: Promise<T>[] = [];
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
const logs = await this.collection.find({
|
||||||
|
group: group,
|
||||||
|
span: span,
|
||||||
|
date: {
|
||||||
|
$gt: gt
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
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;
|
||||||
|
|
||||||
|
const log = logs.find(l => l.date.getTime() == current.getTime());
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
promisedChart.unshift(Promise.resolve(log.data));
|
||||||
|
} else { // 隙間埋め
|
||||||
|
const latest = logs.find(l => l.date.getTime() < current.getTime());
|
||||||
|
promisedChart.unshift(this.getTemplate(false, latest ? latest.data : null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const chart = await Promise.all(promisedChart);
|
||||||
|
|
||||||
|
const res: ArrayValue<T> = {} as any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [{
|
||||||
|
* xxxxx: 1,
|
||||||
|
* yyyyy: 5
|
||||||
|
* }, {
|
||||||
|
* xxxxx: 2,
|
||||||
|
* yyyyy: 6
|
||||||
|
* }, {
|
||||||
|
* xxxxx: 3,
|
||||||
|
* yyyyy: 7
|
||||||
|
* }]
|
||||||
|
*
|
||||||
|
* を
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* xxxxx: [1, 2, 3],
|
||||||
|
* yyyyy: [5, 6, 7]
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* にする
|
||||||
|
*/
|
||||||
|
const dive = (x: Obj, path?: string) => {
|
||||||
|
Object.entries(x).forEach(([k, v]) => {
|
||||||
|
const p = path ? `${path}.${k}` : k;
|
||||||
|
if (typeof v == 'object') {
|
||||||
|
dive(v, p);
|
||||||
|
} else {
|
||||||
|
nestedProperty.set(res, p, chart.map(s => nestedProperty.get(s, p)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
dive(chart[0]);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endregion
|
64
src/chart/network.ts
Normal file
64
src/chart/network.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import Chart, { Partial } from './';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ネットワークに関するチャート
|
||||||
|
*/
|
||||||
|
type NetworkLog = {
|
||||||
|
/**
|
||||||
|
* 受信したリクエスト数
|
||||||
|
*/
|
||||||
|
incomingRequests: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 送信したリクエスト数
|
||||||
|
*/
|
||||||
|
outgoingRequests: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 応答時間の合計
|
||||||
|
* TIP: (totalTime / incomingRequests) でひとつのリクエストに平均でどれくらいの時間がかかったか知れる
|
||||||
|
*/
|
||||||
|
totalTime: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合計受信データ量
|
||||||
|
*/
|
||||||
|
incomingBytes: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合計送信データ量
|
||||||
|
*/
|
||||||
|
outgoingBytes: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NetworkChart extends Chart<NetworkLog> {
|
||||||
|
constructor() {
|
||||||
|
super('network');
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
protected async getTemplate(init: boolean, latest?: NetworkLog): Promise<NetworkLog> {
|
||||||
|
return {
|
||||||
|
incomingRequests: 0,
|
||||||
|
outgoingRequests: 0,
|
||||||
|
totalTime: 0,
|
||||||
|
incomingBytes: 0,
|
||||||
|
outgoingBytes: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async update(incomingRequests: number, time: number, incomingBytes: number, outgoingBytes: number) {
|
||||||
|
const inc: Partial<NetworkLog> = {
|
||||||
|
incomingRequests: incomingRequests,
|
||||||
|
totalTime: time,
|
||||||
|
incomingBytes: incomingBytes,
|
||||||
|
outgoingBytes: outgoingBytes
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.inc(inc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new NetworkChart();
|
114
src/chart/notes.ts
Normal file
114
src/chart/notes.ts
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import Chart, { Obj } from '.';
|
||||||
|
import Note, { INote } from '../models/note';
|
||||||
|
import { isLocalUser } from '../models/user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 投稿に関するチャート
|
||||||
|
*/
|
||||||
|
type NotesLog = {
|
||||||
|
local: {
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全投稿数
|
||||||
|
*/
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加した投稿数
|
||||||
|
*/
|
||||||
|
inc: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 減少した投稿数
|
||||||
|
*/
|
||||||
|
dec: number;
|
||||||
|
|
||||||
|
diffs: {
|
||||||
|
/**
|
||||||
|
* 通常の投稿数の差分
|
||||||
|
*/
|
||||||
|
normal: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* リプライの投稿数の差分
|
||||||
|
*/
|
||||||
|
reply: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renoteの投稿数の差分
|
||||||
|
*/
|
||||||
|
renote: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
remote: NotesLog['local'];
|
||||||
|
};
|
||||||
|
|
||||||
|
class NotesChart extends Chart<NotesLog> {
|
||||||
|
constructor() {
|
||||||
|
super('notes');
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
protected async getTemplate(init: boolean, latest?: NotesLog): Promise<NotesLog> {
|
||||||
|
const [localCount, remoteCount] = init ? await Promise.all([
|
||||||
|
Note.count({ '_user.host': null }),
|
||||||
|
Note.count({ '_user.host': { $ne: null } })
|
||||||
|
]) : [
|
||||||
|
latest ? latest.local.total : 0,
|
||||||
|
latest ? latest.remote.total : 0
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
local: {
|
||||||
|
total: localCount,
|
||||||
|
inc: 0,
|
||||||
|
dec: 0,
|
||||||
|
diffs: {
|
||||||
|
normal: 0,
|
||||||
|
reply: 0,
|
||||||
|
renote: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
total: remoteCount,
|
||||||
|
inc: 0,
|
||||||
|
dec: 0,
|
||||||
|
diffs: {
|
||||||
|
normal: 0,
|
||||||
|
reply: 0,
|
||||||
|
renote: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async update(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({
|
||||||
|
[isLocalUser(note._user) ? 'local' : 'remote']: update
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new NotesChart();
|
101
src/chart/per-user-drive.ts
Normal file
101
src/chart/per-user-drive.ts
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import Chart, { Obj } from './';
|
||||||
|
import DriveFile, { IDriveFile } from '../models/drive-file';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ユーザーごとのドライブに関するチャート
|
||||||
|
*/
|
||||||
|
type PerUserDriveLog = {
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全ドライブファイル数
|
||||||
|
*/
|
||||||
|
totalCount: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全ドライブファイルの合計サイズ
|
||||||
|
*/
|
||||||
|
totalSize: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加したドライブファイル数
|
||||||
|
*/
|
||||||
|
incCount: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加したドライブ使用量
|
||||||
|
*/
|
||||||
|
incSize: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 減少したドライブファイル数
|
||||||
|
*/
|
||||||
|
decCount: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 減少したドライブ使用量
|
||||||
|
*/
|
||||||
|
decSize: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PerUserDriveChart extends Chart<PerUserDriveLog> {
|
||||||
|
constructor() {
|
||||||
|
super('perUserDrive', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
protected async getTemplate(init: boolean, latest?: PerUserDriveLog, group?: any): Promise<PerUserDriveLog> {
|
||||||
|
const calcSize = () => DriveFile
|
||||||
|
.aggregate([{
|
||||||
|
$match: {
|
||||||
|
'metadata.userId': group,
|
||||||
|
'metadata.deletedAt': { $exists: false }
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
$project: {
|
||||||
|
length: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
$group: {
|
||||||
|
_id: null,
|
||||||
|
usage: { $sum: '$length' }
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
.then(res => res.length > 0 ? res[0].usage : 0);
|
||||||
|
|
||||||
|
const [count, size] = init ? await Promise.all([
|
||||||
|
DriveFile.count({ 'metadata.userId': group }),
|
||||||
|
calcSize()
|
||||||
|
]) : [
|
||||||
|
latest ? latest.totalCount : 0,
|
||||||
|
latest ? latest.totalSize : 0
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalCount: count,
|
||||||
|
totalSize: size,
|
||||||
|
incCount: 0,
|
||||||
|
incSize: 0,
|
||||||
|
decCount: 0,
|
||||||
|
decSize: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async update(file: IDriveFile, isAdditional: boolean) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.inc(update, file.metadata.userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new PerUserDriveChart();
|
128
src/chart/per-user-following.ts
Normal file
128
src/chart/per-user-following.ts
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import Chart, { Obj } from './';
|
||||||
|
import Following from '../models/following';
|
||||||
|
import { IUser, isLocalUser } from '../models/user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ユーザーごとのフォローに関するチャート
|
||||||
|
*/
|
||||||
|
type PerUserFollowingLog = {
|
||||||
|
local: {
|
||||||
|
/**
|
||||||
|
* フォローしている
|
||||||
|
*/
|
||||||
|
followings: {
|
||||||
|
/**
|
||||||
|
* 合計
|
||||||
|
*/
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* フォローした数
|
||||||
|
*/
|
||||||
|
inc: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* フォロー解除した数
|
||||||
|
*/
|
||||||
|
dec: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* フォローされている
|
||||||
|
*/
|
||||||
|
followers: {
|
||||||
|
/**
|
||||||
|
* 合計
|
||||||
|
*/
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* フォローされた数
|
||||||
|
*/
|
||||||
|
inc: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* フォロー解除された数
|
||||||
|
*/
|
||||||
|
dec: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
remote: PerUserFollowingLog['local'];
|
||||||
|
};
|
||||||
|
|
||||||
|
class PerUserFollowingChart extends Chart<PerUserFollowingLog> {
|
||||||
|
constructor() {
|
||||||
|
super('perUserFollowing', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
protected async getTemplate(init: boolean, latest?: PerUserFollowingLog, group?: any): Promise<PerUserFollowingLog> {
|
||||||
|
const [
|
||||||
|
localFollowingsCount,
|
||||||
|
localFollowersCount,
|
||||||
|
remoteFollowingsCount,
|
||||||
|
remoteFollowersCount
|
||||||
|
] = init ? await Promise.all([
|
||||||
|
Following.count({ followerId: group, '_followee.host': null }),
|
||||||
|
Following.count({ followeeId: group, '_follower.host': null }),
|
||||||
|
Following.count({ followerId: group, '_followee.host': { $ne: null } }),
|
||||||
|
Following.count({ followeeId: group, '_follower.host': { $ne: null } })
|
||||||
|
]) : [
|
||||||
|
latest ? latest.local.followings.total : 0,
|
||||||
|
latest ? latest.local.followers.total : 0,
|
||||||
|
latest ? latest.remote.followings.total : 0,
|
||||||
|
latest ? latest.remote.followers.total : 0
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
local: {
|
||||||
|
followings: {
|
||||||
|
total: localFollowingsCount,
|
||||||
|
inc: 0,
|
||||||
|
dec: 0
|
||||||
|
},
|
||||||
|
followers: {
|
||||||
|
total: localFollowersCount,
|
||||||
|
inc: 0,
|
||||||
|
dec: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
followings: {
|
||||||
|
total: remoteFollowingsCount,
|
||||||
|
inc: 0,
|
||||||
|
dec: 0
|
||||||
|
},
|
||||||
|
followers: {
|
||||||
|
total: remoteFollowersCount,
|
||||||
|
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 }
|
||||||
|
}, follower._id);
|
||||||
|
this.inc({
|
||||||
|
[isLocalUser(followee) ? 'local' : 'remote']: { followers: update }
|
||||||
|
}, followee._id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new PerUserFollowingChart();
|
94
src/chart/per-user-notes.ts
Normal file
94
src/chart/per-user-notes.ts
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import Chart, { Obj } from './';
|
||||||
|
import Note, { INote } from '../models/note';
|
||||||
|
import { IUser } from '../models/user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ユーザーごとの投稿に関するチャート
|
||||||
|
*/
|
||||||
|
type PerUserNotesLog = {
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全投稿数
|
||||||
|
*/
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加した投稿数
|
||||||
|
*/
|
||||||
|
inc: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 減少した投稿数
|
||||||
|
*/
|
||||||
|
dec: number;
|
||||||
|
|
||||||
|
diffs: {
|
||||||
|
/**
|
||||||
|
* 通常の投稿数の差分
|
||||||
|
*/
|
||||||
|
normal: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* リプライの投稿数の差分
|
||||||
|
*/
|
||||||
|
reply: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renoteの投稿数の差分
|
||||||
|
*/
|
||||||
|
renote: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class PerUserNotesChart extends Chart<PerUserNotesLog> {
|
||||||
|
constructor() {
|
||||||
|
super('perUserNotes', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 default new PerUserNotesChart();
|
45
src/chart/per-user-reactions.ts
Normal file
45
src/chart/per-user-reactions.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import Chart from './';
|
||||||
|
import { IUser, isLocalUser } from '../models/user';
|
||||||
|
import { INote } from '../models/note';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ユーザーごとのリアクションに関するチャート
|
||||||
|
*/
|
||||||
|
type PerUserReactionsLog = {
|
||||||
|
local: {
|
||||||
|
/**
|
||||||
|
* リアクションされた数
|
||||||
|
*/
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
remote: PerUserReactionsLog['local'];
|
||||||
|
};
|
||||||
|
|
||||||
|
class PerUserReactionsChart extends Chart<PerUserReactionsLog> {
|
||||||
|
constructor() {
|
||||||
|
super('perUserReaction', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
protected async getTemplate(init: boolean, latest?: PerUserReactionsLog, group?: any): Promise<PerUserReactionsLog> {
|
||||||
|
return {
|
||||||
|
local: {
|
||||||
|
count: 0
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
count: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async update(user: IUser, note: INote) {
|
||||||
|
this.inc({
|
||||||
|
[isLocalUser(user) ? 'local' : 'remote']: { count: 1 }
|
||||||
|
}, note.userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new PerUserReactionsChart();
|
75
src/chart/users.ts
Normal file
75
src/chart/users.ts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import Chart, { Obj } from './';
|
||||||
|
import User, { IUser, isLocalUser } from '../models/user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ユーザーに関するチャート
|
||||||
|
*/
|
||||||
|
type UsersLog = {
|
||||||
|
local: {
|
||||||
|
/**
|
||||||
|
* 集計期間時点での、全ユーザー数
|
||||||
|
*/
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 増加したユーザー数
|
||||||
|
*/
|
||||||
|
inc: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 減少したユーザー数
|
||||||
|
*/
|
||||||
|
dec: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
remote: UsersLog['local'];
|
||||||
|
};
|
||||||
|
|
||||||
|
class UsersChart extends Chart<UsersLog> {
|
||||||
|
constructor() {
|
||||||
|
super('users');
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
protected async getTemplate(init: boolean, latest?: UsersLog): Promise<UsersLog> {
|
||||||
|
const [localCount, remoteCount] = init ? await Promise.all([
|
||||||
|
User.count({ host: null }),
|
||||||
|
User.count({ host: { $ne: null } })
|
||||||
|
]) : [
|
||||||
|
latest ? latest.local.total : 0,
|
||||||
|
latest ? latest.remote.total : 0
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
local: {
|
||||||
|
total: localCount,
|
||||||
|
inc: 0,
|
||||||
|
dec: 0
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
total: remoteCount,
|
||||||
|
inc: 0,
|
||||||
|
dec: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async update(user: IUser, isAdditional: boolean) {
|
||||||
|
const update: Obj = {};
|
||||||
|
|
||||||
|
update.total = isAdditional ? 1 : -1;
|
||||||
|
if (isAdditional) {
|
||||||
|
update.inc = 1;
|
||||||
|
} else {
|
||||||
|
update.dec = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.inc({
|
||||||
|
[isLocalUser(user) ? 'local' : 'remote']: update
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UsersChart();
|
|
@ -10,7 +10,7 @@ import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type'
|
||||||
import { IDriveFile } from '../../../models/drive-file';
|
import { IDriveFile } from '../../../models/drive-file';
|
||||||
import Meta from '../../../models/meta';
|
import Meta from '../../../models/meta';
|
||||||
import htmlToMFM from '../../../mfm/html-to-mfm';
|
import htmlToMFM from '../../../mfm/html-to-mfm';
|
||||||
import { usersStats } from '../../../services/stats';
|
import usersChart from '../../../chart/users';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
import { resolveNote } from './note';
|
import { resolveNote } from './note';
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
|
||||||
}
|
}
|
||||||
}, { upsert: true });
|
}, { upsert: true });
|
||||||
|
|
||||||
usersStats.update(user, true);
|
usersChart.update(user, true);
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region アイコンとヘッダー画像をフェッチ
|
//#region アイコンとヘッダー画像をフェッチ
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
import { driveStats } from '../../../../services/stats';
|
import driveChart from '../../../../chart/drive';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': 'ドライブの統計を取得します。'
|
'ja-JP': 'ドライブのチャートを取得します。'
|
||||||
},
|
},
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
|
@ -27,7 +27,7 @@ export default (params: any) => new Promise(async (res, rej) => {
|
||||||
const [ps, psErr] = getParams(meta, params);
|
const [ps, psErr] = getParams(meta, params);
|
||||||
if (psErr) throw psErr;
|
if (psErr) throw psErr;
|
||||||
|
|
||||||
const stats = await driveStats.getChart(ps.span as any, ps.limit);
|
const stats = await driveChart.getChart(ps.span as any, ps.limit);
|
||||||
|
|
||||||
res(stats);
|
res(stats);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
import { hashtagStats } from '../../../../services/stats';
|
import hashtagChart from '../../../../chart/hashtag';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': 'ハッシュタグごとの統計を取得します。'
|
'ja-JP': 'ハッシュタグごとのチャートを取得します。'
|
||||||
},
|
},
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
|
@ -33,7 +33,7 @@ export default (params: any) => new Promise(async (res, rej) => {
|
||||||
const [ps, psErr] = getParams(meta, params);
|
const [ps, psErr] = getParams(meta, params);
|
||||||
if (psErr) throw psErr;
|
if (psErr) throw psErr;
|
||||||
|
|
||||||
const stats = await hashtagStats.getChart(ps.span as any, ps.limit, ps.tag);
|
const stats = await hashtagChart.getChart(ps.span as any, ps.limit, ps.tag);
|
||||||
|
|
||||||
res(stats);
|
res(stats);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
import { networkStats } from '../../../../services/stats';
|
import networkChart from '../../../../chart/network';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': 'ネットワークの統計を取得します。'
|
'ja-JP': 'ネットワークのチャートを取得します。'
|
||||||
},
|
},
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
|
@ -27,7 +27,7 @@ export default (params: any) => new Promise(async (res, rej) => {
|
||||||
const [ps, psErr] = getParams(meta, params);
|
const [ps, psErr] = getParams(meta, params);
|
||||||
if (psErr) throw psErr;
|
if (psErr) throw psErr;
|
||||||
|
|
||||||
const stats = await networkStats.getChart(ps.span as any, ps.limit);
|
const stats = await networkChart.getChart(ps.span as any, ps.limit);
|
||||||
|
|
||||||
res(stats);
|
res(stats);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
import { notesStats } from '../../../../services/stats';
|
import notesChart from '../../../../chart/notes';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': '投稿の統計を取得します。'
|
'ja-JP': '投稿のチャートを取得します。'
|
||||||
},
|
},
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
|
@ -27,7 +27,7 @@ export default (params: any) => new Promise(async (res, rej) => {
|
||||||
const [ps, psErr] = getParams(meta, params);
|
const [ps, psErr] = getParams(meta, params);
|
||||||
if (psErr) throw psErr;
|
if (psErr) throw psErr;
|
||||||
|
|
||||||
const stats = await notesStats.getChart(ps.span as any, ps.limit);
|
const stats = await notesChart.getChart(ps.span as any, ps.limit);
|
||||||
|
|
||||||
res(stats);
|
res(stats);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import getParams from '../../../get-params';
|
import getParams from '../../../get-params';
|
||||||
import { perUserDriveStats } from '../../../../../services/stats';
|
import perUserDriveChart from '../../../../../chart/per-user-drive';
|
||||||
import ID from '../../../../../misc/cafy-id';
|
import ID from '../../../../../misc/cafy-id';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': 'ユーザーごとのドライブの統計を取得します。'
|
'ja-JP': 'ユーザーごとのドライブのチャートを取得します。'
|
||||||
},
|
},
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
|
@ -35,7 +35,7 @@ export default (params: any) => new Promise(async (res, rej) => {
|
||||||
const [ps, psErr] = getParams(meta, params);
|
const [ps, psErr] = getParams(meta, params);
|
||||||
if (psErr) throw psErr;
|
if (psErr) throw psErr;
|
||||||
|
|
||||||
const stats = await perUserDriveStats.getChart(ps.span as any, ps.limit, ps.userId);
|
const stats = await perUserDriveChart.getChart(ps.span as any, ps.limit, ps.userId);
|
||||||
|
|
||||||
res(stats);
|
res(stats);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import getParams from '../../../get-params';
|
import getParams from '../../../get-params';
|
||||||
import { perUserFollowingStats } from '../../../../../services/stats';
|
import perUserFollowingChart from '../../../../../chart/per-user-following';
|
||||||
import ID from '../../../../../misc/cafy-id';
|
import ID from '../../../../../misc/cafy-id';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': 'ユーザーごとのフォロー/フォロワーの統計を取得します。'
|
'ja-JP': 'ユーザーごとのフォロー/フォロワーのチャートを取得します。'
|
||||||
},
|
},
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
|
@ -35,7 +35,7 @@ export default (params: any) => new Promise(async (res, rej) => {
|
||||||
const [ps, psErr] = getParams(meta, params);
|
const [ps, psErr] = getParams(meta, params);
|
||||||
if (psErr) throw psErr;
|
if (psErr) throw psErr;
|
||||||
|
|
||||||
const stats = await perUserFollowingStats.getChart(ps.span as any, ps.limit, ps.userId);
|
const stats = await perUserFollowingChart.getChart(ps.span as any, ps.limit, ps.userId);
|
||||||
|
|
||||||
res(stats);
|
res(stats);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import getParams from '../../../get-params';
|
import getParams from '../../../get-params';
|
||||||
import { perUserNotesStats } from '../../../../../services/stats';
|
import perUserNotesChart from '../../../../../chart/per-user-notes';
|
||||||
import ID from '../../../../../misc/cafy-id';
|
import ID from '../../../../../misc/cafy-id';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': 'ユーザーごとの投稿の統計を取得します。'
|
'ja-JP': 'ユーザーごとの投稿のチャートを取得します。'
|
||||||
},
|
},
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
|
@ -35,7 +35,7 @@ export default (params: any) => new Promise(async (res, rej) => {
|
||||||
const [ps, psErr] = getParams(meta, params);
|
const [ps, psErr] = getParams(meta, params);
|
||||||
if (psErr) throw psErr;
|
if (psErr) throw psErr;
|
||||||
|
|
||||||
const stats = await perUserNotesStats.getChart(ps.span as any, ps.limit, ps.userId);
|
const stats = await perUserNotesChart.getChart(ps.span as any, ps.limit, ps.userId);
|
||||||
|
|
||||||
res(stats);
|
res(stats);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import getParams from '../../../get-params';
|
import getParams from '../../../get-params';
|
||||||
import { perUserReactionsStats } from '../../../../../services/stats';
|
import perUserReactionsChart from '../../../../../chart/per-user-reactions';
|
||||||
import ID from '../../../../../misc/cafy-id';
|
import ID from '../../../../../misc/cafy-id';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': 'ユーザーごとの被リアクション数の統計を取得します。'
|
'ja-JP': 'ユーザーごとの被リアクション数のチャートを取得します。'
|
||||||
},
|
},
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
|
@ -35,7 +35,7 @@ export default (params: any) => new Promise(async (res, rej) => {
|
||||||
const [ps, psErr] = getParams(meta, params);
|
const [ps, psErr] = getParams(meta, params);
|
||||||
if (psErr) throw psErr;
|
if (psErr) throw psErr;
|
||||||
|
|
||||||
const stats = await perUserReactionsStats.getChart(ps.span as any, ps.limit, ps.userId);
|
const stats = await perUserReactionsChart.getChart(ps.span as any, ps.limit, ps.userId);
|
||||||
|
|
||||||
res(stats);
|
res(stats);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
import { usersStats } from '../../../../services/stats';
|
import usersChart from '../../../../chart/users';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': 'ユーザーの統計を取得します。'
|
'ja-JP': 'ユーザーのチャートを取得します。'
|
||||||
},
|
},
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
|
@ -27,7 +27,7 @@ export default (params: any) => new Promise(async (res, rej) => {
|
||||||
const [ps, psErr] = getParams(meta, params);
|
const [ps, psErr] = getParams(meta, params);
|
||||||
if (psErr) throw psErr;
|
if (psErr) throw psErr;
|
||||||
|
|
||||||
const stats = await usersStats.getChart(ps.span as any, ps.limit);
|
const stats = await usersChart.getChart(ps.span as any, ps.limit);
|
||||||
|
|
||||||
res(stats);
|
res(stats);
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@ import generateUserToken from '../common/generate-native-user-token';
|
||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
import Meta from '../../../models/meta';
|
import Meta from '../../../models/meta';
|
||||||
import RegistrationTicket from '../../../models/registration-tickets';
|
import RegistrationTicket from '../../../models/registration-tickets';
|
||||||
import { usersStats } from '../../../services/stats';
|
import usersChart from '../../../chart/users';
|
||||||
|
|
||||||
if (config.recaptcha) {
|
if (config.recaptcha) {
|
||||||
recaptcha.init({
|
recaptcha.init({
|
||||||
|
@ -130,7 +130,7 @@ export default async (ctx: Koa.Context) => {
|
||||||
}, { upsert: true });
|
}, { upsert: true });
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
usersStats.update(account, true);
|
usersChart.update(account, true);
|
||||||
|
|
||||||
const res = await pack(account, account, {
|
const res = await pack(account, account, {
|
||||||
detail: true,
|
detail: true,
|
||||||
|
|
|
@ -17,7 +17,7 @@ const requestStats = require('request-stats');
|
||||||
import activityPub from './activitypub';
|
import activityPub from './activitypub';
|
||||||
import webFinger from './webfinger';
|
import webFinger from './webfinger';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { networkStats } from '../services/stats';
|
import networkChart from '../chart/network';
|
||||||
import apiServer from './api';
|
import apiServer from './api';
|
||||||
|
|
||||||
// Init app
|
// Init app
|
||||||
|
@ -104,7 +104,7 @@ export default () => new Promise(resolve => {
|
||||||
const outgoingBytes = queue.reduce((a, b) => a + b.res.bytes, 0);
|
const outgoingBytes = queue.reduce((a, b) => a + b.res.bytes, 0);
|
||||||
queue = [];
|
queue = [];
|
||||||
|
|
||||||
networkStats.update(requests, time, incomingBytes, outgoingBytes);
|
networkChart.update(requests, time, incomingBytes, outgoingBytes);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
//#endregion
|
//#endregion
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,8 @@ import { isLocalUser, IUser, IRemoteUser } from '../../models/user';
|
||||||
import delFile from './delete-file';
|
import delFile from './delete-file';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
|
import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
|
||||||
import { driveStats, perUserDriveStats } from '../stats';
|
import driveChart from '../../chart/drive';
|
||||||
|
import perUserDriveChart from '../../chart/per-user-drive';
|
||||||
|
|
||||||
const log = debug('misskey:drive:add-file');
|
const log = debug('misskey:drive:add-file');
|
||||||
|
|
||||||
|
@ -399,8 +400,8 @@ export default async function(
|
||||||
});
|
});
|
||||||
|
|
||||||
// 統計を更新
|
// 統計を更新
|
||||||
driveStats.update(driveFile, true);
|
driveChart.update(driveFile, true);
|
||||||
perUserDriveStats.update(driveFile, true);
|
perUserDriveChart.update(driveFile, true);
|
||||||
|
|
||||||
return driveFile;
|
return driveFile;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,8 @@ import * as Minio from 'minio';
|
||||||
import DriveFile, { DriveFileChunk, IDriveFile } from '../../models/drive-file';
|
import DriveFile, { DriveFileChunk, IDriveFile } from '../../models/drive-file';
|
||||||
import DriveFileThumbnail, { DriveFileThumbnailChunk } from '../../models/drive-file-thumbnail';
|
import DriveFileThumbnail, { DriveFileThumbnailChunk } from '../../models/drive-file-thumbnail';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import { driveStats, perUserDriveStats } from '../stats';
|
import driveChart from '../../chart/drive';
|
||||||
|
import perUserDriveChart from '../../chart/per-user-drive';
|
||||||
|
|
||||||
export default async function(file: IDriveFile, isExpired = false) {
|
export default async function(file: IDriveFile, isExpired = false) {
|
||||||
if (file.metadata.storage == 'minio') {
|
if (file.metadata.storage == 'minio') {
|
||||||
|
@ -48,6 +49,6 @@ export default async function(file: IDriveFile, isExpired = false) {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// 統計を更新
|
// 統計を更新
|
||||||
driveStats.update(file, false);
|
driveChart.update(file, false);
|
||||||
perUserDriveStats.update(file, false);
|
perUserDriveChart.update(file, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import renderFollow from '../../remote/activitypub/renderer/follow';
|
||||||
import renderAccept from '../../remote/activitypub/renderer/accept';
|
import renderAccept from '../../remote/activitypub/renderer/accept';
|
||||||
import { deliver } from '../../queue';
|
import { deliver } from '../../queue';
|
||||||
import createFollowRequest from './requests/create';
|
import createFollowRequest from './requests/create';
|
||||||
import { perUserFollowingStats } from '../stats';
|
import perUserFollowingChart from '../../chart/per-user-following';
|
||||||
|
|
||||||
export default async function(follower: IUser, followee: IUser, requestId?: string) {
|
export default async function(follower: IUser, followee: IUser, requestId?: string) {
|
||||||
// フォロー対象が鍵アカウントである or
|
// フォロー対象が鍵アカウントである or
|
||||||
|
@ -53,7 +53,7 @@ export default async function(follower: IUser, followee: IUser, requestId?: stri
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
perUserFollowingStats.update(follower, followee, true);
|
perUserFollowingChart.update(follower, followee, true);
|
||||||
|
|
||||||
// Publish follow event
|
// Publish follow event
|
||||||
if (isLocalUser(follower)) {
|
if (isLocalUser(follower)) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import pack from '../../remote/activitypub/renderer';
|
||||||
import renderFollow from '../../remote/activitypub/renderer/follow';
|
import renderFollow from '../../remote/activitypub/renderer/follow';
|
||||||
import renderUndo from '../../remote/activitypub/renderer/undo';
|
import renderUndo from '../../remote/activitypub/renderer/undo';
|
||||||
import { deliver } from '../../queue';
|
import { deliver } from '../../queue';
|
||||||
import { perUserFollowingStats } from '../stats';
|
import perUserFollowingChart from '../../chart/per-user-following';
|
||||||
|
|
||||||
export default async function(follower: IUser, followee: IUser) {
|
export default async function(follower: IUser, followee: IUser) {
|
||||||
const following = await Following.findOne({
|
const following = await Following.findOne({
|
||||||
|
@ -38,7 +38,7 @@ export default async function(follower: IUser, followee: IUser) {
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
perUserFollowingStats.update(follower, followee, false);
|
perUserFollowingChart.update(follower, followee, false);
|
||||||
|
|
||||||
// Publish unfollow event
|
// Publish unfollow event
|
||||||
if (isLocalUser(follower)) {
|
if (isLocalUser(follower)) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import renderAccept from '../../../remote/activitypub/renderer/accept';
|
||||||
import { deliver } from '../../../queue';
|
import { deliver } from '../../../queue';
|
||||||
import Following from '../../../models/following';
|
import Following from '../../../models/following';
|
||||||
import { publishMainStream } from '../../../stream';
|
import { publishMainStream } from '../../../stream';
|
||||||
import { perUserFollowingStats } from '../../stats';
|
import perUserFollowingChart from '../../../chart/per-user-following';
|
||||||
|
|
||||||
export default async function(followee: IUser, follower: IUser) {
|
export default async function(followee: IUser, follower: IUser) {
|
||||||
await Following.insert({
|
await Following.insert({
|
||||||
|
@ -58,7 +58,7 @@ export default async function(followee: IUser, follower: IUser) {
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
perUserFollowingStats.update(follower, followee, true);
|
perUserFollowingChart.update(follower, followee, true);
|
||||||
|
|
||||||
await User.update({ _id: followee._id }, {
|
await User.update({ _id: followee._id }, {
|
||||||
$inc: {
|
$inc: {
|
||||||
|
|
|
@ -23,7 +23,9 @@ import registerHashtag from '../register-hashtag';
|
||||||
import isQuote from '../../misc/is-quote';
|
import isQuote from '../../misc/is-quote';
|
||||||
import { TextElementMention } from '../../mfm/parse/elements/mention';
|
import { TextElementMention } from '../../mfm/parse/elements/mention';
|
||||||
import { TextElementHashtag } from '../../mfm/parse/elements/hashtag';
|
import { TextElementHashtag } from '../../mfm/parse/elements/hashtag';
|
||||||
import { notesStats, perUserNotesStats } from '../stats';
|
import notesChart from '../../chart/notes';
|
||||||
|
import perUserNotesChart from '../../chart/per-user-notes';
|
||||||
|
|
||||||
import { erase, unique } from '../../prelude/array';
|
import { erase, unique } from '../../prelude/array';
|
||||||
import insertNoteUnread from './unread';
|
import insertNoteUnread from './unread';
|
||||||
|
|
||||||
|
@ -165,8 +167,8 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
|
||||||
}
|
}
|
||||||
|
|
||||||
// 統計を更新
|
// 統計を更新
|
||||||
notesStats.update(note, true);
|
notesChart.update(note, true);
|
||||||
perUserNotesStats.update(user, note, true);
|
perUserNotesChart.update(user, note, true);
|
||||||
|
|
||||||
// ハッシュタグ登録
|
// ハッシュタグ登録
|
||||||
tags.map(tag => registerHashtag(user, tag));
|
tags.map(tag => registerHashtag(user, tag));
|
||||||
|
|
|
@ -6,7 +6,8 @@ import pack from '../../remote/activitypub/renderer';
|
||||||
import { deliver } from '../../queue';
|
import { deliver } from '../../queue';
|
||||||
import Following from '../../models/following';
|
import Following from '../../models/following';
|
||||||
import renderTombstone from '../../remote/activitypub/renderer/tombstone';
|
import renderTombstone from '../../remote/activitypub/renderer/tombstone';
|
||||||
import { notesStats, perUserNotesStats } from '../stats';
|
import notesChart from '../../chart/notes';
|
||||||
|
import perUserNotesChart from '../../chart/per-user-notes';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import NoteUnread from '../../models/note-unread';
|
import NoteUnread from '../../models/note-unread';
|
||||||
import read from './read';
|
import read from './read';
|
||||||
|
@ -63,6 +64,6 @@ export default async function(user: IUser, note: INote) {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// 統計を更新
|
// 統計を更新
|
||||||
notesStats.update(note, false);
|
notesChart.update(note, false);
|
||||||
perUserNotesStats.update(user, note, false);
|
perUserNotesChart.update(user, note, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import watch from '../watch';
|
||||||
import renderLike from '../../../remote/activitypub/renderer/like';
|
import renderLike from '../../../remote/activitypub/renderer/like';
|
||||||
import { deliver } from '../../../queue';
|
import { deliver } from '../../../queue';
|
||||||
import pack from '../../../remote/activitypub/renderer';
|
import pack from '../../../remote/activitypub/renderer';
|
||||||
import { perUserReactionsStats } from '../../stats';
|
import perUserReactionsChart from '../../../chart/per-user-reactions';
|
||||||
|
|
||||||
export default async (user: IUser, note: INote, reaction: string) => new Promise(async (res, rej) => {
|
export default async (user: IUser, note: INote, reaction: string) => new Promise(async (res, rej) => {
|
||||||
// Myself
|
// Myself
|
||||||
|
@ -44,7 +44,7 @@ export default async (user: IUser, note: INote, reaction: string) => new Promise
|
||||||
$inc: inc
|
$inc: inc
|
||||||
});
|
});
|
||||||
|
|
||||||
perUserReactionsStats.update(user, note);
|
perUserReactionsChart.update(user, note);
|
||||||
|
|
||||||
publishNoteStream(note._id, 'reacted', {
|
publishNoteStream(note._id, 'reacted', {
|
||||||
reaction: reaction,
|
reaction: reaction,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { IUser } from '../models/user';
|
import { IUser } from '../models/user';
|
||||||
import Hashtag from '../models/hashtag';
|
import Hashtag from '../models/hashtag';
|
||||||
import { hashtagStats } from './stats';
|
import hashtagChart from '../chart/hashtag';
|
||||||
|
|
||||||
export default async function(user: IUser, tag: string) {
|
export default async function(user: IUser, tag: string) {
|
||||||
tag = tag.toLowerCase();
|
tag = tag.toLowerCase();
|
||||||
|
@ -27,5 +27,5 @@ export default async function(user: IUser, tag: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hashtagStats.update(tag, user._id);
|
hashtagChart.update(tag, user._id);
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue