From b134467bd345598f4386992f0e6e4462114612ee Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sun, 16 Jun 2019 21:26:43 +0900
Subject: [PATCH 01/19] Add some MFM tests

---
 test/mfm.ts | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/test/mfm.ts b/test/mfm.ts
index 89b414eba8..34dd324319 100644
--- a/test/mfm.ts
+++ b/test/mfm.ts
@@ -1173,6 +1173,24 @@ describe('MFM', () => {
 					text('foo_bar_baz'),
 				]);
 			});
+
+			it('require spaces', () => {
+				const tokens = parse('湊おじたん@4日目_L38b #pixiv https://www.pixiv.net/member_illust.php');
+				assert.deepStrictEqual(tokens, [
+					text('湊おじたん@4日目_L38b #pixiv https://www.pixiv.net/member_illust.php'),
+				]);
+			});
+
+			it('newline sandwich', () => {
+				const tokens = parse('foo\n_bar_\nbaz');
+				assert.deepStrictEqual(tokens, [
+					text('foo\n'),
+					tree('italic', [
+						text('bar')
+					], {}),
+					text('baz'),
+				]);
+			});
 		});
 	});
 

From 5042d23bc43dcb80d246cac8cad369ef90a5b600 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sun, 16 Jun 2019 21:29:31 +0900
Subject: [PATCH 02/19] Simplify test

---
 test/mfm.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/mfm.ts b/test/mfm.ts
index 34dd324319..0be683d56f 100644
--- a/test/mfm.ts
+++ b/test/mfm.ts
@@ -1175,9 +1175,9 @@ describe('MFM', () => {
 			});
 
 			it('require spaces', () => {
-				const tokens = parse('湊おじたん@4日目_L38b #pixiv https://www.pixiv.net/member_illust.php');
+				const tokens = parse('4日目_L38b a_b');
 				assert.deepStrictEqual(tokens, [
-					text('湊おじたん@4日目_L38b #pixiv https://www.pixiv.net/member_illust.php'),
+					text('4日目_L38b a_b'),
 				]);
 			});
 

From e7effd606d500e0491266be112ef611270e7c3af Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sun, 16 Jun 2019 21:30:26 +0900
Subject: [PATCH 03/19] Fix test

---
 test/mfm.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/mfm.ts b/test/mfm.ts
index 0be683d56f..cdd07788ba 100644
--- a/test/mfm.ts
+++ b/test/mfm.ts
@@ -1188,7 +1188,7 @@ describe('MFM', () => {
 					tree('italic', [
 						text('bar')
 					], {}),
-					text('baz'),
+					text('\nbaz'),
 				]);
 			});
 		});

From af60b45ee7823a3f01294a6fec238cee25f6ec79 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sun, 16 Jun 2019 21:30:51 +0900
Subject: [PATCH 04/19] Fix MFM italic parsing

---
 src/mfm/language.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mfm/language.ts b/src/mfm/language.ts
index 003ae348a4..7da1af7cff 100644
--- a/src/mfm/language.ts
+++ b/src/mfm/language.ts
@@ -98,7 +98,7 @@ export const mfmLanguage = P.createLanguage({
 			const text = input.substr(i);
 			const match = text.match(/^(\*|_)([a-zA-Z0-9]+?[\s\S]*?)\1/);
 			if (!match) return P.makeFailure(i, 'not a italic');
-			if (input[i - 1] != null && input[i - 1].match(/[a-z0-9]/i)) return P.makeFailure(i, 'not a italic');
+			if (input[i - 1] != null && input[i - 1] != ' ' && input[i - 1] != '\n') return P.makeFailure(i, 'not a italic');
 			return P.makeSuccess(i + match[0].length, match[2]);
 		});
 

From 63c659bc8fd8ddd6eda1462f29a61315955c112f Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sun, 16 Jun 2019 21:42:57 +0900
Subject: [PATCH 05/19] Fix MFM strike parsing

