From 8f714b5b126226346af850337566f0f70bd02d4f Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 6 Nov 2018 07:14:43 +0900
Subject: [PATCH] =?UTF-8?q?=E3=83=89=E3=83=A9=E3=82=A4=E3=83=96=E5=AE=B9?=
 =?UTF-8?q?=E9=87=8F=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=82=92DB=E3=81=AB?=
 =?UTF-8?q?=E4=BF=9D=E5=AD=98=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?=
 =?UTF-8?q?=E3=81=97=E3=81=9F=E3=82=8A=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF?=
 =?UTF-8?q?=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0=E3=81=97=E3=81=9F=E3=82=8A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .config/example.yml                           |  6 ----
 locales/ja-JP.yml                             |  3 ++
 src/client/app/admin/views/instance.vue       |  8 +++++
 src/config/load.ts                            |  3 --
 src/config/types.ts                           |  2 --
 src/misc/fetch-meta.ts                        | 19 +++++++++++
 src/models/meta.ts                            | 32 +++++++++++++++++++
 src/server/api/endpoints/admin/update-meta.ts | 26 ++++++++++++++-
 .../api/endpoints/aggregation/hashtags.ts     |  6 ++--
 src/server/api/endpoints/drive.ts             |  6 ++--
 src/server/api/endpoints/hashtags/trend.ts    |  6 ++--
 src/server/api/endpoints/meta.ts              | 27 ++++++++--------
 src/server/api/endpoints/notes/create.ts      |  6 ++--
 src/server/api/endpoints/stats.ts             |  6 ++--
 src/server/api/mastodon/index.ts              |  9 ++----
 src/server/api/private/signup.ts              |  5 +--
 src/services/drive/add-file.ts                |  4 ++-
 src/stream.ts                                 | 13 ++++----
 18 files changed, 133 insertions(+), 54 deletions(-)
 create mode 100644 src/misc/fetch-meta.ts

diff --git a/.config/example.yml b/.config/example.yml
index 44c58ccf3..abbbea50a 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -57,12 +57,6 @@ mongodb:
   user: example-misskey-user
   pass: example-misskey-pass
 
-# Drive capacity of a local user (MB)
-localDriveCapacityMb: 256
-
-# Drive capacity of a remote user (MB)
-remoteDriveCapacityMb: 8
-
 # If enabled:
 #  Server will not cache remote files (Using direct link instead).
 #  You can save your storage.
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index f8298c301..e18247ba7 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1078,6 +1078,9 @@ admin/views/instance.vue:
   instance-name: "インスタンス名"
   instance-description: "インスタンスの紹介"
   banner-url: "バナー画像URL"
+  local-drive-capacity-mb: "ローカルユーザーひとりあたりのドライブ容量"
+  remote-drive-capacity-mb: "リモートユーザーひとりあたりのドライブ容量"
+  mb: "メガバイト単位"
   max-note-text-length: "投稿の最大文字数"
   disable-registration: "ユーザー登録の受付を停止する"
   disable-local-timeline: "ローカルタイムラインを無効にする"
diff --git a/src/client/app/admin/views/instance.vue b/src/client/app/admin/views/instance.vue
index 4e9377967..65be1b404 100644
--- a/src/client/app/admin/views/instance.vue
+++ b/src/client/app/admin/views/instance.vue
@@ -6,6 +6,8 @@
 			<ui-input v-model="name">%i18n:@instance-name%</ui-input>
 			<ui-textarea v-model="description">%i18n:@instance-description%</ui-textarea>
 			<ui-input v-model="bannerUrl">%i18n:@banner-url%</ui-input>
+			<ui-input v-model="localDriveCapacityMb">%i18n:@local-drive-capacity-mb%<span slot="text">%i18n:@mb%</span></ui-input>
+			<ui-input v-model="remoteDriveCapacityMb">%i18n:@remote-drive-capacity-mb%<span slot="text">%i18n:@mb%</span></ui-input>
 			<ui-input v-model="maxNoteTextLength">%i18n:@max-note-text-length%</ui-input>
 			<ui-button @click="updateMeta">%i18n:@save%</ui-button>
 		</section>
@@ -40,6 +42,8 @@ export default Vue.extend({
 			bannerUrl: null,
 			name: null,
 			description: null,
+			localDriveCapacityMb: null,
+			remoteDriveCapacityMb: null,
 			maxNoteTextLength: null,
 			inviteCode: null,
 		};
@@ -50,6 +54,8 @@ export default Vue.extend({
 			this.bannerUrl = meta.bannerUrl;
 			this.name = meta.name;
 			this.description = meta.description;
+			this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb;
+			this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb;
 			this.maxNoteTextLength = meta.maxNoteTextLength;
 		});
 	},
