Refactoring

This commit is contained in:
syuilo 2018-10-23 05:36:35 +09:00
parent c8b6b6e44f
commit 7c7f32d9a6
No known key found for this signature in database
GPG Key ID: BDC4C49D06AB9D69
32 changed files with 1125 additions and 1111 deletions

122
src/chart/drive.ts Normal file
View 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
View 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
View 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
View 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
View 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
View 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();

View 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();

View 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();

View 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
View 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();

View File

@ -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 アイコンとヘッダー画像をフェッチ

View File

@ -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);
}); });

View File

@ -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);
}); });

View File

@ -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);
}); });

View File

@ -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);
}); });

View File

@ -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);
}); });

View File

@ -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);
}); });

View File

@ -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);
}); });

View File

@ -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);
}); });

View File

@ -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);
}); });

View File

@ -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,

View File

@ -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
}); });

View File

@ -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;
} }

View File

@ -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);
} }

View File

@ -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)) {

View File

@ -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)) {

View File

@ -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: {

View File

@ -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));

View File

@ -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);
} }

View File

@ -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,

View File

@ -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