From 69e2576387819fa40493fd582121b45b7f27935d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 18 Aug 2018 20:22:56 +0900 Subject: [PATCH 1/9] wip --- src/models/chart.ts | 88 +++++++++++++++++++++ src/services/update-chart.ts | 143 +++++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 src/models/chart.ts create mode 100644 src/services/update-chart.ts diff --git a/src/models/chart.ts b/src/models/chart.ts new file mode 100644 index 000000000..2063408d8 --- /dev/null +++ b/src/models/chart.ts @@ -0,0 +1,88 @@ +import * as mongo from 'mongodb'; +import db from '../db/mongodb'; + +const Chart = db.get('chart'); +Chart.createIndex('date', { unique: true }); +export default Chart; + +export interface IChart { + _id: mongo.ObjectID; + + date: Date; + + users: { + local: { + /** + * この日時点での、ローカルのユーザーの総計 + */ + total: number; + + /** + * この日時点での、ローカルのユーザー数の前日比 + */ + diff: number; + }; + + remote: { + /** + * この日時点での、リモートのユーザーの総計 + */ + total: number; + + /** + * この日時点での、リモートのユーザー数の前日比 + */ + diff: number; + }; + }; + + notes: { + local: { + /** + * この日時点での、ローカルの投稿の総計 + */ + total: number; + + diffs: { + /** + * この日に行われた、ローカルの通常の投稿数の前日比 + */ + normal: number; + + /** + * この日に行われた、ローカルのリプライの投稿数の前日比 + */ + reply: number; + + /** + * この日に行われた、ローカルのRenoteの投稿数の前日比 + */ + renote: number; + }; + }; + + remote: { + /** + * この日時点での、リモートの投稿の総計 + */ + total: number; + + diffs: { + /** + * この日に行われた、リモートの通常の投稿数の前日比 + */ + normal: number; + + /** + * この日に行われた、リモートのリプライの投稿数の前日比 + */ + reply: number; + + /** + * この日に行われた、リモートのRenoteの投稿数の前日比 + */ + renote: number; + }; + }; + }; +} diff --git a/src/services/update-chart.ts b/src/services/update-chart.ts new file mode 100644 index 000000000..c1f212c9d --- /dev/null +++ b/src/services/update-chart.ts @@ -0,0 +1,143 @@ +import { INote } from '../models/note'; +import Chart, { IChart } from '../models/chart'; +import { isLocalUser } from '../models/user'; + +async function getTodayStats(): Promise { + const now = new Date(); + const y = now.getFullYear(); + const m = now.getMonth(); + const d = now.getDate(); + const today = new Date(y, m, d); + + // 今日の統計 + const todayStats = await Chart.findOne({ + date: today + }); + + // 日付が変わってから、初めてのチャート更新なら + if (todayStats == null) { + // 最も最近の統計を持ってくる + // * 昨日何もチャートを更新するような出来事がなかった場合は、 + // 統計がそもそも作られずドキュメントが存在しないということがあり得るため、 + // 「昨日の」と決め打ちせずに「もっとも最近の」とします + const mostRecentStats = await Chart.findOne({}, { + sort: { + createdAt: -1 + } + }); + + // 統計が存在しなかったら + // * Misskeyインスタンスを建てて初めてのチャート更新時など + if (mostRecentStats == null) { + // 空の統計を作成 + const stats = await Chart.insert({ + date: today, + users: { + local: { + total: 0, + diff: 0 + }, + remote: { + total: 0, + diff: 0 + } + }, + notes: { + local: { + total: 0, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + }, + remote: { + total: 0, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + } + } + }); + + return stats; + } else { + // 今日の統計を初期挿入 + const stats = await Chart.insert({ + date: today, + users: { + local: { + total: mostRecentStats.users.local.total, + diff: 0 + }, + remote: { + total: mostRecentStats.users.remote.total, + diff: 0 + } + }, + notes: { + local: { + total: mostRecentStats.notes.local.total, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + }, + remote: { + total: mostRecentStats.notes.remote.total, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + } + } + }); + + return stats; + } + } else { + return todayStats; + } +} + +async function update(inc: any) { + const stats = await getTodayStats(); + + await Chart.findOneAndUpdate({ + _id: stats._id + }, { + $inc: inc + }); +} + +export async function incNote(note: INote) { + const inc = {} as any; + + if (isLocalUser(note._user)) { + inc['notes.local.total'] = 1; + + if (note.replyId != null) { + inc['notes.local.diffs.reply'] = 1; + } else if (note.renoteId != null) { + inc['notes.local.diffs.renote'] = 1; + } else { + inc['notes.local.diffs.normal'] = 1; + } + } else { + inc['notes.remote.total'] = 1; + + if (note.replyId != null) { + inc['notes.remote.diffs.reply'] = 1; + } else if (note.renoteId != null) { + inc['notes.remote.diffs.renote'] = 1; + } else { + inc['notes.remote.diffs.normal'] = 1; + } + } + + await update(inc); +} From 773fe28fcb681e16d5bab6f09b8ee4bb372c0ee9 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 18 Aug 2018 20:26:01 +0900 Subject: [PATCH 2/9] wip --- src/services/update-chart.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/services/update-chart.ts b/src/services/update-chart.ts index c1f212c9d..f6835fb78 100644 --- a/src/services/update-chart.ts +++ b/src/services/update-chart.ts @@ -114,28 +114,30 @@ async function update(inc: any) { }); } -export async function incNote(note: INote) { +export async function updateNoteStats(note: INote, isAdditional: boolean) { const inc = {} as any; + const val = isAdditional ? 1 : -1; + if (isLocalUser(note._user)) { - inc['notes.local.total'] = 1; + inc['notes.local.total'] = val; if (note.replyId != null) { - inc['notes.local.diffs.reply'] = 1; + inc['notes.local.diffs.reply'] = val; } else if (note.renoteId != null) { - inc['notes.local.diffs.renote'] = 1; + inc['notes.local.diffs.renote'] = val; } else { - inc['notes.local.diffs.normal'] = 1; + inc['notes.local.diffs.normal'] = val; } } else { - inc['notes.remote.total'] = 1; + inc['notes.remote.total'] = val; if (note.replyId != null) { - inc['notes.remote.diffs.reply'] = 1; + inc['notes.remote.diffs.reply'] = val; } else if (note.renoteId != null) { - inc['notes.remote.diffs.renote'] = 1; + inc['notes.remote.diffs.renote'] = val; } else { - inc['notes.remote.diffs.normal'] = 1; + inc['notes.remote.diffs.normal'] = val; } } From c3cd0451ad4ba4ef7c15bb1a3cf54a19d8c5d2ef Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 18 Aug 2018 23:15:16 +0900 Subject: [PATCH 3/9] wip --- src/services/update-chart.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/services/update-chart.ts b/src/services/update-chart.ts index f6835fb78..f7997cb77 100644 --- a/src/services/update-chart.ts +++ b/src/services/update-chart.ts @@ -1,6 +1,6 @@ import { INote } from '../models/note'; import Chart, { IChart } from '../models/chart'; -import { isLocalUser } from '../models/user'; +import { isLocalUser, IUser } from '../models/user'; async function getTodayStats(): Promise { const now = new Date(); @@ -22,7 +22,7 @@ async function getTodayStats(): Promise { // 「昨日の」と決め打ちせずに「もっとも最近の」とします const mostRecentStats = await Chart.findOne({}, { sort: { - createdAt: -1 + date: -1 } }); @@ -114,6 +114,22 @@ async function update(inc: any) { }); } +export async function updateUserStats(user: IUser, isAdditional: boolean) { + const inc = {} as any; + + const val = isAdditional ? 1 : -1; + + if (isLocalUser(user)) { + inc['users.local.total'] = val; + inc['users.local.diff'] = val; + } else { + inc['users.remote.total'] = val; + inc['users.remote.diff'] = val; + } + + await update(inc); +} + export async function updateNoteStats(note: INote, isAdditional: boolean) { const inc = {} as any; From ef799038110f5a3a40752b5e80e0f146fbe126f4 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 18 Aug 2018 23:23:55 +0900 Subject: [PATCH 4/9] wip --- src/models/chart.ts | 26 ++++++++++++++++++-------- src/services/update-chart.ts | 20 ++++++++++++++++---- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/models/chart.ts b/src/models/chart.ts index 2063408d8..e7e85c948 100644 --- a/src/models/chart.ts +++ b/src/models/chart.ts @@ -18,7 +18,7 @@ export interface IChart { total: number; /** - * この日時点での、ローカルのユーザー数の前日比 + * ローカルのユーザー数の前日比 */ diff: number; }; @@ -30,7 +30,7 @@ export interface IChart { total: number; /** - * この日時点での、リモートのユーザー数の前日比 + * リモートのユーザー数の前日比 */ diff: number; }; @@ -43,19 +43,24 @@ export interface IChart { */ total: number; + /** + * ローカルの投稿数の前日比 + */ + diff: number; + diffs: { /** - * この日に行われた、ローカルの通常の投稿数の前日比 + * ローカルの通常の投稿数の前日比 */ normal: number; /** - * この日に行われた、ローカルのリプライの投稿数の前日比 + * ローカルのリプライの投稿数の前日比 */ reply: number; /** - * この日に行われた、ローカルのRenoteの投稿数の前日比 + * ローカルのRenoteの投稿数の前日比 */ renote: number; }; @@ -67,19 +72,24 @@ export interface IChart { */ total: number; + /** + * リモートの投稿数の前日比 + */ + diff: number; + diffs: { /** - * この日に行われた、リモートの通常の投稿数の前日比 + * リモートの通常の投稿数の前日比 */ normal: number; /** - * この日に行われた、リモートのリプライの投稿数の前日比 + * リモートのリプライの投稿数の前日比 */ reply: number; /** - * この日に行われた、リモートのRenoteの投稿数の前日比 + * リモートのRenoteの投稿数の前日比 */ renote: number; }; diff --git a/src/services/update-chart.ts b/src/services/update-chart.ts index f7997cb77..bd4fcbbed 100644 --- a/src/services/update-chart.ts +++ b/src/services/update-chart.ts @@ -2,6 +2,8 @@ import { INote } from '../models/note'; import Chart, { IChart } from '../models/chart'; import { isLocalUser, IUser } from '../models/user'; +type Omit = Pick>; + async function getTodayStats(): Promise { const now = new Date(); const y = now.getFullYear(); @@ -30,7 +32,7 @@ async function getTodayStats(): Promise { // * Misskeyインスタンスを建てて初めてのチャート更新時など if (mostRecentStats == null) { // 空の統計を作成 - const stats = await Chart.insert({ + const chart: Omit = { date: today, users: { local: { @@ -45,6 +47,7 @@ async function getTodayStats(): Promise { notes: { local: { total: 0, + diff: 0, diffs: { normal: 0, reply: 0, @@ -53,6 +56,7 @@ async function getTodayStats(): Promise { }, remote: { total: 0, + diff: 0, diffs: { normal: 0, reply: 0, @@ -60,12 +64,14 @@ async function getTodayStats(): Promise { } } } - }); + }; + + const stats = await Chart.insert(chart); return stats; } else { // 今日の統計を初期挿入 - const stats = await Chart.insert({ + const chart: Omit = { date: today, users: { local: { @@ -80,6 +86,7 @@ async function getTodayStats(): Promise { notes: { local: { total: mostRecentStats.notes.local.total, + diff: 0, diffs: { normal: 0, reply: 0, @@ -88,6 +95,7 @@ async function getTodayStats(): Promise { }, remote: { total: mostRecentStats.notes.remote.total, + diff: 0, diffs: { normal: 0, reply: 0, @@ -95,7 +103,9 @@ async function getTodayStats(): Promise { } } } - }); + }; + + const stats = await Chart.insert(chart); return stats; } @@ -137,6 +147,7 @@ export async function updateNoteStats(note: INote, isAdditional: boolean) { if (isLocalUser(note._user)) { inc['notes.local.total'] = val; + inc['notes.local.diff'] = val; if (note.replyId != null) { inc['notes.local.diffs.reply'] = val; @@ -147,6 +158,7 @@ export async function updateNoteStats(note: INote, isAdditional: boolean) { } } else { inc['notes.remote.total'] = val; + inc['notes.remote.diff'] = val; if (note.replyId != null) { inc['notes.remote.diffs.reply'] = val; From df71c90f9f85b1e1f0eecbdf25d749dc96b5b2ff Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 18 Aug 2018 23:48:54 +0900 Subject: [PATCH 5/9] wip --- CHANGELOG.md | 9 +++ cli/migration/7.0.0.js | 134 +++++++++++++++++++++++++++++++++++ package.json | 2 +- src/models/chart.ts | 55 ++++++++++++++ src/models/drive-file.ts | 5 ++ src/services/update-chart.ts | 82 ++++++++++++++++----- 6 files changed, 270 insertions(+), 17 deletions(-) create mode 100644 cli/migration/7.0.0.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b5d8c830..e8879b747 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ ChangeLog This document describes breaking changes only. +7.0.0 +----- + +### Migration + +起動する前に、`node cli/migration/7.0.0`してください。 + +Please run `node cli/migration/7.0.0` before launch. + 6.0.0 ----- diff --git a/cli/migration/7.0.0.js b/cli/migration/7.0.0.js new file mode 100644 index 000000000..d40670c6c --- /dev/null +++ b/cli/migration/7.0.0.js @@ -0,0 +1,134 @@ +const { default: Chart } = require('../../built/models/chart'); +const { default: User } = require('../../built/models/user'); +const { default: Note } = require('../../built/models/note'); +const { default: DriveFile } = require('../../built/models/drive-file'); + +const now = new Date(); +const y = now.getFullYear(); +const m = now.getMonth(); +const d = now.getDate(); +const today = new Date(y, m, d); + +async function main() { + const localUsersCount = await User.count({ + host: null + }); + + const remoteUsersCount = await User.count({ + host: { $ne: null } + }); + + const localNotesCount = await Note.count({ + '_user.host': null + }); + + const remoteNotesCount = await Note.count({ + '_user.host': { $ne: null } + }); + + const localDriveFilesCount = await DriveFile.count({ + 'metadata._user.host': null + }); + + const remoteDriveFilesCount = await DriveFile.count({ + 'metadata._user.host': { $ne: null } + }); + + const localDriveFilesSize = await DriveFile + .aggregate([{ + $match: { + 'metadata._user.host': null, + 'metadata.deletedAt': { $exists: false } + } + }, { + $project: { + length: true + } + }, { + $group: { + _id: null, + usage: { $sum: '$length' } + } + }]) + .then(aggregates => { + if (aggregates.length > 0) { + return aggregates[0].usage; + } + return 0; + }); + + const remoteDriveFilesSize = await DriveFile + .aggregate([{ + $match: { + 'metadata._user.host': { $ne: null }, + 'metadata.deletedAt': { $exists: false } + } + }, { + $project: { + length: true + } + }, { + $group: { + _id: null, + usage: { $sum: '$length' } + } + }]) + .then(aggregates => { + if (aggregates.length > 0) { + return aggregates[0].usage; + } + return 0; + }); + + await Chart.insert({ + date: today, + users: { + local: { + total: localUsersCount, + diff: 0 + }, + remote: { + total: remoteUsersCount, + diff: 0 + } + }, + notes: { + local: { + total: localNotesCount, + diff: 0, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + }, + remote: { + total: remoteNotesCount, + diff: 0, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + } + }, + drive: { + local: { + totalCount: localDriveFilesCount, + totalSize: localDriveFilesSize, + diffCount: 0, + diffSize: 0 + }, + remote: { + totalCount: remoteDriveFilesCount, + totalSize: remoteDriveFilesSize, + diffCount: 0, + diffSize: 0 + } + } + }); + + console.log('done'); +} + +main(); diff --git a/package.json b/package.json index 1101230f6..ff5eeae3f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "6.4.1", + "version": "7.0.0", "clientVersion": "1.0.8520", "codename": "nighthike", "main": "./built/index.js", diff --git a/src/models/chart.ts b/src/models/chart.ts index e7e85c948..a2154be4f 100644 --- a/src/models/chart.ts +++ b/src/models/chart.ts @@ -10,6 +10,9 @@ export interface IChart { date: Date; + /** + * ユーザーに関する統計 + */ users: { local: { /** @@ -36,6 +39,9 @@ export interface IChart { }; }; + /** + * 投稿に関する統計 + */ notes: { local: { /** @@ -95,4 +101,53 @@ export interface IChart { }; }; }; + + /** + * ドライブ(のファイル)に関する統計 + */ + drive: { + local: { + /** + * この日時点での、ローカルのドライブファイル数の総計 + */ + totalCount: number; + + /** + * この日時点での、ローカルのドライブファイルサイズの総計 + */ + totalSize: number; + + /** + * ローカルのドライブファイル数の前日比 + */ + diffCount: number; + + /** + * ローカルのドライブファイルサイズの前日比 + */ + diffSize: number; + }; + + remote: { + /** + * この日時点での、リモートのドライブファイル数の総計 + */ + totalCount: number; + + /** + * この日時点での、リモートのドライブファイルサイズの総計 + */ + totalSize: number; + + /** + * リモートのドライブファイル数の前日比 + */ + diffCount: number; + + /** + * リモートのドライブファイルサイズの前日比 + */ + diffSize: number; + }; + }; } diff --git a/src/models/drive-file.ts b/src/models/drive-file.ts index 2b9efc404..dbbc1f1cd 100644 --- a/src/models/drive-file.ts +++ b/src/models/drive-file.ts @@ -52,6 +52,11 @@ export type IDriveFile = { filename: string; contentType: string; metadata: IMetadata; + + /** + * ファイルサイズ + */ + length: number; }; export function validateFileName(name: string): boolean { diff --git a/src/services/update-chart.ts b/src/services/update-chart.ts index bd4fcbbed..64ba9e53c 100644 --- a/src/services/update-chart.ts +++ b/src/services/update-chart.ts @@ -1,6 +1,7 @@ import { INote } from '../models/note'; import Chart, { IChart } from '../models/chart'; import { isLocalUser, IUser } from '../models/user'; +import { IDriveFile } from '../models/drive-file'; type Omit = Pick>; @@ -63,6 +64,20 @@ async function getTodayStats(): Promise { renote: 0 } } + }, + drive: { + local: { + totalCount: 0, + totalSize: 0, + diffCount: 0, + diffSize: 0 + }, + remote: { + totalCount: 0, + totalSize: 0, + diffCount: 0, + diffSize: 0 + } } }; @@ -102,6 +117,20 @@ async function getTodayStats(): Promise { renote: 0 } } + }, + drive: { + local: { + totalCount: mostRecentStats.drive.local.totalCount, + totalSize: mostRecentStats.drive.local.totalSize, + diffCount: 0, + diffSize: 0 + }, + remote: { + totalCount: mostRecentStats.drive.remote.totalCount, + totalSize: mostRecentStats.drive.remote.totalSize, + diffCount: 0, + diffSize: 0 + } } }; @@ -127,14 +156,14 @@ async function update(inc: any) { export async function updateUserStats(user: IUser, isAdditional: boolean) { const inc = {} as any; - const val = isAdditional ? 1 : -1; + const amount = isAdditional ? 1 : -1; if (isLocalUser(user)) { - inc['users.local.total'] = val; - inc['users.local.diff'] = val; + inc['users.local.total'] = amount; + inc['users.local.diff'] = amount; } else { - inc['users.remote.total'] = val; - inc['users.remote.diff'] = val; + inc['users.remote.total'] = amount; + inc['users.remote.diff'] = amount; } await update(inc); @@ -143,31 +172,52 @@ export async function updateUserStats(user: IUser, isAdditional: boolean) { export async function updateNoteStats(note: INote, isAdditional: boolean) { const inc = {} as any; - const val = isAdditional ? 1 : -1; + const amount = isAdditional ? 1 : -1; if (isLocalUser(note._user)) { - inc['notes.local.total'] = val; - inc['notes.local.diff'] = val; + inc['notes.local.total'] = amount; + inc['notes.local.diff'] = amount; if (note.replyId != null) { - inc['notes.local.diffs.reply'] = val; + inc['notes.local.diffs.reply'] = amount; } else if (note.renoteId != null) { - inc['notes.local.diffs.renote'] = val; + inc['notes.local.diffs.renote'] = amount; } else { - inc['notes.local.diffs.normal'] = val; + inc['notes.local.diffs.normal'] = amount; } } else { - inc['notes.remote.total'] = val; - inc['notes.remote.diff'] = val; + inc['notes.remote.total'] = amount; + inc['notes.remote.diff'] = amount; if (note.replyId != null) { - inc['notes.remote.diffs.reply'] = val; + inc['notes.remote.diffs.reply'] = amount; } else if (note.renoteId != null) { - inc['notes.remote.diffs.renote'] = val; + inc['notes.remote.diffs.renote'] = amount; } else { - inc['notes.remote.diffs.normal'] = val; + inc['notes.remote.diffs.normal'] = amount; } } await update(inc); } + +export async function updateDriveStats(user: IUser, file: IDriveFile, isAdditional: boolean) { + const inc = {} as any; + + const amount = isAdditional ? 1 : -1; + const size = isAdditional ? file.length : -file.length; + + if (isLocalUser(user)) { + inc['drive.local.totalCount'] = amount; + inc['drive.local.diffCount'] = amount; + inc['drive.local.totalSize'] = size; + inc['drive.local.diffSize'] = size; + } else { + inc['drive.remote.total'] = amount; + inc['drive.remote.diff'] = amount; + inc['drive.remote.totalSize'] = size; + inc['drive.remote.diffSize'] = size; + } + + await update(inc); +} From 335200c31e0c1f623f3b16d277ef14ffcab64384 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 18 Aug 2018 23:56:44 +0900 Subject: [PATCH 6/9] wip --- cli/clean-cached-remote-files.js | 101 ------------------------------ cli/clean-unused-drive-files.js | 80 ----------------------- docs/manage.en.md | 11 ---- docs/manage.ja.md | 11 ---- src/services/drive/add-file.ts | 4 +- src/services/drive/delete-file.ts | 4 ++ src/services/note/create.ts | 4 ++ src/services/note/delete.ts | 4 ++ src/services/update-chart.ts | 4 +- 9 files changed, 17 insertions(+), 206 deletions(-) delete mode 100644 cli/clean-cached-remote-files.js delete mode 100644 cli/clean-unused-drive-files.js diff --git a/cli/clean-cached-remote-files.js b/cli/clean-cached-remote-files.js deleted file mode 100644 index 5b388c73b..000000000 --- a/cli/clean-cached-remote-files.js +++ /dev/null @@ -1,101 +0,0 @@ -const chalk = require('chalk'); -const log = require('single-line-log').stdout; -const sequential = require('promise-sequential'); -const { default: DriveFile, DriveFileChunk } = require('../built/models/drive-file'); -const { default: DriveFileThumbnail, DriveFileThumbnailChunk } = require('../built/models/drive-file-thumbnail'); -const { default: User } = require('../built/models/user'); - -const q = { - 'metadata._user.host': { - $ne: null - }, - 'metadata.withoutChunks': false -}; - -async function main() { - const promiseGens = []; - - const count = await DriveFile.count(q); - - let prev; - - for (let i = 0; i < count; i++) { - promiseGens.push(() => { - const promise = new Promise(async (res, rej) => { - const file = await DriveFile.findOne(prev ? Object.assign({ - _id: { $lt: prev._id } - }, q) : q, { - sort: { - _id: -1 - } - }); - - prev = file; - - function skip() { - res([i, file, false]); - } - - if (file == null) return skip(); - - log(chalk`{gray ${i}} scanning {bold ${file._id}} ${file.filename} ...`); - - const attachingUsersCount = await User.count({ - $or: [{ - avatarId: file._id - }, { - bannerId: file._id - }] - }, { limit: 1 }); - if (attachingUsersCount !== 0) return skip(); - - Promise.all([ - // チャンクをすべて削除 - DriveFileChunk.remove({ - files_id: file._id - }), - - DriveFile.update({ _id: file._id }, { - $set: { - 'metadata.withoutChunks': true - } - }) - ]).then(async () => { - res([i, file, true]); - - //#region サムネイルもあれば削除 - const thumbnail = await DriveFileThumbnail.findOne({ - 'metadata.originalId': file._id - }); - - if (thumbnail) { - DriveFileThumbnailChunk.remove({ - files_id: thumbnail._id - }); - - DriveFileThumbnail.remove({ _id: thumbnail._id }); - } - //#endregion - }); - }); - - promise.then(([i, file, deleted]) => { - if (deleted) { - log(chalk`{gray ${i}} {red deleted: {bold ${file._id}} ${file.filename}}`); - } else { - log(chalk`{gray ${i}} {green skipped: {bold ${file._id}} ${file.filename}}`); - } - log.clear(); - console.log(); - }); - - return promise; - }); - } - - return await sequential(promiseGens); -} - -main().then(() => { - console.log('ALL DONE'); -}).catch(console.error); diff --git a/cli/clean-unused-drive-files.js b/cli/clean-unused-drive-files.js deleted file mode 100644 index 87b158b9e..000000000 --- a/cli/clean-unused-drive-files.js +++ /dev/null @@ -1,80 +0,0 @@ -const chalk = require('chalk'); -const log = require('single-line-log').stdout; -const sequential = require('promise-sequential'); -const { default: DriveFile, deleteDriveFile } = require('../built/models/drive-file'); -const { default: Note } = require('../built/models/note'); -const { default: MessagingMessage } = require('../built/models/messaging-message'); -const { default: User } = require('../built/models/user'); - -async function main() { - const promiseGens = []; - - const count = await DriveFile.count({}); - - let prev; - - for (let i = 0; i < count; i++) { - promiseGens.push(() => { - const promise = new Promise(async (res, rej) => { - const file = await DriveFile.findOne(prev ? { - _id: { $lt: prev._id } - } : {}, { - sort: { - _id: -1 - } - }); - - prev = file; - - function skip() { - res([i, file, false]); - } - - if (file == null) return skip(); - - log(chalk`{gray ${i}} scanning {bold ${file._id}} ${file.filename} ...`); - - const attachingUsersCount = await User.count({ - $or: [{ - avatarId: file._id - }, { - bannerId: file._id - }] - }, { limit: 1 }); - if (attachingUsersCount !== 0) return skip(); - - const attachingNotesCount = await Note.count({ - mediaIds: file._id - }, { limit: 1 }); - if (attachingNotesCount !== 0) return skip(); - - const attachingMessagesCount = await MessagingMessage.count({ - fileId: file._id - }, { limit: 1 }); - if (attachingMessagesCount !== 0) return skip(); - - deleteDriveFile(file).then(() => { - res([i, file, true]); - }).catch(rej); - }); - - promise.then(([i, file, deleted]) => { - if (deleted) { - log(chalk`{gray ${i}} {red deleted: {bold ${file._id}} ${file.filename}}`); - } else { - log(chalk`{gray ${i}} {green skipped: {bold ${file._id}} ${file.filename}}`); - } - log.clear(); - console.log(); - }); - - return promise; - }); - } - - return await sequential(promiseGens); -} - -main().then(() => { - console.log('done'); -}).catch(console.error); diff --git a/docs/manage.en.md b/docs/manage.en.md index a7296ce47..713070517 100644 --- a/docs/manage.en.md +++ b/docs/manage.en.md @@ -33,14 +33,3 @@ node cli/suspend @syuilo@misskey.xyz ``` shell node cli/reset-password (User-ID or Username) ``` - -## Clean up cached remote files -``` shell -node cli/clean-cached-remote-files -``` - -## Clean up unused drive files -``` shell -node cli/clean-unused-drive-files -``` -> We recommend that you announce a user that unused drive files will be deleted before performing this operation, as it may delete the user's important files. diff --git a/docs/manage.ja.md b/docs/manage.ja.md index f289037ad..897fae7ec 100644 --- a/docs/manage.ja.md +++ b/docs/manage.ja.md @@ -33,14 +33,3 @@ node cli/suspend @syuilo@misskey.xyz ``` shell node cli/reset-password (ユーザーID または ユーザー名) ``` - -## キャッシュされたリモートファイルをクリーンアップする -``` shell -node cli/clean-cached-remote-files -``` - -## 使われていないドライブのファイルをクリーンアップする -``` shell -node cli/clean-unused-drive-files -``` -> ユーザーの大事なファイルを削除する可能性があるので、この操作を実行する前にユーザーに告知することをお勧めします。 diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index da0d3fd82..b090d56ce 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -17,6 +17,7 @@ import { isLocalUser, IUser, IRemoteUser } from '../../models/user'; import delFile from './delete-file'; import config from '../../config'; import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail'; +import { updateDriveStats } from '../update-chart'; const log = debug('misskey:drive:add-file'); @@ -377,7 +378,8 @@ export default async function( publishDriveStream(user._id, 'file_created', packedFile); }); - // TODO: サムネイル生成 + // 統計を更新 + updateDriveStats(driveFile, true); return driveFile; } diff --git a/src/services/drive/delete-file.ts b/src/services/drive/delete-file.ts index 445d231d6..73532a295 100644 --- a/src/services/drive/delete-file.ts +++ b/src/services/drive/delete-file.ts @@ -2,6 +2,7 @@ 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 { updateDriveStats } from '../update-chart'; export default async function(file: IDriveFile, isExpired = false) { if (file.metadata.storage == 'minio') { @@ -45,4 +46,7 @@ export default async function(file: IDriveFile, isExpired = false) { await DriveFileThumbnail.remove({ _id: thumbnail._id }); } //#endregion + + // 統計を更新 + updateDriveStats(file, false); } diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 521750dc8..d8f0f57b6 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -23,6 +23,7 @@ import registerHashtag from '../register-hashtag'; import isQuote from '../../misc/is-quote'; import { TextElementMention } from '../../mfm/parse/elements/mention'; import { TextElementHashtag } from '../../mfm/parse/elements/hashtag'; +import { updateNoteStats } from '../update-chart'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -142,6 +143,9 @@ export default async (user: IUser, data: Option, silent = false) => new Promise< return; } + // 統計を更新 + updateNoteStats(note, true); + // ハッシュタグ登録 tags.map(tag => registerHashtag(user, tag)); diff --git a/src/services/note/delete.ts b/src/services/note/delete.ts index 7f245958b..d444b13a8 100644 --- a/src/services/note/delete.ts +++ b/src/services/note/delete.ts @@ -6,6 +6,7 @@ import pack from '../../remote/activitypub/renderer'; import { deliver } from '../../queue'; import Following from '../../models/following'; import renderNote from '../../remote/activitypub/renderer/note'; +import { updateNoteStats } from '../update-chart'; /** * 投稿を削除します。 @@ -43,4 +44,7 @@ export default async function(user: IUser, note: INote) { }); } //#endregion + + // 統計を更新 + updateNoteStats(note, false); } diff --git a/src/services/update-chart.ts b/src/services/update-chart.ts index 64ba9e53c..9175d61f7 100644 --- a/src/services/update-chart.ts +++ b/src/services/update-chart.ts @@ -201,13 +201,13 @@ export async function updateNoteStats(note: INote, isAdditional: boolean) { await update(inc); } -export async function updateDriveStats(user: IUser, file: IDriveFile, isAdditional: boolean) { +export async function updateDriveStats(file: IDriveFile, isAdditional: boolean) { const inc = {} as any; const amount = isAdditional ? 1 : -1; const size = isAdditional ? file.length : -file.length; - if (isLocalUser(user)) { + if (isLocalUser(file.metadata._user)) { inc['drive.local.totalCount'] = amount; inc['drive.local.diffCount'] = amount; inc['drive.local.totalSize'] = size; From 0481de6629536f7f3144321d9e4fe2144c62d7f0 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 19 Aug 2018 00:27:23 +0900 Subject: [PATCH 7/9] wip --- cli/migration/7.0.0.js | 4 +- .../pages/admin/admin.notes-chart.chart.vue | 25 +-- .../views/pages/admin/admin.notes-chart.vue | 17 +- .../pages/admin/admin.users-chart.chart.vue | 14 +- .../views/pages/admin/admin.users-chart.vue | 17 +- .../app/desktop/views/pages/admin/admin.vue | 12 +- src/client/app/stats/style.styl | 10 - src/client/app/stats/tags/index.tag | 209 ------------------ src/client/app/stats/tags/index.ts | 1 - src/client/app/status/style.styl | 10 - src/client/app/status/tags/index.tag | 201 ----------------- src/client/app/status/tags/index.ts | 1 - src/models/{chart.ts => stats.ts} | 8 +- src/server/api/endpoints/admin/chart.ts | 97 ++++++++ src/server/api/endpoints/aggregation/notes.ts | 116 ---------- src/server/api/endpoints/aggregation/users.ts | 92 -------- src/services/update-chart.ts | 18 +- 17 files changed, 149 insertions(+), 703 deletions(-) delete mode 100644 src/client/app/stats/style.styl delete mode 100644 src/client/app/stats/tags/index.tag delete mode 100644 src/client/app/stats/tags/index.ts delete mode 100644 src/client/app/status/style.styl delete mode 100644 src/client/app/status/tags/index.tag delete mode 100644 src/client/app/status/tags/index.ts rename src/models/{chart.ts => stats.ts} (94%) create mode 100644 src/server/api/endpoints/admin/chart.ts delete mode 100644 src/server/api/endpoints/aggregation/notes.ts delete mode 100644 src/server/api/endpoints/aggregation/users.ts diff --git a/cli/migration/7.0.0.js b/cli/migration/7.0.0.js index d40670c6c..fa5e363db 100644 --- a/cli/migration/7.0.0.js +++ b/cli/migration/7.0.0.js @@ -1,4 +1,4 @@ -const { default: Chart } = require('../../built/models/chart'); +const { default: Stats } = require('../../built/models/stats'); const { default: User } = require('../../built/models/user'); const { default: Note } = require('../../built/models/note'); const { default: DriveFile } = require('../../built/models/drive-file'); @@ -80,7 +80,7 @@ async function main() { return 0; }); - await Chart.insert({ + await Stats.insert({ date: today, users: { local: { diff --git a/src/client/app/desktop/views/pages/admin/admin.notes-chart.chart.vue b/src/client/app/desktop/views/pages/admin/admin.notes-chart.chart.vue index 52bd8e7c7..3e9462f03 100644 --- a/src/client/app/desktop/views/pages/admin/admin.notes-chart.chart.vue +++ b/src/client/app/desktop/views/pages/admin/admin.notes-chart.chart.vue @@ -29,7 +29,7 @@ import Vue from 'vue'; export default Vue.extend({ props: { - data: { + chart: { required: true }, type: { @@ -39,7 +39,6 @@ export default Vue.extend({ }, data() { return { - chart: this.data, viewBoxX: 365, viewBoxY: 70, pointsNote: null, @@ -49,21 +48,17 @@ export default Vue.extend({ }; }, created() { - this.chart.forEach(d => { - d.notes = this.type == 'local' ? d.localNotes : d.remoteNotes; - d.replies = this.type == 'local' ? d.localReplies : d.remoteReplies; - d.renotes = this.type == 'local' ? d.localRenotes : d.remoteRenotes; - }); - - this.chart.forEach(d => { - d.total = d.notes + d.replies + d.renotes; - }); - - const peak = Math.max.apply(null, this.chart.map(d => d.total)); + const peak = Math.max.apply(null, this.chart.map(d => this.type == 'local' ? d.notes.local.diff : d.notes.remote.diff)); if (peak != 0) { - const data = this.chart.slice().reverse(); - this.pointsNote = data.map((d, i) => `${i},${(1 - (d.notes / peak)) * this.viewBoxY}`).join(' '); + const data = this.chart.slice().reverse().map(x => ({ + normal: this.type == 'local' ? x.notes.local.diffs.normal : x.notes.remote.diffs.normal, + replies: this.type == 'local' ? x.notes.local.diffs.replies : x.notes.remote.diffs.replies, + renotes: this.type == 'local' ? x.notes.local.diffs.renotes : x.notes.remote.diffs.renotes, + total: this.type == 'local' ? x.notes.local.diff : x.notes.remote.diff + })); + + this.pointsNote = data.map((d, i) => `${i},${(1 - (d.normal / peak)) * this.viewBoxY}`).join(' '); this.pointsReply = data.map((d, i) => `${i},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' '); this.pointsRenote = data.map((d, i) => `${i},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' '); this.pointsTotal = data.map((d, i) => `${i},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' '); diff --git a/src/client/app/desktop/views/pages/admin/admin.notes-chart.vue b/src/client/app/desktop/views/pages/admin/admin.notes-chart.vue index 8b33e59ec..e4d396d9c 100644 --- a/src/client/app/desktop/views/pages/admin/admin.notes-chart.vue +++ b/src/client/app/desktop/views/pages/admin/admin.notes-chart.vue @@ -3,11 +3,11 @@
%i18n:@title%
%i18n:@local%
- +
%i18n:@remote%
- +
@@ -20,15 +20,10 @@ export default Vue.extend({ components: { XChart }, - data() { - return { - data: null - }; - }, - created() { - (this as any).api('aggregation/notes').then(res => { - this.data = res; - }); + props: { + chart: { + required: true + } } }); diff --git a/src/client/app/desktop/views/pages/admin/admin.users-chart.chart.vue b/src/client/app/desktop/views/pages/admin/admin.users-chart.chart.vue index 10eab8527..c2ab4a78e 100644 --- a/src/client/app/desktop/views/pages/admin/admin.users-chart.chart.vue +++ b/src/client/app/desktop/views/pages/admin/admin.users-chart.chart.vue @@ -13,7 +13,7 @@ import Vue from 'vue'; export default Vue.extend({ props: { - data: { + chart: { required: true }, type: { @@ -23,21 +23,19 @@ export default Vue.extend({ }, data() { return { - chart: this.data, viewBoxX: 365, viewBoxY: 70, points: null }; }, created() { - this.chart.forEach(d => { - d.count = this.type == 'local' ? d.local : d.remote; - }); - - const peak = Math.max.apply(null, this.chart.map(d => d.count)); + const peak = Math.max.apply(null, this.chart.map(d => this.type == 'local' ? d.users.local.diff : d.users.remote.diff)); if (peak != 0) { - const data = this.chart.slice().reverse(); + const data = this.chart.slice().reverse().map(x => ({ + count: this.type == 'local' ? x.users.local.diff : x.users.remote.diff + })); + this.points = data.map((d, i) => `${i},${(1 - (d.count / peak)) * this.viewBoxY}`).join(' '); } } diff --git a/src/client/app/desktop/views/pages/admin/admin.users-chart.vue b/src/client/app/desktop/views/pages/admin/admin.users-chart.vue index bbd342e51..e62001270 100644 --- a/src/client/app/desktop/views/pages/admin/admin.users-chart.vue +++ b/src/client/app/desktop/views/pages/admin/admin.users-chart.vue @@ -3,11 +3,11 @@
%i18n:@title%
%i18n:@local%
- +
%i18n:@remote%
- +
@@ -20,15 +20,10 @@ export default Vue.extend({ components: { XChart }, - data() { - return { - data: null - }; - }, - created() { - (this as any).api('aggregation/users').then(res => { - this.data = res; - }); + props: { + chart: { + required: true + } } }); diff --git a/src/client/app/desktop/views/pages/admin/admin.vue b/src/client/app/desktop/views/pages/admin/admin.vue index 7c1dace78..add95a1a0 100644 --- a/src/client/app/desktop/views/pages/admin/admin.vue +++ b/src/client/app/desktop/views/pages/admin/admin.vue @@ -11,8 +11,8 @@
- - + +
@@ -48,9 +48,15 @@ export default Vue.extend({ }, data() { return { - page: 'dashboard' + page: 'dashboard', + chart: null }; }, + created() { + (this as any).api('admin/chart').then(chart => { + this.chart = chart; + }); + }, methods: { nav(page: string) { this.page = page; diff --git a/src/client/app/stats/style.styl b/src/client/app/stats/style.styl deleted file mode 100644 index 5ae230ea5..000000000 --- a/src/client/app/stats/style.styl +++ /dev/null @@ -1,10 +0,0 @@ -@import "../app" -@import "../reset" - -html - color #456267 - background #fff - -body - margin 0 - padding 0 diff --git a/src/client/app/stats/tags/index.tag b/src/client/app/stats/tags/index.tag deleted file mode 100644 index f8944c083..000000000 --- a/src/client/app/stats/tags/index.tag +++ /dev/null @@ -1,209 +0,0 @@ - -

MisskeyStatistics

-
- - -
- - - -
- - -

%i18n:stats.notes-count% { stats.notesCount }

- - - -
- - -

%i18n:stats.users-count% { stats.usersCount }

- - - -
- - - - Black ... Total
Blue ... Notes
Red ... Replies
Green ... Renotes
- - - - -
- - -
- - - - - - - - - diff --git a/src/client/app/stats/tags/index.ts b/src/client/app/stats/tags/index.ts deleted file mode 100644 index f41151949..000000000 --- a/src/client/app/stats/tags/index.ts +++ /dev/null @@ -1 +0,0 @@ -require('./index.tag'); diff --git a/src/client/app/status/style.styl b/src/client/app/status/style.styl deleted file mode 100644 index 5ae230ea5..000000000 --- a/src/client/app/status/style.styl +++ /dev/null @@ -1,10 +0,0 @@ -@import "../app" -@import "../reset" - -html - color #456267 - background #fff - -body - margin 0 - padding 0 diff --git a/src/client/app/status/tags/index.tag b/src/client/app/status/tags/index.tag deleted file mode 100644 index 899467097..000000000 --- a/src/client/app/status/tags/index.tag +++ /dev/null @@ -1,201 +0,0 @@ - -

MisskeyStatus

-

%fa:info-circle%%i18n:status.all-systems-maybe-operational%

-
- - -
- - - -
- - -

CPU { percentage }%

- - - -
- - -

MEM { percentage }%

- - - -
- - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/client/app/status/tags/index.ts b/src/client/app/status/tags/index.ts deleted file mode 100644 index f41151949..000000000 --- a/src/client/app/status/tags/index.ts +++ /dev/null @@ -1 +0,0 @@ -require('./index.tag'); diff --git a/src/models/chart.ts b/src/models/stats.ts similarity index 94% rename from src/models/chart.ts rename to src/models/stats.ts index a2154be4f..7bff475c6 100644 --- a/src/models/chart.ts +++ b/src/models/stats.ts @@ -1,11 +1,11 @@ import * as mongo from 'mongodb'; import db from '../db/mongodb'; -const Chart = db.get('chart'); -Chart.createIndex('date', { unique: true }); -export default Chart; +const Stats = db.get('stats'); +Stats.createIndex({ date: -1 }, { unique: true }); +export default Stats; -export interface IChart { +export interface IStats { _id: mongo.ObjectID; date: Date; diff --git a/src/server/api/endpoints/admin/chart.ts b/src/server/api/endpoints/admin/chart.ts new file mode 100644 index 000000000..4ad29a701 --- /dev/null +++ b/src/server/api/endpoints/admin/chart.ts @@ -0,0 +1,97 @@ +import Stats, { IStats } from '../../../../models/stats'; + +type Omit = Pick>; + +export const meta = { + requireCredential: true, + requireAdmin: true +}; + +export default (params: any) => new Promise(async (res, rej) => { + const now = new Date(); + const y = now.getFullYear(); + const m = now.getMonth(); + const d = now.getDate(); + + const stats = await Stats.find({ + date: { + $gt: new Date(y - 1, m, d) + } + }, { + sort: { + date: -1 + }, + fields: { + _id: 0 + } + }); + + const chart: Array> = []; + + for (let i = 364; i >= 0; i--) { + const day = new Date(y, m, d - i); + + const stat = stats.find(s => s.date.getTime() == day.getTime()); + + if (stat) { + chart.push(stat); + } else { // 隙間埋め + const mostRecent = stats.find(s => s.date.getTime() < day.getTime()); + if (mostRecent) { + chart.push(Object.assign({}, mostRecent, { + date: day + })); + } else { + chart.push({ + date: day, + users: { + local: { + total: 0, + diff: 0 + }, + remote: { + total: 0, + diff: 0 + } + }, + notes: { + local: { + total: 0, + diff: 0, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + }, + remote: { + total: 0, + diff: 0, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + } + }, + drive: { + local: { + totalCount: 0, + totalSize: 0, + diffCount: 0, + diffSize: 0 + }, + remote: { + totalCount: 0, + totalSize: 0, + diffCount: 0, + diffSize: 0 + } + } + }); + } + } + } + + res(chart); +}); diff --git a/src/server/api/endpoints/aggregation/notes.ts b/src/server/api/endpoints/aggregation/notes.ts deleted file mode 100644 index 77ed07ef4..000000000 --- a/src/server/api/endpoints/aggregation/notes.ts +++ /dev/null @@ -1,116 +0,0 @@ -import Note from '../../../../models/note'; - -export const meta = { - requireCredential: true, - requireAdmin: true -}; - -/** - * Aggregate notes - */ -export default (params: any) => new Promise(async (res, rej) => { - const query = [{ - $match: { - createdAt: { - $gt: new Date(new Date().setFullYear(new Date().getFullYear() - 1)) - } - } - }, { - $project: { - renoteId: '$renoteId', - replyId: '$replyId', - user: '$_user', - createdAt: { $add: ['$createdAt', 9 * 60 * 60 * 1000] } // Convert into JST - } - }, { - $project: { - date: { - year: { $year: '$createdAt' }, - month: { $month: '$createdAt' }, - day: { $dayOfMonth: '$createdAt' } - }, - type: { - $cond: { - if: { $ne: ['$renoteId', null] }, - then: 'renote', - else: { - $cond: { - if: { $ne: ['$replyId', null] }, - then: 'reply', - else: 'note' - } - } - } - }, - origin: { - $cond: { - if: { $eq: ['$user.host', null] }, - then: 'local', - else: 'remote' - } - } - } - }, { - $group: { - _id: { - date: '$date', - type: '$type', - origin: '$origin' - }, - count: { $sum: 1 } - } - }, { - $group: { - _id: '$_id.date', - data: { - $addToSet: { - type: '$_id.type', - origin: '$_id.origin', - count: '$count' - } - } - } - }] as any; - - const datas = await Note.aggregate(query); - - datas.forEach((data: any) => { - data.date = data._id; - delete data._id; - - data.localNotes = (data.data.filter((x: any) => x.type == 'note' && x.origin == 'local')[0] || { count: 0 }).count; - data.localRenotes = (data.data.filter((x: any) => x.type == 'renote' && x.origin == 'local')[0] || { count: 0 }).count; - data.localReplies = (data.data.filter((x: any) => x.type == 'reply' && x.origin == 'local')[0] || { count: 0 }).count; - data.remoteNotes = (data.data.filter((x: any) => x.type == 'note' && x.origin == 'remote')[0] || { count: 0 }).count; - data.remoteRenotes = (data.data.filter((x: any) => x.type == 'renote' && x.origin == 'remote')[0] || { count: 0 }).count; - data.remoteReplies = (data.data.filter((x: any) => x.type == 'reply' && x.origin == 'remote')[0] || { count: 0 }).count; - - delete data.data; - }); - - const graph = []; - - for (let i = 0; i < 365; i++) { - const day = new Date(new Date().setDate(new Date().getDate() - i)); - - const data = datas.filter((d: any) => - d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() - )[0]; - - if (data) { - graph.push(data); - } else { - graph.push({ - date: { year: day.getFullYear(), month: day.getMonth() + 1, day: day.getDate() }, - localNotes: 0, - localRenotes: 0, - localReplies: 0, - remoteNotes: 0, - remoteRenotes: 0, - remoteReplies: 0 - }); - } - } - - res(graph); -}); diff --git a/src/server/api/endpoints/aggregation/users.ts b/src/server/api/endpoints/aggregation/users.ts deleted file mode 100644 index d01648423..000000000 --- a/src/server/api/endpoints/aggregation/users.ts +++ /dev/null @@ -1,92 +0,0 @@ -import User from '../../../../models/user'; - -export const meta = { - requireCredential: true, - requireAdmin: true -}; - -/** - * Aggregate users - */ -export default (params: any) => new Promise(async (res, rej) => { - const query = [{ - $match: { - createdAt: { - $gt: new Date(new Date().setFullYear(new Date().getFullYear() - 1)) - } - } - }, { - $project: { - host: '$host', - createdAt: { $add: ['$createdAt', 9 * 60 * 60 * 1000] } // Convert into JST - } - }, { - $project: { - date: { - year: { $year: '$createdAt' }, - month: { $month: '$createdAt' }, - day: { $dayOfMonth: '$createdAt' } - }, - origin: { - $cond: { - if: { $eq: ['$host', null] }, - then: 'local', - else: 'remote' - } - } - } - }, { - $group: { - _id: { - date: '$date', - origin: '$origin' - }, - count: { $sum: 1 } - } - }, { - $group: { - _id: '$_id.date', - data: { - $addToSet: { - type: '$_id.type', - origin: '$_id.origin', - count: '$count' - } - } - } - }] as any; - - const datas = await User.aggregate(query); - - datas.forEach((data: any) => { - data.date = data._id; - delete data._id; - - data.local = (data.data.filter((x: any) => x.origin == 'local')[0] || { count: 0 }).count; - data.remote = (data.data.filter((x: any) => x.origin == 'remote')[0] || { count: 0 }).count; - - delete data.data; - }); - - const graph = []; - - for (let i = 0; i < 365; i++) { - const day = new Date(new Date().setDate(new Date().getDate() - i)); - - const data = datas.filter((d: any) => - d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() - )[0]; - - if (data) { - graph.push(data); - } else { - graph.push({ - date: { year: day.getFullYear(), month: day.getMonth() + 1, day: day.getDate() }, - local: 0, - remote: 0 - }); - } - } - - res(graph); -}); diff --git a/src/services/update-chart.ts b/src/services/update-chart.ts index 9175d61f7..7998baca9 100644 --- a/src/services/update-chart.ts +++ b/src/services/update-chart.ts @@ -1,11 +1,11 @@ import { INote } from '../models/note'; -import Chart, { IChart } from '../models/chart'; +import Stats, { IStats } from '../models/stats'; import { isLocalUser, IUser } from '../models/user'; import { IDriveFile } from '../models/drive-file'; type Omit = Pick>; -async function getTodayStats(): Promise { +async function getTodayStats(): Promise { const now = new Date(); const y = now.getFullYear(); const m = now.getMonth(); @@ -13,7 +13,7 @@ async function getTodayStats(): Promise { const today = new Date(y, m, d); // 今日の統計 - const todayStats = await Chart.findOne({ + const todayStats = await Stats.findOne({ date: today }); @@ -23,7 +23,7 @@ async function getTodayStats(): Promise { // * 昨日何もチャートを更新するような出来事がなかった場合は、 // 統計がそもそも作られずドキュメントが存在しないということがあり得るため、 // 「昨日の」と決め打ちせずに「もっとも最近の」とします - const mostRecentStats = await Chart.findOne({}, { + const mostRecentStats = await Stats.findOne({}, { sort: { date: -1 } @@ -33,7 +33,7 @@ async function getTodayStats(): Promise { // * Misskeyインスタンスを建てて初めてのチャート更新時など if (mostRecentStats == null) { // 空の統計を作成 - const chart: Omit = { + const chart: Omit = { date: today, users: { local: { @@ -81,12 +81,12 @@ async function getTodayStats(): Promise { } }; - const stats = await Chart.insert(chart); + const stats = await Stats.insert(chart); return stats; } else { // 今日の統計を初期挿入 - const chart: Omit = { + const chart: Omit = { date: today, users: { local: { @@ -134,7 +134,7 @@ async function getTodayStats(): Promise { } }; - const stats = await Chart.insert(chart); + const stats = await Stats.insert(chart); return stats; } @@ -146,7 +146,7 @@ async function getTodayStats(): Promise { async function update(inc: any) { const stats = await getTodayStats(); - await Chart.findOneAndUpdate({ + await Stats.findOneAndUpdate({ _id: stats._id }, { $inc: inc From f59c68022ff75f2e77dd804b21f57748fcf4fdce Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 19 Aug 2018 00:42:09 +0900 Subject: [PATCH 8/9] Fix bug --- .../views/pages/admin/admin.notes-chart.chart.vue | 8 ++++---- src/server/api/endpoints/admin/chart.ts | 10 +++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/client/app/desktop/views/pages/admin/admin.notes-chart.chart.vue b/src/client/app/desktop/views/pages/admin/admin.notes-chart.chart.vue index 3e9462f03..83c61c131 100644 --- a/src/client/app/desktop/views/pages/admin/admin.notes-chart.chart.vue +++ b/src/client/app/desktop/views/pages/admin/admin.notes-chart.chart.vue @@ -53,14 +53,14 @@ export default Vue.extend({ if (peak != 0) { const data = this.chart.slice().reverse().map(x => ({ normal: this.type == 'local' ? x.notes.local.diffs.normal : x.notes.remote.diffs.normal, - replies: this.type == 'local' ? x.notes.local.diffs.replies : x.notes.remote.diffs.replies, - renotes: this.type == 'local' ? x.notes.local.diffs.renotes : x.notes.remote.diffs.renotes, + reply: this.type == 'local' ? x.notes.local.diffs.reply : x.notes.remote.diffs.reply, + renote: this.type == 'local' ? x.notes.local.diffs.renote : x.notes.remote.diffs.renote, total: this.type == 'local' ? x.notes.local.diff : x.notes.remote.diff })); this.pointsNote = data.map((d, i) => `${i},${(1 - (d.normal / peak)) * this.viewBoxY}`).join(' '); - this.pointsReply = data.map((d, i) => `${i},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' '); - this.pointsRenote = data.map((d, i) => `${i},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' '); + this.pointsReply = data.map((d, i) => `${i},${(1 - (d.reply / peak)) * this.viewBoxY}`).join(' '); + this.pointsRenote = data.map((d, i) => `${i},${(1 - (d.renote / peak)) * this.viewBoxY}`).join(' '); this.pointsTotal = data.map((d, i) => `${i},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' '); } } diff --git a/src/server/api/endpoints/admin/chart.ts b/src/server/api/endpoints/admin/chart.ts index 4ad29a701..a0566b11f 100644 --- a/src/server/api/endpoints/admin/chart.ts +++ b/src/server/api/endpoints/admin/chart.ts @@ -34,15 +34,15 @@ export default (params: any) => new Promise(async (res, rej) => { const stat = stats.find(s => s.date.getTime() == day.getTime()); if (stat) { - chart.push(stat); + chart.unshift(stat); } else { // 隙間埋め const mostRecent = stats.find(s => s.date.getTime() < day.getTime()); if (mostRecent) { - chart.push(Object.assign({}, mostRecent, { + chart.unshift(Object.assign({}, mostRecent, { date: day })); } else { - chart.push({ + chart.unshift({ date: day, users: { local: { @@ -93,5 +93,9 @@ export default (params: any) => new Promise(async (res, rej) => { } } + chart.forEach(x => { + delete x.date; + }); + res(chart); }); From 8ecf3db608bf488c13695b62f691e4c269f46361 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 19 Aug 2018 00:45:54 +0900 Subject: [PATCH 9/9] Add drive chart --- locales/ja.yml | 5 ++ .../pages/admin/admin.drive-chart.chart.vue | 51 +++++++++++++++++++ .../views/pages/admin/admin.drive-chart.vue | 34 +++++++++++++ .../app/desktop/views/pages/admin/admin.vue | 5 +- 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 src/client/app/desktop/views/pages/admin/admin.drive-chart.chart.vue create mode 100644 src/client/app/desktop/views/pages/admin/admin.drive-chart.vue diff --git a/locales/ja.yml b/locales/ja.yml index 55c37fab6..5d5ef76ea 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -946,6 +946,11 @@ desktop/views/pages/admin/admin.users-chart.vue: local: "ローカル" remote: "リモート" +desktop/views/pages/admin/admin.drive-chart.vue: + title: "ドライブ" + local: "ローカル" + remote: "リモート" + desktop/views/pages/deck/deck.tl-column.vue: is-media-only: "メディア投稿のみ" is-media-view: "メディアビュー" diff --git a/src/client/app/desktop/views/pages/admin/admin.drive-chart.chart.vue b/src/client/app/desktop/views/pages/admin/admin.drive-chart.chart.vue new file mode 100644 index 000000000..3c537d8d6 --- /dev/null +++ b/src/client/app/desktop/views/pages/admin/admin.drive-chart.chart.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/client/app/desktop/views/pages/admin/admin.drive-chart.vue b/src/client/app/desktop/views/pages/admin/admin.drive-chart.vue new file mode 100644 index 000000000..4f94fd237 --- /dev/null +++ b/src/client/app/desktop/views/pages/admin/admin.drive-chart.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/client/app/desktop/views/pages/admin/admin.vue b/src/client/app/desktop/views/pages/admin/admin.vue index add95a1a0..cbb1890cc 100644 --- a/src/client/app/desktop/views/pages/admin/admin.vue +++ b/src/client/app/desktop/views/pages/admin/admin.vue @@ -13,6 +13,7 @@ +
@@ -35,6 +36,7 @@ import XVerifyUser from "./admin.verify-user.vue"; import XUnverifyUser from "./admin.unverify-user.vue"; import XUsersChart from "./admin.users-chart.vue"; import XNotesChart from "./admin.notes-chart.vue"; +import XDriveChart from "./admin.drive-chart.vue"; export default Vue.extend({ components: { @@ -44,7 +46,8 @@ export default Vue.extend({ XVerifyUser, XUnverifyUser, XUsersChart, - XNotesChart + XNotesChart, + XDriveChart }, data() { return {