From 4fb7ee760aacdd2d52e8c43b3156fc0ef6deb11e Mon Sep 17 00:00:00 2001
From: rinsuki <428rinsuki+git@gmail.com>
Date: Thu, 23 Aug 2018 11:58:44 +0900
Subject: [PATCH 1/3] =?UTF-8?q?https://misskey.xyz/notes/5b7e20bd248403003?=
 =?UTF-8?q?019b860=20=E3=81=AE=E4=BF=AE=E6=AD=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/remote/resolve-user.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/remote/resolve-user.ts b/src/remote/resolve-user.ts
index 1e8fc5d750..e199b6f147 100644
--- a/src/remote/resolve-user.ts
+++ b/src/remote/resolve-user.ts
@@ -15,7 +15,7 @@ export default async (username: string, _host: string, option?: any): Promise<IU
 	const host = toUnicode(hostAscii);
 
 	if (config.host == host) {
-		return await User.findOne({ usernameLower });
+		return await User.findOne({ usernameLower, host: null });
 	}
 
 	let user = await User.findOne({ usernameLower, host }, option);

From e31a2f7e55bb96d661945d0475cc5cc678c0eb18 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 23 Aug 2018 14:56:39 +0900
Subject: [PATCH 2/3] Fix bug: Check following request existance

---
 src/server/api/endpoints/following/requests/cancel.ts | 6 +++++-
 src/services/following/requests/cancel.ts             | 9 +++++++++
 2 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/src/server/api/endpoints/following/requests/cancel.ts b/src/server/api/endpoints/following/requests/cancel.ts
index 9bfc40ce65..c46b948d29 100644
--- a/src/server/api/endpoints/following/requests/cancel.ts
+++ b/src/server/api/endpoints/following/requests/cancel.ts
@@ -27,7 +27,11 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
 		return rej('followee not found');
 	}
 
-	await cancelFollowRequest(followee, user);
+	try {
+		await cancelFollowRequest(followee, user);
+	} catch (e) {
+		return rej(e);
+	}
 
 	// Send response
 	res(await pack(followee._id, user));
diff --git a/src/services/following/requests/cancel.ts b/src/services/following/requests/cancel.ts
index b0b574da58..26e4544d5c 100644
--- a/src/services/following/requests/cancel.ts
+++ b/src/services/following/requests/cancel.ts
@@ -12,6 +12,15 @@ export default async function(followee: IUser, follower: IUser) {
 		deliver(follower as ILocalUser, content, followee.inbox);
 	}
 