---
 src/mfm/language.ts | 2 +-
 test/mfm.ts         | 8 ++++++++
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/src/mfm/language.ts b/src/mfm/language.ts
index 7da1af7cff..bfa22e8c3b 100644
--- a/src/mfm/language.ts
+++ b/src/mfm/language.ts
@@ -104,7 +104,7 @@ export const mfmLanguage = P.createLanguage({
 
 		return P.alt(xml, underscore).map(x => createTree('italic', r.inline.atLeast(1).tryParse(x), {}));
 	},
-	strike: r => P.regexp(/~~(.+?)~~/, 1).map(x => createTree('strike', r.inline.atLeast(1).tryParse(x), {})),
+	strike: r => P.regexp(/~~([^\n~]+?)~~/, 1).map(x => createTree('strike', r.inline.atLeast(1).tryParse(x), {})),
 	motion: r => {
 		const paren = P.regexp(/\(\(\(([\s\S]+?)\)\)\)/, 1);
 		const xml = P.regexp(/<motion>(.+?)<\/motion>/, 1);
diff --git a/test/mfm.ts b/test/mfm.ts
index cdd07788ba..c772a62dcb 100644
--- a/test/mfm.ts
+++ b/test/mfm.ts
@@ -1116,6 +1116,14 @@ describe('MFM', () => {
 					], {}),
 				]);
 			});
+
+			// https://misskey.io/notes/7u1kv5dmia
+			it('ignore internal tilde', () => {
+				const tokens = parse('~~~~~');
+				assert.deepStrictEqual(tokens, [
+					text('~~~~~')
+				]);
+			});
 		});
 
 		describe('italic', () => {

From 285d0d13f9c3af4f709e0fca6ce39fcef90d90e0 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Mon, 17 Jun 2019 20:15:19 +0900
Subject: [PATCH 06/19] Fix MFM URL parsing

---
 src/mfm/language.ts | 6 ++++--
 test/mfm.ts         | 8 ++++++++
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/src/mfm/language.ts b/src/mfm/language.ts
index bfa22e8c3b..4750ea3380 100644
--- a/src/mfm/language.ts
+++ b/src/mfm/language.ts
@@ -164,8 +164,10 @@ export const mfmLanguage = P.createLanguage({
 			} else
 				url = match[0];
 			url = removeOrphanedBrackets(url);
-			if (url.endsWith('.')) url = url.substr(0, url.lastIndexOf('.'));
-			if (url.endsWith(',')) url = url.substr(0, url.lastIndexOf(','));
+			while (url.endsWith('.') || url.endsWith(',')) {
+				if (url.endsWith('.')) url = url.substr(0, url.lastIndexOf('.'));
+				if (url.endsWith(',')) url = url.substr(0, url.lastIndexOf(','));
+			}
 			return P.makeSuccess(i + url.length, url);
 		}).map(x => createLeaf('url', { url: x }));
 	},
diff --git a/test/mfm.ts b/test/mfm.ts
index c772a62dcb..be8b65264a 100644
--- a/test/mfm.ts
+++ b/test/mfm.ts
@@ -804,6 +804,14 @@ describe('MFM', () => {
 				]);
 			});
 
