diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2018-10-08 15:37:24 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-10-08 15:37:24 +0900 |
| commit | 9c170c426be01773afb15a9868ff3c278e09409c (patch) | |
| tree | 0229bb52dd9197308d193f4e41bbc11d3dcb95a1 /src/server/api/endpoints/notes | |
| parent | New translations ja-JP.yml (Norwegian) (diff) | |
| parent | fix(package): update @types/mongodb to version 3.1.10 (#2849) (diff) | |
| download | misskey-9c170c426be01773afb15a9868ff3c278e09409c.tar.gz misskey-9c170c426be01773afb15a9868ff3c278e09409c.tar.bz2 misskey-9c170c426be01773afb15a9868ff3c278e09409c.zip | |
Merge branch 'develop' into l10n_develop
Diffstat (limited to 'src/server/api/endpoints/notes')
| -rw-r--r-- | src/server/api/endpoints/notes/conversation.ts | 4 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/create.ts | 40 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/delete.ts | 11 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/favorites/create.ts | 18 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/global-timeline.ts | 86 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/hybrid-timeline.ts | 30 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/local-timeline.ts | 113 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/mentions.ts | 71 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/polls/vote.ts | 5 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/reactions/create.ts | 4 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/replies.ts | 4 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/reposts.ts | 5 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/search.ts | 4 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/search_by_tag.ts | 250 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/timeline.ts | 27 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/trend.ts | 2 | ||||
| -rw-r--r-- | src/server/api/endpoints/notes/user-list-timeline.ts | 24 |
17 files changed, 421 insertions, 277 deletions
diff --git a/src/server/api/endpoints/notes/conversation.ts b/src/server/api/endpoints/notes/conversation.ts index 2782d14155..0c23f9e5fc 100644 --- a/src/server/api/endpoints/notes/conversation.ts +++ b/src/server/api/endpoints/notes/conversation.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; -import Note, { pack, INote } from '../../../../models/note'; +import Note, { packMany, INote } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; /** @@ -52,5 +52,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = } // Serialize - res(await Promise.all(conversation.map(note => pack(note, user)))); + res(await packMany(conversation, user)); }); diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 04f5f7562e..96745132a3 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,26 +130,16 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( } let files: IDriveFile[] = []; - if (ps.mediaIds !== undefined) { - // Fetch files - // forEach だと途中でエラーなどがあっても return できないので - // 敢えて for を使っています。 - for (const mediaId of ps.mediaIds) { - // Fetch file - // SELECT _id - const entity = await DriveFile.findOne({ - _id: mediaId, + const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; + if (fileIds != null) { + 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; @@ -155,7 +151,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 +172,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 +187,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/delete.ts b/src/server/api/endpoints/notes/delete.ts index 6d9826cf7b..2fe36897c0 100644 --- a/src/server/api/endpoints/notes/delete.ts +++ b/src/server/api/endpoints/notes/delete.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import deleteNote from '../../../../services/note/delete'; -import { ILocalUser } from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; export const meta = { desc: { @@ -21,15 +21,18 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = // Fetch note const note = await Note.findOne({ - _id: noteId, - userId: user._id + _id: noteId }); if (note === null) { return rej('note not found'); } - await deleteNote(user, note); + if (!user.isAdmin && !note.userId.equals(user._id)) { + return rej('access denied'); + } + + await deleteNote(await User.findOne({ _id: note.userId }), note); res(); }); diff --git a/src/server/api/endpoints/notes/favorites/create.ts b/src/server/api/endpoints/notes/favorites/create.ts index daf7780abc..9aefb701ae 100644 --- a/src/server/api/endpoints/notes/favorites/create.ts +++ b/src/server/api/endpoints/notes/favorites/create.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import Favorite from '../../../../../models/favorite'; import Note from '../../../../../models/note'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { desc: { @@ -11,17 +12,24 @@ export const meta = { requireCredential: true, - kind: 'favorite-write' + kind: 'favorite-write', + + params: { + noteId: $.type(ID).note({ + desc: { + 'ja-JP': '対象の投稿のID' + } + }) + } }; export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - // Get 'noteId' parameter - const [noteId, noteIdErr] = $.type(ID).get(params.noteId); - if (noteIdErr) return rej('invalid noteId param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Get favoritee const note = await Note.findOne({ - _id: noteId + _id: ps.noteId }); if (note === null) { diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index 8f7233e308..8362143bb2 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -1,42 +1,52 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; +import { countIf } from '../../../../prelude/array'; -/** - * 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 (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) { 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'; - // ミュートしているユーザーを取得 const mutedUserIds = user ? (await Mute.find({ muterId: user._id @@ -68,27 +78,29 @@ export default async (params: any, user: ILocalUser) => { }; } - if (mediaOnly) { - query.mediaIds = { $exists: true, $ne: [] }; + 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,10 +108,10 @@ export default async (params: any, user: ILocalUser) => { // Issue query const timeline = await Note .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); // Serialize - return await Promise.all(timeline.map(note => pack(note, user))); + return await packMany(timeline, user); }; diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 2dbb1190c1..14b4432b33 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -2,13 +2,12 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { getFriends } from '../../common/get-friends'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; +import { countIf } from '../../../../prelude/array'; export const meta = { - name: 'notes/hybrid-timeline', - desc: { 'ja-JP': 'ハイブリッドタイムラインを取得します。' }, @@ -66,23 +65,26 @@ export const meta = { } }), + withFiles: $.bool.optional.note({ + desc: { + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' + } + }), + mediaOnly: $.bool.optional.note({ desc: { - 'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します' + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } }), } }; -/** - * Get hybrid timeline of myself - */ 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 ([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'; } @@ -164,7 +166,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -180,7 +182,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -196,16 +198,16 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] }); } - if (ps.mediaOnly) { + if (ps.withFiles || ps.mediaOnly) { query.$and.push({ - mediaIds: { $exists: true, $ne: [] } + fileIds: { $exists: true, $ne: [] } }); } @@ -238,5 +240,5 @@ export default async (params: any, user: ILocalUser) => { }); // Serialize - return await Promise.all(timeline.map(note => pack(note, user))); + return await packMany(timeline, user); }; diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index bbcc6303ca..8ab07d8ea7 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -1,42 +1,65 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; +import { countIf } from '../../../../prelude/array'; -/** - * 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 を使ってください。)' + } + }), + + fileType: $.arr($.str).optional.note({ + desc: { + 'ja-JP': '指定された種類のファイルが添付された投稿のみを取得します' + } + }), + + excludeNsfw: $.bool.optional.note({ + default: false, + desc: { + 'ja-JP': 'true にすると、NSFW指定されたファイルを除外します(fileTypeが指定されている場合のみ有効)' + } + }), - // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); - if (sinceIdErr) throw 'invalid sinceId param'; + limit: $.num.optional.range(1, 100).note({ + default: 10 + }), - // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); - if (untilIdErr) throw 'invalid untilId param'; + sinceId: $.type(ID).optional.note({}), - // Get 'sinceDate' parameter - const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate); - if (sinceDateErr) throw 'invalid sinceDate param'; + untilId: $.type(ID).optional.note({}), - // Get 'untilDate' parameter - const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate); - if (untilDateErr) throw 'invalid untilDate param'; + sinceDate: $.num.optional.note({}), + + untilDate: $.num.optional.note({}), + } +}; + +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 (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) { 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'; - // ミュートしているユーザーを取得 const mutedUserIds = user ? (await Mute.find({ muterId: user._id @@ -69,27 +92,43 @@ export default async (params: any, user: ILocalUser) => { }; } - if (mediaOnly) { - query.mediaIds = { $exists: true, $ne: [] }; + const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; + + if (withFiles) { + query.fileIds = { $exists: true, $ne: [] }; + } + + if (ps.fileType) { + query.fileIds = { $exists: true, $ne: [] }; + + query['_files.contentType'] = { + $in: ps.fileType + }; + + if (ps.excludeNsfw) { + query['_files.metadata.isSensitive'] = { + $ne: true + }; + } } - 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,10 +136,10 @@ export default async (params: any, user: ILocalUser) => { // Issue query const timeline = await Note .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); // Serialize - return await Promise.all(timeline.map(note => pack(note, user))); + return await packMany(timeline, user); }; diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index a7fb14d8a9..592a94263d 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -1,8 +1,10 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import { getFriendIds } from '../../common/get-friends'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; +import read from '../../../../services/note/read'; export const meta = { desc: { @@ -10,42 +12,55 @@ export const meta = { 'en-US': 'Get mentions of myself.' }, - requireCredential: true -}; + requireCredential: true, -export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - // Get 'following' parameter - const [following = false, followingError] = - $.bool.optional.get(params.following); - if (followingError) return rej('invalid following param'); + params: { + following: $.bool.optional.note({ + default: false + }), + + limit: $.num.optional.range(1, 100).note({ + default: 10 + }), - // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); - if (limitErr) return rej('invalid limit param'); + sinceId: $.type(ID).optional.note({ + }), - // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); - if (sinceIdErr) return rej('invalid sinceId param'); + untilId: $.type(ID).optional.note({ + }), + + visibility: $.str.optional.note({ + }), + } +}; - // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); - if (untilIdErr) return rej('invalid untilId param'); +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + 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'); } // Construct query const query = { - mentions: user._id + $or: [{ + mentions: user._id + }, { + visibleUserIds: user._id + }] } as any; const sort = { _id: -1 }; - if (following) { + if (ps.visibility) { + query.visibility = ps.visibility; + } + + if (ps.following) { const followingIds = await getFriendIds(user._id); query.userId = { @@ -53,26 +68,26 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = }; } - 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 }; } // Issue query const mentions = await Note .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); + mentions.forEach(note => read(user._id, note._id)); + // Serialize - res(await Promise.all(mentions.map(async mention => - await pack(mention, user) - ))); + res(await packMany(mentions, user)); }); diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts index ab80e7f5d0..3b78d62fd3 100644 --- a/src/server/api/endpoints/notes/polls/vote.ts +++ b/src/server/api/endpoints/notes/polls/vote.ts @@ -72,7 +72,10 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = $inc: inc }); - publishNoteStream(note._id, 'poll_voted'); + publishNoteStream(note._id, 'pollVoted', { + choice: choice, + userId: user._id.toHexString() + }); // Notify notify(note.userId, user._id, 'poll_vote', { 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) { diff --git a/src/server/api/endpoints/notes/replies.ts b/src/server/api/endpoints/notes/replies.ts index 44c80afc4a..b2f8f94f69 100644 --- a/src/server/api/endpoints/notes/replies.ts +++ b/src/server/api/endpoints/notes/replies.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; -import Note, { pack } from '../../../../models/note'; +import Note, { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; /** @@ -30,5 +30,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = const ids = (note._replyIds || []).slice(offset, offset + limit); // Serialize - res(await Promise.all(ids.map(id => pack(id, user)))); + res(await packMany(ids, user)); }); diff --git a/src/server/api/endpoints/notes/reposts.ts b/src/server/api/endpoints/notes/reposts.ts index 05e68302ba..2c6e1a499f 100644 --- a/src/server/api/endpoints/notes/reposts.ts +++ b/src/server/api/endpoints/notes/reposts.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; -import Note, { pack } from '../../../../models/note'; +import Note, { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; /** @@ -62,6 +62,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = }); // Serialize - res(await Promise.all(renotes.map(async note => - await pack(note, user)))); + res(await packMany(renotes, user)); }); diff --git a/src/server/api/endpoints/notes/search.ts b/src/server/api/endpoints/notes/search.ts index 9124899ad8..2755a70483 100644 --- a/src/server/api/endpoints/notes/search.ts +++ b/src/server/api/endpoints/notes/search.ts @@ -2,7 +2,7 @@ import $ from 'cafy'; import * as mongo from 'mongodb'; import Note from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import es from '../../../../db/elasticsearch'; export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { @@ -60,6 +60,6 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } }); - res(await Promise.all(notes.map(note => pack(note, me)))); + res(await packMany(notes, me)); }); }); diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts index e092275fe8..d380f27f9c 100644 --- a/src/server/api/endpoints/notes/search_by_tag.ts +++ b/src/server/api/endpoints/notes/search_by_tag.ts @@ -3,120 +3,171 @@ import Note from '../../../../models/note'; import User, { ILocalUser } from '../../../../models/user'; import Mute from '../../../../models/mute'; import { getFriendIds } from '../../common/get-friends'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; +import getParams from '../../get-params'; +import { erase } from '../../../../prelude/array'; -/** - * 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': '指定されたタグが付けられた投稿を取得します。' + }, - // Get 'includeUserIds' parameter - const [includeUserIds = [], includeUserIdsErr] = $.arr($.type(ID)).optional.get(params.includeUserIds); - if (includeUserIdsErr) return rej('invalid includeUserIds param'); + params: { + tag: $.str.optional.note({ + desc: { + 'ja-JP': 'タグ' + } + }), - // Get 'excludeUserIds' parameter - const [excludeUserIds = [], excludeUserIdsErr] = $.arr($.type(ID)).optional.get(params.excludeUserIds); - if (excludeUserIdsErr) return rej('invalid excludeUserIds param'); + query: $.arr($.arr($.str)).optional.note({ + desc: { + 'ja-JP': 'クエリ' + } + }), - // Get 'includeUserUsernames' parameter - const [includeUserUsernames = [], includeUserUsernamesErr] = $.arr($.str).optional.get(params.includeUserUsernames); - if (includeUserUsernamesErr) return rej('invalid includeUserUsernames param'); + includeUserIds: $.arr($.type(ID)).optional.note({ + default: [] + }), - // Get 'excludeUserUsernames' parameter - const [excludeUserUsernames = [], excludeUserUsernamesErr] = $.arr($.str).optional.get(params.excludeUserUsernames); - if (excludeUserUsernamesErr) return rej('invalid excludeUserUsernames param'); + excludeUserIds: $.arr($.type(ID)).optional.note({ + default: [] + }), - // Get 'following' parameter - const [following = null, followingErr] = $.bool.optional.nullable.get(params.following); - if (followingErr) return rej('invalid following param'); + includeUserUsernames: $.arr($.str).optional.note({ + default: [] + }), - // Get 'mute' parameter - const [mute = 'mute_all', muteErr] = $.str.optional.get(params.mute); - if (muteErr) return rej('invalid mute param'); + excludeUserUsernames: $.arr($.str).optional.note({ + default: [] + }), - // Get 'reply' parameter - const [reply = null, replyErr] = $.bool.optional.nullable.get(params.reply); - if (replyErr) return rej('invalid reply param'); + following: $.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'); + mute: $.str.optional.note({ + default: 'mute_all' + }), - // Get 'media' parameter - const [media = null, mediaErr] = $.bool.optional.nullable.get(params.media); - if (mediaErr) return rej('invalid media param'); + reply: $.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'; + renote: $.bool.optional.nullable.note({ + default: null, - // Get 'untilDate' parameter - const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate); - if (untilDateErr) throw 'invalid untilDate param'; + desc: { + 'ja-JP': 'Renoteに限定するか否か' + } + }), - // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); - if (offsetErr) return rej('invalid offset param'); + withFiles: $.bool.optional.note({ + desc: { + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' + } + }), - // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional.range(1, 30).get(params.limit); - if (limitErr) return rej('invalid limit param'); + media: $.bool.optional.nullable.note({ + default: null, + + desc: { + 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' + } + }), + + poll: $.bool.optional.nullable.note({ + default: null, + + desc: { + 'ja-JP': 'アンケートが添付された投稿に限定するか否か' + } + }), - if (includeUserUsernames != null) { - const ids = (await Promise.all(includeUserUsernames.map(async (username) => { + untilId: $.type(ID).optional.note({ + desc: { + 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' + } + }), + + sinceDate: $.num.optional.note({ + }), + + untilDate: $.num.optional.note({ + }), + + offset: $.num.optional.min(0).note({ + default: 0 + }), + + 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 (ps.includeUserUsernames != null) { + const ids = erase(null, 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 = erase(null, 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() - }] + const q: any = { + $and: [ps.tag ? { + tagsLower: ps.tag.toLowerCase() + } : { + $or: ps.query.map(tags => ({ + $and: tags.map(t => ({ + tagsLower: t.toLowerCase() + })) + })) + }], + deletedAt: { $exists: false } }; 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 +182,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 +253,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 +274,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,29 +295,16 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } } - if (media != null) { - if (media) { - push({ - mediaIds: { - $exists: true, - $ne: null - } - }); - } else { - push({ - $or: [{ - mediaIds: { - $exists: false - } - }, { - mediaIds: null - }] - }); - } + const withFiles = ps.withFiles != null ? ps.withFiles : ps.media; + + if (withFiles) { + push({ + fileIds: { $exists: true, $ne: [] } + }); } - if (poll != null) { - if (poll) { + if (ps.poll != null) { + if (ps.poll) { push({ poll: { $exists: true, @@ -286,24 +324,32 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } } - if (sinceDate) { + if (ps.untilId) { + push({ + _id: { + $lt: ps.untilId + } + }); + } + + 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) } }); } if (q.$and.length == 0) { - q = {}; + delete q.$and; } // Search notes @@ -312,10 +358,10 @@ 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 - res(await Promise.all(notes.map(note => pack(note, me)))); + res(await packMany(notes, me)); }); diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 099bf2010b..44a504eb18 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -2,9 +2,10 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { getFriends } from '../../common/get-friends'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; +import { countIf } from '../../../../prelude/array'; export const meta = { desc: { @@ -67,9 +68,15 @@ export const meta = { } }), + withFiles: $.bool.optional.note({ + desc: { + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' + } + }), + mediaOnly: $.bool.optional.note({ desc: { - 'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します' + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } }), } @@ -80,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'; } @@ -154,7 +161,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -170,7 +177,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -186,16 +193,18 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] }); } - if (ps.mediaOnly) { + const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; + + if (withFiles) { query.$and.push({ - mediaIds: { $exists: true, $ne: [] } + fileIds: { $exists: true, $ne: [] } }); } @@ -228,5 +237,5 @@ export default async (params: any, user: ILocalUser) => { }); // Serialize - return await Promise.all(timeline.map(note => pack(note, user))); + return await packMany(timeline, user); }; 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..6758b4eb73 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import UserList from '../../../../models/user-list'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; @@ -73,9 +73,15 @@ export const meta = { } }), + withFiles: $.bool.optional.note({ + desc: { + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' + } + }), + mediaOnly: $.bool.optional.note({ desc: { - 'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します' + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } }), } @@ -160,7 +166,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -176,7 +182,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -192,16 +198,18 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] }); } - if (ps.mediaOnly) { + const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; + + if (withFiles) { query.$and.push({ - mediaIds: { $exists: true, $ne: [] } + fileIds: { $exists: true, $ne: [] } }); } @@ -234,5 +242,5 @@ export default async (params: any, user: ILocalUser) => { }); // Serialize - return await Promise.all(timeline.map(note => pack(note, user))); + return await packMany(timeline, user); }; |