From a1b82e97230eab994c06fc7c902e71539664d3d7 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 5 Sep 2018 19:32:46 +0900 Subject: #2620 --- src/server/api/endpoints/notes.ts | 10 +++++----- src/server/api/endpoints/notes/create.ts | 23 ++++++++++++++-------- src/server/api/endpoints/notes/global-timeline.ts | 10 +++++----- src/server/api/endpoints/notes/hybrid-timeline.ts | 12 +++++------ src/server/api/endpoints/notes/local-timeline.ts | 10 +++++----- src/server/api/endpoints/notes/search_by_tag.ts | 6 +++--- src/server/api/endpoints/notes/timeline.ts | 12 +++++------ src/server/api/endpoints/notes/trend.ts | 2 +- .../api/endpoints/notes/user-list-timeline.ts | 12 +++++------ src/server/api/endpoints/users/notes.ts | 10 +++++----- 10 files changed, 57 insertions(+), 50 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts index 029bc1a95e..4d15e9483f 100644 --- a/src/server/api/endpoints/notes.ts +++ b/src/server/api/endpoints/notes.ts @@ -20,9 +20,9 @@ export default (params: any) => new Promise(async (res, rej) => { const [renote, renoteErr] = $.bool.optional.get(params.renote); if (renoteErr) return rej('invalid renote param'); - // Get 'media' parameter - const [media, mediaErr] = $.bool.optional.get(params.media); - if (mediaErr) return rej('invalid media param'); + // Get 'files' parameter + const [files, filesErr] = $.bool.optional.get(params.files); + if (filesErr) return rej('invalid files param'); // Get 'poll' parameter const [poll, pollErr] = $.bool.optional.get(params.poll); @@ -79,8 +79,8 @@ export default (params: any) => new Promise(async (res, rej) => { query.renoteId = renote ? { $exists: true, $ne: null } : null; } - if (media != undefined) { - query.mediaIds = media ? { $exists: true, $ne: null } : []; + if (files != undefined) { + query.fileIds = files ? { $exists: true, $ne: null } : []; } if (poll != undefined) { diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 04f5f7562e..47b53c943b 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -71,9 +71,15 @@ export const meta = { ref: 'geo' }), + fileIds: $.arr($.type(ID)).optional.unique().range(1, 4).note({ + desc: { + 'ja-JP': '添付するファイル' + } + }), + mediaIds: $.arr($.type(ID)).optional.unique().range(1, 4).note({ desc: { - 'ja-JP': '添付するメディア' + 'ja-JP': '添付するファイル (このパラメータは廃止予定です。代わりに fileIds を使ってください。)' } }), @@ -124,15 +130,16 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( } let files: IDriveFile[] = []; - if (ps.mediaIds !== undefined) { + const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; + if (fileIds != null) { // Fetch files // forEach だと途中でエラーなどがあっても return できないので // 敢えて for を使っています。 - for (const mediaId of ps.mediaIds) { + for (const fileId of fileIds) { // Fetch file // SELECT _id const entity = await DriveFile.findOne({ - _id: mediaId, + _id: fileId, 'metadata.userId': user._id }); @@ -155,7 +162,7 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( if (renote == null) { return rej('renoteee is not found'); - } else if (renote.renoteId && !renote.text && !renote.mediaIds) { + } else if (renote.renoteId && !renote.text && !renote.fileIds) { return rej('cannot renote to renote'); } } @@ -176,7 +183,7 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( } // 返信対象が引用でないRenoteだったらエラー - if (reply.renoteId && !reply.text && !reply.mediaIds) { + if (reply.renoteId && !reply.text && !reply.fileIds) { return rej('cannot reply to renote'); } } @@ -191,13 +198,13 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( // テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー if ((ps.text === undefined || ps.text === null) && files === null && renote === null && ps.poll === undefined) { - return rej('text, mediaIds, renoteId or poll is required'); + return rej('text, fileIds, renoteId or poll is required'); } // 投稿を作成 const note = await create(user, { createdAt: new Date(), - media: files, + files: files, poll: ps.poll, text: ps.text, reply, diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index 8f7233e308..554245a0f4 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -33,9 +33,9 @@ export default async (params: any, user: ILocalUser) => { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } - // Get 'mediaOnly' parameter - const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly); - if (mediaOnlyErr) throw 'invalid mediaOnly param'; + // Get 'withFiles' parameter + const [withFiles, withFilesErr] = $.bool.optional.get(params.withFiles); + if (withFilesErr) throw 'invalid withFiles param'; // ミュートしているユーザーを取得 const mutedUserIds = user ? (await Mute.find({ @@ -68,8 +68,8 @@ export default async (params: any, user: ILocalUser) => { }; } - if (mediaOnly) { - query.mediaIds = { $exists: true, $ne: [] }; + if (withFiles) { + query.fileIds = { $exists: true, $ne: [] }; } if (sinceId) { diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 2dbb1190c1..1060792683 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -66,7 +66,7 @@ export const meta = { } }), - mediaOnly: $.bool.optional.note({ + withFiles: $.bool.optional.note({ desc: { 'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します' } @@ -164,7 +164,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -180,7 +180,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -196,16 +196,16 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] }); } - if (ps.mediaOnly) { + if (ps.withFiles) { query.$and.push({ - mediaIds: { $exists: true, $ne: [] } + fileIds: { $exists: true, $ne: [] } }); } diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index bbcc6303ca..018e636ab5 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -33,9 +33,9 @@ export default async (params: any, user: ILocalUser) => { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } - // Get 'mediaOnly' parameter - const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly); - if (mediaOnlyErr) throw 'invalid mediaOnly param'; + // Get 'withFiles' parameter + const [withFiles, withFilesErr] = $.bool.optional.get(params.withFiles); + if (withFilesErr) throw 'invalid withFiles param'; // ミュートしているユーザーを取得 const mutedUserIds = user ? (await Mute.find({ @@ -69,8 +69,8 @@ export default async (params: any, user: ILocalUser) => { }; } - if (mediaOnly) { - query.mediaIds = { $exists: true, $ne: [] }; + if (withFiles) { + query.fileIds = { $exists: true, $ne: [] }; } if (sinceId) { diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts index e092275fe8..317a0726d3 100644 --- a/src/server/api/endpoints/notes/search_by_tag.ts +++ b/src/server/api/endpoints/notes/search_by_tag.ts @@ -247,7 +247,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => if (media != null) { if (media) { push({ - mediaIds: { + fileIds: { $exists: true, $ne: null } @@ -255,11 +255,11 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } else { push({ $or: [{ - mediaIds: { + fileIds: { $exists: false } }, { - mediaIds: null + fileIds: null }] }); } diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 099bf2010b..145f648c56 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -67,7 +67,7 @@ export const meta = { } }), - mediaOnly: $.bool.optional.note({ + withFiles: $.bool.optional.note({ desc: { 'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します' } @@ -154,7 +154,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -170,7 +170,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -186,16 +186,16 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] }); } - if (ps.mediaOnly) { + if (ps.withFiles) { query.$and.push({ - mediaIds: { $exists: true, $ne: [] } + fileIds: { $exists: true, $ne: [] } }); } diff --git a/src/server/api/endpoints/notes/trend.ts b/src/server/api/endpoints/notes/trend.ts index 7a0a098f28..9f55ed3243 100644 --- a/src/server/api/endpoints/notes/trend.ts +++ b/src/server/api/endpoints/notes/trend.ts @@ -52,7 +52,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = } if (media != undefined) { - query.mediaIds = media ? { $exists: true, $ne: null } : null; + query.fileIds = media ? { $exists: true, $ne: null } : null; } if (poll != undefined) { diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index a7b43014ed..e00a7de371 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -73,7 +73,7 @@ export const meta = { } }), - mediaOnly: $.bool.optional.note({ + withFiles: $.bool.optional.note({ desc: { 'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します' } @@ -160,7 +160,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -176,7 +176,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -192,16 +192,16 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] }); } - if (ps.mediaOnly) { + if (ps.withFiles) { query.$and.push({ - mediaIds: { $exists: true, $ne: [] } + fileIds: { $exists: true, $ne: [] } }); } diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index ff7855bde0..d894e52dba 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -27,9 +27,9 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => const [includeReplies = true, includeRepliesErr] = $.bool.optional.get(params.includeReplies); if (includeRepliesErr) return rej('invalid includeReplies param'); - // Get 'withMedia' parameter - const [withMedia = false, withMediaErr] = $.bool.optional.get(params.withMedia); - if (withMediaErr) return rej('invalid withMedia param'); + // Get 'withFiles' parameter + const [withFiles = false, withFilesErr] = $.bool.optional.get(params.withFiles); + if (withFilesErr) return rej('invalid withFiles param'); // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); @@ -104,8 +104,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => query.replyId = null; } - if (withMedia) { - query.mediaIds = { + if (withFiles) { + query.fileIds = { $exists: true, $ne: [] }; -- cgit v1.2.3-freya From 37058e3480041a922ac5c193e4cc3bc29fd663ae Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 5 Sep 2018 19:35:57 +0900 Subject: Fix parameter name --- src/server/api/endpoints/notes/search_by_tag.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts index 317a0726d3..f982dc01e9 100644 --- a/src/server/api/endpoints/notes/search_by_tag.ts +++ b/src/server/api/endpoints/notes/search_by_tag.ts @@ -45,9 +45,9 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => const [renote = null, renoteErr] = $.bool.optional.nullable.get(params.renote); if (renoteErr) return rej('invalid renote param'); - // Get 'media' parameter - const [media = null, mediaErr] = $.bool.optional.nullable.get(params.media); - if (mediaErr) return rej('invalid media param'); + // Get 'withFiles' parameter + const [withFiles = null, withFilesErr] = $.bool.optional.nullable.get(params.withFiles); + if (withFilesErr) return rej('invalid withFiles param'); // Get 'poll' parameter const [poll = null, pollErr] = $.bool.optional.nullable.get(params.poll); @@ -244,8 +244,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } } - if (media != null) { - if (media) { + if (withFiles != null) { + if (withFiles) { push({ fileIds: { $exists: true, -- cgit v1.2.3-freya From b5ff2abdb9ee0c086c8970c738cce5d61761f8f5 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 5 Sep 2018 23:55:51 +0900 Subject: 互換性のためのコードを追加 & #2623 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/api/endpoints/notes.ts | 128 +++++++------- src/server/api/endpoints/notes/global-timeline.ts | 77 +++++---- src/server/api/endpoints/notes/hybrid-timeline.ts | 12 +- src/server/api/endpoints/notes/local-timeline.ts | 77 +++++---- src/server/api/endpoints/notes/search_by_tag.ts | 185 ++++++++++++--------- src/server/api/endpoints/notes/timeline.ts | 12 +- .../api/endpoints/notes/user-list-timeline.ts | 12 +- src/server/api/endpoints/users/notes.ts | 178 +++++++++++++------- 8 files changed, 416 insertions(+), 265 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts index 4d15e9483f..5fa58d19de 100644 --- a/src/server/api/endpoints/notes.ts +++ b/src/server/api/endpoints/notes.ts @@ -1,51 +1,65 @@ -/** - * Module dependencies - */ import $ from 'cafy'; import ID from '../../../misc/cafy-id'; import Note, { pack } from '../../../models/note'; +import getParams from '../get-params'; + +export const meta = { + desc: { + 'ja-JP': '投稿を取得します。' + }, + + params: { + local: $.bool.optional.note({ + desc: { + 'ja-JP': 'ローカルの投稿に限定するか否か' + } + }), + + reply: $.bool.optional.note({ + desc: { + 'ja-JP': '返信に限定するか否か' + } + }), + + renote: $.bool.optional.note({ + desc: { + 'ja-JP': 'Renoteに限定するか否か' + } + }), + + withFiles: $.bool.optional.note({ + desc: { + 'ja-JP': 'ファイルが添付された投稿に限定するか否か' + } + }), + + media: $.bool.optional.note({ + desc: { + 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' + } + }), + + poll: $.bool.optional.note({ + desc: { + 'ja-JP': 'アンケートが添付された投稿に限定するか否か' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 10 + }), + + sinceId: $.type(ID).optional.note({}), + + untilId: $.type(ID).optional.note({}), + } +}; -/** - * Get all notes - */ export default (params: any) => new Promise(async (res, rej) => { - // Get 'local' parameter - const [local, localErr] = $.bool.optional.get(params.local); - if (localErr) return rej('invalid local param'); - - // Get 'reply' parameter - const [reply, replyErr] = $.bool.optional.get(params.reply); - if (replyErr) return rej('invalid reply param'); - - // Get 'renote' parameter - const [renote, renoteErr] = $.bool.optional.get(params.renote); - if (renoteErr) return rej('invalid renote param'); - - // Get 'files' parameter - const [files, filesErr] = $.bool.optional.get(params.files); - if (filesErr) return rej('invalid files param'); - - // Get 'poll' parameter - const [poll, pollErr] = $.bool.optional.get(params.poll); - if (pollErr) return rej('invalid poll param'); - - // Get 'bot' parameter - //const [bot, botErr] = $.bool.optional.get(params.bot); - //if (botErr) return rej('invalid bot param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); - if (limitErr) return rej('invalid limit param'); - - // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); - if (sinceIdErr) return rej('invalid sinceId param'); - - // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); - if (untilIdErr) return rej('invalid untilId param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; // Check if both of sinceId and untilId is specified - if (sinceId && untilId) { + if (ps.sinceId && ps.untilId) { return rej('cannot set sinceId and untilId'); } @@ -56,35 +70,37 @@ export default (params: any) => new Promise(async (res, rej) => { const query = { visibility: 'public' } as any; - if (sinceId) { + if (ps.sinceId) { sort._id = 1; query._id = { - $gt: sinceId + $gt: ps.sinceId }; - } else if (untilId) { + } else if (ps.untilId) { query._id = { - $lt: untilId + $lt: ps.untilId }; } - if (local) { + if (ps.local) { query['_user.host'] = null; } - if (reply != undefined) { - query.replyId = reply ? { $exists: true, $ne: null } : null; + if (ps.reply != undefined) { + query.replyId = ps.reply ? { $exists: true, $ne: null } : null; } - if (renote != undefined) { - query.renoteId = renote ? { $exists: true, $ne: null } : null; + if (ps.renote != undefined) { + query.renoteId = ps.renote ? { $exists: true, $ne: null } : null; } - if (files != undefined) { - query.fileIds = files ? { $exists: true, $ne: null } : []; + const withFiles = ps.withFiles != undefined ? ps.withFiles : ps.media; + + if (withFiles) { + query.fileIds = withFiles ? { $exists: true, $ne: null } : []; } - if (poll != undefined) { - query.poll = poll ? { $exists: true, $ne: null } : null; + if (ps.poll != undefined) { + query.poll = ps.poll ? { $exists: true, $ne: null } : null; } // TODO @@ -95,7 +111,7 @@ export default (params: any) => new Promise(async (res, rej) => { // Issue query const notes = await Note .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index 554245a0f4..e70fc5d76f 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -3,40 +3,49 @@ import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; -/** - * Get timeline of global - */ -export default async (params: any, user: ILocalUser) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); - if (limitErr) throw 'invalid limit param'; +export const meta = { + desc: { + 'ja-JP': 'グローバルタイムラインを取得します。' + }, + + params: { + withFiles: $.bool.optional.note({ + desc: { + 'ja-JP': 'ファイルが添付された投稿に限定するか否か' + } + }), + + mediaOnly: $.bool.optional.note({ + desc: { + 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 10 + }), + + sinceId: $.type(ID).optional.note({}), - // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); - if (sinceIdErr) throw 'invalid sinceId param'; + untilId: $.type(ID).optional.note({}), - // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); - if (untilIdErr) throw 'invalid untilId param'; + sinceDate: $.num.optional.note({}), - // Get 'sinceDate' parameter - const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate); - if (sinceDateErr) throw 'invalid sinceDate param'; + untilDate: $.num.optional.note({}), + } +}; - // Get 'untilDate' parameter - const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate); - if (untilDateErr) throw 'invalid untilDate param'; +export default async (params: any, user: ILocalUser) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; // Check if only one of sinceId, untilId, sinceDate, untilDate specified - if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) { + if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } - // Get 'withFiles' parameter - const [withFiles, withFilesErr] = $.bool.optional.get(params.withFiles); - if (withFilesErr) throw 'invalid withFiles param'; - // ミュートしているユーザーを取得 const mutedUserIds = user ? (await Mute.find({ muterId: user._id @@ -68,27 +77,29 @@ export default async (params: any, user: ILocalUser) => { }; } + const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; + if (withFiles) { query.fileIds = { $exists: true, $ne: [] }; } - if (sinceId) { + if (ps.sinceId) { sort._id = 1; query._id = { - $gt: sinceId + $gt: ps.sinceId }; - } else if (untilId) { + } else if (ps.untilId) { query._id = { - $lt: untilId + $lt: ps.untilId }; - } else if (sinceDate) { + } else if (ps.sinceDate) { sort._id = 1; query.createdAt = { - $gt: new Date(sinceDate) + $gt: new Date(ps.sinceDate) }; - } else if (untilDate) { + } else if (ps.untilDate) { query.createdAt = { - $lt: new Date(untilDate) + $lt: new Date(ps.untilDate) }; } //#endregion @@ -96,7 +107,7 @@ export default async (params: any, user: ILocalUser) => { // Issue query const timeline = await Note .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 1060792683..16cec86797 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -7,8 +7,6 @@ import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; export const meta = { - name: 'notes/hybrid-timeline', - desc: { 'ja-JP': 'ハイブリッドタイムラインを取得します。' }, @@ -68,7 +66,13 @@ export const meta = { withFiles: $.bool.optional.note({ desc: { - 'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します' + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' + } + }), + + mediaOnly: $.bool.optional.note({ + desc: { + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } }), } @@ -203,7 +207,7 @@ export default async (params: any, user: ILocalUser) => { }); } - if (ps.withFiles) { + if (ps.withFiles || ps.mediaOnly) { query.$and.push({ fileIds: { $exists: true, $ne: [] } }); diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index 018e636ab5..2458a70556 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -3,40 +3,49 @@ import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; -/** - * Get timeline of local - */ -export default async (params: any, user: ILocalUser) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); - if (limitErr) throw 'invalid limit param'; +export const meta = { + desc: { + 'ja-JP': 'ローカルタイムラインを取得します。' + }, + + params: { + withFiles: $.bool.optional.note({ + desc: { + 'ja-JP': 'ファイルが添付された投稿に限定するか否か' + } + }), + + mediaOnly: $.bool.optional.note({ + desc: { + 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 10 + }), + + sinceId: $.type(ID).optional.note({}), - // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); - if (sinceIdErr) throw 'invalid sinceId param'; + untilId: $.type(ID).optional.note({}), - // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); - if (untilIdErr) throw 'invalid untilId param'; + sinceDate: $.num.optional.note({}), - // Get 'sinceDate' parameter - const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate); - if (sinceDateErr) throw 'invalid sinceDate param'; + untilDate: $.num.optional.note({}), + } +}; - // Get 'untilDate' parameter - const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate); - if (untilDateErr) throw 'invalid untilDate param'; +export default async (params: any, user: ILocalUser) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; // Check if only one of sinceId, untilId, sinceDate, untilDate specified - if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) { + if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } - // Get 'withFiles' parameter - const [withFiles, withFilesErr] = $.bool.optional.get(params.withFiles); - if (withFilesErr) throw 'invalid withFiles param'; - // ミュートしているユーザーを取得 const mutedUserIds = user ? (await Mute.find({ muterId: user._id @@ -69,27 +78,29 @@ export default async (params: any, user: ILocalUser) => { }; } + const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; + if (withFiles) { query.fileIds = { $exists: true, $ne: [] }; } - if (sinceId) { + if (ps.sinceId) { sort._id = 1; query._id = { - $gt: sinceId + $gt: ps.sinceId }; - } else if (untilId) { + } else if (ps.untilId) { query._id = { - $lt: untilId + $lt: ps.untilId }; - } else if (sinceDate) { + } else if (ps.sinceDate) { sort._id = 1; query.createdAt = { - $gt: new Date(sinceDate) + $gt: new Date(ps.sinceDate) }; - } else if (untilDate) { + } else if (ps.untilDate) { query.createdAt = { - $lt: new Date(untilDate) + $lt: new Date(ps.untilDate) }; } //#endregion @@ -97,7 +108,7 @@ export default async (params: any, user: ILocalUser) => { // Issue query const timeline = await Note .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts index f982dc01e9..82f11a9775 100644 --- a/src/server/api/endpoints/notes/search_by_tag.ts +++ b/src/server/api/endpoints/notes/search_by_tag.ts @@ -4,119 +4,152 @@ import User, { ILocalUser } from '../../../../models/user'; import Mute from '../../../../models/mute'; import { getFriendIds } from '../../common/get-friends'; import { pack } from '../../../../models/note'; +import getParams from '../../get-params'; -/** - * Search notes by tag - */ -export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { - // Get 'tag' parameter - const [tag, tagError] = $.str.get(params.tag); - if (tagError) return rej('invalid tag param'); +export const meta = { + desc: { + 'ja-JP': '指定されたタグが付けられた投稿を取得します。' + }, + + params: { + tag: $.str.note({ + desc: { + 'ja-JP': 'タグ' + } + }), + + includeUserIds: $.arr($.type(ID)).optional.note({ + default: [] + }), + + excludeUserIds: $.arr($.type(ID)).optional.note({ + default: [] + }), + + includeUserUsernames: $.arr($.str).optional.note({ + default: [] + }), + + excludeUserUsernames: $.arr($.str).optional.note({ + default: [] + }), + + following: $.bool.optional.nullable.note({ + default: null + }), + + mute: $.str.optional.note({ + default: 'mute_all' + }), - // Get 'includeUserIds' parameter - const [includeUserIds = [], includeUserIdsErr] = $.arr($.type(ID)).optional.get(params.includeUserIds); - if (includeUserIdsErr) return rej('invalid includeUserIds param'); + reply: $.bool.optional.nullable.note({ + default: null, - // Get 'excludeUserIds' parameter - const [excludeUserIds = [], excludeUserIdsErr] = $.arr($.type(ID)).optional.get(params.excludeUserIds); - if (excludeUserIdsErr) return rej('invalid excludeUserIds param'); + desc: { + 'ja-JP': '返信に限定するか否か' + } + }), - // Get 'includeUserUsernames' parameter - const [includeUserUsernames = [], includeUserUsernamesErr] = $.arr($.str).optional.get(params.includeUserUsernames); - if (includeUserUsernamesErr) return rej('invalid includeUserUsernames param'); + renote: $.bool.optional.nullable.note({ + default: null, - // Get 'excludeUserUsernames' parameter - const [excludeUserUsernames = [], excludeUserUsernamesErr] = $.arr($.str).optional.get(params.excludeUserUsernames); - if (excludeUserUsernamesErr) return rej('invalid excludeUserUsernames param'); + desc: { + 'ja-JP': 'Renoteに限定するか否か' + } + }), - // Get 'following' parameter - const [following = null, followingErr] = $.bool.optional.nullable.get(params.following); - if (followingErr) return rej('invalid following param'); + withFiles: $.bool.optional.nullable.note({ + default: null, - // Get 'mute' parameter - const [mute = 'mute_all', muteErr] = $.str.optional.get(params.mute); - if (muteErr) return rej('invalid mute param'); + desc: { + 'ja-JP': 'ファイルが添付された投稿に限定するか否か' + } + }), - // Get 'reply' parameter - const [reply = null, replyErr] = $.bool.optional.nullable.get(params.reply); - if (replyErr) return rej('invalid reply param'); + media: $.bool.optional.nullable.note({ + default: null, - // Get 'renote' parameter - const [renote = null, renoteErr] = $.bool.optional.nullable.get(params.renote); - if (renoteErr) return rej('invalid renote param'); + desc: { + 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' + } + }), - // Get 'withFiles' parameter - const [withFiles = null, withFilesErr] = $.bool.optional.nullable.get(params.withFiles); - if (withFilesErr) return rej('invalid withFiles param'); + poll: $.bool.optional.nullable.note({ + default: null, - // Get 'poll' parameter - const [poll = null, pollErr] = $.bool.optional.nullable.get(params.poll); - if (pollErr) return rej('invalid poll param'); + desc: { + 'ja-JP': 'アンケートが添付された投稿に限定するか否か' + } + }), - // Get 'sinceDate' parameter - const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate); - if (sinceDateErr) throw 'invalid sinceDate param'; + sinceDate: $.num.optional.note({ + }), - // Get 'untilDate' parameter - const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate); - if (untilDateErr) throw 'invalid untilDate param'; + untilDate: $.num.optional.note({ + }), - // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); - if (offsetErr) return rej('invalid offset param'); + offset: $.num.optional.min(0).note({ + default: 0 + }), - // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional.range(1, 30).get(params.limit); - if (limitErr) return rej('invalid limit param'); + limit: $.num.optional.range(1, 30).note({ + default: 10 + }), + } +}; + +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; - if (includeUserUsernames != null) { - const ids = (await Promise.all(includeUserUsernames.map(async (username) => { + if (ps.includeUserUsernames != null) { + const ids = (await Promise.all(ps.includeUserUsernames.map(async (username) => { const _user = await User.findOne({ usernameLower: username.toLowerCase() }); return _user ? _user._id : null; }))).filter(id => id != null); - ids.forEach(id => includeUserIds.push(id)); + ids.forEach(id => ps.includeUserIds.push(id)); } - if (excludeUserUsernames != null) { - const ids = (await Promise.all(excludeUserUsernames.map(async (username) => { + if (ps.excludeUserUsernames != null) { + const ids = (await Promise.all(ps.excludeUserUsernames.map(async (username) => { const _user = await User.findOne({ usernameLower: username.toLowerCase() }); return _user ? _user._id : null; }))).filter(id => id != null); - ids.forEach(id => excludeUserIds.push(id)); + ids.forEach(id => ps.excludeUserIds.push(id)); } let q: any = { $and: [{ - tagsLower: tag.toLowerCase() + tagsLower: ps.tag.toLowerCase() }] }; const push = (x: any) => q.$and.push(x); - if (includeUserIds && includeUserIds.length != 0) { + if (ps.includeUserIds && ps.includeUserIds.length != 0) { push({ userId: { - $in: includeUserIds + $in: ps.includeUserIds } }); - } else if (excludeUserIds && excludeUserIds.length != 0) { + } else if (ps.excludeUserIds && ps.excludeUserIds.length != 0) { push({ userId: { - $nin: excludeUserIds + $nin: ps.excludeUserIds } }); } - if (following != null && me != null) { + if (ps.following != null && me != null) { const ids = await getFriendIds(me._id, false); push({ - userId: following ? { + userId: ps.following ? { $in: ids } : { $nin: ids.concat(me._id) @@ -131,7 +164,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => }); const mutedUserIds = mutes.map(m => m.muteeId); - switch (mute) { + switch (ps.mute) { case 'mute_all': push({ userId: { @@ -202,8 +235,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } } - if (reply != null) { - if (reply) { + if (ps.reply != null) { + if (ps.reply) { push({ replyId: { $exists: true, @@ -223,8 +256,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } } - if (renote != null) { - if (renote) { + if (ps.renote != null) { + if (ps.renote) { push({ renoteId: { $exists: true, @@ -244,6 +277,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } } + const withFiles = ps.withFiles != null ? ps.withFiles : ps.media; + if (withFiles != null) { if (withFiles) { push({ @@ -265,8 +300,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } } - if (poll != null) { - if (poll) { + if (ps.poll != null) { + if (ps.poll) { push({ poll: { $exists: true, @@ -286,18 +321,18 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } } - if (sinceDate) { + if (ps.sinceDate) { push({ createdAt: { - $gt: new Date(sinceDate) + $gt: new Date(ps.sinceDate) } }); } - if (untilDate) { + if (ps.untilDate) { push({ createdAt: { - $lt: new Date(untilDate) + $lt: new Date(ps.untilDate) } }); } @@ -312,8 +347,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => sort: { _id: -1 }, - limit: limit, - skip: offset + limit: ps.limit, + skip: ps.offset }); // Serialize diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 145f648c56..089e7a182a 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -69,7 +69,13 @@ export const meta = { withFiles: $.bool.optional.note({ desc: { - 'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します' + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' + } + }), + + mediaOnly: $.bool.optional.note({ + desc: { + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } }), } @@ -193,7 +199,9 @@ export default async (params: any, user: ILocalUser) => { }); } - if (ps.withFiles) { + const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; + + if (withFiles) { query.$and.push({ fileIds: { $exists: true, $ne: [] } }); diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index e00a7de371..61192d7d3e 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -75,7 +75,13 @@ export const meta = { withFiles: $.bool.optional.note({ desc: { - 'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します' + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' + } + }), + + mediaOnly: $.bool.optional.note({ + desc: { + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } }), } @@ -199,7 +205,9 @@ export default async (params: any, user: ILocalUser) => { }); } - if (ps.withFiles) { + const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; + + if (withFiles) { query.$and.push({ fileIds: { $exists: true, $ne: [] } }); diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index d894e52dba..42c31189d6 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -2,63 +2,121 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import getHostLower from '../../common/get-host-lower'; import Note, { pack } from '../../../../models/note'; import User, { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; + +export const meta = { + desc: { + 'ja-JP': '指定したユーザーのタイムラインを取得します。' + }, + + params: { + userId: $.type(ID).optional.note({ + desc: { + 'ja-JP': 'ユーザーID' + } + }), + + username: $.str.optional.note({ + desc: { + 'ja-JP': 'ユーザー名' + } + }), + + host: $.str.optional.note({ + }), + + includeReplies: $.bool.optional.note({ + default: true, + + desc: { + 'ja-JP': 'リプライを含めるか否か' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 10, + desc: { + 'ja-JP': '最大数' + } + }), + + sinceId: $.type(ID).optional.note({ + desc: { + 'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します' + } + }), + + untilId: $.type(ID).optional.note({ + desc: { + 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' + } + }), + + sinceDate: $.num.optional.note({ + desc: { + 'ja-JP': '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' + } + }), + + untilDate: $.num.optional.note({ + desc: { + 'ja-JP': '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' + } + }), + + includeMyRenotes: $.bool.optional.note({ + default: true, + desc: { + 'ja-JP': '自分の行ったRenoteを含めるかどうか' + } + }), + + includeRenotedMyNotes: $.bool.optional.note({ + default: true, + desc: { + 'ja-JP': 'Renoteされた自分の投稿を含めるかどうか' + } + }), + + includeLocalRenotes: $.bool.optional.note({ + default: true, + desc: { + 'ja-JP': 'Renoteされたローカルの投稿を含めるかどうか' + } + }), + + withFiles: $.bool.optional.note({ + default: false, + desc: { + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' + } + }), + + mediaOnly: $.bool.optional.note({ + default: false, + desc: { + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' + } + }), + } +}; -/** - * Get notes of a user - */ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { - // Get 'userId' parameter - const [userId, userIdErr] = $.type(ID).optional.get(params.userId); - if (userIdErr) return rej('invalid userId param'); - - // Get 'username' parameter - const [username, usernameErr] = $.str.optional.get(params.username); - if (usernameErr) return rej('invalid username param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; - if (userId === undefined && username === undefined) { + if (ps.userId === undefined && ps.username === undefined) { return rej('userId or username is required'); } - // Get 'host' parameter - const [host, hostErr] = $.str.optional.get(params.host); - if (hostErr) return rej('invalid host param'); - - // Get 'includeReplies' parameter - const [includeReplies = true, includeRepliesErr] = $.bool.optional.get(params.includeReplies); - if (includeRepliesErr) return rej('invalid includeReplies param'); - - // Get 'withFiles' parameter - const [withFiles = false, withFilesErr] = $.bool.optional.get(params.withFiles); - if (withFilesErr) return rej('invalid withFiles param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); - if (limitErr) return rej('invalid limit param'); - - // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); - if (sinceIdErr) return rej('invalid sinceId param'); - - // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); - if (untilIdErr) return rej('invalid untilId param'); - - // Get 'sinceDate' parameter - const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate); - if (sinceDateErr) throw 'invalid sinceDate param'; - - // Get 'untilDate' parameter - const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate); - if (untilDateErr) throw 'invalid untilDate param'; - // Check if only one of sinceId, untilId, sinceDate, untilDate specified - if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) { + if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } - const q = userId !== undefined - ? { _id: userId } - : { usernameLower: username.toLowerCase(), host: getHostLower(host) } ; + const q = ps.userId !== undefined + ? { _id: ps.userId } + : { usernameLower: ps.username.toLowerCase(), host: getHostLower(ps.host) } ; // Lookup user const user = await User.findOne(q, { @@ -80,30 +138,32 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => userId: user._id } as any; - if (sinceId) { + if (ps.sinceId) { sort._id = 1; query._id = { - $gt: sinceId + $gt: ps.sinceId }; - } else if (untilId) { + } else if (ps.untilId) { query._id = { - $lt: untilId + $lt: ps.untilId }; - } else if (sinceDate) { + } else if (ps.sinceDate) { sort._id = 1; query.createdAt = { - $gt: new Date(sinceDate) + $gt: new Date(ps.sinceDate) }; - } else if (untilDate) { + } else if (ps.untilDate) { query.createdAt = { - $lt: new Date(untilDate) + $lt: new Date(ps.untilDate) }; } - if (!includeReplies) { + if (!ps.includeReplies) { query.replyId = null; } + const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; + if (withFiles) { query.fileIds = { $exists: true, @@ -115,12 +175,10 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => // Issue query const notes = await Note .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); // Serialize - res(await Promise.all(notes.map(async (note) => - await pack(note, me) - ))); + res(await Promise.all(notes.map(note => pack(note, me)))); }); -- cgit v1.2.3-freya From 395cfa61089efc4ab3ea1b6c6842521bad31efed Mon Sep 17 00:00:00 2001 From: Aya Morisawa Date: Thu, 6 Sep 2018 02:16:08 +0900 Subject: Resolve #2625 (#2627) --- src/client/app/desktop/views/pages/deck/deck.column.vue | 3 ++- src/games/reversi/core.ts | 6 ++++-- src/prelude/array.ts | 7 +++++++ src/server/activitypub/outbox.ts | 3 ++- src/server/api/endpoints/notes/global-timeline.ts | 3 ++- src/server/api/endpoints/notes/hybrid-timeline.ts | 3 ++- src/server/api/endpoints/notes/local-timeline.ts | 3 ++- src/server/api/endpoints/notes/timeline.ts | 3 ++- src/server/api/endpoints/users/notes.ts | 3 ++- 9 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 src/prelude/array.ts (limited to 'src/server/api') diff --git a/src/client/app/desktop/views/pages/deck/deck.column.vue b/src/client/app/desktop/views/pages/deck/deck.column.vue index 239b1b0447..abb09775fb 100644 --- a/src/client/app/desktop/views/pages/deck/deck.column.vue +++ b/src/client/app/desktop/views/pages/deck/deck.column.vue @@ -28,6 +28,7 @@ import Vue from 'vue'; import Menu from '../../../../common/views/components/menu.vue'; import contextmenu from '../../../api/contextmenu'; +import { countIf } from '../../../../../../prelude/array'; export default Vue.extend({ props: { @@ -117,7 +118,7 @@ export default Vue.extend({ toggleActive() { if (!this.isStacked) return; const vms = this.$store.state.settings.deck.layout.find(ids => ids.indexOf(this.column.id) != -1).map(id => this.getColumnVm(id)); - if (this.active && vms.filter(vm => vm.$el.classList.contains('active')).length == 1) return; + if (this.active && countIf(vm => vm.$el.classList.contains('active'), vms) == 1) return; this.active = !this.active; }, diff --git a/src/games/reversi/core.ts b/src/games/reversi/core.ts index b610d46884..34eb03becb 100644 --- a/src/games/reversi/core.ts +++ b/src/games/reversi/core.ts @@ -1,3 +1,5 @@ +import { count } from "../../prelude/array"; + // MISSKEY REVERSI ENGINE /** @@ -101,14 +103,14 @@ export default class Reversi { * 黒石の数 */ public get blackCount() { - return this.board.filter(x => x === BLACK).length; + return count(BLACK, this.board); } /** * 白石の数 */ public get whiteCount() { - return this.board.filter(x => x === WHITE).length; + return count(BLACK, this.board); } /** diff --git a/src/prelude/array.ts b/src/prelude/array.ts new file mode 100644 index 0000000000..e944030a7f --- /dev/null +++ b/src/prelude/array.ts @@ -0,0 +1,7 @@ +export function countIf(f: (x: T) => boolean, xs: T[]): number { + return xs.filter(f).length; +} + +export function count(x: T, xs: T[]): number { + return countIf(y => x === y, xs); +} diff --git a/src/server/activitypub/outbox.ts b/src/server/activitypub/outbox.ts index cc7e55b5df..a5e762eea8 100644 --- a/src/server/activitypub/outbox.ts +++ b/src/server/activitypub/outbox.ts @@ -10,6 +10,7 @@ import { setResponseType } from '../activitypub'; import Note from '../../models/note'; import renderNote from '../../remote/activitypub/renderer/note'; +import { countIf } from '../../prelude/array'; export default async (ctx: Router.IRouterContext) => { const userId = new mongo.ObjectID(ctx.params.user); @@ -25,7 +26,7 @@ export default async (ctx: Router.IRouterContext) => { const page: boolean = ctx.request.query.page === 'true'; // Validate parameters - if (sinceIdErr || untilIdErr || pageErr || [sinceId, untilId].filter(x => x != null).length > 1) { + if (sinceIdErr || untilIdErr || pageErr || countIf(x => x != null, [sinceId, untilId]) > 1) { ctx.status = 400; return; } diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index e70fc5d76f..5d93cd78ec 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -4,6 +4,7 @@ import Mute from '../../../../models/mute'; import { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; +import { countIf } from '../../../../prelude/array'; export const meta = { desc: { @@ -42,7 +43,7 @@ export default async (params: any, user: ILocalUser) => { if (psErr) throw psErr; // Check if only one of sinceId, untilId, sinceDate, untilDate specified - if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) { + if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 16cec86797..0eb7b61830 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -5,6 +5,7 @@ import { getFriends } from '../../common/get-friends'; import { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; +import { countIf } from '../../../../prelude/array'; export const meta = { desc: { @@ -86,7 +87,7 @@ export default async (params: any, user: ILocalUser) => { if (psErr) throw psErr; // Check if only one of sinceId, untilId, sinceDate, untilDate specified - if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) { + if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index 2458a70556..39c385853d 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -4,6 +4,7 @@ import Mute from '../../../../models/mute'; import { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; +import { countIf } from '../../../../prelude/array'; export const meta = { desc: { @@ -42,7 +43,7 @@ export default async (params: any, user: ILocalUser) => { if (psErr) throw psErr; // Check if only one of sinceId, untilId, sinceDate, untilDate specified - if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) { + if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 089e7a182a..5f3844987c 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -5,6 +5,7 @@ import { getFriends } from '../../common/get-friends'; import { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; +import { countIf } from '../../../../prelude/array'; export const meta = { desc: { @@ -86,7 +87,7 @@ export default async (params: any, user: ILocalUser) => { if (psErr) throw psErr; // Check if only one of sinceId, untilId, sinceDate, untilDate specified - if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) { + if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index 42c31189d6..1ab7786a18 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -3,6 +3,7 @@ import getHostLower from '../../common/get-host-lower'; import Note, { pack } from '../../../../models/note'; import User, { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; +import { countIf } from '../../../../prelude/array'; export const meta = { desc: { @@ -110,7 +111,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } // Check if only one of sinceId, untilId, sinceDate, untilDate specified - if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) { + if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } -- cgit v1.2.3-freya From e2c6227f4713b91d94355c69d619ac2d5d865c1b Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 6 Sep 2018 04:28:22 +0900 Subject: Improve local timeline API --- src/server/api/endpoints/notes/local-timeline.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index 2458a70556..ce84b4135a 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -23,6 +23,12 @@ export const meta = { } }), + fileType: $.arr($.str).optional.note({ + desc: { + 'ja-JP': '指定された種類のファイルが添付された投稿のみを取得します' + } + }), + limit: $.num.optional.range(1, 100).note({ default: 10 }), @@ -84,6 +90,14 @@ export default async (params: any, user: ILocalUser) => { query.fileIds = { $exists: true, $ne: [] }; } + if (ps.fileType) { + query.fileIds = { $exists: true, $ne: [] }; + + query['_files.contentType'] = { + $in: ps.fileType + }; + } + if (ps.sinceId) { sort._id = 1; query._id = { -- cgit v1.2.3-freya From 8e359d54bdd05f6718d8dab28ae19e1d1f657f89 Mon Sep 17 00:00:00 2001 From: Aya Morisawa Date: Thu, 6 Sep 2018 06:06:22 +0900 Subject: if elimination (#2635) --- src/server/api/call.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/call.ts b/src/server/api/call.ts index e9abc11f54..ee79e0a13c 100644 --- a/src/server/api/call.ts +++ b/src/server/api/call.ts @@ -25,10 +25,8 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any) return rej('YOU_ARE_NOT_ADMIN'); } - if (app && ep.meta.kind) { - if (!app.permission.some(p => p === ep.meta.kind)) { - return rej('PERMISSION_DENIED'); - } + if (app && ep.meta.kind && !app.permission.some(p => p === ep.meta.kind)) { + return rej('PERMISSION_DENIED'); } if (ep.meta.requireCredential && ep.meta.limit) { -- cgit v1.2.3-freya From 00d79487cd89f3cae675ecc0392c47ba547b69a5 Mon Sep 17 00:00:00 2001 From: Aya Morisawa Date: Fri, 7 Sep 2018 00:02:55 +0900 Subject: Add erase function (#2641) --- src/client/app/common/scripts/streaming/stream-manager.ts | 3 ++- src/client/app/common/views/components/poll-editor.vue | 3 ++- src/client/app/desktop/views/components/post-form.vue | 3 ++- src/client/app/mios.ts | 3 ++- src/client/app/mobile/views/components/post-form.vue | 3 ++- src/client/app/store.ts | 7 ++++--- src/client/app/sw.js | 4 ++-- src/prelude/array.ts | 4 ++++ src/server/api/endpoints/hashtags/trend.ts | 4 ++-- src/server/api/endpoints/notes/search_by_tag.ts | 9 +++++---- src/services/note/create.ts | 7 ++++--- 11 files changed, 31 insertions(+), 19 deletions(-) (limited to 'src/server/api') diff --git a/src/client/app/common/scripts/streaming/stream-manager.ts b/src/client/app/common/scripts/streaming/stream-manager.ts index 568b8b0372..8dd06f67d3 100644 --- a/src/client/app/common/scripts/streaming/stream-manager.ts +++ b/src/client/app/common/scripts/streaming/stream-manager.ts @@ -1,6 +1,7 @@ import { EventEmitter } from 'eventemitter3'; import * as uuid from 'uuid'; import Connection from './stream'; +import { erase } from '../../../../../prelude/array'; /** * ストリーム接続を管理するクラス @@ -89,7 +90,7 @@ export default abstract class StreamManager extends EventE * @param userId use で発行したユーザーID */ public dispose(userId) { - this.users = this.users.filter(id => id != userId); + this.users = erase(userId, this.users); this._connection.user = `Managed (${ this.users.length })`; diff --git a/src/client/app/common/views/components/poll-editor.vue b/src/client/app/common/views/components/poll-editor.vue index 115c934c8b..30d9799fec 100644 --- a/src/client/app/common/views/components/poll-editor.vue +++ b/src/client/app/common/views/components/poll-editor.vue @@ -20,6 +20,7 @@ + + diff --git a/src/client/app/desktop/views/pages/admin/admin.vue b/src/client/app/desktop/views/pages/admin/admin.vue index a71059c378..510252b447 100644 --- a/src/client/app/desktop/views/pages/admin/admin.vue +++ b/src/client/app/desktop/views/pages/admin/admin.vue @@ -5,6 +5,8 @@
  • %fa:chalkboard .fw%%i18n:@dashboard%
  • %fa:users .fw%%i18n:@users%
  • %fa:broadcast-tower .fw%%i18n:@announcements%
  • +
  • %fa:hashtag .fw%%i18n:@hashtags%
  • + @@ -17,6 +19,9 @@
    +
    + +
    @@ -33,6 +38,7 @@ import Vue from "vue"; import XDashboard from "./admin.dashboard.vue"; import XAnnouncements from "./admin.announcements.vue"; +import XHashtags from "./admin.hashtags.vue"; import XSuspendUser from "./admin.suspend-user.vue"; import XUnsuspendUser from "./admin.unsuspend-user.vue"; import XVerifyUser from "./admin.verify-user.vue"; @@ -43,6 +49,7 @@ export default Vue.extend({ components: { XDashboard, XAnnouncements, + XHashtags, XSuspendUser, XUnsuspendUser, XVerifyUser, diff --git a/src/models/meta.ts b/src/models/meta.ts index aef0163dfe..4f1977f3b5 100644 --- a/src/models/meta.ts +++ b/src/models/meta.ts @@ -4,12 +4,13 @@ const Meta = db.get('meta'); export default Meta; export type IMeta = { - broadcasts: any[]; - stats: { + broadcasts?: any[]; + stats?: { notesCount: number; originalNotesCount: number; usersCount: number; originalUsersCount: number; }; - disableRegistration: boolean; + disableRegistration?: boolean; + hidedTags?: string[]; }; diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index 10ca15d329..f903628774 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -21,7 +21,13 @@ export const meta = { desc: { 'ja-JP': '招待制か否か' } - }) + }), + + hidedTags: $.arr($.str).optional.nullable.note({ + desc: { + 'ja-JP': '統計などで無視するハッシュタグ' + } + }), } }; @@ -39,6 +45,10 @@ export default (params: any) => new Promise(async (res, rej) => { set.disableRegistration = ps.disableRegistration; } + if (Array.isArray(ps.hidedTags)) { + set.hidedTags = ps.hidedTags; + } + await Meta.update({}, { $set: set }, { upsert: true }); diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts index e7c08ca9f0..bfa475619c 100644 --- a/src/server/api/endpoints/hashtags/trend.ts +++ b/src/server/api/endpoints/hashtags/trend.ts @@ -1,5 +1,6 @@ import Note from '../../../../models/note'; import { erase } from '../../../../prelude/array'; +import Meta from '../../../../models/meta'; /* トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要 @@ -17,6 +18,9 @@ const max = 5; * Get trends of hashtags */ export default () => new Promise(async (res, rej) => { + const meta = await Meta.findOne({}); + const hidedTags = (meta.hidedTags || []).map(t => t.toLowerCase()); + //#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計 const data = await Note.aggregate([{ $match: { @@ -53,6 +57,9 @@ export default () => new Promise(async (res, rej) => { // カウント data.map(x => x._id).forEach(x => { + // ブラックリストに登録されているタグなら弾く + if (hidedTags.includes(x.tag)) return; + const i = tags.findIndex(tag => tag.name == x.tag); if (i != -1) { tags[i].count++; diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index b0876eaafd..4472d8d779 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -4,6 +4,7 @@ import * as os from 'os'; import config from '../../../config'; import Meta from '../../../models/meta'; +import { ILocalUser } from '../../../models/user'; const pkg = require('../../../../package.json'); const client = require('../../../../built/client/meta.json'); @@ -11,7 +12,7 @@ const client = require('../../../../built/client/meta.json'); /** * Show core info */ -export default () => new Promise(async (res, rej) => { +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { const meta: any = (await Meta.findOne()) || {}; res({ @@ -35,6 +36,7 @@ export default () => new Promise(async (res, rej) => { disableRegistration: meta.disableRegistration, driveCapacityPerLocalUserMb: config.localDriveCapacityMb, recaptchaSitekey: config.recaptcha ? config.recaptcha.site_key : null, - swPublickey: config.sw ? config.sw.public_key : null + swPublickey: config.sw ? config.sw.public_key : null, + hidedTags: (me && me.isAdmin) ? meta.hidedTags : undefined }); }); -- cgit v1.2.3-freya From e0deaec695650d22c92512cc2672ba3aade96eed Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 7 Sep 2018 19:23:39 +0900 Subject: Implement new endpoint --- src/server/api/endpoints/aggregation/hashtags.ts | 64 ++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/server/api/endpoints/aggregation/hashtags.ts (limited to 'src/server/api') diff --git a/src/server/api/endpoints/aggregation/hashtags.ts b/src/server/api/endpoints/aggregation/hashtags.ts new file mode 100644 index 0000000000..0daf42427b --- /dev/null +++ b/src/server/api/endpoints/aggregation/hashtags.ts @@ -0,0 +1,64 @@ +import Note from '../../../../models/note'; +import Meta from '../../../../models/meta'; + +export default () => new Promise(async (res, rej) => { + const meta = await Meta.findOne({}); + const hidedTags = (meta.hidedTags || []).map(t => t.toLowerCase()); + + const span = 1000 * 60 * 60 * 24 * 7; // 1週間 + + //#region 1. 指定期間の内に投稿されたハッシュタグ(とユーザーのペア)を集計 + const data = await Note.aggregate([{ + $match: { + createdAt: { + $gt: new Date(Date.now() - span) + }, + tagsLower: { + $exists: true, + $ne: [] + } + } + }, { + $unwind: '$tagsLower' + }, { + $group: { + _id: { tag: '$tagsLower', userId: '$userId' } + } + }]) as Array<{ + _id: { + tag: string; + userId: any; + } + }>; + //#endregion + + if (data.length == 0) { + return res([]); + } + + let tags: Array<{ + name: string; + count: number; + }> = []; + + // カウント + data.map(x => x._id).forEach(x => { + // ブラックリストに登録されているタグなら弾く + if (hidedTags.includes(x.tag)) return; + + const i = tags.findIndex(tag => tag.name == x.tag); + if (i != -1) { + tags[i].count++; + } else { + tags.push({ + name: x.tag, + count: 1 + }); + } + }); + + // タグを人気順に並べ替え + tags = tags.sort((a, b) => b.count - a.count); + + res(tags); +}); -- cgit v1.2.3-freya From d9fe9cc5df7d3b7964a303544dd3dbbdf1cf5dd7 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 7 Sep 2018 20:23:46 +0900 Subject: 返すタグの数を制限 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/api/endpoints/aggregation/hashtags.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/aggregation/hashtags.ts b/src/server/api/endpoints/aggregation/hashtags.ts index 0daf42427b..c5aacd89cd 100644 --- a/src/server/api/endpoints/aggregation/hashtags.ts +++ b/src/server/api/endpoints/aggregation/hashtags.ts @@ -60,5 +60,7 @@ export default () => new Promise(async (res, rej) => { // タグを人気順に並べ替え tags = tags.sort((a, b) => b.count - a.count); + tags = tags.slice(0, 30); + res(tags); }); -- cgit v1.2.3-freya From fd06fd4dc18313100db3f06e771c3c4258f24176 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 10 Sep 2018 01:51:46 +0900 Subject: Resolve #2560 --- src/server/api/endpoints/notes/create.ts | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 47b53c943b..96745132a3 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -132,25 +132,14 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( let files: IDriveFile[] = []; const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; if (fileIds != null) { - // Fetch files - // forEach だと途中でエラーなどがあっても return できないので - // 敢えて for を使っています。 - for (const fileId of fileIds) { - // Fetch file - // SELECT _id - const entity = await DriveFile.findOne({ + files = await Promise.all(fileIds.map(fileId => { + return DriveFile.findOne({ _id: fileId, 'metadata.userId': user._id }); + })); - if (entity === null) { - return rej('file not found'); - } else { - files.push(entity); - } - } - } else { - files = null; + files = files.filter(file => file != null); } let renote: INote = null; -- cgit v1.2.3-freya From 1344ffa67dbe74505bd922ac7185a4d41dfdddd7 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 10 Sep 2018 01:55:14 +0900 Subject: 削除された投稿にリアクションできないように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1778 --- src/server/api/endpoints/notes/reactions/create.ts | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/notes/reactions/create.ts b/src/server/api/endpoints/notes/reactions/create.ts index 0781db16c5..ec68f065d8 100644 --- a/src/server/api/endpoints/notes/reactions/create.ts +++ b/src/server/api/endpoints/notes/reactions/create.ts @@ -43,6 +43,10 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = return rej('note not found'); } + if (note.deletedAt != null) { + return rej('this not is already deleted'); + } + try { await create(user, note, ps.reaction); } catch (e) { -- cgit v1.2.3-freya From e32884f07f0b05028a581c73073fa87da08d9fd0 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 10 Sep 2018 01:57:19 +0900 Subject: Fix #1740 --- src/server/api/endpoints/notes/search_by_tag.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts index 77082c2600..fadebe4efe 100644 --- a/src/server/api/endpoints/notes/search_by_tag.ts +++ b/src/server/api/endpoints/notes/search_by_tag.ts @@ -128,7 +128,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => let q: any = { $and: [{ tagsLower: ps.tag.toLowerCase() - }] + }], + deletedAt: { $exists: false } }; const push = (x: any) => q.$and.push(x); @@ -339,7 +340,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } if (q.$and.length == 0) { - q = {}; + delete q.$and; } // Search notes -- cgit v1.2.3-freya From eb4f625bbdadd63a32b9d6f09714b721e510defe Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 10 Sep 2018 02:09:33 +0900 Subject: Fix #2096 --- src/server/api/endpoints/i/update.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 585339e249..953a6aec2a 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -84,6 +84,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a }); if (avatar == null) return rej('avatar not found'); + if (!avatar.contentType.startsWith('image/')) return rej('avatar not an image'); updates.avatarUrl = avatar.metadata.thumbnailUrl || avatar.metadata.url || `${config.drive_url}/${avatar._id}`; @@ -98,6 +99,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a }); if (banner == null) return rej('banner not found'); + if (!banner.contentType.startsWith('image/')) return rej('banner not an image'); updates.bannerUrl = banner.metadata.url || `${config.drive_url}/${banner._id}`; -- cgit v1.2.3-freya From a32071541a0f76c25a8246e1d324f912a6843cfc Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 10 Sep 2018 02:20:09 +0900 Subject: Clean up --- src/server/api/endpoints/notes/hybrid-timeline.ts | 3 --- 1 file changed, 3 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 0eb7b61830..5e39d8c78a 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -79,9 +79,6 @@ export const meta = { } }; -/** - * Get hybrid timeline of myself - */ export default async (params: any, user: ILocalUser) => { const [ps, psErr] = getParams(meta, params); if (psErr) throw psErr; -- cgit v1.2.3-freya From d9a1cd082c53e27fe984157b99c26d8e623aeee7 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 10 Sep 2018 02:21:16 +0900 Subject: #2623 --- src/server/api/endpoints/i/update.ts | 157 +++++++++++++++++++++-------------- 1 file changed, 93 insertions(+), 64 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 953a6aec2a..6aa4cc1148 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -6,6 +6,7 @@ import acceptAllFollowRequests from '../../../../services/following/requests/acc import { IApp } from '../../../../models/app'; import config from '../../../../config'; import { publishToFollowers } from '../../../../services/i/update'; +import getParams from '../../get-params'; export const meta = { desc: { @@ -15,72 +16,100 @@ export const meta = { requireCredential: true, - kind: 'account-write' + kind: 'account-write', + + params: { + name: $.str.optional.nullable.pipe(isValidName).note({ + desc: { + 'ja-JP': '名前(ハンドルネームやニックネーム)' + } + }), + + description: $.str.optional.nullable.pipe(isValidDescription).note({ + desc: { + 'ja-JP': 'アカウントの説明や自己紹介' + } + }), + + location: $.str.optional.nullable.pipe(isValidLocation).note({ + desc: { + 'ja-JP': '住んでいる地域、所在' + } + }), + + birthday: $.str.optional.nullable.pipe(isValidBirthday).note({ + desc: { + 'ja-JP': '誕生日 (YYYY-MM-DD形式)' + } + }), + + avatarId: $.type(ID).optional.nullable.note({ + desc: { + 'ja-JP': 'アイコンに設定する画像のドライブファイルID' + } + }), + + bannerId: $.type(ID).optional.nullable.note({ + desc: { + 'ja-JP': 'バナーに設定する画像のドライブファイルID' + } + }), + + wallpaperId: $.type(ID).optional.nullable.note({ + desc: { + 'ja-JP': '壁紙に設定する画像のドライブファイルID' + } + }), + + isLocked: $.bool.optional.note({ + desc: { + 'ja-JP': '鍵アカウントか否か' + } + }), + + isBot: $.bool.optional.note({ + desc: { + 'ja-JP': 'Botか否か' + } + }), + + isCat: $.bool.optional.note({ + desc: { + 'ja-JP': '猫か否か' + } + }), + + autoWatch: $.bool.optional.note({ + desc: { + 'ja-JP': '投稿の自動ウォッチをするか否か' + } + }), + } }; export default async (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; + const isSecure = user != null && app == null; const updates = {} as any; - // Get 'name' parameter - const [name, nameErr] = $.str.optional.nullable.pipe(isValidName).get(params.name); - if (nameErr) return rej('invalid name param'); - if (name) updates.name = name; - - // Get 'description' parameter - const [description, descriptionErr] = $.str.optional.nullable.pipe(isValidDescription).get(params.description); - if (descriptionErr) return rej('invalid description param'); - if (description !== undefined) updates.description = description; - - // Get 'location' parameter - const [location, locationErr] = $.str.optional.nullable.pipe(isValidLocation).get(params.location); - if (locationErr) return rej('invalid location param'); - if (location !== undefined) updates['profile.location'] = location; - - // Get 'birthday' parameter - const [birthday, birthdayErr] = $.str.optional.nullable.pipe(isValidBirthday).get(params.birthday); - if (birthdayErr) return rej('invalid birthday param'); - if (birthday !== undefined) updates['profile.birthday'] = birthday; - - // Get 'avatarId' parameter - const [avatarId, avatarIdErr] = $.type(ID).optional.nullable.get(params.avatarId); - if (avatarIdErr) return rej('invalid avatarId param'); - if (avatarId !== undefined) updates.avatarId = avatarId; - - // Get 'bannerId' parameter - const [bannerId, bannerIdErr] = $.type(ID).optional.nullable.get(params.bannerId); - if (bannerIdErr) return rej('invalid bannerId param'); - if (bannerId !== undefined) updates.bannerId = bannerId; - - // Get 'wallpaperId' parameter - const [wallpaperId, wallpaperIdErr] = $.type(ID).optional.nullable.get(params.wallpaperId); - if (wallpaperIdErr) return rej('invalid wallpaperId param'); - if (wallpaperId !== undefined) updates.wallpaperId = wallpaperId; - - // Get 'isLocked' parameter - const [isLocked, isLockedErr] = $.bool.optional.get(params.isLocked); - if (isLockedErr) return rej('invalid isLocked param'); - if (isLocked != null) updates.isLocked = isLocked; - - // Get 'isBot' parameter - const [isBot, isBotErr] = $.bool.optional.get(params.isBot); - if (isBotErr) return rej('invalid isBot param'); - if (isBot != null) updates.isBot = isBot; - - // Get 'isCat' parameter - const [isCat, isCatErr] = $.bool.optional.get(params.isCat); - if (isCatErr) return rej('invalid isCat param'); - if (isCat != null) updates.isCat = isCat; - - // Get 'autoWatch' parameter - const [autoWatch, autoWatchErr] = $.bool.optional.get(params.autoWatch); - if (autoWatchErr) return rej('invalid autoWatch param'); - if (autoWatch != null) updates['settings.autoWatch'] = autoWatch; - - if (avatarId) { + if (ps.name !== undefined) updates.name = ps.name; + if (ps.description !== undefined) updates.description = ps.description; + if (ps.location !== undefined) updates['profile.location'] = ps.location; + if (ps.birthday !== undefined) updates['profile.birthday'] = ps.birthday; + if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId; + if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId; + if (ps.wallpaperId !== undefined) updates.wallpaperId = ps.wallpaperId; + if (typeof ps.isLocked == 'boolean') updates.isLocked = ps.isLocked; + if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot; + if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat; + if (typeof ps.autoWatch == 'boolean') updates['settings.autoWatch'] = ps.autoWatch; + + if (ps.avatarId) { const avatar = await DriveFile.findOne({ - _id: avatarId + _id: ps.avatarId }); if (avatar == null) return rej('avatar not found'); @@ -93,9 +122,9 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a } } - if (bannerId) { + if (ps.bannerId) { const banner = await DriveFile.findOne({ - _id: bannerId + _id: ps.bannerId }); if (banner == null) return rej('banner not found'); @@ -108,13 +137,13 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a } } - if (wallpaperId !== undefined) { - if (wallpaperId === null) { + if (ps.wallpaperId !== undefined) { + if (ps.wallpaperId === null) { updates.wallpaperUrl = null; updates.wallpaperColor = null; } else { const wallpaper = await DriveFile.findOne({ - _id: wallpaperId + _id: ps.wallpaperId }); if (wallpaper == null) return rej('wallpaper not found'); @@ -144,7 +173,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a publishUserStream(user._id, 'meUpdated', iObj); // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 - if (user.isLocked && isLocked === false) { + if (user.isLocked && ps.isLocked === false) { acceptAllFollowRequests(user); } -- cgit v1.2.3-freya From 23efaae85ebfa722fba132004a52c2444a614808 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 10 Sep 2018 03:39:00 +0900 Subject: lint --- src/server/api/endpoints/notes/search_by_tag.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts index fadebe4efe..11bfe34724 100644 --- a/src/server/api/endpoints/notes/search_by_tag.ts +++ b/src/server/api/endpoints/notes/search_by_tag.ts @@ -125,7 +125,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => ids.forEach(id => ps.excludeUserIds.push(id)); } - let q: any = { + const q: any = { $and: [{ tagsLower: ps.tag.toLowerCase() }], -- cgit v1.2.3-freya From 1fea2cdcbe341bb56be1cd2b79a8115b482fab65 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 11 Sep 2018 20:57:25 +0900 Subject: Fix bug --- src/server/api/stream/home.ts | 14 +++++++------- src/server/api/stream/hybrid-timeline.ts | 14 +++++++------- src/server/api/stream/local-timeline.ts | 14 +++++++------- 3 files changed, 21 insertions(+), 21 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/stream/home.ts b/src/server/api/stream/home.ts index dc3ce9d19f..5f3b6744b2 100644 --- a/src/server/api/stream/home.ts +++ b/src/server/api/stream/home.ts @@ -36,6 +36,13 @@ export default async function( // Subscribe Home stream channel subscriber.on(`user-stream:${user._id}`, async x => { + // Renoteなら再pack + if (x.type == 'note' && x.body.renoteId != null) { + x.body.renote = await pack(x.body.renoteId, user, { + detail: true + }); + } + //#region 流れてきたメッセージがミュートしているユーザーが関わるものだったら無視する if (x.type == 'note') { if (mutedUserIds.includes(x.body.userId)) { @@ -54,13 +61,6 @@ export default async function( } //#endregion - // Renoteなら再pack - if (x.type == 'note' && x.body.renoteId != null) { - x.body.renote = await pack(x.body.renoteId, user, { - detail: true - }); - } - connection.send(JSON.stringify(x)); }); diff --git a/src/server/api/stream/hybrid-timeline.ts b/src/server/api/stream/hybrid-timeline.ts index c401145abe..d0dae9b0dd 100644 --- a/src/server/api/stream/hybrid-timeline.ts +++ b/src/server/api/stream/hybrid-timeline.ts @@ -19,6 +19,13 @@ export default async function( subscriber.on(`hybrid-timeline:${user._id}`, onEvent); async function onEvent(note: any) { + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await pack(note.renoteId, user, { + detail: true + }); + } + //#region 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (mutedUserIds.indexOf(note.userId) != -1) { return; @@ -31,13 +38,6 @@ export default async function( } //#endregion - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await pack(note.renoteId, user, { - detail: true - }); - } - connection.send(JSON.stringify({ type: 'note', body: note diff --git a/src/server/api/stream/local-timeline.ts b/src/server/api/stream/local-timeline.ts index 25e0e00c9f..e21c071bab 100644 --- a/src/server/api/stream/local-timeline.ts +++ b/src/server/api/stream/local-timeline.ts @@ -16,6 +16,13 @@ export default async function( // Subscribe stream subscriber.on('local-timeline', async note => { + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await pack(note.renoteId, user, { + detail: true + }); + } + //#region 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (mutedUserIds.indexOf(note.userId) != -1) { return; @@ -28,13 +35,6 @@ export default async function( } //#endregion - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await pack(note.renoteId, user, { - detail: true - }); - } - connection.send(JSON.stringify({ type: 'note', body: note -- cgit v1.2.3-freya From 046976dffc1aa8bc02259ab4a65e74b1216a0ec3 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 12 Sep 2018 02:48:19 +0900 Subject: Resolve #2691 --- .../app/desktop/views/components/timeline.vue | 11 +- .../desktop/views/pages/admin/admin.dashboard.vue | 35 ++++-- src/client/app/mobile/views/pages/home.vue | 11 +- src/models/meta.ts | 1 + src/server/api/endpoints/admin/update-meta.ts | 10 ++ src/server/api/endpoints/meta.ts | 1 + src/stream.ts | 119 ++++++++++++++------- 7 files changed, 134 insertions(+), 54 deletions(-) (limited to 'src/server/api') diff --git a/src/client/app/desktop/views/components/timeline.vue b/src/client/app/desktop/views/components/timeline.vue index 52a7753438..8d72016f22 100644 --- a/src/client/app/desktop/views/components/timeline.vue +++ b/src/client/app/desktop/views/components/timeline.vue @@ -2,8 +2,8 @@
    %fa:home% %i18n:@home% - %fa:R comments% %i18n:@local% - %fa:share-alt% %i18n:@hybrid% + %fa:R comments% %i18n:@local% + %fa:share-alt% %i18n:@hybrid% %fa:globe% %i18n:@global% %fa:list% {{ list.title }} @@ -29,7 +29,8 @@ export default Vue.extend({ data() { return { src: 'home', - list: null + list: null, + enableLocalTimeline: false }; }, @@ -44,6 +45,10 @@ export default Vue.extend({ }, created() { + (this as any).os.getMeta().then(meta => { + this.enableLocalTimeline = !meta.disableLocalTimeline; + }); + if (this.$store.state.device.tl) { this.src = this.$store.state.device.tl.src; if (this.src == 'list') { diff --git a/src/client/app/desktop/views/pages/admin/admin.dashboard.vue b/src/client/app/desktop/views/pages/admin/admin.dashboard.vue index ebb54d782e..c86c30db17 100644 --- a/src/client/app/desktop/views/pages/admin/admin.dashboard.vue +++ b/src/client/app/desktop/views/pages/admin/admin.dashboard.vue @@ -1,22 +1,34 @@ @@ -33,6 +45,7 @@ export default Vue.extend({ return { stats: null, disableRegistration: false, + disableLocalTimeline: false, inviteCode: null, connection: null, connectionId: null @@ -44,6 +57,7 @@ export default Vue.extend({ (this as any).os.getMeta().then(meta => { this.disableRegistration = meta.disableRegistration; + this.disableLocalTimeline = meta.disableLocalTimeline; }); (this as any).api('stats').then(stats => { @@ -61,7 +75,8 @@ export default Vue.extend({ }, updateMeta() { (this as any).api('admin/update-meta', { - disableRegistration: this.disableRegistration + disableRegistration: this.disableRegistration, + disableLocalTimeline: this.disableLocalTimeline }); } } @@ -97,4 +112,8 @@ export default Vue.extend({ border solid 1px #eee border-radius: 8px + > .form + > div + border-bottom solid 1px #eee + diff --git a/src/client/app/mobile/views/pages/home.vue b/src/client/app/mobile/views/pages/home.vue index 706c9cd28b..333ca1a7a1 100644 --- a/src/client/app/mobile/views/pages/home.vue +++ b/src/client/app/mobile/views/pages/home.vue @@ -24,8 +24,8 @@
    %fa:home% %i18n:@home% - %fa:R comments% %i18n:@local% - %fa:share-alt% %i18n:@hybrid% + %fa:R comments% %i18n:@local% + %fa:share-alt% %i18n:@hybrid% %fa:globe% %i18n:@global% @@ -46,6 +47,12 @@ export default Vue.extend({ birthday: null, }; }, + computed: { + alwaysMarkNsfw: { + get() { return this.$store.state.i.settings.alwaysMarkNsfw; }, + set(value) { (this as any).api('i/update', { alwaysMarkNsfw: value }); } + }, + }, created() { this.name = this.$store.state.i.name || ''; this.location = this.$store.state.i.profile.location; diff --git a/src/client/app/mobile/views/pages/settings/settings.profile.vue b/src/client/app/mobile/views/pages/settings/settings.profile.vue index 6f5ac9ae93..127f531902 100644 --- a/src/client/app/mobile/views/pages/settings/settings.profile.vue +++ b/src/client/app/mobile/views/pages/settings/settings.profile.vue @@ -49,6 +49,7 @@
    %i18n:@is-cat% + %i18n:common.always-mark-nsfw%
    @@ -85,6 +86,13 @@ export default Vue.extend({ }; }, + computed: { + alwaysMarkNsfw: { + get() { return this.$store.state.i.settings.alwaysMarkNsfw; }, + set(value) { (this as any).api('i/update', { alwaysMarkNsfw: value }); } + }, + }, + created() { this.name = this.$store.state.i.name || ''; this.username = this.$store.state.i.username; diff --git a/src/models/user.ts b/src/models/user.ts index 8f3fbbdc8f..64197c91c2 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -102,7 +102,10 @@ export interface ILocalUser extends IUserBase { twoFactorEnabled: boolean; twoFactorTempSecret?: string; clientSettings: any; - settings: any; + settings: { + autoWatch: boolean; + alwaysMarkNsfw?: boolean; + }; hasUnreadNotification: boolean; hasUnreadMessagingMessage: boolean; } diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts index dfbd11d0c2..4b5ffa90e0 100644 --- a/src/server/api/endpoints/drive/files/create.ts +++ b/src/server/api/endpoints/drive/files/create.ts @@ -31,8 +31,8 @@ export const meta = { } }), - isSensitive: $.bool.optional.note({ - default: false, + isSensitive: $.bool.optional.nullable.note({ + default: null, desc: { 'ja-JP': 'このメディアが「閲覧注意」(NSFW)かどうか', 'en-US': 'Whether this media is NSFW' diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 6aa4cc1148..c1be0b6ebc 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -84,6 +84,12 @@ export const meta = { 'ja-JP': '投稿の自動ウォッチをするか否か' } }), + + alwaysMarkNsfw: $.bool.optional.note({ + desc: { + 'ja-JP': 'アップロードするメディアをデフォルトで「閲覧注意」として設定するか' + } + }), } }; @@ -106,6 +112,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot; if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat; if (typeof ps.autoWatch == 'boolean') updates['settings.autoWatch'] = ps.autoWatch; + if (typeof ps.alwaysMarkNsfw == 'boolean') updates['settings.alwaysMarkNsfw'] = ps.alwaysMarkNsfw; if (ps.avatarId) { const avatar = await DriveFile.findOne({ diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index 828ebcbb94..666a6ca742 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -153,7 +153,7 @@ export default async function( isLink: boolean = false, url: string = null, uri: string = null, - sensitive = false + sensitive: boolean = null ): Promise { // Calc md5 hash const calcHash = new Promise((res, rej) => { @@ -329,7 +329,13 @@ export default async function( properties: properties, withoutChunks: isLink, isRemote: isLink, - isSensitive: sensitive + isSensitive: (sensitive !== null && sensitive !== undefined) + ? sensitive + : isLocalUser(user) + ? user.settings.alwaysMarkNsfw + ? true + : false + : false } as IMetadata; if (url !== null) { -- cgit v1.2.3-freya From c985fed3e43bae05f9e6e854f651f49f2bc3e83a Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 15 Sep 2018 05:40:58 +0900 Subject: Resolve #2328 --- locales/ja-JP.yml | 4 + package.json | 1 + src/client/app/desktop/views/components/charts.vue | 103 ++++++++++++++++++++- src/models/stats.ts | 26 ++++++ src/server/api/endpoints/chart.ts | 21 +++++ src/server/index.ts | 25 +++++ src/services/update-chart.ts | 22 +++++ 7 files changed, 200 insertions(+), 2 deletions(-) (limited to 'src/server/api') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 5257d616c4..72dc88b7b3 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -518,6 +518,7 @@ desktop/views/components/charts.vue: notes: "投稿" users: "ユーザー" drive: "ドライブ" + network: "ネットワーク" charts: notes: "投稿の増減 (統合)" local-notes: "投稿の増減 (ローカル)" @@ -529,6 +530,9 @@ desktop/views/components/charts.vue: drive-total: "ドライブ使用量の累計" drive-files: "ドライブのファイル数の増減" drive-files-total: "ドライブのファイル数の累計" + network-requests: "リクエスト" + network-time: "応答時間" + network-usage: "通信量" desktop/views/components/choose-file-from-drive-window.vue: choose-file: "ファイル選択中" diff --git a/package.json b/package.json index 4d2a4b285b..4b9eaf9e22 100644 --- a/package.json +++ b/package.json @@ -179,6 +179,7 @@ "redis": "2.8.0", "request": "2.88.0", "request-promise-native": "1.0.5", + "request-stats": "3.0.0", "rimraf": "2.6.2", "rndstr": "1.0.0", "s-age": "1.1.2", diff --git a/src/client/app/desktop/views/components/charts.vue b/src/client/app/desktop/views/components/charts.vue index c4e92e429f..6514cdf788 100644 --- a/src/client/app/desktop/views/components/charts.vue +++ b/src/client/app/desktop/views/components/charts.vue @@ -19,6 +19,11 @@ + + + + +
    %i18n:@per-day% | %i18n:@per-hour% @@ -41,7 +46,10 @@ const colors = { localPlus: 'rgb(52, 178, 118)', remotePlus: 'rgb(158, 255, 209)', localMinus: 'rgb(255, 97, 74)', - remoteMinus: 'rgb(255, 149, 134)' + remoteMinus: 'rgb(255, 149, 134)', + + incoming: 'rgb(52, 178, 118)', + outgoing: 'rgb(255, 97, 74)', }; const rgba = (color: string): string => { @@ -75,6 +83,9 @@ export default Vue.extend({ case 'drive-total': return this.driveTotalChart(); case 'drive-files': return this.driveFilesChart(); case 'drive-files-total': return this.driveFilesTotalChart(); + case 'network-requests': return this.networkRequestsChart(); + case 'network-time': return this.networkTimeChart(); + case 'network-usage': return this.networkUsageChart(); } }, @@ -544,7 +555,95 @@ export default Vue.extend({ } } }]; - } + }, + + networkRequestsChart(): any { + const data = this.stats.slice().reverse().map(x => ({ + date: new Date(x.date), + requests: x.network.requests + })); + + return [{ + datasets: [{ + label: 'Requests', + fill: true, + backgroundColor: rgba(colors.localPlus), + borderColor: colors.localPlus, + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.requests })) + }] + }]; + }, + + networkTimeChart(): any { + const data = this.stats.slice().reverse().map(x => ({ + date: new Date(x.date), + time: x.network.requests != 0 ? (x.network.totalTime / x.network.requests) : 0, + })); + + return [{ + datasets: [{ + label: 'Avg time (ms)', + fill: true, + backgroundColor: rgba(colors.localPlus), + borderColor: colors.localPlus, + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.time })) + }] + }]; + }, + + networkUsageChart(): any { + const data = this.stats.slice().reverse().map(x => ({ + date: new Date(x.date), + incoming: x.network.incomingBytes, + outgoing: x.network.outgoingBytes + })); + + return [{ + datasets: [{ + label: 'Incoming', + fill: true, + backgroundColor: rgba(colors.incoming), + borderColor: colors.incoming, + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.incoming })) + }, { + label: 'Outgoing', + fill: true, + backgroundColor: rgba(colors.outgoing), + borderColor: colors.outgoing, + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.outgoing })) + }] + }, { + scales: { + yAxes: [{ + ticks: { + callback: value => { + return Vue.filter('bytes')(value, 1); + } + } + }] + }, + tooltips: { + callbacks: { + label: (tooltipItem, data) => { + const label = data.datasets[tooltipItem.datasetIndex].label || ''; + return `${label}: ${Vue.filter('bytes')(tooltipItem.yLabel, 1)}`; + } + } + } + }]; + }, } }); diff --git a/src/models/stats.ts b/src/models/stats.ts index d496f2c480..c4c838caeb 100644 --- a/src/models/stats.ts +++ b/src/models/stats.ts @@ -204,4 +204,30 @@ export interface IStats { decSize: number; }; }; + + /** + * ネットワークに関する統計 + */ + network: { + /** + * サーバーへのリクエスト数 + */ + requests: number; + + /** + * 応答時間の合計 + * TIP: (totalTime / requests) でひとつのリクエストに平均でどれくらいの時間がかかったか知れる + */ + totalTime: number; + + /** + * 合計受信データ量 + */ + incomingBytes: number; + + /** + * 合計送信データ量 + */ + outgoingBytes: number; + }; } diff --git a/src/server/api/endpoints/chart.ts b/src/server/api/endpoints/chart.ts index 7da970131e..3b1a3b56fc 100644 --- a/src/server/api/endpoints/chart.ts +++ b/src/server/api/endpoints/chart.ts @@ -6,6 +6,15 @@ type Omit = Pick>; function migrateStats(stats: IStats[]) { stats.forEach(stat => { + if (stat.network == null) { + stat.network = { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 + }; + } + const isOldData = stat.users.local.inc == null || stat.users.local.dec == null || @@ -180,6 +189,12 @@ export default (params: any) => new Promise(async (res, rej) => { decCount: 0, decSize: 0 } + }, + network: { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 } }); } else { @@ -236,6 +251,12 @@ export default (params: any) => new Promise(async (res, rej) => { decCount: 0, decSize: 0 } + }, + network: { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 } }); } diff --git a/src/server/index.ts b/src/server/index.ts index f1fcf58c8d..dc60b0d9ec 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -11,11 +11,13 @@ import * as Router from 'koa-router'; import * as mount from 'koa-mount'; import * as compress from 'koa-compress'; import * as logger from 'koa-logger'; +const requestStats = require('request-stats'); //const slow = require('koa-slow'); import activityPub from './activitypub'; import webFinger from './webfinger'; import config from '../config'; +import { updateNetworkStats } from '../services/update-chart'; // Init app const app = new Koa(); @@ -81,4 +83,27 @@ export default () => new Promise(resolve => { // Listen server.listen(config.port, resolve); + + //#region Network stats + let queue: any[] = []; + + requestStats(server, (stats: any) => { + if (stats.ok) { + queue.push(stats); + } + }); + + // Bulk write + setInterval(() => { + if (queue.length == 0) return; + + const requests = queue.length; + const time = queue.reduce((a, b) => a + b.time, 0); + const incomingBytes = queue.reduce((a, b) => a + b.req.bytes, 0); + const outgoingBytes = queue.reduce((a, b) => a + b.res.bytes, 0); + queue = []; + + updateNetworkStats(requests, time, incomingBytes, outgoingBytes); + }, 5000); + //#endregion }); diff --git a/src/services/update-chart.ts b/src/services/update-chart.ts index 1f8da6be9f..78834ba601 100644 --- a/src/services/update-chart.ts +++ b/src/services/update-chart.ts @@ -96,6 +96,12 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise { decCount: 0, decSize: 0 } + }, + network: { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 } }; @@ -161,6 +167,12 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise { decCount: 0, decSize: 0 } + }, + network: { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 } }; @@ -243,3 +255,13 @@ export async function updateDriveStats(file: IDriveFile, isAdditional: boolean) await update(inc); } + +export async function updateNetworkStats(requests: number, time: number, incomingBytes: number, outgoingBytes: number) { + const inc = {} as any; + inc['network.requests'] = requests; + inc['network.totalTime'] = time; + inc['network.incomingBytes'] = incomingBytes; + inc['network.outgoingBytes'] = outgoingBytes; + + await update(inc); +} -- cgit v1.2.3-freya From fc31e44fd2d70ae8f4114009a26f00fc5db01fca Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 15 Sep 2018 05:42:14 +0900 Subject: Fix bug --- src/server/api/endpoints/aggregation/hashtags.ts | 2 +- src/server/api/endpoints/hashtags/trend.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/aggregation/hashtags.ts b/src/server/api/endpoints/aggregation/hashtags.ts index c5aacd89cd..ffeafb2538 100644 --- a/src/server/api/endpoints/aggregation/hashtags.ts +++ b/src/server/api/endpoints/aggregation/hashtags.ts @@ -3,7 +3,7 @@ import Meta from '../../../../models/meta'; export default () => new Promise(async (res, rej) => { const meta = await Meta.findOne({}); - const hidedTags = (meta.hidedTags || []).map(t => t.toLowerCase()); + const hidedTags = meta ? (meta.hidedTags || []).map(t => t.toLowerCase()) : []; const span = 1000 * 60 * 60 * 24 * 7; // 1週間 diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts index bfa475619c..0ec6a4ffec 100644 --- a/src/server/api/endpoints/hashtags/trend.ts +++ b/src/server/api/endpoints/hashtags/trend.ts @@ -19,7 +19,7 @@ const max = 5; */ export default () => new Promise(async (res, rej) => { const meta = await Meta.findOne({}); - const hidedTags = (meta.hidedTags || []).map(t => t.toLowerCase()); + const hidedTags = meta ? (meta.hidedTags || []).map(t => t.toLowerCase()) : []; //#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計 const data = await Note.aggregate([{ -- cgit v1.2.3-freya