+			it('ignore trailing periods', () => {
+				const tokens = parse('https://example.com...');
+				assert.deepStrictEqual(tokens, [
+					leaf('url', { url: 'https://example.com' }),
+					text('...')
+				]);
+			});
+
 			it('with comma', () => {
 				const tokens = parse('https://example.com/foo?bar=a,b');
 				assert.deepStrictEqual(tokens, [

From 1a984de8e83c615faa631d868b242eb679e4eea6 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 18 Jun 2019 13:58:59 +0900
Subject: [PATCH 07/19] Better error handling

---
 src/server/api/endpoints/i/update-email.ts | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/server/api/endpoints/i/update-email.ts b/src/server/api/endpoints/i/update-email.ts
index 56284499d3..ca95e612a3 100644
--- a/src/server/api/endpoints/i/update-email.ts
+++ b/src/server/api/endpoints/i/update-email.ts
@@ -8,6 +8,7 @@ import * as bcrypt from 'bcryptjs';
 import { Users, UserProfiles } from '../../../../models';
 import { ensure } from '../../../../prelude/ensure';
 import { sendEmail } from '../../../../services/send-email';
+import { ApiError } from '../../error';
 
 export const meta = {
 	requireCredential: true,
@@ -27,6 +28,14 @@ export const meta = {
 		email: {
 			validator: $.optional.nullable.str
 		},
+	},
+
+	errors: {
+		incorrectPassword: {
+			message: 'Incorrect password.',
+			code: 'INCORRECT_PASSWORD',
+			id: 'e54c1d7e-e7d6-4103-86b6-0a95069b4ad3'
+		},
 	}
 };
 
@@ -37,7 +46,7 @@ export default define(meta, async (ps, user) => {
 	const same = await bcrypt.compare(ps.password, profile.password!);
 
 	if (!same) {
-		throw new Error('incorrect password');
+		throw new ApiError(meta.errors.incorrectPassword);
 	}
 
 	await UserProfiles.update({ userId: user.id }, {

From 7096c0ca49227af6182d08974d36bae6d1fc8363 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 18 Jun 2019 14:04:41 +0900
Subject: [PATCH 08/19] Add note

---
 src/misc/donwload-url.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/misc/donwload-url.ts b/src/misc/donwload-url.ts
index 167e01fdd1..9ab5fe0b0a 100644
--- a/src/misc/donwload-url.ts
+++ b/src/misc/donwload-url.ts
@@ -25,6 +25,7 @@ export async function downloadUrl(url: string, path: string) {
 			rej(error);
 		});
 
+		// 多バイト文字が含まれてそうだったらリクエスト前にURIエンコードする
 		const requestUrl = new URL(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
 
 		const req = request({

From 736fdabc1dab2a65e98f6abbb858bc93a8078933 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 18 Jun 2019 15:19:19 +0900
Subject: [PATCH 09/19] Fix #2637

---
 src/misc/donwload-url.ts | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/misc/donwload-url.ts b/src/misc/donwload-url.ts
index 9ab5fe0b0a..e2f10c0e40 100644
--- a/src/misc/donwload-url.ts
+++ b/src/misc/donwload-url.ts
@@ -25,11 +25,8 @@ export async function downloadUrl(url: string, path: string) {
 			rej(error);
 		});
 
-		// 多バイト文字が含まれてそうだったらリクエスト前にURIエンコードする
-		const requestUrl = new URL(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
-
 		const req = request({
-			url: requestUrl,
+			url: new URL(url).href, // https://github.com/syuilo/misskey/issues/2637
 			proxy: config.proxy,
 			timeout: 10 * 1000,
 			headers: {

From 1331f0b953d7db15a7a12edc00ec20d8bf789d53 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 18 Jun 2019 15:27:13 +0900
Subject: [PATCH 10/19] Use WHATWG API

> New application code should use the WHATWG API.
---
 src/config/load.ts                      | 1 -
 src/mfm/fromHtml.ts                     | 1 -
 src/misc/convert-host.ts                | 1 -
 src/queue/processors/inbox.ts           | 1 -
 src/remote/activitypub/models/person.ts | 1 -
 src/remote/activitypub/request.ts       | 1 -
 src/remote/resolve-user.ts              | 1 -
 src/remote/webfinger.ts                 | 1 -
 8 files changed, 8 deletions(-)

diff --git a/src/config/load.ts b/src/config/load.ts
index 26b25eab4e..aeed54d74e 100644
--- a/src/config/load.ts
+++ b/src/config/load.ts
@@ -3,7 +3,6 @@
  */
 
 import * as fs from 'fs';
-import { URL } from 'url';
 import * as yaml from 'js-yaml';
 import { Source, Mixin } from './types';
 import * as pkg from '../../package.json';
diff --git a/src/mfm/fromHtml.ts b/src/mfm/fromHtml.ts
index 5fc4a16416..60293b07f7 100644
--- a/src/mfm/fromHtml.ts
+++ b/src/mfm/fromHtml.ts
@@ -1,5 +1,4 @@
 import { parseFragment, DefaultTreeDocumentFragment } from 'parse5';
-import { URL } from 'url';
 import { urlRegex } from './prelude';
 
 export function fromHtml(html: string): string {
diff --git a/src/misc/convert-host.ts b/src/misc/convert-host.ts
index a5fb15c66f..ad52e12588 100644
--- a/src/misc/convert-host.ts
+++ b/src/misc/convert-host.ts
@@ -1,6 +1,5 @@
 import config from '../config';
 import { toASCII } from 'punycode';
-import { URL } from 'url';
 
 export function getFullApAccount(username: string, host: string | null) {
 	return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.host)}`;
diff --git a/src/queue/processors/inbox.ts b/src/queue/processors/inbox.ts
index 21a51c962b..e71181ee73 100644
--- a/src/queue/processors/inbox.ts
+++ b/src/queue/processors/inbox.ts
@@ -3,7 +3,6 @@ import * as httpSignature from 'http-signature';
 import { IRemoteUser } from '../../models/entities/user';
 import perform from '../../remote/activitypub/perform';
 import { resolvePerson, updatePerson } from '../../remote/activitypub/models/person';
-import { URL } from 'url';
 import { publishApLogStream } from '../../services/stream';
 import Logger from '../../services/logger';
 import { registerOrFetchInstanceDoc } from '../../services/register-or-fetch-instance-doc';
diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts
index dcd64e0cd7..bfcad100fe 100644
--- a/src/remote/activitypub/models/person.ts
+++ b/src/remote/activitypub/models/person.ts
@@ -6,7 +6,6 @@ import { resolveImage } from './image';
 import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type';
 import { DriveFile } from '../../../models/entities/drive-file';
 import { fromHtml } from '../../../mfm/fromHtml';
-import { URL } from 'url';
 import { resolveNote, extractEmojis } from './note';
 import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
 import { ITag, extractHashtags } from './tag';
diff --git a/src/remote/activitypub/request.ts b/src/remote/activitypub/request.ts
index bde4921c3a..6d18e53281 100644
--- a/src/remote/activitypub/request.ts
+++ b/src/remote/activitypub/request.ts
@@ -1,6 +1,5 @@
 import { request } from 'https';
 import { sign } from 'http-signature';
-import { URL } from 'url';
 import * as crypto from 'crypto';
 import { lookup, IRunOptions } from 'lookup-dns-cache';
 import * as promiseAny from 'promise-any';
diff --git a/src/remote/resolve-user.ts b/src/remote/resolve-user.ts
index 0e2b88f05b..5253d684de 100644
--- a/src/remote/resolve-user.ts
+++ b/src/remote/resolve-user.ts
@@ -1,7 +1,6 @@
 import webFinger from './webfinger';
 import config from '../config';
 import { createPerson, updatePerson } from './activitypub/models/person';
-import { URL } from 'url';
 import { remoteLogger } from './logger';
 import chalk from 'chalk';
 import { User, IRemoteUser } from '../models/entities/user';
diff --git a/src/remote/webfinger.ts b/src/remote/webfinger.ts
index 800673943b..101a31aabb 100644
--- a/src/remote/webfinger.ts
+++ b/src/remote/webfinger.ts
@@ -1,6 +1,5 @@
 import config from '../config';
 import * as request from 'request-promise-native';
-import { URL } from 'url';
 import { query as urlQuery } from '../prelude/url';
 
 type ILink = {

From 5f2fda85badb14848098044b2b7fda12528b514a Mon Sep 17 00:00:00 2001
From: Satsuki Yanagi <17376330+u1-liquid@users.noreply.github.com>
Date: Tue, 18 Jun 2019 15:42:08 +0900
Subject: [PATCH 11/19] Resolve #5069 (#5070)

---
 src/client/app/common/views/components/poll-editor.vue | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/client/app/common/views/components/poll-editor.vue b/src/client/app/common/views/components/poll-editor.vue
index ed1d02aa2c..f7a4d3af8c 100644
--- a/src/client/app/common/views/components/poll-editor.vue
+++ b/src/client/app/common/views/components/poll-editor.vue
@@ -89,9 +89,7 @@ export default Vue.extend({
 
 		get() {
 			const at = () => {
-				const [date] = moment(this.atDate).toISOString().split('T');
-				const [hour, minute] = this.atTime.split(':');
-				return moment(`${date}T${hour}:${minute}Z`).valueOf();
+				return moment(`${this.atDate} ${this.atTime}`).valueOf();
 			};
 
 			const after = () => {

From bb52ebdc3eb53db826e488ff50af04478edc50cb Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 18 Jun 2019 15:56:03 +0900
Subject: [PATCH 12/19] Provide version of postgresql

---
 src/client/app/common/views/widgets/server.info.vue | 1 +
 src/server/api/endpoints/meta.ts                    | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/src/client/app/common/views/widgets/server.info.vue b/src/client/app/common/views/widgets/server.info.vue
index a97b4ec496..41ccd23bfe 100644
--- a/src/client/app/common/views/widgets/server.info.vue
+++ b/src/client/app/common/views/widgets/server.info.vue
@@ -3,6 +3,7 @@
 	<p>Maintainer: <b><a :href="'mailto:' + meta.maintainerEmail" target="_blank">{{ meta.maintainerName }}</a></b></p>
 	<p>Machine: {{ meta.machine }}</p>
 	<p>Node: {{ meta.node }}</p>
+	<p>PSQL: {{ meta.psql }}</p>
 	<p>Version: {{ meta.version }} </p>
 </div>
 </template>
diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts
index d18543f56a..4da0c7476c 100644
--- a/src/server/api/endpoints/meta.ts
+++ b/src/server/api/endpoints/meta.ts
@@ -6,6 +6,7 @@ import { fetchMeta } from '../../../misc/fetch-meta';
 import * as pkg from '../../../../package.json';
 import { Emojis } from '../../../models';
 import { types, bool } from '../../../misc/schema';
+import { getConnection } from 'typeorm';
 
 export const meta = {
 	stability: 'stable',
@@ -114,6 +115,7 @@ export default define(meta, async (ps, me) => {
 		machine: os.hostname(),
 		os: os.platform(),
 		node: process.version,
+		psql: await getConnection().query('SHOW server_version').then(x => x[0].server_version),
 
 		cpu: {
 			model: os.cpus()[0].model,

From b0280355e8f261919fab01aad911f77415605059 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 18 Jun 2019 16:17:20 +0900
Subject: [PATCH 13/19] Better request interval

---
 src/client/app/admin/views/dashboard.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/client/app/admin/views/dashboard.vue b/src/client/app/admin/views/dashboard.vue
index 639ec190e2..15bd853c14 100644
--- a/src/client/app/admin/views/dashboard.vue
+++ b/src/client/app/admin/views/dashboard.vue
@@ -124,7 +124,7 @@ export default Vue.extend({
 		this.connection = this.$root.stream.useSharedConnection('serverStats');
 
 		this.updateStats();
-		this.clock = setInterval(this.updateStats, 1000);
+		this.clock = setInterval(this.updateStats, 3000);
 
 		this.$root.getMeta().then(meta => {
 			this.meta = meta;

From 4f284e1bc0a5d18eed5d6ad945ca23be2091238f Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 18 Jun 2019 16:49:58 +0900
Subject: [PATCH 14/19] Resolve #5063

---
 locales/ja-JP.yml                             |  4 ++
 src/client/app/admin/views/db.vue             | 39 +++++++++++++++++++
 src/client/app/admin/views/index.vue          | 31 ++++++++-------
 .../api/endpoints/admin/get-table-stats.ts    | 37 ++++++++++++++++++
 4 files changed, 98 insertions(+), 13 deletions(-)
 create mode 100644 src/client/app/admin/views/db.vue
 create mode 100644 src/server/api/endpoints/admin/get-table-stats.ts

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 7053360a11..d9223b1c4b 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1226,8 +1226,12 @@ admin/views/index.vue:
   abuse: "スパム報告"
   queue: "ジョブキュー"
   logs: "ログ"
+  db: "データベース"
   back-to-misskey: "Misskeyに戻る"
 
+admin/views/db.vue:
+  tables: "テーブル"
+
 admin/views/dashboard.vue:
   dashboard: "ダッシュボード"
   accounts: "アカウント"
diff --git a/src/client/app/admin/views/db.vue b/src/client/app/admin/views/db.vue
new file mode 100644
index 0000000000..27bb81039e
--- /dev/null
+++ b/src/client/app/admin/views/db.vue
@@ -0,0 +1,39 @@
+<template>
+<div>
+	<ui-card>
+		<template #title><fa :icon="faDatabase"/> {{ $t('tables') }}</template>
+		<section v-if="tables">
+			<div v-for="table in Object.keys(tables)"><b>{{ table }}</b> {{ tables[table].count }} {{ tables[table].size | bytes }}</div>
+		</section>
+	</ui-card>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import i18n from '../../i18n';
+import { faDatabase } from '@fortawesome/free-solid-svg-icons';
+
+export default Vue.extend({
+	i18n: i18n('admin/views/db.vue'),
+
+	data() {
+		return {
+			tables: null,
+			faDatabase
+		};
+	},
+
+	mounted() {
+		this.fetch();
+	},
+
+	methods: {
+		fetch() {
+			this.$root.api('admin/get-table-stats').then(tables => {
+				this.tables = tables;
+			});
+		},
+	}
+});
+</script>
diff --git a/src/client/app/admin/views/index.vue b/src/client/app/admin/views/index.vue
index 43e47038f3..8a13fe1bf6 100644
--- a/src/client/app/admin/views/index.vue
+++ b/src/client/app/admin/views/index.vue
@@ -22,6 +22,7 @@
 			<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>{{ $t('instance') }}</li>
 			<li @click="nav('queue')" :class="{ active: page == 'queue' }"><fa :icon="faTasks" fixed-width/>{{ $t('queue') }}</li>
 			<li @click="nav('logs')" :class="{ active: page == 'logs' }"><fa :icon="faStream" fixed-width/>{{ $t('logs') }}</li>
+			<li @click="nav('db')" :class="{ active: page == 'db' }"><fa :icon="faDatabase" fixed-width/>{{ $t('db') }}</li>
 			<li @click="nav('moderators')" :class="{ active: page == 'moderators' }"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</li>
 			<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>{{ $t('users') }}</li>
 			<li @click="nav('drive')" :class="{ active: page == 'drive' }"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</li>
@@ -43,6 +44,7 @@
 			<div v-if="page == 'instance'"><x-instance/></div>
 			<div v-if="page == 'queue'"><x-queue/></div>
 			<div v-if="page == 'logs'"><x-logs/></div>
+			<div v-if="page == 'db'"><x-db/></div>
 			<div v-if="page == 'moderators'"><x-moderators/></div>
 			<div v-if="page == 'users'"><x-users/></div>
 			<div v-if="page == 'emoji'"><x-emoji/></div>
@@ -59,19 +61,20 @@
 import Vue from 'vue';
 import i18n from '../../i18n';
 import { version } from '../../config';
-import XDashboard from "./dashboard.vue";
-import XInstance from "./instance.vue";
-import XQueue from "./queue.vue";
-import XLogs from "./logs.vue";
-import XModerators from "./moderators.vue";
-import XEmoji from "./emoji.vue";
-import XAnnouncements from "./announcements.vue";
-import XUsers from "./users.vue";
-import XDrive from "./drive.vue";
-import XAbuse from "./abuse.vue";
-import XFederation from "./federation.vue";
+import XDashboard from './dashboard.vue';
+import XInstance from './instance.vue';
+import XQueue from './queue.vue';
+import XLogs from './logs.vue';
+import XDb from './db.vue';
+import XModerators from './moderators.vue';
+import XEmoji from './emoji.vue';
+import XAnnouncements from './announcements.vue';
+import XUsers from './users.vue';
+import XDrive from './drive.vue';
+import XAbuse from './abuse.vue';
+import XFederation from './federation.vue';
 
-import { faHeadset, faArrowLeft, faGlobe, faExclamationCircle, faTasks, faStream } from '@fortawesome/free-solid-svg-icons';
+import { faHeadset, faArrowLeft, faGlobe, faExclamationCircle, faTasks, faStream, faDatabase } from '@fortawesome/free-solid-svg-icons';
 import { faGrin } from '@fortawesome/free-regular-svg-icons';
 
 // Detect the user agent
@@ -85,6 +88,7 @@ export default Vue.extend({
 		XInstance,
 		XQueue,
 		XLogs,
+		XDb,
 		XModerators,
 		XEmoji,
 		XAnnouncements,
@@ -108,7 +112,8 @@ export default Vue.extend({
 			faGlobe,
 			faExclamationCircle,
 			faTasks,
-			faStream
+			faStream,
+			faDatabase,
 		};
 	},
 	methods: {
diff --git a/src/server/api/endpoints/admin/get-table-stats.ts b/src/server/api/endpoints/admin/get-table-stats.ts
new file mode 100644
index 0000000000..1abea18492
--- /dev/null
+++ b/src/server/api/endpoints/admin/get-table-stats.ts
@@ -0,0 +1,37 @@
+import define from '../../define';
+import { getConnection } from 'typeorm';
+
+export const meta = {
+	requireCredential: false,
+
+	desc: {
+		'en-US': 'Get table stats'
+	},
+
+	tags: ['meta'],
+
+	params: {
+	},
+};
+
+export default define(meta, async () => {
+	const sizes = await
+		getConnection().query(`
+			SELECT relname AS "table", reltuples as "count", pg_total_relation_size(C.oid) AS "size"
+			FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
+			WHERE nspname NOT IN ('pg_catalog', 'information_schema')
+				AND C.relkind <> 'i'
+				AND nspname !~ '^pg_toast';`)
+		.then(recs => {
+			const res = {} as Record<string, { count: number; size: number; }>;
+			for (const rec of recs) {
+				res[rec.table] = {
+					count: parseInt(rec.count, 10),
+					size: parseInt(rec.size, 10),
+				};
+			}
+			return res;
+		});
+
+	return sizes;
+});

From 2dac8d3d1f6fffd56c550fecb6b3122e34342123 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 18 Jun 2019 16:53:14 +0900
Subject: [PATCH 15/19] Update db.vue

---
 src/client/app/admin/views/db.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/client/app/admin/views/db.vue b/src/client/app/admin/views/db.vue
index 27bb81039e..7818546e76 100644
--- a/src/client/app/admin/views/db.vue
+++ b/src/client/app/admin/views/db.vue
@@ -3,7 +3,7 @@
 	<ui-card>
 		<template #title><fa :icon="faDatabase"/> {{ $t('tables') }}</template>
 		<section v-if="tables">
-			<div v-for="table in Object.keys(tables)"><b>{{ table }}</b> {{ tables[table].count }} {{ tables[table].size | bytes }}</div>
+			<div v-for="table in Object.keys(tables)"><b>{{ table }}</b> {{ tables[table].count | number }} {{ tables[table].size | bytes }}</div>
 		</section>
 	</ui-card>
 </div>

From b6f985abaf7cba760313e35a116d402403a4b87b Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 18 Jun 2019 16:58:32 +0900
Subject: [PATCH 16/19] Update summaly to 2.3.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 22cb43cb30..776e2400c2 100644
--- a/package.json
+++ b/package.json
@@ -214,7 +214,7 @@
 		"style-loader": "0.23.1",
 		"stylus": "0.54.5",
 		"stylus-loader": "3.0.2",
-		"summaly": "2.2.0",
+		"summaly": "2.3.0",
 		"systeminformation": "4.11.1",
 		"syuilo-password-strength": "0.0.1",
 		"terser-webpack-plugin": "1.3.0",

From 4015ccef2f308a6417b984570377d9360fe84ec7 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 18 Jun 2019 17:10:28 +0900
Subject: [PATCH 17/19] Add chart indices

---
 src/services/chart/core.ts | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/services/chart/core.ts b/src/services/chart/core.ts
index 6a69a21b7e..8b89c4009d 100644
--- a/src/services/chart/core.ts
+++ b/src/services/chart/core.ts
@@ -163,6 +163,17 @@ export default abstract class Chart<T extends Record<string, any>> {
 				},
 				...Chart.convertSchemaToFlatColumnDefinitions(schema)
 			},
+			indices: [{
+				columns: ['date']
+			}, {
+				columns: ['span']
+			}, {
+				columns: ['group']
+			}, {
+				columns: ['span', 'date']
+			}, {
+				columns: ['span', 'date', 'group']
+			}]
 		});
 	}
 

From d64dffbdda2fbe6f6bba1e654ea29567a509b40d Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 18 Jun 2019 17:11:28 +0900
Subject: [PATCH 18/19] Add index

---
 src/services/chart/core.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/services/chart/core.ts b/src/services/chart/core.ts
index 8b89c4009d..6564ef7813 100644
--- a/src/services/chart/core.ts
+++ b/src/services/chart/core.ts
@@ -171,6 +171,8 @@ export default abstract class Chart<T extends Record<string, any>> {
 				columns: ['group']
 			}, {
 				columns: ['span', 'date']
+			}, {
+				columns: ['date', 'group']
 			}, {
 				columns: ['span', 'date', 'group']
 			}]

From aac519bf80ba5c0cf1462c03c5e449321886f38a Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 18 Jun 2019 19:15:55 +0900
Subject: [PATCH 19/19] 11.22.0

---
 CHANGELOG.md | 11 +++++++++++
 package.json |  2 +-
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2da31c0f21..cbd6e095bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,17 @@ npm i -g ts-node
 npm run migrate
 ```
 
+11.22.0 (2019/06/18)
+--------------------
+### ✨Improvements
+* 管理画面でデータベースの各テーブルのレコード数やサイズを確認できるように
+* サーバー情報にPostgreSQLのバージョンを追加
+
+### 🐛Fixes
+* リモートファイルのダウンロードに失敗することがある問題を修正
+* アンケートの期間を日時指定で選択すると日時がUTCになってしまう問題を修正
+* MFMのパースを修正
+
 11.21.0 (2019/06/16)
 --------------------
 ### ✨Improvements
diff --git a/package.json b/package.json
index 776e2400c2..e5946f04f5 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
 	"name": "misskey",
 	"author": "syuilo <i@syuilo.com>",
-	"version": "11.21.0",
+	"version": "11.22.0",
 	"codename": "daybreak",
 	"repository": {
 		"type": "git",