diff --git a/.config/example.yml b/.config/example.yml index 44c58ccf30..abbbea50ad 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 f8298c301b..e18247ba71 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 4e93779670..65be1b4046 100644 --- a/src/client/app/admin/views/instance.vue +++ b/src/client/app/admin/views/instance.vue @@ -6,6 +6,8 @@ %i18n:@instance-name% %i18n:@instance-description% %i18n:@banner-url% + %i18n:@local-drive-capacity-mb%%i18n:@mb% + %i18n:@remote-drive-capacity-mb%%i18n:@mb% %i18n:@max-note-text-length% %i18n:@save% @@ -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 56a8ff0abd..4e9a72edd7 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 f5049beb80..f1cbb84c8a 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 0000000000..778aa029c1 --- /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 { + const meta = await Meta.findOne({}); + + return Object.assign({}, defaultMeta, meta); +} diff --git a/src/models/meta.ts b/src/models/meta.ts index d8a9b46037..7caf41f19c 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 a0f2b329aa..4c4a3ac85c 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 59706908fa..f8fc7162f5 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 43fe771385..3480fe6002 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 02d398a683..ed4c8e337f 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 90a5952e9f..f7a5ed4f16 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 f4d7e96265..4f031aa43d 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 773a8f7a81..79e2fdf5e8 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 e84074a2bb..98e9c20be1 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 ec9892680c..eefffd8554 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 4a06b62ae2..0fb365c91f 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 543421726a..8ca8c3254c 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 => { - 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 => { - const meta = await this.getMeta(); + const meta = await this.fetchMeta(); if (meta.disableLocalTimeline) return; this.publish(userId ? `hybridTimeline:${userId}` : 'hybridTimeline', null, note); }