@@ -73,6 +79,8 @@ export default Vue.extend({
 				bannerUrl: this.bannerUrl,
 				name: this.name,
 				description: this.description,
+				localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
+				remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
 				maxNoteTextLength: parseInt(this.maxNoteTextLength, 10)
 			}).then(() => {
 				this.$swal({
diff --git a/src/config/load.ts b/src/config/load.ts
index 56a8ff0ab..4e9a72edd 100644
--- a/src/config/load.ts
+++ b/src/config/load.ts
@@ -46,9 +46,6 @@ export default function load() {
 	mixin.drive_url = `${mixin.scheme}://${mixin.host}/files`;
 	mixin.user_agent = `Misskey/${pkg.version} (${config.url})`;
 
-	if (config.localDriveCapacityMb == null) config.localDriveCapacityMb = 256;
-	if (config.remoteDriveCapacityMb == null) config.remoteDriveCapacityMb = 8;
-
 	if (config.autoAdmin == null) config.autoAdmin = false;
 
 	return Object.assign(config, mixin);
diff --git a/src/config/types.ts b/src/config/types.ts
index f5049beb8..f1cbb84c8 100644
--- a/src/config/types.ts
+++ b/src/config/types.ts
@@ -46,8 +46,6 @@ export type Source = {
 		secret_key: string;
 	};
 
-	localDriveCapacityMb: number;
-	remoteDriveCapacityMb: number;
 	preventCacheRemoteFiles: boolean;
 
 	drive?: {
diff --git a/src/misc/fetch-meta.ts b/src/misc/fetch-meta.ts
new file mode 100644
index 000000000..778aa029c
--- /dev/null
+++ b/src/misc/fetch-meta.ts
@@ -0,0 +1,19 @@
+import Meta, { IMeta } from '../models/meta';
+
+const defaultMeta: any = {
+	name: 'Misskey',
+	localDriveCapacityMb: 256,
+	remoteDriveCapacityMb: 8,
+	hidedTags: [],
+	stats: {
+		originalNotesCount: 0,
+		originalUsersCount: 0
+	},
+	maxNoteTextLength: 1000
+};
+
+export default async function(): Promise<IMeta> {
+	const meta = await Meta.findOne({});
+
+	return Object.assign({}, defaultMeta, meta);
+}
diff --git a/src/models/meta.ts b/src/models/meta.ts
index d8a9b4603..7caf41f19 100644
--- a/src/models/meta.ts
+++ b/src/models/meta.ts
@@ -28,6 +28,28 @@ if ((config as any).description) {
 		}
 	});
 }
+if ((config as any).localDriveCapacityMb) {
+	Meta.findOne({}).then(m => {
+		if (m != null && m.localDriveCapacityMb == null) {
+			Meta.update({}, {
+				$set: {
+					localDriveCapacityMb: (config as any).localDriveCapacityMb
+				}
+			});
+		}
+	});
+}
+if ((config as any).remoteDriveCapacityMb) {
+	Meta.findOne({}).then(m => {
+		if (m != null && m.remoteDriveCapacityMb == null) {
+			Meta.update({}, {
+				$set: {
+					remoteDriveCapacityMb: (config as any).remoteDriveCapacityMb
+				}
+			});
+		}
+	});
+}
 
 export type IMeta = {
 	name?: string;
@@ -44,6 +66,16 @@ export type IMeta = {
 	hidedTags?: string[];
 	bannerUrl?: string;
 
+	/**
+	 * Drive capacity of a local user (MB)
+	 */
+	localDriveCapacityMb?: number;
+
+	/**
+	 * Drive capacity of a remote user (MB)
+	 */
+	remoteDriveCapacityMb?: number;
+
 	/**
 	 * Max allowed note text length in charactors
 	 */
diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts
index a0f2b329a..4c4a3ac85 100644
--- a/src/server/api/endpoints/admin/update-meta.ts
+++ b/src/server/api/endpoints/admin/update-meta.ts
@@ -65,7 +65,23 @@ export const meta = {
 			desc: {
 				'ja-JP': '投稿の最大文字数'
 			}
-		}
+		},
+
+		localDriveCapacityMb: {
+			validator: $.num.optional.min(0),
+			desc: {
+				'ja-JP': 'ローカルユーザーひとりあたりのドライブ容量 (メガバイト単位)',
+				'en-US': 'Drive capacity of a local user (MB)'
+			}
+		},
+
+		remoteDriveCapacityMb: {
+			validator: $.num.optional.min(0),
+			desc: {
+				'ja-JP': 'リモートユーザーひとりあたりのドライブ容量 (メガバイト単位)',
+				'en-US': 'Drive capacity of a remote user (MB)'
+			}
+		},
 	}
 };
 
@@ -104,6 +120,14 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
 		set.maxNoteTextLength = ps.maxNoteTextLength;
 	}
 
+	if (ps.localDriveCapacityMb !== undefined) {
+		set.localDriveCapacityMb = ps.localDriveCapacityMb;
+	}
+
+	if (ps.remoteDriveCapacityMb !== undefined) {
+		set.remoteDriveCapacityMb = ps.remoteDriveCapacityMb;
+	}
+
 	await Meta.update({}, {
 		$set: set
 	}, { upsert: true });
diff --git a/src/server/api/endpoints/aggregation/hashtags.ts b/src/server/api/endpoints/aggregation/hashtags.ts
index 59706908f..f8fc7162f 100644
--- a/src/server/api/endpoints/aggregation/hashtags.ts
+++ b/src/server/api/endpoints/aggregation/hashtags.ts
@@ -1,14 +1,14 @@
 import Note from '../../../../models/note';
-import Meta from '../../../../models/meta';
 import define from '../../define';
+import fetchMeta from '../../../../misc/fetch-meta';
 
 export const meta = {
 	requireCredential: false,
 };
 
 export default define(meta, (ps) => new Promise(async (res, rej) => {
-	const meta = await Meta.findOne({});
-	const hidedTags = meta ? (meta.hidedTags || []).map(t => t.toLowerCase()) : [];
+	const instance = await fetchMeta();
+	const hidedTags = instance.hidedTags.map(t => t.toLowerCase());
 
 	const span = 1000 * 60 * 60 * 24 * 7; // 1週間
 
diff --git a/src/server/api/endpoints/drive.ts b/src/server/api/endpoints/drive.ts
index 43fe77138..3480fe600 100644
--- a/src/server/api/endpoints/drive.ts
+++ b/src/server/api/endpoints/drive.ts
@@ -1,6 +1,6 @@
 import DriveFile from '../../../models/drive-file';
-import config from '../../../config';
 import define from '../define';
+import fetchMeta from '../../../misc/fetch-meta';
 
 export const meta = {
 	desc: {
@@ -14,6 +14,8 @@ export const meta = {
 };
 
 export default define(meta, (ps, user) => new Promise(async (res, rej) => {
+	const instance = await fetchMeta();
+
 	// Calculate drive usage
 	const usage = await DriveFile
 		.aggregate([{
@@ -39,7 +41,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
 		});
 
 	res({
-		capacity: 1024 * 1024 * config.localDriveCapacityMb,
+		capacity: 1024 * 1024 * instance.localDriveCapacityMb,
 		usage: usage
 	});
 }));
diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts
index 02d398a68..ed4c8e337 100644
--- a/src/server/api/endpoints/hashtags/trend.ts
+++ b/src/server/api/endpoints/hashtags/trend.ts
@@ -1,7 +1,7 @@
 import Note from '../../../../models/note';
 import { erase } from '../../../../prelude/array';
-import Meta from '../../../../models/meta';
 import define from '../../define';
+import fetchMeta from '../../../../misc/fetch-meta';
 
 /*
 トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要
@@ -20,8 +20,8 @@ export const meta = {
 };
 
 export default define(meta, () => new Promise(async (res, rej) => {
-	const meta = await Meta.findOne({});
-	const hidedTags = meta ? (meta.hidedTags || []).map(t => t.toLowerCase()) : [];
+	const instance = await fetchMeta();
+	const hidedTags = instance.hidedTags.map(t => t.toLowerCase());
 
 	//#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計
 	const data = await Note.aggregate([{
diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts
index 90a5952e9..f7a5ed4f1 100644
--- a/src/server/api/endpoints/meta.ts
+++ b/src/server/api/endpoints/meta.ts
@@ -1,9 +1,9 @@
 import $ from 'cafy';
 import * as os from 'os';
 import config from '../../../config';
-import Meta from '../../../models/meta';
 import Emoji from '../../../models/emoji';
 import define from '../define';
+import fetchMeta from '../../../misc/fetch-meta';
 
 const pkg = require('../../../../package.json');
 const client = require('../../../../built/client/meta.json');
@@ -27,7 +27,7 @@ export const meta = {
 };
 
 export default define(meta, (ps, me) => new Promise(async (res, rej) => {
-	const met: any = (await Meta.findOne()) || {};
+	const instance = await fetchMeta();
 
 	const emojis = await Emoji.find({ host: null }, {
 		fields: {
@@ -41,8 +41,8 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
 		version: pkg.version,
 		clientVersion: client.version,
 
-		name: met.name || 'Misskey',
-		description: met.description,
+		name: instance.name,
+		description: instance.description,
 
 		secure: config.https != null,
 		machine: os.hostname(),
@@ -54,21 +54,22 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
 			cores: os.cpus().length
 		},
 
-		broadcasts: met.broadcasts || [],
-		disableRegistration: met.disableRegistration,
-		disableLocalTimeline: met.disableLocalTimeline,
-		driveCapacityPerLocalUserMb: config.localDriveCapacityMb,
+		broadcasts: instance.broadcasts || [],
+		disableRegistration: instance.disableRegistration,
+		disableLocalTimeline: instance.disableLocalTimeline,
+		driveCapacityPerLocalUserMb: instance.localDriveCapacityMb,
+		driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb,
 		recaptchaSitekey: config.recaptcha ? config.recaptcha.site_key : null,
 		swPublickey: config.sw ? config.sw.public_key : null,
-		hidedTags: (me && me.isAdmin) ? met.hidedTags : undefined,
-		bannerUrl: met.bannerUrl,
-		maxNoteTextLength: met.maxNoteTextLength || 1000,
+		hidedTags: (me && me.isAdmin) ? instance.hidedTags : undefined,
+		bannerUrl: instance.bannerUrl,
+		maxNoteTextLength: instance.maxNoteTextLength,
 
 		emojis: emojis,
 
 		features: ps.detail ? {
-			registration: !met.disableRegistration,
-			localTimeLine: !met.disableLocalTimeline,
+			registration: !instance.disableRegistration,
+			localTimeLine: !instance.disableLocalTimeline,
 			elasticsearch: config.elasticsearch ? true : false,
 			recaptcha: config.recaptcha ? true : false,
 			objectStorage: config.drive && config.drive.storage === 'minio',
diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts
index f4d7e9626..4f031aa43 100644
--- a/src/server/api/endpoints/notes/create.ts
+++ b/src/server/api/endpoints/notes/create.ts
@@ -6,13 +6,13 @@ import User, { IUser } from '../../../../models/user';
 import DriveFile, { IDriveFile } from '../../../../models/drive-file';
 import create from '../../../../services/note/create';
 import define from '../../define';
-import Meta from '../../../../models/meta';
+import fetchMeta from '../../../../misc/fetch-meta';
 
 let maxNoteTextLength = 1000;
 
 setInterval(() => {
-	Meta.findOne({}).then(m => {
-		if (m.maxNoteTextLength) maxNoteTextLength = m.maxNoteTextLength;
+	fetchMeta().then(m => {
+		maxNoteTextLength = m.maxNoteTextLength;
 	});
 }, 3000);
 
diff --git a/src/server/api/endpoints/stats.ts b/src/server/api/endpoints/stats.ts
index 773a8f7a8..79e2fdf5e 100644
--- a/src/server/api/endpoints/stats.ts
+++ b/src/server/api/endpoints/stats.ts
@@ -1,7 +1,7 @@
-import Meta from '../../../models/meta';
 import define from '../define';
 import driveChart from '../../../chart/drive';
 import federationChart from '../../../chart/federation';
+import fetchMeta from '../../../misc/fetch-meta';
 
 export const meta = {
 	requireCredential: false,
@@ -15,9 +15,9 @@ export const meta = {
 };
 
 export default define(meta, () => new Promise(async (res, rej) => {
-	const meta = await Meta.findOne();
+	const instance = await fetchMeta();
 
-	const stats: any = meta ? meta.stats : {};
+	const stats: any = instance.stats;
 
 	const driveStats = await driveChart.getChart('hour', 1);
 	stats.driveUsageLocal = driveStats.local.totalSize[0];
diff --git a/src/server/api/mastodon/index.ts b/src/server/api/mastodon/index.ts
index e84074a2b..98e9c20be 100644
--- a/src/server/api/mastodon/index.ts
+++ b/src/server/api/mastodon/index.ts
@@ -2,10 +2,10 @@ import * as Router from 'koa-router';
 import User from '../../../models/user';
 import { toASCII } from 'punycode';
 import config from '../../../config';
-import Meta from '../../../models/meta';
 import { ObjectID } from 'bson';
 import Emoji from '../../../models/emoji';
 import { toMastodonEmojis } from './emoji';
+import fetchMeta from '../../../misc/fetch-meta';
 const pkg = require('../../../../package.json');
 
 // Init router
@@ -19,11 +19,8 @@ router.get('/v1/custom_emojis', async ctx => ctx.body =
 	})).map(x => toMastodonEmojis(x)));
 
 router.get('/v1/instance', async ctx => { // TODO: This is a temporary implementation. Consider creating helper methods!
-	const meta = await Meta.findOne() || {};
-	const { originalNotesCount, originalUsersCount } = meta.stats || {
-		originalNotesCount: 0,
-		originalUsersCount: 0
-	};
+	const meta = await fetchMeta();
+	const { originalNotesCount, originalUsersCount } = meta.stats;
 	const domains = await User.distinct('host', { host: { $ne: null } }) as any as [] || [];
 	const maintainer = await User.findOne({ isAdmin: true }) || {
 		_id: ObjectID.createFromTime(0),
diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts
index ec9892680..eefffd855 100644
--- a/src/server/api/private/signup.ts
+++ b/src/server/api/private/signup.ts
@@ -8,6 +8,7 @@ import config from '../../../config';
 import Meta from '../../../models/meta';
 import RegistrationTicket from '../../../models/registration-tickets';
 import usersChart from '../../../chart/users';
+import fetchMeta from '../../../misc/fetch-meta';
 
 if (config.recaptcha) {
 	recaptcha.init({
@@ -33,9 +34,9 @@ export default async (ctx: Koa.Context) => {
 	const password = body['password'];
 	const invitationCode = body['invitationCode'];
 
-	const meta = await Meta.findOne({});
+	const instance = await fetchMeta();
 
-	if (meta && meta.disableRegistration) {
+	if (instance && instance.disableRegistration) {
 		if (invitationCode == null || typeof invitationCode != 'string') {
 			ctx.status = 400;
 			return;
diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts
index 4a06b62ae..0fb365c91 100644
--- a/src/services/drive/add-file.ts
+++ b/src/services/drive/add-file.ts
@@ -19,6 +19,7 @@ import config from '../../config';
 import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
 import driveChart from '../../chart/drive';
 import perUserDriveChart from '../../chart/per-user-drive';
+import fetchMeta from '../../misc/fetch-meta';
 
 const log = debug('misskey:drive:add-file');
 
@@ -255,7 +256,8 @@ export default async function(
 
 		log(`drive usage is ${usage}`);
 
-		const driveCapacity = 1024 * 1024 * (isLocalUser(user) ? config.localDriveCapacityMb : config.remoteDriveCapacityMb);
+		const instance = await fetchMeta();
+		const driveCapacity = 1024 * 1024 * (isLocalUser(user) ? instance.localDriveCapacityMb : instance.remoteDriveCapacityMb);
 
 		// If usage limit exceeded
 		if (usage + size > driveCapacity) {
diff --git a/src/stream.ts b/src/stream.ts
index 543421726..8ca8c3254 100644
--- a/src/stream.ts
+++ b/src/stream.ts
@@ -1,7 +1,8 @@
 import * as mongo from 'mongodb';
 import redis from './db/redis';
 import Xev from 'xev';
-import Meta, { IMeta } from './models/meta';
+import { IMeta } from './models/meta';
+import fetchMeta from './misc/fetch-meta';
 
 type ID = string | mongo.ObjectID;
 
@@ -16,14 +17,14 @@ class Publisher {
 		}
 
 		setInterval(async () => {
-			this.meta = await Meta.findOne({});
+			this.meta = await fetchMeta();
 		}, 5000);
 	}
 
-	public getMeta = async () => {
+	public fetchMeta = async () => {
 		if (this.meta != null) return this.meta;
 
-		this.meta = await Meta.findOne({});
+		this.meta = await fetchMeta();
 		return this.meta;
 	}
 
@@ -82,13 +83,13 @@ class Publisher {
 	}
 
 	public publishLocalTimelineStream = async (note: any): Promise<void> => {
-		const meta = await this.getMeta();
+		const meta = await this.fetchMeta();
 		if (meta.disableLocalTimeline) return;
 		this.publish('localTimeline', null, note);
 	}
 
 	public publishHybridTimelineStream = async (userId: ID, note: any): Promise<void> => {
-		const meta = await this.getMeta();
+		const meta = await this.fetchMeta();
 		if (meta.disableLocalTimeline) return;
 		this.publish(userId ? `hybridTimeline:${userId}` : 'hybridTimeline', null, note);
 	}