From aba85b977dfc868c1a65ce06ed58ea59d0371f7f Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 8 Feb 2019 04:31:33 +0900 Subject: Refactoring: Move chart dir into services dir --- src/chart/active-users.ts | 48 --- src/chart/drive.ts | 122 -------- src/chart/federation.ts | 66 ---- src/chart/hashtag.ts | 56 ---- src/chart/index.ts | 348 ---------------------- src/chart/network.ts | 64 ---- src/chart/notes.ts | 114 ------- src/chart/per-user-drive.ts | 101 ------- src/chart/per-user-following.ts | 128 -------- src/chart/per-user-notes.ts | 94 ------ src/chart/per-user-reactions.ts | 45 --- src/chart/users.ts | 75 ----- src/remote/activitypub/models/person.ts | 2 +- src/server/api/endpoints/charts/active-users.ts | 2 +- src/server/api/endpoints/charts/drive.ts | 2 +- src/server/api/endpoints/charts/federation.ts | 2 +- src/server/api/endpoints/charts/hashtag.ts | 2 +- src/server/api/endpoints/charts/network.ts | 2 +- src/server/api/endpoints/charts/notes.ts | 2 +- src/server/api/endpoints/charts/user/drive.ts | 2 +- src/server/api/endpoints/charts/user/following.ts | 2 +- src/server/api/endpoints/charts/user/notes.ts | 2 +- src/server/api/endpoints/charts/user/reactions.ts | 2 +- src/server/api/endpoints/charts/users.ts | 2 +- src/server/api/endpoints/notes/hybrid-timeline.ts | 2 +- src/server/api/endpoints/notes/local-timeline.ts | 2 +- src/server/api/endpoints/notes/timeline.ts | 2 +- src/server/api/endpoints/stats.ts | 4 +- src/server/api/private/signup.ts | 2 +- src/server/index.ts | 2 +- src/services/blocking/create.ts | 2 +- src/services/chart/active-users.ts | 48 +++ src/services/chart/drive.ts | 122 ++++++++ src/services/chart/federation.ts | 66 ++++ src/services/chart/hashtag.ts | 56 ++++ src/services/chart/index.ts | 348 ++++++++++++++++++++++ src/services/chart/network.ts | 64 ++++ src/services/chart/notes.ts | 114 +++++++ src/services/chart/per-user-drive.ts | 101 +++++++ src/services/chart/per-user-following.ts | 128 ++++++++ src/services/chart/per-user-notes.ts | 94 ++++++ src/services/chart/per-user-reactions.ts | 45 +++ src/services/chart/users.ts | 75 +++++ src/services/drive/add-file.ts | 4 +- src/services/drive/delete-file.ts | 4 +- src/services/following/create.ts | 2 +- src/services/following/delete.ts | 2 +- src/services/following/requests/accept.ts | 2 +- src/services/note/create.ts | 6 +- src/services/note/delete.ts | 4 +- src/services/note/reaction/create.ts | 2 +- src/services/register-hashtag.ts | 2 +- src/services/register-or-fetch-instance-doc.ts | 2 +- 53 files changed, 1296 insertions(+), 1296 deletions(-) delete mode 100644 src/chart/active-users.ts delete mode 100644 src/chart/drive.ts delete mode 100644 src/chart/federation.ts delete mode 100644 src/chart/hashtag.ts delete mode 100644 src/chart/index.ts delete mode 100644 src/chart/network.ts delete mode 100644 src/chart/notes.ts delete mode 100644 src/chart/per-user-drive.ts delete mode 100644 src/chart/per-user-following.ts delete mode 100644 src/chart/per-user-notes.ts delete mode 100644 src/chart/per-user-reactions.ts delete mode 100644 src/chart/users.ts create mode 100644 src/services/chart/active-users.ts create mode 100644 src/services/chart/drive.ts create mode 100644 src/services/chart/federation.ts create mode 100644 src/services/chart/hashtag.ts create mode 100644 src/services/chart/index.ts create mode 100644 src/services/chart/network.ts create mode 100644 src/services/chart/notes.ts create mode 100644 src/services/chart/per-user-drive.ts create mode 100644 src/services/chart/per-user-following.ts create mode 100644 src/services/chart/per-user-notes.ts create mode 100644 src/services/chart/per-user-reactions.ts create mode 100644 src/services/chart/users.ts (limited to 'src') diff --git a/src/chart/active-users.ts b/src/chart/active-users.ts deleted file mode 100644 index 06d9b8aa90..0000000000 --- a/src/chart/active-users.ts +++ /dev/null @@ -1,48 +0,0 @@ -import autobind from 'autobind-decorator'; -import Chart, { Obj } from '.'; -import { IUser, isLocalUser } from '../models/user'; - -/** - * アクティブユーザーに関するチャート - */ -type ActiveUsersLog = { - local: { - /** - * アクティブユーザー数 - */ - count: number; - }; - - remote: ActiveUsersLog['local']; -}; - -class ActiveUsersChart extends Chart { - constructor() { - super('activeUsers'); - } - - @autobind - protected async getTemplate(init: boolean, latest?: ActiveUsersLog): Promise { - return { - local: { - count: 0 - }, - remote: { - count: 0 - } - }; - } - - @autobind - public async update(user: IUser) { - const update: Obj = { - count: 1 - }; - - await this.incIfUnique({ - [isLocalUser(user) ? 'local' : 'remote']: update - }, 'users', user._id.toHexString()); - } -} - -export default new ActiveUsersChart(); diff --git a/src/chart/drive.ts b/src/chart/drive.ts deleted file mode 100644 index ff454c750a..0000000000 --- a/src/chart/drive.ts +++ /dev/null @@ -1,122 +0,0 @@ -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 { - constructor() { - super('drive'); - } - - @autobind - protected async getTemplate(init: boolean, latest?: DriveLog): Promise { - 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(); diff --git a/src/chart/federation.ts b/src/chart/federation.ts deleted file mode 100644 index 5bb41f00a2..0000000000 --- a/src/chart/federation.ts +++ /dev/null @@ -1,66 +0,0 @@ -import autobind from 'autobind-decorator'; -import Chart, { Obj } from '.'; -import Instance from '../models/instance'; - -/** - * フェデレーションに関するチャート - */ -type FederationLog = { - instance: { - /** - * インスタンス数の合計 - */ - total: number; - - /** - * 増加インスタンス数 - */ - inc: number; - - /** - * 減少インスタンス数 - */ - dec: number; - }; -}; - -class FederationChart extends Chart { - constructor() { - super('federation'); - } - - @autobind - protected async getTemplate(init: boolean, latest?: FederationLog): Promise { - const [total] = init ? await Promise.all([ - Instance.count({}) - ]) : [ - latest ? latest.instance.total : 0 - ]; - - return { - instance: { - total: total, - inc: 0, - dec: 0 - } - }; - } - - @autobind - public async update(isAdditional: boolean) { - const update: Obj = {}; - - update.total = isAdditional ? 1 : -1; - if (isAdditional) { - update.inc = 1; - } else { - update.dec = 1; - } - - await this.inc({ - instance: update - }); - } -} - -export default new FederationChart(); diff --git a/src/chart/hashtag.ts b/src/chart/hashtag.ts deleted file mode 100644 index 5b03d8ba34..0000000000 --- a/src/chart/hashtag.ts +++ /dev/null @@ -1,56 +0,0 @@ -import autobind from 'autobind-decorator'; -import Chart, { Obj } from './'; -import { IUser, isLocalUser } from '../models/user'; -import db from '../db/mongodb'; - -/** - * ハッシュタグに関するチャート - */ -type HashtagLog = { - local: { - /** - * 投稿された数 - */ - count: number; - }; - - remote: HashtagLog['local']; -}; - -class HashtagChart extends Chart { - constructor() { - super('hashtag', true); - - // 後方互換性のため - db.get('chart.hashtag').findOne().then(doc => { - if (doc != null && doc.data.local == null) { - db.get('chart.hashtag').drop(); - } - }); - } - - @autobind - protected async getTemplate(init: boolean, latest?: HashtagLog): Promise { - return { - local: { - count: 0 - }, - remote: { - count: 0 - } - }; - } - - @autobind - public async update(hashtag: string, user: IUser) { - const update: Obj = { - count: 1 - }; - - await this.incIfUnique({ - [isLocalUser(user) ? 'local' : 'remote']: update - }, 'users', user._id.toHexString(), hashtag); - } -} - -export default new HashtagChart(); diff --git a/src/chart/index.ts b/src/chart/index.ts deleted file mode 100644 index cf61d90730..0000000000 --- a/src/chart/index.ts +++ /dev/null @@ -1,348 +0,0 @@ -/** - * チャートエンジン - */ - -import * as moment from 'moment'; -import * as nestedProperty from 'nested-property'; -import autobind from 'autobind-decorator'; -import * as mongo from 'mongodb'; -import db from '../db/mongodb'; -import { ICollection } from 'monk'; -import Logger from '../misc/logger'; - -const logger = new Logger('chart'); - -const utc = moment.utc; - -export type Obj = { [key: string]: any }; - -export type Partial = { - [P in keyof T]?: Partial; -}; - -type ArrayValue = { - [P in keyof T]: T[P] extends number ? T[P][] : ArrayValue; -}; - -type Span = 'day' | 'hour'; - -type Log = { - _id: mongo.ObjectID; - - /** - * 集計のグループ - */ - group?: any; - - /** - * 集計日時 - */ - date: Date; - - /** - * 集計期間 - */ - span: Span; - - /** - * データ - */ - data: T; - - /** - * ユニークインクリメント用 - */ - unique?: Obj; -}; - -/** - * 様々なチャートの管理を司るクラス - */ -export default abstract class Chart { - protected collection: ICollection>; - protected abstract async getTemplate(init: boolean, latest?: T, group?: any): Promise; - private name: string; - - constructor(name: string, grouped = false) { - this.name = name; - this.collection = db.get>(`chart.${name}`); - - const keys = { - span: -1, - date: -1 - } as { [key: string]: 1 | -1; }; - if (grouped) keys.group = -1; - - this.collection.createIndex(keys, { unique: true }); - } - - @autobind - private convertQuery(x: Obj, path: string): Obj { - const query: Obj = {}; - - const dive = (x: Obj, path: string) => { - for (const [k, v] of Object.entries(x)) { - const p = path ? `${path}.${k}` : k; - if (typeof v === 'number') { - query[p] = v; - } else { - dive(v, p); - } - } - }; - - dive(x, path); - - return query; - } - - @autobind - private getCurrentDate(): [number, number, number, number] { - const now = moment().utc(); - - const y = now.year(); - const m = now.month(); - const d = now.date(); - const h = now.hour(); - - return [y, m, d, h]; - } - - @autobind - private getLatestLog(span: Span, group?: any): Promise> { - return this.collection.findOne({ - group: group, - span: span - }, { - sort: { - date: -1 - } - }); - } - - @autobind - private async getCurrentLog(span: Span, group?: any): Promise> { - const [y, m, d, h] = this.getCurrentDate(); - - const current = - span == 'day' ? utc([y, m, d]) : - span == 'hour' ? utc([y, m, d, h]) : - null; - - // 現在(今日または今のHour)のログ - const currentLog = await this.collection.findOne({ - group: group, - span: span, - date: current.toDate() - }); - - // ログがあればそれを返して終了 - if (currentLog != null) { - return currentLog; - } - - let log: Log; - let data: T; - - // 集計期間が変わってから、初めてのチャート更新なら - // 最も最近のログを持ってくる - // * 例えば集計期間が「日」である場合で考えると、 - // * 昨日何もチャートを更新するような出来事がなかった場合は、 - // * ログがそもそも作られずドキュメントが存在しないということがあり得るため、 - // * 「昨日の」と決め打ちせずに「もっとも最近の」とします - const latest = await this.getLatestLog(span, group); - - if (latest != null) { - // 空ログデータを作成 - data = await this.getTemplate(false, latest.data); - } else { - // ログが存在しなかったら - // (Misskeyインスタンスを建てて初めてのチャート更新時など - // または何らかの理由でチャートコレクションを抹消した場合) - - // 初期ログデータを作成 - data = await this.getTemplate(true, null, group); - - logger.info(`${this.name}: Initial commit created`); - } - - try { - // 新規ログ挿入 - log = await this.collection.insert({ - group: group, - span: span, - date: current.toDate(), - data: data - }); - } catch (e) { - // 11000 is duplicate key error - // 並列動作している他のチャートエンジンプロセスと処理が重なる場合がある - // その場合は再度最も新しいログを持ってくる - if (e.code === 11000) { - log = await this.getLatestLog(span, group); - } else { - logger.error(e); - throw e; - } - } - - return log; - } - - @autobind - protected commit(query: Obj, group?: any, uniqueKey?: string, uniqueValue?: string): void { - const update = (log: Log) => { - // ユニークインクリメントの場合、指定のキーに指定の値が既に存在していたら弾く - 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, group?: any): void { - this.commit({ - $inc: this.convertQuery(inc, 'data') - }, group); - } - - @autobind - protected incIfUnique(inc: Partial, 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> { - const promisedChart: Promise[] = []; - - const [y, m, d, h] = this.getCurrentDate(); - - const gt = - span == 'day' ? utc([y, m, d]).subtract(range, 'days') : - span == 'hour' ? utc([y, m, d, h]).subtract(range, 'hours') : - null; - - // ログ取得 - let logs = await this.collection.find({ - group: group, - span: span, - date: { - $gte: gt.toDate() - } - }, { - sort: { - date: -1 - }, - fields: { - _id: 0 - } - }); - - // 要求された範囲にログがひとつもなかったら - if (logs.length == 0) { - // もっとも新しいログを持ってくる - // (すくなくともひとつログが無いと隙間埋めできないため) - const recentLog = await this.collection.findOne({ - group: group, - span: span - }, { - sort: { - date: -1 - }, - fields: { - _id: 0 - } - }); - - if (recentLog) { - logs = [recentLog]; - } - - // 要求された範囲の最も古い箇所に位置するログが存在しなかったら - } else if (!utc(logs[logs.length - 1].date).isSame(gt)) { - // 要求された範囲の最も古い箇所時点での最も新しいログを持ってきて末尾に追加する - // (隙間埋めできないため) - const outdatedLog = await this.collection.findOne({ - group: group, - span: span, - date: { - $lt: gt.toDate() - } - }, { - sort: { - date: -1 - }, - fields: { - _id: 0 - } - }); - - if (outdatedLog) { - logs.push(outdatedLog); - } - } - - // 整形 - for (let i = (range - 1); i >= 0; i--) { - const current = - span == 'day' ? utc([y, m, d]).subtract(i, 'days') : - span == 'hour' ? utc([y, m, d, h]).subtract(i, 'hours') : - null; - - const log = logs.find(l => utc(l.date).isSame(current)); - - if (log) { - promisedChart.unshift(Promise.resolve(log.data)); - } else { - // 隙間埋め - const latest = logs.find(l => utc(l.date).isBefore(current)); - promisedChart.unshift(this.getTemplate(false, latest ? latest.data : null)); - } - } - - const chart = await Promise.all(promisedChart); - - const res: ArrayValue = {} as any; - - /** - * [{ foo: 1, bar: 5 }, { foo: 2, bar: 6 }, { foo: 3, bar: 7 }] - * を - * { foo: [1, 2, 3], bar: [5, 6, 7] } - * にする - */ - const dive = (x: Obj, path?: string) => { - for (const [k, v] of Object.entries(x)) { - 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; - } -} diff --git a/src/chart/network.ts b/src/chart/network.ts deleted file mode 100644 index fce47099d1..0000000000 --- a/src/chart/network.ts +++ /dev/null @@ -1,64 +0,0 @@ -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 { - constructor() { - super('network'); - } - - @autobind - protected async getTemplate(init: boolean, latest?: NetworkLog): Promise { - 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 = { - incomingRequests: incomingRequests, - totalTime: time, - incomingBytes: incomingBytes, - outgoingBytes: outgoingBytes - }; - - await this.inc(inc); - } -} - -export default new NetworkChart(); diff --git a/src/chart/notes.ts b/src/chart/notes.ts deleted file mode 100644 index 738778e72a..0000000000 --- a/src/chart/notes.ts +++ /dev/null @@ -1,114 +0,0 @@ -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 { - constructor() { - super('notes'); - } - - @autobind - protected async getTemplate(init: boolean, latest?: NotesLog): Promise { - 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(); diff --git a/src/chart/per-user-drive.ts b/src/chart/per-user-drive.ts deleted file mode 100644 index 3decedeb3b..0000000000 --- a/src/chart/per-user-drive.ts +++ /dev/null @@ -1,101 +0,0 @@ -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 { - constructor() { - super('perUserDrive', true); - } - - @autobind - protected async getTemplate(init: boolean, latest?: PerUserDriveLog, group?: any): Promise { - 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(); diff --git a/src/chart/per-user-following.ts b/src/chart/per-user-following.ts deleted file mode 100644 index fac4a1619f..0000000000 --- a/src/chart/per-user-following.ts +++ /dev/null @@ -1,128 +0,0 @@ -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 { - constructor() { - super('perUserFollowing', true); - } - - @autobind - protected async getTemplate(init: boolean, latest?: PerUserFollowingLog, group?: any): Promise { - 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(); diff --git a/src/chart/per-user-notes.ts b/src/chart/per-user-notes.ts deleted file mode 100644 index 9558f5c839..0000000000 --- a/src/chart/per-user-notes.ts +++ /dev/null @@ -1,94 +0,0 @@ -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 { - constructor() { - super('perUserNotes', true); - } - - @autobind - protected async getTemplate(init: boolean, latest?: PerUserNotesLog, group?: any): Promise { - 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(); diff --git a/src/chart/per-user-reactions.ts b/src/chart/per-user-reactions.ts deleted file mode 100644 index a31952ea22..0000000000 --- a/src/chart/per-user-reactions.ts +++ /dev/null @@ -1,45 +0,0 @@ -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 { - constructor() { - super('perUserReaction', true); - } - - @autobind - protected async getTemplate(init: boolean, latest?: PerUserReactionsLog, group?: any): Promise { - 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(); diff --git a/src/chart/users.ts b/src/chart/users.ts deleted file mode 100644 index 547e595b01..0000000000 --- a/src/chart/users.ts +++ /dev/null @@ -1,75 +0,0 @@ -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 { - constructor() { - super('users'); - } - - @autobind - protected async getTemplate(init: boolean, latest?: UsersLog): Promise { - 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(); diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index 2902b4b7f9..c3b26b6577 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -9,7 +9,7 @@ import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type' import { IDriveFile } from '../../../models/drive-file'; import Meta from '../../../models/meta'; import { fromHtml } from '../../../mfm/fromHtml'; -import usersChart from '../../../chart/users'; +import usersChart from '../../../services/chart/users'; import { URL } from 'url'; import { resolveNote, extractEmojis } from './note'; import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc'; diff --git a/src/server/api/endpoints/charts/active-users.ts b/src/server/api/endpoints/charts/active-users.ts index 5187e5b353..828edd5e92 100644 --- a/src/server/api/endpoints/charts/active-users.ts +++ b/src/server/api/endpoints/charts/active-users.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../define'; -import activeUsersChart from '../../../../chart/active-users'; +import activeUsersChart from '../../../../services/chart/active-users'; export const meta = { stability: 'stable', diff --git a/src/server/api/endpoints/charts/drive.ts b/src/server/api/endpoints/charts/drive.ts index 2fd0139e8e..072aec6726 100644 --- a/src/server/api/endpoints/charts/drive.ts +++ b/src/server/api/endpoints/charts/drive.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../define'; -import driveChart from '../../../../chart/drive'; +import driveChart from '../../../../services/chart/drive'; export const meta = { stability: 'stable', diff --git a/src/server/api/endpoints/charts/federation.ts b/src/server/api/endpoints/charts/federation.ts index f7d12e5c33..3a433e7aa2 100644 --- a/src/server/api/endpoints/charts/federation.ts +++ b/src/server/api/endpoints/charts/federation.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../define'; -import federationChart from '../../../../chart/federation'; +import federationChart from '../../../../services/chart/federation'; export const meta = { stability: 'stable', diff --git a/src/server/api/endpoints/charts/hashtag.ts b/src/server/api/endpoints/charts/hashtag.ts index 3df2dd8500..496afae991 100644 --- a/src/server/api/endpoints/charts/hashtag.ts +++ b/src/server/api/endpoints/charts/hashtag.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../define'; -import hashtagChart from '../../../../chart/hashtag'; +import hashtagChart from '../../../../services/chart/hashtag'; export const meta = { stability: 'stable', diff --git a/src/server/api/endpoints/charts/network.ts b/src/server/api/endpoints/charts/network.ts index af5b799a80..7dd7f905f3 100644 --- a/src/server/api/endpoints/charts/network.ts +++ b/src/server/api/endpoints/charts/network.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../define'; -import networkChart from '../../../../chart/network'; +import networkChart from '../../../../services/chart/network'; export const meta = { stability: 'stable', diff --git a/src/server/api/endpoints/charts/notes.ts b/src/server/api/endpoints/charts/notes.ts index 906cef1fa9..5019e1ec9e 100644 --- a/src/server/api/endpoints/charts/notes.ts +++ b/src/server/api/endpoints/charts/notes.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../define'; -import notesChart from '../../../../chart/notes'; +import notesChart from '../../../../services/chart/notes'; export const meta = { stability: 'stable', diff --git a/src/server/api/endpoints/charts/user/drive.ts b/src/server/api/endpoints/charts/user/drive.ts index 1ef133c6bf..cea8ac6fa3 100644 --- a/src/server/api/endpoints/charts/user/drive.ts +++ b/src/server/api/endpoints/charts/user/drive.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../../define'; -import perUserDriveChart from '../../../../../chart/per-user-drive'; +import perUserDriveChart from '../../../../../services/chart/per-user-drive'; import ID, { transform } from '../../../../../misc/cafy-id'; export const meta = { diff --git a/src/server/api/endpoints/charts/user/following.ts b/src/server/api/endpoints/charts/user/following.ts index c8a64455e5..63f96dd241 100644 --- a/src/server/api/endpoints/charts/user/following.ts +++ b/src/server/api/endpoints/charts/user/following.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../../define'; -import perUserFollowingChart from '../../../../../chart/per-user-following'; +import perUserFollowingChart from '../../../../../services/chart/per-user-following'; import ID, { transform } from '../../../../../misc/cafy-id'; export const meta = { diff --git a/src/server/api/endpoints/charts/user/notes.ts b/src/server/api/endpoints/charts/user/notes.ts index f8a3c726ef..109e32e540 100644 --- a/src/server/api/endpoints/charts/user/notes.ts +++ b/src/server/api/endpoints/charts/user/notes.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../../define'; -import perUserNotesChart from '../../../../../chart/per-user-notes'; +import perUserNotesChart from '../../../../../services/chart/per-user-notes'; import ID, { transform } from '../../../../../misc/cafy-id'; export const meta = { diff --git a/src/server/api/endpoints/charts/user/reactions.ts b/src/server/api/endpoints/charts/user/reactions.ts index f24c84593c..bf46b708ba 100644 --- a/src/server/api/endpoints/charts/user/reactions.ts +++ b/src/server/api/endpoints/charts/user/reactions.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../../define'; -import perUserReactionsChart from '../../../../../chart/per-user-reactions'; +import perUserReactionsChart from '../../../../../services/chart/per-user-reactions'; import ID, { transform } from '../../../../../misc/cafy-id'; export const meta = { diff --git a/src/server/api/endpoints/charts/users.ts b/src/server/api/endpoints/charts/users.ts index 00c2871148..593fc7c197 100644 --- a/src/server/api/endpoints/charts/users.ts +++ b/src/server/api/endpoints/charts/users.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../define'; -import usersChart from '../../../../chart/users'; +import usersChart from '../../../../services/chart/users'; export const meta = { stability: 'stable', diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 55b6af9131..f1ef7cea10 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -6,7 +6,7 @@ import { packMany } from '../../../../models/note'; import define from '../../define'; import { countIf } from '../../../../prelude/array'; import fetchMeta from '../../../../misc/fetch-meta'; -import activeUsersChart from '../../../../chart/active-users'; +import activeUsersChart from '../../../../services/chart/active-users'; import { getHideUserIds } from '../../common/get-hide-users'; export const meta = { diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index 5d6e4b2e1a..d3bc7dc6e0 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -5,7 +5,7 @@ import { packMany } from '../../../../models/note'; import define from '../../define'; import { countIf } from '../../../../prelude/array'; import fetchMeta from '../../../../misc/fetch-meta'; -import activeUsersChart from '../../../../chart/active-users'; +import activeUsersChart from '../../../../services/chart/active-users'; import { getHideUserIds } from '../../common/get-hide-users'; export const meta = { diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index b0b02c4c9d..20bb7f7902 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -5,7 +5,7 @@ import { getFriends } from '../../common/get-friends'; import { packMany } from '../../../../models/note'; import define from '../../define'; import { countIf } from '../../../../prelude/array'; -import activeUsersChart from '../../../../chart/active-users'; +import activeUsersChart from '../../../../services/chart/active-users'; import { getHideUserIds } from '../../common/get-hide-users'; export const meta = { diff --git a/src/server/api/endpoints/stats.ts b/src/server/api/endpoints/stats.ts index 79e2fdf5e8..bb5b1a6ad5 100644 --- a/src/server/api/endpoints/stats.ts +++ b/src/server/api/endpoints/stats.ts @@ -1,6 +1,6 @@ import define from '../define'; -import driveChart from '../../../chart/drive'; -import federationChart from '../../../chart/federation'; +import driveChart from '../../../services/chart/drive'; +import federationChart from '../../../services/chart/federation'; import fetchMeta from '../../../misc/fetch-meta'; export const meta = { diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 6318eabdb8..89b7b330d2 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -6,7 +6,7 @@ import generateUserToken from '../common/generate-native-user-token'; import config from '../../../config'; import Meta from '../../../models/meta'; import RegistrationTicket from '../../../models/registration-tickets'; -import usersChart from '../../../chart/users'; +import usersChart from '../../../services/chart/users'; import fetchMeta from '../../../misc/fetch-meta'; import * as recaptcha from 'recaptcha-promise'; diff --git a/src/server/index.ts b/src/server/index.ts index 0e1c701050..470562d802 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -19,7 +19,7 @@ import activityPub from './activitypub'; import nodeinfo from './nodeinfo'; import wellKnown from './well-known'; import config from '../config'; -import networkChart from '../chart/network'; +import networkChart from '../services/chart/network'; import apiServer from './api'; import { sum } from '../prelude/array'; import User from '../models/user'; diff --git a/src/services/blocking/create.ts b/src/services/blocking/create.ts index 2b46d6b94a..c20666ef26 100644 --- a/src/services/blocking/create.ts +++ b/src/services/blocking/create.ts @@ -8,7 +8,7 @@ import renderUndo from '../../remote/activitypub/renderer/undo'; import renderBlock from '../../remote/activitypub/renderer/block'; import { deliver } from '../../queue'; import renderReject from '../../remote/activitypub/renderer/reject'; -import perUserFollowingChart from '../../chart/per-user-following'; +import perUserFollowingChart from '../../services/chart/per-user-following'; import Blocking from '../../models/blocking'; export default async function(blocker: IUser, blockee: IUser) { diff --git a/src/services/chart/active-users.ts b/src/services/chart/active-users.ts new file mode 100644 index 0000000000..2a4e1a97ac --- /dev/null +++ b/src/services/chart/active-users.ts @@ -0,0 +1,48 @@ +import autobind from 'autobind-decorator'; +import Chart, { Obj } from '.'; +import { IUser, isLocalUser } from '../../models/user'; + +/** + * アクティブユーザーに関するチャート + */ +type ActiveUsersLog = { + local: { + /** + * アクティブユーザー数 + */ + count: number; + }; + + remote: ActiveUsersLog['local']; +}; + +class ActiveUsersChart extends Chart { + constructor() { + super('activeUsers'); + } + + @autobind + protected async getTemplate(init: boolean, latest?: ActiveUsersLog): Promise { + return { + local: { + count: 0 + }, + remote: { + count: 0 + } + }; + } + + @autobind + public async update(user: IUser) { + const update: Obj = { + count: 1 + }; + + await this.incIfUnique({ + [isLocalUser(user) ? 'local' : 'remote']: update + }, 'users', user._id.toHexString()); + } +} + +export default new ActiveUsersChart(); diff --git a/src/services/chart/drive.ts b/src/services/chart/drive.ts new file mode 100644 index 0000000000..972f8c5709 --- /dev/null +++ b/src/services/chart/drive.ts @@ -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 { + constructor() { + super('drive'); + } + + @autobind + protected async getTemplate(init: boolean, latest?: DriveLog): Promise { + 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(); diff --git a/src/services/chart/federation.ts b/src/services/chart/federation.ts new file mode 100644 index 0000000000..20da7a7421 --- /dev/null +++ b/src/services/chart/federation.ts @@ -0,0 +1,66 @@ +import autobind from 'autobind-decorator'; +import Chart, { Obj } from '.'; +import Instance from '../../models/instance'; + +/** + * フェデレーションに関するチャート + */ +type FederationLog = { + instance: { + /** + * インスタンス数の合計 + */ + total: number; + + /** + * 増加インスタンス数 + */ + inc: number; + + /** + * 減少インスタンス数 + */ + dec: number; + }; +}; + +class FederationChart extends Chart { + constructor() { + super('federation'); + } + + @autobind + protected async getTemplate(init: boolean, latest?: FederationLog): Promise { + const [total] = init ? await Promise.all([ + Instance.count({}) + ]) : [ + latest ? latest.instance.total : 0 + ]; + + return { + instance: { + total: total, + inc: 0, + dec: 0 + } + }; + } + + @autobind + public async update(isAdditional: boolean) { + const update: Obj = {}; + + update.total = isAdditional ? 1 : -1; + if (isAdditional) { + update.inc = 1; + } else { + update.dec = 1; + } + + await this.inc({ + instance: update + }); + } +} + +export default new FederationChart(); diff --git a/src/services/chart/hashtag.ts b/src/services/chart/hashtag.ts new file mode 100644 index 0000000000..7a31e9cced --- /dev/null +++ b/src/services/chart/hashtag.ts @@ -0,0 +1,56 @@ +import autobind from 'autobind-decorator'; +import Chart, { Obj } from './'; +import { IUser, isLocalUser } from '../../models/user'; +import db from '../../db/mongodb'; + +/** + * ハッシュタグに関するチャート + */ +type HashtagLog = { + local: { + /** + * 投稿された数 + */ + count: number; + }; + + remote: HashtagLog['local']; +}; + +class HashtagChart extends Chart { + constructor() { + super('hashtag', true); + + // 後方互換性のため + db.get('chart.hashtag').findOne().then(doc => { + if (doc != null && doc.data.local == null) { + db.get('chart.hashtag').drop(); + } + }); + } + + @autobind + protected async getTemplate(init: boolean, latest?: HashtagLog): Promise { + return { + local: { + count: 0 + }, + remote: { + count: 0 + } + }; + } + + @autobind + public async update(hashtag: string, user: IUser) { + const update: Obj = { + count: 1 + }; + + await this.incIfUnique({ + [isLocalUser(user) ? 'local' : 'remote']: update + }, 'users', user._id.toHexString(), hashtag); + } +} + +export default new HashtagChart(); diff --git a/src/services/chart/index.ts b/src/services/chart/index.ts new file mode 100644 index 0000000000..30ef2847d6 --- /dev/null +++ b/src/services/chart/index.ts @@ -0,0 +1,348 @@ +/** + * チャートエンジン + */ + +import * as moment from 'moment'; +import * as nestedProperty from 'nested-property'; +import autobind from 'autobind-decorator'; +import * as mongo from 'mongodb'; +import db from '../../db/mongodb'; +import { ICollection } from 'monk'; +import Logger from '../../misc/logger'; + +const logger = new Logger('chart'); + +const utc = moment.utc; + +export type Obj = { [key: string]: any }; + +export type Partial = { + [P in keyof T]?: Partial; +}; + +type ArrayValue = { + [P in keyof T]: T[P] extends number ? T[P][] : ArrayValue; +}; + +type Span = 'day' | 'hour'; + +type Log = { + _id: mongo.ObjectID; + + /** + * 集計のグループ + */ + group?: any; + + /** + * 集計日時 + */ + date: Date; + + /** + * 集計期間 + */ + span: Span; + + /** + * データ + */ + data: T; + + /** + * ユニークインクリメント用 + */ + unique?: Obj; +}; + +/** + * 様々なチャートの管理を司るクラス + */ +export default abstract class Chart { + protected collection: ICollection>; + protected abstract async getTemplate(init: boolean, latest?: T, group?: any): Promise; + private name: string; + + constructor(name: string, grouped = false) { + this.name = name; + this.collection = db.get>(`chart.${name}`); + + const keys = { + span: -1, + date: -1 + } as { [key: string]: 1 | -1; }; + if (grouped) keys.group = -1; + + this.collection.createIndex(keys, { unique: true }); + } + + @autobind + private convertQuery(x: Obj, path: string): Obj { + const query: Obj = {}; + + const dive = (x: Obj, path: string) => { + for (const [k, v] of Object.entries(x)) { + const p = path ? `${path}.${k}` : k; + if (typeof v === 'number') { + query[p] = v; + } else { + dive(v, p); + } + } + }; + + dive(x, path); + + return query; + } + + @autobind + private getCurrentDate(): [number, number, number, number] { + const now = moment().utc(); + + const y = now.year(); + const m = now.month(); + const d = now.date(); + const h = now.hour(); + + return [y, m, d, h]; + } + + @autobind + private getLatestLog(span: Span, group?: any): Promise> { + return this.collection.findOne({ + group: group, + span: span + }, { + sort: { + date: -1 + } + }); + } + + @autobind + private async getCurrentLog(span: Span, group?: any): Promise> { + const [y, m, d, h] = this.getCurrentDate(); + + const current = + span == 'day' ? utc([y, m, d]) : + span == 'hour' ? utc([y, m, d, h]) : + null; + + // 現在(今日または今のHour)のログ + const currentLog = await this.collection.findOne({ + group: group, + span: span, + date: current.toDate() + }); + + // ログがあればそれを返して終了 + if (currentLog != null) { + return currentLog; + } + + let log: Log; + let data: T; + + // 集計期間が変わってから、初めてのチャート更新なら + // 最も最近のログを持ってくる + // * 例えば集計期間が「日」である場合で考えると、 + // * 昨日何もチャートを更新するような出来事がなかった場合は、 + // * ログがそもそも作られずドキュメントが存在しないということがあり得るため、 + // * 「昨日の」と決め打ちせずに「もっとも最近の」とします + const latest = await this.getLatestLog(span, group); + + if (latest != null) { + // 空ログデータを作成 + data = await this.getTemplate(false, latest.data); + } else { + // ログが存在しなかったら + // (Misskeyインスタンスを建てて初めてのチャート更新時など + // または何らかの理由でチャートコレクションを抹消した場合) + + // 初期ログデータを作成 + data = await this.getTemplate(true, null, group); + + logger.info(`${this.name}: Initial commit created`); + } + + try { + // 新規ログ挿入 + log = await this.collection.insert({ + group: group, + span: span, + date: current.toDate(), + data: data + }); + } catch (e) { + // 11000 is duplicate key error + // 並列動作している他のチャートエンジンプロセスと処理が重なる場合がある + // その場合は再度最も新しいログを持ってくる + if (e.code === 11000) { + log = await this.getLatestLog(span, group); + } else { + logger.error(e); + throw e; + } + } + + return log; + } + + @autobind + protected commit(query: Obj, group?: any, uniqueKey?: string, uniqueValue?: string): void { + const update = (log: Log) => { + // ユニークインクリメントの場合、指定のキーに指定の値が既に存在していたら弾く + 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, group?: any): void { + this.commit({ + $inc: this.convertQuery(inc, 'data') + }, group); + } + + @autobind + protected incIfUnique(inc: Partial, 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> { + const promisedChart: Promise[] = []; + + const [y, m, d, h] = this.getCurrentDate(); + + const gt = + span == 'day' ? utc([y, m, d]).subtract(range, 'days') : + span == 'hour' ? utc([y, m, d, h]).subtract(range, 'hours') : + null; + + // ログ取得 + let logs = await this.collection.find({ + group: group, + span: span, + date: { + $gte: gt.toDate() + } + }, { + sort: { + date: -1 + }, + fields: { + _id: 0 + } + }); + + // 要求された範囲にログがひとつもなかったら + if (logs.length == 0) { + // もっとも新しいログを持ってくる + // (すくなくともひとつログが無いと隙間埋めできないため) + const recentLog = await this.collection.findOne({ + group: group, + span: span + }, { + sort: { + date: -1 + }, + fields: { + _id: 0 + } + }); + + if (recentLog) { + logs = [recentLog]; + } + + // 要求された範囲の最も古い箇所に位置するログが存在しなかったら + } else if (!utc(logs[logs.length - 1].date).isSame(gt)) { + // 要求された範囲の最も古い箇所時点での最も新しいログを持ってきて末尾に追加する + // (隙間埋めできないため) + const outdatedLog = await this.collection.findOne({ + group: group, + span: span, + date: { + $lt: gt.toDate() + } + }, { + sort: { + date: -1 + }, + fields: { + _id: 0 + } + }); + + if (outdatedLog) { + logs.push(outdatedLog); + } + } + + // 整形 + for (let i = (range - 1); i >= 0; i--) { + const current = + span == 'day' ? utc([y, m, d]).subtract(i, 'days') : + span == 'hour' ? utc([y, m, d, h]).subtract(i, 'hours') : + null; + + const log = logs.find(l => utc(l.date).isSame(current)); + + if (log) { + promisedChart.unshift(Promise.resolve(log.data)); + } else { + // 隙間埋め + const latest = logs.find(l => utc(l.date).isBefore(current)); + promisedChart.unshift(this.getTemplate(false, latest ? latest.data : null)); + } + } + + const chart = await Promise.all(promisedChart); + + const res: ArrayValue = {} as any; + + /** + * [{ foo: 1, bar: 5 }, { foo: 2, bar: 6 }, { foo: 3, bar: 7 }] + * を + * { foo: [1, 2, 3], bar: [5, 6, 7] } + * にする + */ + const dive = (x: Obj, path?: string) => { + for (const [k, v] of Object.entries(x)) { + 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; + } +} diff --git a/src/services/chart/network.ts b/src/services/chart/network.ts new file mode 100644 index 0000000000..fce47099d1 --- /dev/null +++ b/src/services/chart/network.ts @@ -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 { + constructor() { + super('network'); + } + + @autobind + protected async getTemplate(init: boolean, latest?: NetworkLog): Promise { + 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 = { + incomingRequests: incomingRequests, + totalTime: time, + incomingBytes: incomingBytes, + outgoingBytes: outgoingBytes + }; + + await this.inc(inc); + } +} + +export default new NetworkChart(); diff --git a/src/services/chart/notes.ts b/src/services/chart/notes.ts new file mode 100644 index 0000000000..8f95f63638 --- /dev/null +++ b/src/services/chart/notes.ts @@ -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 { + constructor() { + super('notes'); + } + + @autobind + protected async getTemplate(init: boolean, latest?: NotesLog): Promise { + 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(); diff --git a/src/services/chart/per-user-drive.ts b/src/services/chart/per-user-drive.ts new file mode 100644 index 0000000000..d23852bdd9 --- /dev/null +++ b/src/services/chart/per-user-drive.ts @@ -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 { + constructor() { + super('perUserDrive', true); + } + + @autobind + protected async getTemplate(init: boolean, latest?: PerUserDriveLog, group?: any): Promise { + 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(); diff --git a/src/services/chart/per-user-following.ts b/src/services/chart/per-user-following.ts new file mode 100644 index 0000000000..9d6d347ef6 --- /dev/null +++ b/src/services/chart/per-user-following.ts @@ -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 { + constructor() { + super('perUserFollowing', true); + } + + @autobind + protected async getTemplate(init: boolean, latest?: PerUserFollowingLog, group?: any): Promise { + 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(); diff --git a/src/services/chart/per-user-notes.ts b/src/services/chart/per-user-notes.ts new file mode 100644 index 0000000000..9ce4e67c50 --- /dev/null +++ b/src/services/chart/per-user-notes.ts @@ -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 { + constructor() { + super('perUserNotes', true); + } + + @autobind + protected async getTemplate(init: boolean, latest?: PerUserNotesLog, group?: any): Promise { + 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(); diff --git a/src/services/chart/per-user-reactions.ts b/src/services/chart/per-user-reactions.ts new file mode 100644 index 0000000000..60495aeb02 --- /dev/null +++ b/src/services/chart/per-user-reactions.ts @@ -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 { + constructor() { + super('perUserReaction', true); + } + + @autobind + protected async getTemplate(init: boolean, latest?: PerUserReactionsLog, group?: any): Promise { + 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(); diff --git a/src/services/chart/users.ts b/src/services/chart/users.ts new file mode 100644 index 0000000000..ce23209aea --- /dev/null +++ b/src/services/chart/users.ts @@ -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 { + constructor() { + super('users'); + } + + @autobind + protected async getTemplate(init: boolean, latest?: UsersLog): Promise { + 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(); diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index 31902b2425..0e588d3442 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -18,8 +18,8 @@ import delFile from './delete-file'; import config from '../../config'; import { getDriveFileWebpublicBucket } from '../../models/drive-file-webpublic'; import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail'; -import driveChart from '../../chart/drive'; -import perUserDriveChart from '../../chart/per-user-drive'; +import driveChart from '../../services/chart/drive'; +import perUserDriveChart from '../../services/chart/per-user-drive'; import fetchMeta from '../../misc/fetch-meta'; import { GenerateVideoThumbnail } from './generate-video-thumbnail'; import { driveLogger } from './logger'; diff --git a/src/services/drive/delete-file.ts b/src/services/drive/delete-file.ts index 609c3a86ea..4211cd8291 100644 --- a/src/services/drive/delete-file.ts +++ b/src/services/drive/delete-file.ts @@ -2,8 +2,8 @@ import * as Minio from 'minio'; import DriveFile, { DriveFileChunk, IDriveFile } from '../../models/drive-file'; import DriveFileThumbnail, { DriveFileThumbnailChunk } from '../../models/drive-file-thumbnail'; import config from '../../config'; -import driveChart from '../../chart/drive'; -import perUserDriveChart from '../../chart/per-user-drive'; +import driveChart from '../../services/chart/drive'; +import perUserDriveChart from '../../services/chart/per-user-drive'; import DriveFileWebpublic, { DriveFileWebpublicChunk } from '../../models/drive-file-webpublic'; export default async function(file: IDriveFile, isExpired = false) { diff --git a/src/services/following/create.ts b/src/services/following/create.ts index 4df271a977..65b80dcf84 100644 --- a/src/services/following/create.ts +++ b/src/services/following/create.ts @@ -9,7 +9,7 @@ import renderAccept from '../../remote/activitypub/renderer/accept'; import renderReject from '../../remote/activitypub/renderer/reject'; import { deliver } from '../../queue'; import createFollowRequest from './requests/create'; -import perUserFollowingChart from '../../chart/per-user-following'; +import perUserFollowingChart from '../../services/chart/per-user-following'; import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc'; import Instance from '../../models/instance'; diff --git a/src/services/following/delete.ts b/src/services/following/delete.ts index d56edd3cc3..87eaf826e5 100644 --- a/src/services/following/delete.ts +++ b/src/services/following/delete.ts @@ -5,7 +5,7 @@ import { renderActivity } from '../../remote/activitypub/renderer'; import renderFollow from '../../remote/activitypub/renderer/follow'; import renderUndo from '../../remote/activitypub/renderer/undo'; import { deliver } from '../../queue'; -import perUserFollowingChart from '../../chart/per-user-following'; +import perUserFollowingChart from '../../services/chart/per-user-following'; import Logger from '../../misc/logger'; const logger = new Logger('following/delete'); diff --git a/src/services/following/requests/accept.ts b/src/services/following/requests/accept.ts index 8c42b5a783..34c7036d19 100644 --- a/src/services/following/requests/accept.ts +++ b/src/services/following/requests/accept.ts @@ -6,7 +6,7 @@ import renderAccept from '../../../remote/activitypub/renderer/accept'; import { deliver } from '../../../queue'; import Following from '../../../models/following'; import { publishMainStream } from '../../stream'; -import perUserFollowingChart from '../../../chart/per-user-following'; +import perUserFollowingChart from '../../../services/chart/per-user-following'; import Logger from '../../../misc/logger'; const logger = new Logger('following/requests/accept'); diff --git a/src/services/note/create.ts b/src/services/note/create.ts index d47f4ea9f1..0b71a9670c 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -21,9 +21,9 @@ import Meta from '../../models/meta'; import config from '../../config'; import registerHashtag from '../register-hashtag'; import isQuote from '../../misc/is-quote'; -import notesChart from '../../chart/notes'; -import perUserNotesChart from '../../chart/per-user-notes'; -import activeUsersChart from '../../chart/active-users'; +import notesChart from '../../services/chart/notes'; +import perUserNotesChart from '../../services/chart/per-user-notes'; +import activeUsersChart from '../../services/chart/active-users'; import { erase, concat } from '../../prelude/array'; import insertNoteUnread from './unread'; diff --git a/src/services/note/delete.ts b/src/services/note/delete.ts index 557872d751..8e8c20bfce 100644 --- a/src/services/note/delete.ts +++ b/src/services/note/delete.ts @@ -6,8 +6,8 @@ import { renderActivity } from '../../remote/activitypub/renderer'; import { deliver } from '../../queue'; import Following from '../../models/following'; import renderTombstone from '../../remote/activitypub/renderer/tombstone'; -import notesChart from '../../chart/notes'; -import perUserNotesChart from '../../chart/per-user-notes'; +import notesChart from '../../services/chart/notes'; +import perUserNotesChart from '../../services/chart/per-user-notes'; import config from '../../config'; import NoteUnread from '../../models/note-unread'; import read from './read'; diff --git a/src/services/note/reaction/create.ts b/src/services/note/reaction/create.ts index e6a9fe7d65..cf72927642 100644 --- a/src/services/note/reaction/create.ts +++ b/src/services/note/reaction/create.ts @@ -8,7 +8,7 @@ import watch from '../watch'; import renderLike from '../../../remote/activitypub/renderer/like'; import { deliver } from '../../../queue'; import { renderActivity } from '../../../remote/activitypub/renderer'; -import perUserReactionsChart from '../../../chart/per-user-reactions'; +import perUserReactionsChart from '../../../services/chart/per-user-reactions'; export default async (user: IUser, note: INote, reaction: string) => new Promise(async (res, rej) => { // Myself diff --git a/src/services/register-hashtag.ts b/src/services/register-hashtag.ts index 57ba2080f2..01b7bc871a 100644 --- a/src/services/register-hashtag.ts +++ b/src/services/register-hashtag.ts @@ -1,6 +1,6 @@ import { IUser } from '../models/user'; import Hashtag from '../models/hashtag'; -import hashtagChart from '../chart/hashtag'; +import hashtagChart from '../services/chart/hashtag'; export default async function(user: IUser, tag: string) { tag = tag.toLowerCase(); diff --git a/src/services/register-or-fetch-instance-doc.ts b/src/services/register-or-fetch-instance-doc.ts index 3b338b48af..d418cd12ce 100644 --- a/src/services/register-or-fetch-instance-doc.ts +++ b/src/services/register-or-fetch-instance-doc.ts @@ -1,5 +1,5 @@ import Instance, { IInstance } from '../models/instance'; -import federationChart from '../chart/federation'; +import federationChart from '../services/chart/federation'; export async function registerOrFetchInstanceDoc(host: string): Promise { if (host == null) return null; -- cgit v1.2.3-freya