+	const request = await FollowRequest.findOne({
+		followeeId: followee._id,
+		followerId: follower._id
+	});
+
+	if (request == null) {
+		throw 'request not found';
+	}
+
 	await FollowRequest.remove({
 		followeeId: followee._id,
 		followerId: follower._id

From 8fc1e07136d5ee203cbd1a1bc2ec00dfeb0e8cf0 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 23 Aug 2018 15:40:24 +0900
Subject: [PATCH 3/3] =?UTF-8?q?1=E6=99=82=E9=96=93=E5=8D=98=E4=BD=8D?=
 =?UTF-8?q?=E3=81=A7=E3=81=AE=E9=9B=86=E8=A8=88=E3=82=92=E8=BF=BD=E5=8A=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 cli/migration/8.0.0.js                  | 144 ++++++++++++++++++++
 package.json                            |   2 +-
 src/models/stats.ts                     |   2 +
 src/server/api/endpoints/admin/chart.ts |   2 +
 src/services/update-chart.ts            | 172 +++++++++++++-----------
 5 files changed, 245 insertions(+), 77 deletions(-)
 create mode 100644 cli/migration/8.0.0.js

diff --git a/cli/migration/8.0.0.js b/cli/migration/8.0.0.js
new file mode 100644
index 0000000000..fd6cb24525
--- /dev/null
+++ b/cli/migration/8.0.0.js
@@ -0,0 +1,144 @@
+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');
+
+const now = new Date();
+const y = now.getFullYear();
+const m = now.getMonth();
+const d = now.getDate();
+const h = now.getHours();
+const date = new Date(y, m, d, h);
+
+async function main() {
+	await Stats.update({}, {
+		$set: {
+			span: 'day'
+		}
+	}, {
+		multi: true
+	});
+
+	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 Stats.insert({
+		date: date,
+		span: 'hour',
+		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 03f63a029d..077d30b96a 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
 	"name": "misskey",
 	"author": "syuilo <i@syuilo.com>",
-	"version": "7.4.1",
+	"version": "8.0.0",
 	"clientVersion": "1.0.8790",
 	"codename": "nighthike",
 	"main": "./built/index.js",
diff --git a/src/models/stats.ts b/src/models/stats.ts
index 7bff475c63..c481c3196e 100644
--- a/src/models/stats.ts
+++ b/src/models/stats.ts
@@ -10,6 +10,8 @@ export interface IStats {
 
 	date: Date;
 
+	span: 'day' | 'hour';
+
 	/**
 	 * ユーザーに関する統計
 	 */
diff --git a/src/server/api/endpoints/admin/chart.ts b/src/server/api/endpoints/admin/chart.ts
index a0566b11f5..c351c7167d 100644
--- a/src/server/api/endpoints/admin/chart.ts
+++ b/src/server/api/endpoints/admin/chart.ts
@@ -14,6 +14,7 @@ export default (params: any) => new Promise(async (res, rej) => {
 	const d = now.getDate();
 
 	const stats = await Stats.find({
+		span: 'day',
 		date: {
 			$gt: new Date(y - 1, m, d)
 		}
@@ -44,6 +45,7 @@ export default (params: any) => new Promise(async (res, rej) => {
 			} else {
 				chart.unshift({
 					date: day,
+					span: 'day',
 					users: {
 						local: {
 							total: 0,
diff --git a/src/services/update-chart.ts b/src/services/update-chart.ts
index 6b69adbdc3..0a0f58bb92 100644
--- a/src/services/update-chart.ts
+++ b/src/services/update-chart.ts
@@ -5,89 +5,46 @@ import { IDriveFile } from '../models/drive-file';
 
 type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
 
-async function getTodayStats(): Promise<IStats> {
+async function getCurrentStats(span: 'day' | 'hour'): Promise<IStats> {
 	const now = new Date();
 	const y = now.getFullYear();
 	const m = now.getMonth();
 	const d = now.getDate();
-	const today = new Date(y, m, d);
+	const h = now.getHours();
 
-	// 今日の統計
-	const todayStats = await Stats.findOne({
-		date: today
+	const current =
+		span == 'day' ? new Date(y, m, d) :
+		span == 'hour' ? new Date(y, m, d, h) :
+		null;
+
+	// 現在(今日または今のHour)の統計
+	const currentStats = await Stats.findOne({
+		span: span,
+		date: current
 	});
 
-	// 日付が変わってから、初めてのチャート更新なら
-	if (todayStats == null) {
+	if (currentStats) {
+		return currentStats;
+	} else {
+		// 集計期間が変わってから、初めてのチャート更新なら
 		// 最も最近の統計を持ってくる
+		// * 例えば集計期間が「日」である場合で考えると、
 		// * 昨日何もチャートを更新するような出来事がなかった場合は、
-		//   統計がそもそも作られずドキュメントが存在しないということがあり得るため、
-		//   「昨日の」と決め打ちせずに「もっとも最近の」とします
-		const mostRecentStats = await Stats.findOne({}, {
+		// * 統計がそもそも作られずドキュメントが存在しないということがあり得るため、
+		// * 「昨日の」と決め打ちせずに「もっとも最近の」とします
+		const mostRecentStats = await Stats.findOne({
+			span: span
+		}, {
 			sort: {
 				date: -1
 			}
 		});
 
-		// 統計が存在しなかったら
-		// * Misskeyインスタンスを建てて初めてのチャート更新時など
-		if (mostRecentStats == null) {
-			// 空の統計を作成
+		if (mostRecentStats) {
+			// 現在の統計を初期挿入
 			const data: Omit<IStats, '_id'> = {
-				date: today,
-				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
-					}
-				}
-			};
-
-			const stats = await Stats.insert(data);
-
-			return stats;
-		} else {
-			// 今日の統計を初期挿入
-			const data: Omit<IStats, '_id'> = {
-				date: today,
+				span: span,
+				date: current,
 				users: {
 					local: {
 						total: mostRecentStats.users.local.total,
@@ -136,20 +93,83 @@ async function getTodayStats(): Promise<IStats> {
 
 			const stats = await Stats.insert(data);
 
+			return stats;
+		} else {
+			// 統計が存在しなかったら
+			// * Misskeyインスタンスを建てて初めてのチャート更新時など
+
+			// 空の統計を作成
+			const emptyStat: Omit<IStats, '_id'> = {
+				span: span,
+				date: current,
+				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
+					}
+				}
+			};
+
+			const stats = await Stats.insert(emptyStat);
+
 			return stats;
 		}
-	} else {
-		return todayStats;
 	}
 }
 
-async function update(inc: any) {
-	const stats = await getTodayStats();
+function update(inc: any) {
+	getCurrentStats('day').then(stats => {
+		Stats.findOneAndUpdate({
+			_id: stats._id
+		}, {
+			$inc: inc
+		});
+	});
 
-	await Stats.findOneAndUpdate({
-		_id: stats._id
-	}, {
-		$inc: inc
+	getCurrentStats('hour').then(stats => {
+		Stats.findOneAndUpdate({
+			_id: stats._id
+		}, {
+			$inc: inc
+		});
 	});
 }