diff options
Diffstat (limited to 'src/server/api/endpoints/notes')
26 files changed, 455 insertions, 1289 deletions
diff --git a/src/server/api/endpoints/notes/children.ts b/src/server/api/endpoints/notes/children.ts index 3738459b71..72f2c39d6a 100644 --- a/src/server/api/endpoints/notes/children.ts +++ b/src/server/api/endpoints/notes/children.ts @@ -1,9 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note, { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import { getFriends } from '../../common/get-friends'; -import { getHideUserIds } from '../../common/get-hide-users'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { Brackets } from 'typeorm'; +import { Notes } from '../../../../models'; export const meta = { desc: { @@ -18,7 +20,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' @@ -32,12 +33,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, }, @@ -50,83 +49,24 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const [followings, hideUserIds] = await Promise.all([ - // フォローを取得 - // Fetch following - user ? getFriends(user._id) : [], + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere(new Brackets(qb => { qb + .where(`note.replyId = :noteId`, { noteId: ps.noteId }) + .orWhere(new Brackets(qb => { qb + .where(`note.renoteId = :noteId`, { noteId: ps.noteId }) + .andWhere(new Brackets(qb => { qb + .where(`note.text IS NOT NULL`) + .orWhere(`note.fileIds != '{}'`) + .orWhere(`note.hasPoll = TRUE`); + })); + })); + })) + .leftJoinAndSelect('note.user', 'user'); - // 隠すユーザーを取得 - getHideUserIds(user) - ]); + if (user) generateVisibilityQuery(query, user); + if (user) generateMuteQuery(query, user); - const visibleQuery = user == null ? [{ - visibility: { $in: [ 'public', 'home' ] } - }] : [{ - visibility: { $in: [ 'public', 'home' ] } - }, { - // myself (for followers/specified/private) - userId: user._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ user._id ] } - }, { - visibility: 'followers', - $or: [{ - // フォロワーの投稿 - userId: { $in: followings.map(f => f.id) }, - }, { - // 自分の投稿へのリプライ - '_reply.userId': user._id, - }, { - // 自分へのメンションが含まれている - mentions: { $in: [ user._id ] } - }] - }]; + const notes = await query.take(ps.limit).getMany(); - const q = { - $and: [{ - $or: [{ - replyId: ps.noteId, - }, { - renoteId: ps.noteId, - $or: [{ - text: { $ne: null } - }, { - fileIds: { $ne: [] } - }, { - poll: { $ne: null } - }] - }] - }, { - $or: visibleQuery - }] - } as any; - - if (hideUserIds && hideUserIds.length > 0) { - q['userId'] = { - $nin: hideUserIds - }; - } - - const sort = { - _id: -1 - }; - - if (ps.sinceId) { - sort._id = 1; - q._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - q._id = { - $lt: ps.untilId - }; - } - - const notes = await Note.find(q, { - limit: ps.limit, - sort: sort - }); - - return await packMany(notes, user); + return await Notes.packMany(notes, user); }); diff --git a/src/server/api/endpoints/notes/conversation.ts b/src/server/api/endpoints/notes/conversation.ts index 702d8dc430..6defd79042 100644 --- a/src/server/api/endpoints/notes/conversation.ts +++ b/src/server/api/endpoints/notes/conversation.ts @@ -1,9 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note, { packMany, INote } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { ApiError } from '../../error'; import { getNote } from '../../common/getters'; +import { Note } from '../../../../models/entities/note'; +import { Notes } from '../../../../models'; export const meta = { desc: { @@ -18,7 +19,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' @@ -58,12 +58,12 @@ export default define(meta, async (ps, user) => { throw e; }); - const conversation: INote[] = []; + const conversation: Note[] = []; let i = 0; async function get(id: any) { i++; - const p = await Note.findOne({ _id: id }); + const p = await Notes.findOne(id); if (i > ps.offset) { conversation.push(p); @@ -82,5 +82,5 @@ export default define(meta, async (ps, user) => { await get(note.replyId); } - return await packMany(conversation, user); + return await Notes.packMany(conversation, user); }); diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 8cc5e4b815..138f05fb3b 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -1,14 +1,15 @@ import $ from 'cafy'; -import ID, { transform, transformMany } from '../../../../misc/cafy-id'; import * as ms from 'ms'; import { length } from 'stringz'; -import Note, { INote, isValidCw, pack } from '../../../../models/note'; -import User, { IUser } from '../../../../models/user'; -import DriveFile, { IDriveFile } from '../../../../models/drive-file'; import create from '../../../../services/note/create'; import define from '../../define'; import fetchMeta from '../../../../misc/fetch-meta'; import { ApiError } from '../../error'; +import { ID } from '../../../../misc/cafy-id'; +import { User } from '../../../../models/entities/user'; +import { Users, DriveFiles, Notes } from '../../../../models'; +import { DriveFile } from '../../../../models/entities/drive-file'; +import { Note } from '../../../../models/entities/note'; let maxNoteTextLength = 1000; @@ -34,7 +35,7 @@ export const meta = { max: 300 }, - kind: 'note-write', + kind: 'write:notes', params: { visibility: { @@ -47,7 +48,6 @@ export const meta = { visibleUserIds: { validator: $.optional.arr($.type(ID)).unique().min(0), - transform: transformMany, desc: { 'ja-JP': '(投稿の公開範囲が specified の場合)投稿を閲覧できるユーザー' } @@ -64,7 +64,7 @@ export const meta = { }, cw: { - validator: $.optional.nullable.str.pipe(isValidCw), + validator: $.optional.nullable.str.pipe(Notes.validateCw), desc: { 'ja-JP': 'コンテンツの警告。このパラメータを指定すると設定したテキストで投稿のコンテンツを隠す事が出来ます。' } @@ -129,7 +129,6 @@ export const meta = { fileIds: { validator: $.optional.arr($.type(ID)).unique().range(1, 4), - transform: transformMany, desc: { 'ja-JP': '添付するファイル' } @@ -137,7 +136,6 @@ export const meta = { mediaIds: { validator: $.optional.arr($.type(ID)).unique().range(1, 4), - transform: transformMany, deprecated: true, desc: { 'ja-JP': '添付するファイル (このパラメータは廃止予定です。代わりに fileIds を使ってください。)' @@ -146,7 +144,6 @@ export const meta = { replyId: { validator: $.optional.type(ID), - transform: transform, desc: { 'ja-JP': '返信対象' } @@ -154,7 +151,6 @@ export const meta = { renoteId: { validator: $.optional.type(ID), - transform: transform, desc: { 'ja-JP': 'Renote対象' } @@ -227,32 +223,28 @@ export const meta = { }; export default define(meta, async (ps, user, app) => { - let visibleUsers: IUser[] = []; + let visibleUsers: User[] = []; if (ps.visibleUserIds) { - visibleUsers = await Promise.all(ps.visibleUserIds.map(id => User.findOne({ - _id: id - }))); + visibleUsers = await Promise.all(ps.visibleUserIds.map(id => Users.findOne(id))); } - let files: IDriveFile[] = []; + let files: DriveFile[] = []; 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 + return DriveFiles.findOne({ + id: fileId, + userId: user.id }); })); files = files.filter(file => file != null); } - let renote: INote = null; + let renote: Note = null; if (ps.renoteId != null) { // Fetch renote to note - renote = await Note.findOne({ - _id: ps.renoteId - }); + renote = await Notes.findOne(ps.renoteId); if (renote == null) { throw new ApiError(meta.errors.noSuchRenoteTarget); @@ -261,14 +253,12 @@ export default define(meta, async (ps, user, app) => { } } - let reply: INote = null; + let reply: Note = null; if (ps.replyId != null) { // Fetch reply - reply = await Note.findOne({ - _id: ps.replyId - }); + reply = await Notes.findOne(ps.replyId); - if (reply === null) { + if (reply == null) { throw new ApiError(meta.errors.noSuchReplyTarget); } @@ -279,12 +269,6 @@ export default define(meta, async (ps, user, app) => { } if (ps.poll) { - (ps.poll as any).choices = (ps.poll as any).choices.map((choice: string, i: number) => ({ - id: i, // IDを付与 - text: choice.trim(), - votes: 0 - })); - if (typeof ps.poll.expiresAt === 'number') { if (ps.poll.expiresAt < Date.now()) throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); @@ -298,11 +282,6 @@ export default define(meta, async (ps, user, app) => { throw new ApiError(meta.errors.contentRequired); } - // 後方互換性のため - if (ps.visibility == 'private') { - ps.visibility = 'specified'; - } - // 投稿を作成 const note = await create(user, { createdAt: new Date(), @@ -311,7 +290,7 @@ export default define(meta, async (ps, user, app) => { choices: ps.poll.choices, multiple: ps.poll.multiple || false, expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null - } : undefined, + } : null, text: ps.text, reply, renote, @@ -321,13 +300,13 @@ export default define(meta, async (ps, user, app) => { localOnly: ps.localOnly, visibility: ps.visibility, visibleUsers, - apMentions: ps.noExtractMentions ? [] : undefined, - apHashtags: ps.noExtractHashtags ? [] : undefined, - apEmojis: ps.noExtractEmojis ? [] : undefined, + apMentions: ps.noExtractMentions ? [] : null, + apHashtags: ps.noExtractHashtags ? [] : null, + apEmojis: ps.noExtractEmojis ? [] : null, geo: ps.geo }); return { - createdNote: await pack(note, user) + createdNote: await Notes.pack(note, user) }; }); diff --git a/src/server/api/endpoints/notes/delete.ts b/src/server/api/endpoints/notes/delete.ts index 399f9288d6..dbaf91bca3 100644 --- a/src/server/api/endpoints/notes/delete.ts +++ b/src/server/api/endpoints/notes/delete.ts @@ -1,11 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import deleteNote from '../../../../services/note/delete'; -import User from '../../../../models/user'; import define from '../../define'; import * as ms from 'ms'; import { getNote } from '../../common/getters'; import { ApiError } from '../../error'; +import { Users } from '../../../../models'; export const meta = { stability: 'stable', @@ -19,7 +19,7 @@ export const meta = { requireCredential: true, - kind: 'note-write', + kind: 'write:notes', limit: { duration: ms('1hour'), @@ -30,7 +30,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' @@ -59,9 +58,10 @@ export default define(meta, async (ps, user) => { throw e; }); - if (!user.isAdmin && !user.isModerator && !note.userId.equals(user._id)) { + if (!user.isAdmin && !user.isModerator && (note.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } - await deleteNote(await User.findOne({ _id: note.userId }), note); + // この操作を行うのが投稿者とは限らない(例えばモデレーター)ため + await deleteNote(await Users.findOne(note.userId), note); }); diff --git a/src/server/api/endpoints/notes/favorites/create.ts b/src/server/api/endpoints/notes/favorites/create.ts index 9cde1a7dcf..7e04637758 100644 --- a/src/server/api/endpoints/notes/favorites/create.ts +++ b/src/server/api/endpoints/notes/favorites/create.ts @@ -1,9 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import Favorite from '../../../../../models/favorite'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getNote } from '../../../common/getters'; +import { NoteFavorites } from '../../../../../models'; +import { genId } from '../../../../../misc/gen-id'; export const meta = { stability: 'stable', @@ -22,7 +23,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' @@ -53,21 +53,20 @@ export default define(meta, async (ps, user) => { }); // if already favorited - const exist = await Favorite.findOne({ - noteId: note._id, - userId: user._id + const exist = await NoteFavorites.findOne({ + noteId: note.id, + userId: user.id }); - if (exist !== null) { + if (exist != null) { throw new ApiError(meta.errors.alreadyFavorited); } // Create favorite - await Favorite.insert({ + await NoteFavorites.save({ + id: genId(), createdAt: new Date(), - noteId: note._id, - userId: user._id + noteId: note.id, + userId: user.id }); - - return; }); diff --git a/src/server/api/endpoints/notes/favorites/delete.ts b/src/server/api/endpoints/notes/favorites/delete.ts index e2c787f3b5..a889c84d4d 100644 --- a/src/server/api/endpoints/notes/favorites/delete.ts +++ b/src/server/api/endpoints/notes/favorites/delete.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import Favorite from '../../../../../models/favorite'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getNote } from '../../../common/getters'; +import { NoteFavorites } from '../../../../../models'; export const meta = { stability: 'stable', @@ -22,7 +22,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' @@ -53,19 +52,15 @@ export default define(meta, async (ps, user) => { }); // if already favorited - const exist = await Favorite.findOne({ - noteId: note._id, - userId: user._id + const exist = await NoteFavorites.findOne({ + noteId: note.id, + userId: user.id }); - if (exist === null) { + if (exist == null) { throw new ApiError(meta.errors.notFavorited); } // Delete favorite - await Favorite.remove({ - _id: exist._id - }); - - return; + await NoteFavorites.delete(exist.id); }); diff --git a/src/server/api/endpoints/notes/featured.ts b/src/server/api/endpoints/notes/featured.ts index 3648b307d7..c44a5275bb 100644 --- a/src/server/api/endpoints/notes/featured.ts +++ b/src/server/api/endpoints/notes/featured.ts @@ -1,8 +1,7 @@ import $ from 'cafy'; -import Note from '../../../../models/note'; -import { packMany } from '../../../../models/note'; import define from '../../define'; -import { getHideUserIds } from '../../common/get-hide-users'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { Notes } from '../../../../models'; export const meta = { desc: { @@ -35,25 +34,14 @@ export const meta = { export default define(meta, async (ps, user) => { const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで - const hideUserIds = await getHideUserIds(user); + const query = Notes.createQueryBuilder('note') + .andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) }) + .andWhere(`note.visibility = 'public'`) + .leftJoinAndSelect('note.user', 'user'); - const notes = await Note.find({ - createdAt: { - $gt: new Date(Date.now() - day) - }, - deletedAt: null, - visibility: 'public', - '_user.host': null, - ...(hideUserIds && hideUserIds.length > 0 ? { userId: { $nin: hideUserIds } } : {}) - }, { - limit: ps.limit, - sort: { - score: -1 - }, - hint: { - score: -1 - } - }); + if (user) generateMuteQuery(query, user); + + const notes = await query.orderBy('note.score', 'DESC').take(ps.limit).getMany(); - return await packMany(notes, user); + return await Notes.packMany(notes, user); }); diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index 0eb761cdb6..7bf62f366b 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -1,11 +1,12 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import fetchMeta from '../../../../misc/fetch-meta'; -import { getHideUserIds } from '../../common/get-hide-users'; import { ApiError } from '../../error'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Notes } from '../../../../models'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { activeUsersChart } from '../../../../services/chart'; export const meta = { desc: { @@ -22,14 +23,6 @@ export const meta = { } }, - mediaOnly: { - validator: $.optional.bool, - deprecated: true, - desc: { - 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, - limit: { validator: $.optional.num.range(1, 100), default: 10 @@ -37,12 +30,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, sinceDate: { @@ -71,6 +62,7 @@ export const meta = { }; export default define(meta, async (ps, user) => { + // TODO どっかにキャッシュ const m = await fetchMeta(); if (m.disableGlobalTimeline) { if (user == null || (!user.isAdmin && !user.isModerator)) { @@ -78,68 +70,25 @@ export default define(meta, async (ps, user) => { } } - // 隠すユーザーを取得 - const hideUserIds = await getHideUserIds(user); - //#region Construct query - const sort = { - _id: -1 - }; - - const query = { - deletedAt: null, - - // public only - visibility: 'public', + const query = makePaginationQuery(Notes.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('note.visibility = \'public\'') + .andWhere('note.replyId IS NULL') + .leftJoinAndSelect('note.user', 'user'); - replyId: null - } as any; + if (user) generateMuteQuery(query, user); - if (hideUserIds && hideUserIds.length > 0) { - query.userId = { - $nin: hideUserIds - }; - - query['_reply.userId'] = { - $nin: hideUserIds - }; - - query['_renote.userId'] = { - $nin: hideUserIds - }; + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } + //#endregion - const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; + const timeline = await query.take(ps.limit).getMany(); - if (withFiles) { - query.fileIds = { $exists: true, $ne: [] }; + if (user) { + activeUsersChart.update(user); } - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } else if (ps.sinceDate) { - sort._id = 1; - query.createdAt = { - $gt: new Date(ps.sinceDate) - }; - } else if (ps.untilDate) { - query.createdAt = { - $lt: new Date(ps.untilDate) - }; - } - //#endregion - - const timeline = await Note.find(query, { - limit: ps.limit, - sort: sort - }); - - return await packMany(timeline, user); + return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index 57ef4c3e15..cd07341342 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -1,12 +1,14 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import fetchMeta from '../../../../misc/fetch-meta'; -import activeUsersChart from '../../../../services/chart/active-users'; -import { getHideUserIds } from '../../common/get-hide-users'; import { ApiError } from '../../error'; +import { Notes } from '../../../../models'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { activeUsersChart } from '../../../../services/chart'; +import { Brackets } from 'typeorm'; export const meta = { desc: { @@ -23,14 +25,6 @@ export const meta = { } }, - mediaOnly: { - validator: $.optional.bool, - deprecated: true, - desc: { - 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, - fileType: { validator: $.optional.arr($.str), desc: { @@ -53,12 +47,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, sinceDate: { @@ -87,6 +79,7 @@ export const meta = { }; export default define(meta, async (ps, user) => { + // TODO どっかにキャッシュ const m = await fetchMeta(); if (m.disableLocalTimeline) { if (user == null || (!user.isAdmin && !user.isModerator)) { @@ -94,90 +87,44 @@ export default define(meta, async (ps, user) => { } } - // 隠すユーザーを取得 - const hideUserIds = await getHideUserIds(user); - //#region Construct query - const sort = { - _id: -1 - }; - - const query = { - deletedAt: null, - - // public only - visibility: 'public', - - // リプライでない - //replyId: null, + const query = makePaginationQuery(Notes.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)') + .leftJoinAndSelect('note.user', 'user'); - // local - '_user.host': null - } as any; + if (user) generateVisibilityQuery(query, user); + if (user) generateMuteQuery(query, user); - if (hideUserIds && hideUserIds.length > 0) { - query.userId = { - $nin: hideUserIds - }; - - query['_reply.userId'] = { - $nin: hideUserIds - }; - - query['_renote.userId'] = { - $nin: hideUserIds - }; - } - - const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; - - if (withFiles) { - query.fileIds = { $exists: true, $ne: [] }; + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } if (ps.fileType) { - query.fileIds = { $exists: true, $ne: [] }; - - query['_files.contentType'] = { - $in: ps.fileType - }; + query.andWhere('note.fileIds != \'{}\''); + query.andWhere(new Brackets(qb => { + for (const type of ps.fileType) { + const i = ps.fileType.indexOf(type); + qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); + } + })); if (ps.excludeNsfw) { - query['_files.metadata.isSensitive'] = { + // v11 TODO + /* + query['_files.isSensitive'] = { $ne: true }; + */ } } - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } else if (ps.sinceDate) { - sort._id = 1; - query.createdAt = { - $gt: new Date(ps.sinceDate) - }; - } else if (ps.untilDate) { - query.createdAt = { - $lt: new Date(ps.untilDate) - }; - } //#endregion - const timeline = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const timeline = await query.take(ps.limit).getMany(); if (user) { activeUsersChart.update(user); } - return await packMany(timeline, user); + return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index 91333174ed..0bbe7d3327 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -1,11 +1,12 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { getFriendIds, getFriends } from '../../common/get-friends'; -import { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import read from '../../../../services/note/read'; -import { getHideUserIds } from '../../common/get-hide-users'; +import { Notes, Followings } from '../../../../models'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Brackets } from 'typeorm'; export const meta = { desc: { @@ -30,12 +31,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, visibility: { @@ -52,97 +51,34 @@ export const meta = { }; export default define(meta, async (ps, user) => { - // フォローを取得 - const followings = await getFriends(user._id); + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: user.id }); - const visibleQuery = [{ - visibility: { $in: [ 'public', 'home' ] } - }, { - // myself (for followers/specified/private) - userId: user._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ user._id ] } - }, { - visibility: 'followers', - $or: [{ - // フォロワーの投稿 - userId: { $in: followings.map(f => f.id) }, - }, { - // 自分の投稿へのリプライ - '_reply.userId': user._id, - }, { - // 自分へのメンションが含まれている - mentions: { $in: [ user._id ] } - }] - }]; + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere(new Brackets(qb => { qb + .where(`:meId = ANY(note.mentions)`, { meId: user.id }) + .orWhere(`:meId = ANY(note.visibleUserIds)`, { meId: user.id }); + })) + .leftJoinAndSelect('note.user', 'user'); - const query = { - $and: [{ - deletedAt: null, - }, { - $or: visibleQuery, - }], - - $or: [{ - mentions: user._id - }, { - visibleUserIds: user._id - }] - } as any; - - // 隠すユーザーを取得 - const hideUserIds = await getHideUserIds(user); - - if (hideUserIds && hideUserIds.length > 0) { - query.userId = { - $nin: hideUserIds - }; - - query['_reply.userId'] = { - $nin: hideUserIds - }; - - query['_renote.userId'] = { - $nin: hideUserIds - }; - } - - const sort = { - _id: -1 - }; + generateVisibilityQuery(query, user); + generateMuteQuery(query, user); if (ps.visibility) { - query.visibility = ps.visibility; + query.andWhere('note.visibility = :visibility', { visibility: ps.visibility }); } if (ps.following) { - const followingIds = await getFriendIds(user._id); - - query.userId = { - $in: followingIds - }; - } - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; + query.andWhere(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id }); + query.setParameters(followingQuery.getParameters()); } - const mentions = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const mentions = await query.take(ps.limit).getMany(); for (const note of mentions) { - read(user._id, note._id); + read(user.id, note.id); } - return await packMany(mentions, user); + return await Notes.packMany(mentions, user); }); diff --git a/src/server/api/endpoints/notes/polls/recommendation.ts b/src/server/api/endpoints/notes/polls/recommendation.ts index 9adabdf0e9..ff838d4f4f 100644 --- a/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/src/server/api/endpoints/notes/polls/recommendation.ts @@ -1,8 +1,7 @@ import $ from 'cafy'; -import Vote from '../../../../../models/poll-vote'; -import Note, { pack } from '../../../../../models/note'; import define from '../../../define'; -import { getHideUserIds } from '../../../common/get-hide-users'; +import { Polls, Mutings, Notes, PollVotes } from '../../../../../models'; +import { Brackets, In } from 'typeorm'; export const meta = { desc: { @@ -28,51 +27,46 @@ export const meta = { }; export default define(meta, async (ps, user) => { - // Get votes - const votes = await Vote.find({ - userId: user._id - }, { - fields: { - _id: false, - noteId: true - } - }); + const query = Polls.createQueryBuilder('poll') + .where('poll.userHost IS NULL') + .andWhere(`poll.userId != :meId`, { meId: user.id }) + .andWhere(`poll.noteVisibility = 'public'`) + .andWhere(new Brackets(qb => { qb + .where('poll.expiresAt IS NULL') + .orWhere('poll.expiresAt > :now', { now: new Date() }); + })); - const nin = votes && votes.length != 0 ? votes.map(v => v.noteId) : []; + //#region exclude arleady voted polls + const votedQuery = PollVotes.createQueryBuilder('vote') + .select('vote.noteId') + .where('vote.userId = :meId', { meId: user.id }); - // 隠すユーザーを取得 - const hideUserIds = await getHideUserIds(user); + query + .andWhere(`poll.noteId NOT IN (${ votedQuery.getQuery() })`); - const notes = await Note.find({ - '_user.host': null, - _id: { - $nin: nin - }, - userId: { - $ne: user._id, - $nin: hideUserIds - }, - visibility: 'public', - poll: { - $exists: true, - $ne: null - }, - $or: [{ - 'poll.expiresAt': null - }, { - 'poll.expiresAt': { - $gt: new Date() - } - }], - }, { - limit: ps.limit, - skip: ps.offset, - sort: { - _id: -1 - } + query.setParameters(votedQuery.getParameters()); + //#endregion + + //#region mute + const mutingQuery = Mutings.createQueryBuilder('muting') + .select('muting.muteeId') + .where('muting.muterId = :muterId', { muterId: user.id }); + + query + .andWhere(`poll.userId NOT IN (${ mutingQuery.getQuery() })`); + + query.setParameters(mutingQuery.getParameters()); + //#endregion + + const polls = await query.take(ps.limit).skip(ps.offset).getMany(); + + if (polls.length === 0) return []; + + const notes = await Notes.find({ + id: In(polls.map(poll => poll.noteId)) }); - return await Promise.all(notes.map(note => pack(note, user, { + return await Notes.packMany(notes, user, { detail: true - }))); + }); }); diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts index ed20e0221f..7d0ed6e4f9 100644 --- a/src/server/api/endpoints/notes/polls/vote.ts +++ b/src/server/api/endpoints/notes/polls/vote.ts @@ -1,19 +1,19 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import Vote from '../../../../../models/poll-vote'; -import Note from '../../../../../models/note'; -import Watching from '../../../../../models/note-watching'; +import { ID } from '../../../../../misc/cafy-id'; import watch from '../../../../../services/note/watch'; import { publishNoteStream } from '../../../../../services/stream'; -import notify from '../../../../../services/create-notification'; +import { createNotification } from '../../../../../services/create-notification'; import define from '../../../define'; -import User, { IRemoteUser } from '../../../../../models/user'; import { ApiError } from '../../../error'; import { getNote } from '../../../common/getters'; import { deliver } from '../../../../../queue'; import { renderActivity } from '../../../../../remote/activitypub/renderer'; import renderVote from '../../../../../remote/activitypub/renderer/vote'; import { deliverQuestionUpdate } from '../../../../../services/note/polls/update'; +import { PollVotes, NoteWatchings, Users, Polls } from '../../../../../models'; +import { Not } from 'typeorm'; +import { IRemoteUser } from '../../../../../models/entities/user'; +import { genId } from '../../../../../misc/gen-id'; export const meta = { desc: { @@ -30,7 +30,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' @@ -84,26 +83,28 @@ export default define(meta, async (ps, user) => { throw e; }); - if (note.poll == null) { + if (!note.hasPoll) { throw new ApiError(meta.errors.noPoll); } - if (note.poll.expiresAt && note.poll.expiresAt < createdAt) { + const poll = await Polls.findOne({ noteId: note.id }); + + if (poll.expiresAt && poll.expiresAt < createdAt) { throw new ApiError(meta.errors.alreadyExpired); } - if (!note.poll.choices.some(x => x.id == ps.choice)) { + if (poll.choices[ps.choice] == null) { throw new ApiError(meta.errors.invalidChoice); } // if already voted - const exist = await Vote.find({ - noteId: note._id, - userId: user._id + const exist = await PollVotes.find({ + noteId: note.id, + userId: user.id }); if (exist.length) { - if (note.poll.multiple) { + if (poll.multiple) { if (exist.some(x => x.choice == ps.choice)) throw new ApiError(meta.errors.alreadyVoted); } else { @@ -112,69 +113,54 @@ export default define(meta, async (ps, user) => { } // Create vote - const vote = await Vote.insert({ + const vote = await PollVotes.save({ + id: genId(), createdAt, - noteId: note._id, - userId: user._id, + noteId: note.id, + userId: user.id, choice: ps.choice }); - const inc: any = {}; - inc[`poll.choices.${note.poll.choices.findIndex(c => c.id == ps.choice)}.votes`] = 1; - // Increment votes count - await Note.update({ _id: note._id }, { - $inc: inc - }); + const index = ps.choice + 1; // In SQL, array index is 1 based + await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE id = '${poll.id}'`); - publishNoteStream(note._id, 'pollVoted', { + publishNoteStream(note.id, 'pollVoted', { choice: ps.choice, - userId: user._id.toHexString() + userId: user.id }); // Notify - notify(note.userId, user._id, 'poll_vote', { - noteId: note._id, + createNotification(note.userId, user.id, 'pollVote', { + noteId: note.id, choice: ps.choice }); // Fetch watchers - Watching - .find({ - noteId: note._id, - userId: { $ne: user._id }, - // 削除されたドキュメントは除く - deletedAt: { $exists: false } - }, { - fields: { - userId: true - } - }) - .then(watchers => { - for (const watcher of watchers) { - notify(watcher.userId, user._id, 'poll_vote', { - noteId: note._id, - choice: ps.choice - }); - } - }); + NoteWatchings.find({ + noteId: note.id, + userId: Not(user.id), + }).then(watchers => { + for (const watcher of watchers) { + createNotification(watcher.userId, user.id, 'pollVote', { + noteId: note.id, + choice: ps.choice + }); + } + }); // この投稿をWatchする - if (user.settings.autoWatch !== false) { - watch(user._id, note); + if (user.autoWatch !== false) { + watch(user.id, note); } // リモート投票の場合リプライ送信 - if (note._user.host != null) { - const pollOwner: IRemoteUser = await User.findOne({ - _id: note.userId - }); + if (note.userHost != null) { + const pollOwner: IRemoteUser = await Users.findOne(note.userId); - deliver(user, renderActivity(await renderVote(user, vote, note, pollOwner)), pollOwner.inbox); + deliver(user, renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox); } // リモートフォロワーにUpdate配信 - deliverQuestionUpdate(note._id); - - return; + deliverQuestionUpdate(note.id); }); diff --git a/src/server/api/endpoints/notes/reactions.ts b/src/server/api/endpoints/notes/reactions.ts index 7d977154f2..b1b5ca9d33 100644 --- a/src/server/api/endpoints/notes/reactions.ts +++ b/src/server/api/endpoints/notes/reactions.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import NoteReaction, { pack } from '../../../../models/note-reaction'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { getNote } from '../../common/getters'; import { ApiError } from '../../error'; +import { NoteReactions } from '../../../../models'; export const meta = { desc: { @@ -18,7 +18,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'The ID of the target note' @@ -37,12 +36,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, }, @@ -69,29 +66,17 @@ export default define(meta, async (ps, user) => { }); const query = { - noteId: note._id - } as any; - - const sort = { - _id: -1 + noteId: note.id }; - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } - - const reactions = await NoteReaction.find(query, { - limit: ps.limit, + const reactions = await NoteReactions.find({ + where: query, + take: ps.limit, skip: ps.offset, - sort: sort + order: { + id: -1 + } }); - return await Promise.all(reactions.map(reaction => pack(reaction, user))); + return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, user))); }); diff --git a/src/server/api/endpoints/notes/reactions/create.ts b/src/server/api/endpoints/notes/reactions/create.ts index 299ed30278..b6aa4c58f3 100644 --- a/src/server/api/endpoints/notes/reactions/create.ts +++ b/src/server/api/endpoints/notes/reactions/create.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import createReaction from '../../../../../services/note/reaction/create'; import define from '../../../define'; import { getNote } from '../../../common/getters'; @@ -17,12 +17,11 @@ export const meta = { requireCredential: true, - kind: 'reaction-write', + kind: 'write:reactions', params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿' } diff --git a/src/server/api/endpoints/notes/reactions/delete.ts b/src/server/api/endpoints/notes/reactions/delete.ts index 08442226c5..0bdea58027 100644 --- a/src/server/api/endpoints/notes/reactions/delete.ts +++ b/src/server/api/endpoints/notes/reactions/delete.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import * as ms from 'ms'; import deleteReaction from '../../../../../services/note/reaction/delete'; @@ -16,7 +16,7 @@ export const meta = { requireCredential: true, - kind: 'reaction-write', + kind: 'write:reactions', limit: { duration: ms('1hour'), @@ -27,7 +27,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' diff --git a/src/server/api/endpoints/notes/renotes.ts b/src/server/api/endpoints/notes/renotes.ts index 15dcf55dce..81b899836d 100644 --- a/src/server/api/endpoints/notes/renotes.ts +++ b/src/server/api/endpoints/notes/renotes.ts @@ -1,9 +1,12 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note, { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { getNote } from '../../common/getters'; import { ApiError } from '../../error'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Notes } from '../../../../models'; export const meta = { desc: { @@ -18,7 +21,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' @@ -32,12 +34,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, } }, @@ -63,29 +63,14 @@ export default define(meta, async (ps, user) => { throw e; }); - const sort = { - _id: -1 - }; + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere(`note.renoteId = :renoteId`, { renoteId: note.id }) + .leftJoinAndSelect('note.user', 'user'); - const query = { - renoteId: note._id - } as any; + if (user) generateVisibilityQuery(query, user); + if (user) generateMuteQuery(query, user); - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } - - const renotes = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const renotes = await query.take(ps.limit).getMany(); - return await packMany(renotes, user); + return await Notes.packMany(renotes, user); }); diff --git a/src/server/api/endpoints/notes/replies.ts b/src/server/api/endpoints/notes/replies.ts index c80fd73205..09b0f17164 100644 --- a/src/server/api/endpoints/notes/replies.ts +++ b/src/server/api/endpoints/notes/replies.ts @@ -1,9 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note, { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import { getFriends } from '../../common/get-friends'; -import { getHideUserIds } from '../../common/get-hide-users'; +import { Notes } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { generateMuteQuery } from '../../common/generate-mute-query'; export const meta = { desc: { @@ -18,22 +19,30 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' } }, + sinceId: { + validator: $.optional.type(ID), + desc: { + 'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します' + } + }, + + untilId: { + validator: $.optional.type(ID), + desc: { + 'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します' + } + }, + limit: { validator: $.optional.num.range(1, 100), default: 10 }, - - offset: { - validator: $.optional.num.min(0), - default: 0 - }, }, res: { @@ -45,54 +54,14 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const [followings, hideUserIds] = await Promise.all([ - // フォローを取得 - // Fetch following - user ? getFriends(user._id) : [], - - // 隠すユーザーを取得 - getHideUserIds(user) - ]); - - const visibleQuery = user == null ? [{ - visibility: { $in: [ 'public', 'home' ] } - }] : [{ - visibility: { $in: [ 'public', 'home' ] } - }, { - // myself (for followers/specified/private) - userId: user._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ user._id ] } - }, { - visibility: 'followers', - $or: [{ - // フォロワーの投稿 - userId: { $in: followings.map(f => f.id) }, - }, { - // 自分の投稿へのリプライ - '_reply.userId': user._id, - }, { - // 自分へのメンションが含まれている - mentions: { $in: [ user._id ] } - }] - }]; - - const q = { - replyId: ps.noteId, - $or: visibleQuery - } as any; + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere('note.replyId = :replyId', { replyId: ps.noteId }) + .leftJoinAndSelect('note.user', 'user'); - if (hideUserIds && hideUserIds.length > 0) { - q['userId'] = { - $nin: hideUserIds - }; - } + if (user) generateVisibilityQuery(query, user); + if (user) generateMuteQuery(query, user); - const notes = await Note.find(q, { - limit: ps.limit, - skip: ps.offset - }); + const timeline = await query.take(ps.limit).getMany(); - return await packMany(notes, user); + return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/notes/search-by-tag.ts b/src/server/api/endpoints/notes/search-by-tag.ts index b33c884049..48de88d36e 100644 --- a/src/server/api/endpoints/notes/search-by-tag.ts +++ b/src/server/api/endpoints/notes/search-by-tag.ts @@ -1,10 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { getFriendIds } from '../../common/get-friends'; -import { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import { getHideUserIds } from '../../common/get-hide-users'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Notes } from '../../../../models'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { Brackets } from 'typeorm'; export const meta = { desc: { @@ -28,16 +29,6 @@ export const meta = { } }, - following: { - validator: $.optional.nullable.bool, - default: null as any - }, - - mute: { - validator: $.optional.str, - default: 'mute_all' - }, - reply: { validator: $.optional.nullable.bool, default: null as any, @@ -61,44 +52,28 @@ export const meta = { } }, - media: { + poll: { validator: $.optional.nullable.bool, default: null as any, - deprecated: true, desc: { - 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' + 'ja-JP': 'アンケートが添付された投稿に限定するか否か' } }, - poll: { - validator: $.optional.nullable.bool, - default: null as any, + sinceId: { + validator: $.optional.type(ID), desc: { - 'ja-JP': 'アンケートが添付された投稿に限定するか否か' + 'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します' } }, untilId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します' } }, - sinceDate: { - validator: $.optional.num, - }, - - untilDate: { - validator: $.optional.num, - }, - - offset: { - validator: $.optional.num.min(0), - default: 0 - }, - limit: { validator: $.optional.num.range(1, 30), default: 10 @@ -114,226 +89,58 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const visibleQuery = me == null ? [{ - visibility: { $in: [ 'public', 'home' ] } - }] : [{ - visibility: { $in: [ 'public', 'home' ] } - }, { - // myself (for specified/private) - userId: me._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ me._id ] } - }]; - - 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 }, - $or: visibleQuery - }; - - const push = (x: any) => q.$and.push(x); + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .leftJoinAndSelect('note.user', 'user'); - if (ps.following != null && me != null) { - const ids = await getFriendIds(me._id, false); - push({ - userId: ps.following ? { - $in: ids - } : { - $nin: ids.concat(me._id) - } - }); - } + if (me) generateVisibilityQuery(query, me); + if (me) generateMuteQuery(query, me); - if (me != null) { - const hideUserIds = await getHideUserIds(me); - - switch (ps.mute) { - case 'mute_all': - push({ - userId: { - $nin: hideUserIds - }, - '_reply.userId': { - $nin: hideUserIds - }, - '_renote.userId': { - $nin: hideUserIds - } - }); - break; - case 'mute_related': - push({ - '_reply.userId': { - $nin: hideUserIds - }, - '_renote.userId': { - $nin: hideUserIds - } - }); - break; - case 'mute_direct': - push({ - userId: { - $nin: hideUserIds + if (ps.tag) { + query.andWhere(':tag = ANY(note.tags)', { tag: ps.tag }); + } else { + let i = 0; + query.andWhere(new Brackets(qb => { + for (const tags of ps.query) { + qb.orWhere(new Brackets(qb => { + for (const tag of tags) { + qb.andWhere(`:tag${i} = ANY(note.tags)`, { [`tag${i}`]: tag }); + i++; } - }); - break; - case 'direct_only': - push({ - userId: { - $in: hideUserIds - } - }); - break; - case 'related_only': - push({ - $or: [{ - '_reply.userId': { - $in: hideUserIds - } - }, { - '_renote.userId': { - $in: hideUserIds - } - }] - }); - break; - case 'all_only': - push({ - $or: [{ - userId: { - $in: hideUserIds - } - }, { - '_reply.userId': { - $in: hideUserIds - } - }, { - '_renote.userId': { - $in: hideUserIds - } - }] - }); - break; - } + })); + } + })); } if (ps.reply != null) { if (ps.reply) { - push({ - replyId: { - $exists: true, - $ne: null - } - }); + query.andWhere('note.replyId IS NOT NULL'); } else { - push({ - $or: [{ - replyId: { - $exists: false - } - }, { - replyId: null - }] - }); + query.andWhere('note.replyId IS NULL'); } } if (ps.renote != null) { if (ps.renote) { - push({ - renoteId: { - $exists: true, - $ne: null - } - }); + query.andWhere('note.renoteId IS NOT NULL'); } else { - push({ - $or: [{ - renoteId: { - $exists: false - } - }, { - renoteId: null - }] - }); + query.andWhere('note.renoteId IS NULL'); } } - const withFiles = ps.withFiles != null ? ps.withFiles : ps.media; - - if (withFiles) { - push({ - fileIds: { $exists: true, $ne: [] } - }); + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } if (ps.poll != null) { if (ps.poll) { - push({ - poll: { - $exists: true, - $ne: null - } - }); + query.andWhere('note.hasPoll = TRUE'); } else { - push({ - $or: [{ - poll: { - $exists: false - } - }, { - poll: null - }] - }); + query.andWhere('note.hasPoll = FALSE'); } } - if (ps.untilId) { - push({ - _id: { - $lt: ps.untilId - } - }); - } - - if (ps.sinceDate) { - push({ - createdAt: { - $gt: new Date(ps.sinceDate) - } - }); - } - - if (ps.untilDate) { - push({ - createdAt: { - $lt: new Date(ps.untilDate) - } - }); - } - - if (q.$and.length == 0) { - delete q.$and; - } - // Search notes - const notes = await Note.find(q, { - sort: { - _id: -1 - }, - limit: ps.limit, - skip: ps.offset - }); + const notes = await query.take(ps.limit).getMany(); - return await packMany(notes, me); + return await Notes.packMany(notes, me); }); diff --git a/src/server/api/endpoints/notes/search.ts b/src/server/api/endpoints/notes/search.ts index edc8a14560..cc88fb9380 100644 --- a/src/server/api/endpoints/notes/search.ts +++ b/src/server/api/endpoints/notes/search.ts @@ -1,10 +1,9 @@ import $ from 'cafy'; -import * as mongo from 'mongodb'; -import Note from '../../../../models/note'; -import { packMany } from '../../../../models/note'; import es from '../../../../db/elasticsearch'; import define from '../../define'; import { ApiError } from '../../error'; +import { Notes } from '../../../../models'; +import { In } from 'typeorm'; export const meta = { desc: { @@ -74,18 +73,19 @@ export default define(meta, async (ps, me) => { return []; } - const hits = response.hits.hits.map(hit => new mongo.ObjectID(hit._id)); + const hits = response.hits.hits.map((hit: any) => hit.id); + + if (hits.length === 0) return []; // Fetch found notes - const notes = await Note.find({ - _id: { - $in: hits - } - }, { - sort: { - _id: -1 + const notes = await Notes.find({ + where: { + id: In(hits) + }, + order: { + id: -1 } }); - return await packMany(notes, me); + return await Notes.packMany(notes, me); }); diff --git a/src/server/api/endpoints/notes/show.ts b/src/server/api/endpoints/notes/show.ts index 6d8dc73ff2..d41dc20c54 100644 --- a/src/server/api/endpoints/notes/show.ts +++ b/src/server/api/endpoints/notes/show.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import { pack } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { getNote } from '../../common/getters'; import { ApiError } from '../../error'; +import { Notes } from '../../../../models'; export const meta = { stability: 'stable', @@ -20,7 +20,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' @@ -47,7 +46,7 @@ export default define(meta, async (ps, user) => { throw e; }); - return await pack(note, user, { + return await Notes.pack(note, user, { detail: true }); }); diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/social-timeline.ts index 9695547f04..10e215d6c4 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/social-timeline.ts @@ -1,17 +1,18 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { getFriends } from '../../common/get-friends'; -import { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import fetchMeta from '../../../../misc/fetch-meta'; -import activeUsersChart from '../../../../services/chart/active-users'; -import { getHideUserIds } from '../../common/get-hide-users'; import { ApiError } from '../../error'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Followings, Notes } from '../../../../models'; +import { Brackets } from 'typeorm'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { activeUsersChart } from '../../../../services/chart'; export const meta = { desc: { - 'ja-JP': 'ハイブリッドタイムラインを取得します。' + 'ja-JP': 'ソーシャルタイムラインを取得します。' }, tags: ['notes'], @@ -27,17 +28,15 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します' } }, untilId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します' } }, @@ -85,14 +84,6 @@ export const meta = { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' } }, - - mediaOnly: { - validator: $.optional.bool, - deprecated: true, - desc: { - 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, }, res: { @@ -112,94 +103,30 @@ export const meta = { }; export default define(meta, async (ps, user) => { + // TODO どっかにキャッシュ const m = await fetchMeta(); if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) { throw new ApiError(meta.errors.stlDisabled); } - const [followings, hideUserIds] = await Promise.all([ - // フォローを取得 - // Fetch following - getFriends(user._id, true, false), - - // 隠すユーザーを取得 - getHideUserIds(user) - ]); - //#region Construct query - const sort = { - _id: -1 - }; - - const followQuery = followings.map(f => ({ - userId: f.id, + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: user.id }); - /*// リプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める) - $or: [{ - // リプライでない - replyId: null - }, { // または - // リプライだが返信先が投稿者自身の投稿 - $expr: { - $eq: ['$_reply.userId', '$userId'] - } - }, { // または - // リプライだが返信先が自分(フォロワー)の投稿 - '_reply.userId': user._id - }, { // または - // 自分(フォロワー)が送信したリプライ - userId: user._id - }]*/ - })); + const query = makePaginationQuery(Notes.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere(new Brackets(qb => { + qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id }) + .orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)'); + })) + .leftJoinAndSelect('note.user', 'user') + .setParameters(followingQuery.getParameters()); - const visibleQuery = user == null ? [{ - visibility: { $in: ['public', 'home'] } - }] : [{ - visibility: { $in: ['public', 'home', 'followers'] } - }, { - // myself (for specified/private) - userId: user._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ user._id ] } - }]; - - const query = { - $and: [{ - deletedAt: null, - - $or: [{ - $and: [{ - // フォローしている人の投稿 - $or: followQuery - }, { - // visible for me - $or: visibleQuery - }] - }, { - // public only - visibility: 'public', - - // リプライでない - //replyId: null, - - // local - '_user.host': null - }], - - // hide - userId: { - $nin: hideUserIds - }, - '_reply.userId': { - $nin: hideUserIds - }, - '_renote.userId': { - $nin: hideUserIds - }, - }] - } as any; + generateVisibilityQuery(query, user); + generateMuteQuery(query, user); + /* TODO // MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。 // つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。 // for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws @@ -207,7 +134,7 @@ export default define(meta, async (ps, user) => { if (ps.includeMyRenotes === false) { query.$and.push({ $or: [{ - userId: { $ne: user._id } + userId: { $ne: user.id } }, { renoteId: null }, { @@ -223,7 +150,7 @@ export default define(meta, async (ps, user) => { if (ps.includeRenotedMyNotes === false) { query.$and.push({ $or: [{ - '_renote.userId': { $ne: user._id } + '_renote.userId': { $ne: user.id } }, { renoteId: null }, { @@ -251,40 +178,18 @@ export default define(meta, async (ps, user) => { }] }); } + */ - if (ps.withFiles || ps.mediaOnly) { - query.$and.push({ - fileIds: { $exists: true, $ne: [] } - }); - } - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } else if (ps.sinceDate) { - sort._id = 1; - query.createdAt = { - $gt: new Date(ps.sinceDate) - }; - } else if (ps.untilDate) { - query.createdAt = { - $lt: new Date(ps.untilDate) - }; + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } //#endregion - const timeline = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const timeline = await query.take(ps.limit).getMany(); - activeUsersChart.update(user); + if (user) { + activeUsersChart.update(user); + } - return await packMany(timeline, user); + return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/notes/state.ts b/src/server/api/endpoints/notes/state.ts index 4944802849..df1d9d9fb0 100644 --- a/src/server/api/endpoints/notes/state.ts +++ b/src/server/api/endpoints/notes/state.ts @@ -1,8 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import Favorite from '../../../../models/favorite'; -import NoteWatching from '../../../../models/note-watching'; +import { NoteFavorites, NoteWatchings } from '../../../../models'; export const meta = { stability: 'stable', @@ -19,7 +18,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' @@ -30,17 +28,19 @@ export const meta = { export default define(meta, async (ps, user) => { const [favorite, watching] = await Promise.all([ - Favorite.count({ - userId: user._id, + NoteFavorites.count({ + where: { + userId: user.id, noteId: ps.noteId - }, { - limit: 1 + }, + take: 1 }), - NoteWatching.count({ - userId: user._id, + NoteWatchings.count({ + where: { + userId: user.id, noteId: ps.noteId - }, { - limit: 1 + }, + take: 1 }) ]); diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 6ff7690c74..e22db4d1b0 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -1,11 +1,12 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { getFriends } from '../../common/get-friends'; -import { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import activeUsersChart from '../../../../services/chart/active-users'; -import { getHideUserIds } from '../../common/get-hide-users'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Notes, Followings } from '../../../../models'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { activeUsersChart } from '../../../../services/chart'; +import { Brackets } from 'typeorm'; export const meta = { desc: { @@ -28,17 +29,15 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します' } }, untilId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します' } }, @@ -86,14 +85,6 @@ export const meta = { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' } }, - - mediaOnly: { - validator: $.optional.bool, - deprecated: true, - desc: { - 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, }, res: { @@ -105,78 +96,24 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const [followings, hideUserIds] = await Promise.all([ - // フォローを取得 - // Fetch following - getFriends(user._id), - - // 隠すユーザーを取得 - getHideUserIds(user) - ]); - //#region Construct query - const sort = { - _id: -1 - }; - - const followQuery = followings.map(f => ({ - userId: f.id, - - /*// リプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める) - $or: [{ - // リプライでない - replyId: null - }, { // または - // リプライだが返信先が投稿者自身の投稿 - $expr: { - $eq: ['$_reply.userId', '$userId'] - } - }, { // または - // リプライだが返信先が自分(フォロワー)の投稿 - '_reply.userId': user._id - }, { // または - // 自分(フォロワー)が送信したリプライ - userId: user._id - }]*/ - })); + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: user.id }); - const visibleQuery = user == null ? [{ - visibility: { $in: [ 'public', 'home' ] } - }] : [{ - visibility: { $in: [ 'public', 'home', 'followers' ] } - }, { - // myself (for specified/private) - userId: user._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ user._id ] } - }]; + const query = makePaginationQuery(Notes.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere(new Brackets(qb => { qb + .where(`note.userId IN (${ followingQuery.getQuery() })`) + .orWhere('note.userId = :meId', { meId: user.id }); + })) + .leftJoinAndSelect('note.user', 'user') + .setParameters(followingQuery.getParameters()); - const query = { - $and: [{ - deletedAt: null, - - $and: [{ - // フォローしている人の投稿 - $or: followQuery - }, { - // visible for me - $or: visibleQuery - }], - - // mute - userId: { - $nin: hideUserIds - }, - '_reply.userId': { - $nin: hideUserIds - }, - '_renote.userId': { - $nin: hideUserIds - }, - }] - } as any; + generateVisibilityQuery(query, user); + generateMuteQuery(query, user); + /* v11 TODO // MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。 // つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。 // for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws @@ -184,7 +121,7 @@ export default define(meta, async (ps, user) => { if (ps.includeMyRenotes === false) { query.$and.push({ $or: [{ - userId: { $ne: user._id } + userId: { $ne: user.id } }, { renoteId: null }, { @@ -200,7 +137,7 @@ export default define(meta, async (ps, user) => { if (ps.includeRenotedMyNotes === false) { query.$and.push({ $or: [{ - '_renote.userId': { $ne: user._id } + '_renote.userId': { $ne: user.id } }, { renoteId: null }, { @@ -227,43 +164,16 @@ export default define(meta, async (ps, user) => { poll: { $ne: null } }] }); - } - - const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; - - if (withFiles) { - query.$and.push({ - fileIds: { $exists: true, $ne: [] } - }); - } + }*/ - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } else if (ps.sinceDate) { - sort._id = 1; - query.createdAt = { - $gt: new Date(ps.sinceDate) - }; - } else if (ps.untilDate) { - query.createdAt = { - $lt: new Date(ps.untilDate) - }; + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } //#endregion - const timeline = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const timeline = await query.take(ps.limit).getMany(); activeUsersChart.update(user); - return await packMany(timeline, user); + return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index 17c24ab119..deda04acb4 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -1,12 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { packMany } from '../../../../models/note'; -import UserList from '../../../../models/user-list'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import { getFriends } from '../../common/get-friends'; -import { getHideUserIds } from '../../common/get-hide-users'; import { ApiError } from '../../error'; +import { UserLists, UserListJoinings, Notes } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { activeUsersChart } from '../../../../services/chart'; export const meta = { desc: { @@ -21,7 +20,6 @@ export const meta = { params: { listId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': 'リストのID' } @@ -37,17 +35,15 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します' } }, untilId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します' } }, @@ -95,14 +91,6 @@ export const meta = { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' } }, - - mediaOnly: { - validator: $.optional.bool, - deprecated: true, - desc: { - 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, }, res: { @@ -122,94 +110,28 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const [list, followings, hideUserIds] = await Promise.all([ - // リストを取得 - // Fetch the list - UserList.findOne({ - _id: ps.listId, - userId: user._id - }), - - // フォローを取得 - // Fetch following - getFriends(user._id, true, false), - - // 隠すユーザーを取得 - getHideUserIds(user) - ]); + const list = await UserLists.findOne({ + id: ps.listId, + userId: user.id + }); if (list == null) { throw new ApiError(meta.errors.noSuchList); } - if (list.userIds.length == 0) { - return []; - } - //#region Construct query - const sort = { - _id: -1 - }; - - const listQuery = list.userIds.map(u => ({ - userId: u, - - /*// リプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める) - $or: [{ - // リプライでない - replyId: null - }, { // または - // リプライだが返信先が投稿者自身の投稿 - $expr: { - $eq: ['$_reply.userId', '$userId'] - } - }, { // または - // リプライだが返信先が自分(フォロワー)の投稿 - '_reply.userId': user._id - }, { // または - // 自分(フォロワー)が送信したリプライ - userId: user._id - }]*/ - })); - - const visibleQuery = [{ - visibility: { $in: ['public', 'home'] } - }, { - // myself (for specified/private) - userId: user._id - }, { - // to me (for specified) - visibleUserIds: { $in: [user._id] } - }, { - visibility: 'followers', - userId: { $in: followings.map(f => f.id) } - }]; - - const query = { - $and: [{ - deletedAt: null, + const listQuery = UserListJoinings.createQueryBuilder('joining') + .select('joining.userId') + .where('joining.userListId = :userListId', { userListId: list.id }); - $and: [{ - // リストに入っている人のタイムラインへの投稿 - $or: listQuery - }, { - // visible for me - $or: visibleQuery - }], + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere(`note.userId IN (${ listQuery.getQuery() })`) + .leftJoinAndSelect('note.user', 'user') + .setParameters(listQuery.getParameters()); - // mute - userId: { - $nin: hideUserIds - }, - '_reply.userId': { - $nin: hideUserIds - }, - '_renote.userId': { - $nin: hideUserIds - }, - }] - } as any; + generateVisibilityQuery(query, user); + /* TODO // MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。 // つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。 // for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws @@ -217,7 +139,7 @@ export default define(meta, async (ps, user) => { if (ps.includeMyRenotes === false) { query.$and.push({ $or: [{ - userId: { $ne: user._id } + userId: { $ne: user.id } }, { renoteId: null }, { @@ -233,7 +155,7 @@ export default define(meta, async (ps, user) => { if (ps.includeRenotedMyNotes === false) { query.$and.push({ $or: [{ - '_renote.userId': { $ne: user._id } + '_renote.userId': { $ne: user.id } }, { renoteId: null }, { @@ -260,41 +182,16 @@ export default define(meta, async (ps, user) => { poll: { $ne: null } }] }); - } - - const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; - - if (withFiles) { - query.$and.push({ - fileIds: { $exists: true, $ne: [] } - }); - } + }*/ - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } else if (ps.sinceDate) { - sort._id = 1; - query.createdAt = { - $gt: new Date(ps.sinceDate) - }; - } else if (ps.untilDate) { - query.createdAt = { - $lt: new Date(ps.untilDate) - }; + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } //#endregion - const timeline = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const timeline = await query.take(ps.limit).getMany(); + + activeUsersChart.update(user); - return await packMany(timeline, user); + return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/notes/watching/create.ts b/src/server/api/endpoints/notes/watching/create.ts index 2b2de1bd3b..b4045fe93c 100644 --- a/src/server/api/endpoints/notes/watching/create.ts +++ b/src/server/api/endpoints/notes/watching/create.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import watch from '../../../../../services/note/watch'; import { getNote } from '../../../common/getters'; @@ -17,12 +17,11 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' @@ -45,5 +44,5 @@ export default define(meta, async (ps, user) => { throw e; }); - await watch(user._id, note); + await watch(user.id, note); }); diff --git a/src/server/api/endpoints/notes/watching/delete.ts b/src/server/api/endpoints/notes/watching/delete.ts index 512db793ea..a272ecc37d 100644 --- a/src/server/api/endpoints/notes/watching/delete.ts +++ b/src/server/api/endpoints/notes/watching/delete.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import unwatch from '../../../../../services/note/unwatch'; import { getNote } from '../../../common/getters'; @@ -17,12 +17,11 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' @@ -45,5 +44,5 @@ export default define(meta, async (ps, user) => { throw e; }); - await unwatch(user._id, note); + await unwatch(user.id, note); }); |