From f0a29721c9fb10f97faf386bc9d6b1b2fad97895 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Apr 2019 21:50:36 +0900 Subject: Use PostgreSQL instead of MongoDB (#4572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * Update note.ts * Update timeline.ts * Update core.ts * wip * Update generate-visibility-query.ts * wip * wip * wip * wip * wip * Update global-timeline.ts * wip * wip * wip * Update vote.ts * wip * wip * Update create.ts * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Update files.ts * wip * wip * Update CONTRIBUTING.md * wip * wip * wip * wip * wip * wip * wip * wip * Update read-notification.ts * wip * wip * wip * wip * wip * wip * wip * Update cancel.ts * wip * wip * wip * Update show.ts * wip * wip * Update gen-id.ts * Update create.ts * Update id.ts * wip * wip * wip * wip * wip * wip * wip * Docker: Update files about Docker (#4599) * Docker: Use cache if files used by `yarn install` was not updated This patch reduces the number of times to installing node_modules. For example, `yarn install` step will be skipped when only ".config/default.yml" is updated. * Docker: Migrate MongoDB to Postgresql Misskey uses Postgresql as a database instead of Mongodb since version 11. * Docker: Uncomment about data persistence This patch will save a lot of databases. * wip * wip * wip * Update activitypub.ts * wip * wip * wip * Update logs.ts * wip * Update drive-file.ts * Update register.ts * wip * wip * Update mentions.ts * wip * wip * wip * Update recommendation.ts * wip * Update index.ts * wip * Update recommendation.ts * Doc: Update docker.ja.md and docker.en.md (#1) (#4608) Update how to set up misskey. * wip * :v: * wip * Update note.ts * Update postgre.ts * wip * wip * wip * wip * Update add-file.ts * wip * wip * wip * Clean up * Update logs.ts * wip * :pizza: * wip * Ad notes * wip * Update api-visibility.ts * Update note.ts * Update add-file.ts * tests * tests * Update postgre.ts * Update utils.ts * wip * wip * Refactor * wip * Refactor * wip * wip * Update show-users.ts * Update update-instance.ts * wip * Update feed.ts * Update outbox.ts * Update outbox.ts * Update user.ts * wip * Update list.ts * Update update-hashtag.ts * wip * Update update-hashtag.ts * Refactor * Update update.ts * wip * wip * :v: * clean up * docs * Update push.ts * wip * Update api.ts * wip * :v: * Update make-pagination-query.ts * :v: * Delete hashtags.ts * Update instances.ts * Update instances.ts * Update create.ts * Update search.ts * Update reversi-game.ts * Update signup.ts * Update user.ts * id * Update example.yml * :art: * objectid * fix * reversi * reversi * Fix bug of chart engine * Add test of chart engine * Improve test * Better testing * Improve chart engine * Refactor * Add test of chart engine * Refactor * Add chart test * Fix bug * コミットし忘れ * Refactoring * :v: * Add tests * Add test * Extarct note tests * Refactor * 存在しないユーザーにメンションできなくなっていた問題を修正 * Fix bug * Update update-meta.ts * Fix bug * Update mention.vue * Fix bug * Update meta.ts * Update CONTRIBUTING.md * Fix bug * Fix bug * Fix bug * Clean up * Clean up * Update notification.ts * Clean up * Add mute tests * Add test * Refactor * Add test * Fix test * Refactor * Refactor * Add tests * Update utils.ts * Update utils.ts * Fix test * Update package.json * Update update.ts * Update manifest.ts * Fix bug * Fix bug * Add test * :art: * Update endpoint permissions * Updaye permisison * Update person.ts #4299 * データベースと同期しないように * Fix bug * Fix bug * Update reversi-game.ts * Use a feature of Node v11.7.0 to extract a public key (#4644) * wip * wip * :v: * Refactoring #1540 * test * test * test * test * test * test * test * Fix bug * Fix test * :sushi: * wip * #4471 * Add test for #4335 * Refactor * Fix test * Add tests * :clock4: * Fix bug * Add test * Add test * rename * Fix bug --- src/server/api/authenticate.ts | 24 +- src/server/api/call.ts | 6 +- src/server/api/common/generate-mute-query.ts | 36 +++ .../api/common/generate-native-user-token.ts | 2 +- src/server/api/common/generate-visibility-query.ts | 40 +++ src/server/api/common/get-friends.ts | 49 ---- src/server/api/common/get-hide-users.ts | 25 -- src/server/api/common/getters.ts | 42 +-- src/server/api/common/make-pagination-query.ts | 28 ++ src/server/api/common/read-messaging-message.ts | 76 ++---- src/server/api/common/read-notification.ts | 74 ++---- src/server/api/common/signin.ts | 2 +- src/server/api/define.ts | 8 +- .../api/endpoints/admin/abuse-user-reports.ts | 30 +-- src/server/api/endpoints/admin/drive/files.ts | 34 ++- src/server/api/endpoints/admin/drive/show-file.ts | 9 +- src/server/api/endpoints/admin/emoji/add.ts | 8 +- src/server/api/endpoints/admin/emoji/list.ts | 6 +- src/server/api/endpoints/admin/emoji/remove.ts | 12 +- src/server/api/endpoints/admin/emoji/update.ts | 24 +- .../admin/federation/remove-all-following.ts | 11 +- .../endpoints/admin/federation/update-instance.ts | 17 +- src/server/api/endpoints/admin/invite.ts | 6 +- src/server/api/endpoints/admin/logs.ts | 66 ++--- src/server/api/endpoints/admin/moderators/add.ts | 19 +- .../api/endpoints/admin/moderators/remove.ts | 19 +- .../endpoints/admin/remove-abuse-user-report.ts | 15 +- src/server/api/endpoints/admin/reset-password.ts | 17 +- src/server/api/endpoints/admin/show-user.ts | 9 +- src/server/api/endpoints/admin/show-users.ts | 74 +++--- src/server/api/endpoints/admin/silence-user.ts | 19 +- src/server/api/endpoints/admin/suspend-user.ts | 31 +-- src/server/api/endpoints/admin/unsilence-user.ts | 19 +- src/server/api/endpoints/admin/unsuspend-user.ts | 19 +- src/server/api/endpoints/admin/unverify-user.ts | 19 +- src/server/api/endpoints/admin/update-meta.ts | 58 +---- .../api/endpoints/admin/update-remote-user.ts | 13 +- src/server/api/endpoints/admin/verify-user.ts | 19 +- src/server/api/endpoints/aggregation/hashtags.ts | 72 ----- src/server/api/endpoints/ap/show.ts | 61 +++-- src/server/api/endpoints/app/create.ts | 10 +- src/server/api/endpoints/app/show.ts | 13 +- src/server/api/endpoints/auth/accept.ts | 30 +-- src/server/api/endpoints/auth/session/generate.ts | 11 +- src/server/api/endpoints/auth/session/show.ts | 6 +- src/server/api/endpoints/auth/session/userkey.ts | 34 +-- src/server/api/endpoints/blocking/create.ts | 25 +- src/server/api/endpoints/blocking/delete.ts | 22 +- src/server/api/endpoints/blocking/list.ts | 39 +-- src/server/api/endpoints/charts/active-users.ts | 10 +- src/server/api/endpoints/charts/drive.ts | 6 +- src/server/api/endpoints/charts/federation.ts | 10 +- src/server/api/endpoints/charts/hashtag.ts | 10 +- src/server/api/endpoints/charts/instance.ts | 10 +- src/server/api/endpoints/charts/network.ts | 10 +- src/server/api/endpoints/charts/notes.ts | 6 +- src/server/api/endpoints/charts/user/drive.ts | 9 +- src/server/api/endpoints/charts/user/following.ts | 11 +- src/server/api/endpoints/charts/user/notes.ts | 9 +- src/server/api/endpoints/charts/user/reactions.ts | 13 +- src/server/api/endpoints/charts/users.ts | 6 +- src/server/api/endpoints/drive.ts | 26 +- src/server/api/endpoints/drive/files.ts | 48 ++-- .../api/endpoints/drive/files/attached-notes.ts | 23 +- .../api/endpoints/drive/files/check-existence.ts | 13 +- src/server/api/endpoints/drive/files/create.ts | 11 +- src/server/api/endpoints/drive/files/delete.ts | 21 +- src/server/api/endpoints/drive/files/find.ts | 20 +- src/server/api/endpoints/drive/files/show.ts | 54 ++-- src/server/api/endpoints/drive/files/update.ts | 72 ++--- .../api/endpoints/drive/files/upload-from-url.ts | 9 +- src/server/api/endpoints/drive/folders.ts | 41 +-- src/server/api/endpoints/drive/folders/create.ts | 32 +-- src/server/api/endpoints/drive/folders/delete.ts | 29 +-- src/server/api/endpoints/drive/folders/find.ts | 22 +- src/server/api/endpoints/drive/folders/show.ts | 20 +- src/server/api/endpoints/drive/folders/update.ts | 57 ++-- src/server/api/endpoints/drive/stream.ts | 44 +--- src/server/api/endpoints/federation/instances.ts | 128 ++++----- .../api/endpoints/federation/show-instance.ts | 4 +- src/server/api/endpoints/following/create.ts | 20 +- src/server/api/endpoints/following/delete.ts | 20 +- .../api/endpoints/following/requests/accept.ts | 5 +- .../api/endpoints/following/requests/cancel.ts | 9 +- .../api/endpoints/following/requests/list.ts | 10 +- .../api/endpoints/following/requests/reject.ts | 5 +- src/server/api/endpoints/games/reversi/games.ts | 47 +--- .../api/endpoints/games/reversi/games/show.ts | 20 +- .../api/endpoints/games/reversi/games/surrender.ts | 29 +-- .../api/endpoints/games/reversi/invitations.ts | 12 +- src/server/api/endpoints/games/reversi/match.ts | 65 +++-- .../api/endpoints/games/reversi/match/cancel.ts | 8 +- src/server/api/endpoints/hashtags/list.ts | 67 +++-- src/server/api/endpoints/hashtags/search.ts | 23 +- src/server/api/endpoints/hashtags/trend.ts | 137 ++++------ src/server/api/endpoints/hashtags/users.ts | 55 ++-- src/server/api/endpoints/i.ts | 4 +- src/server/api/endpoints/i/2fa/done.ts | 12 +- src/server/api/endpoints/i/2fa/register.ts | 8 +- src/server/api/endpoints/i/2fa/unregister.ts | 10 +- src/server/api/endpoints/i/authorized-apps.ts | 25 +- src/server/api/endpoints/i/change-password.ts | 10 +- .../i/clear-follow-request-notification.ts | 23 -- src/server/api/endpoints/i/delete-account.ts | 29 +-- src/server/api/endpoints/i/export-blocking.ts | 2 - src/server/api/endpoints/i/export-following.ts | 2 - src/server/api/endpoints/i/export-mute.ts | 2 - src/server/api/endpoints/i/export-notes.ts | 2 - src/server/api/endpoints/i/export-user-lists.ts | 2 - src/server/api/endpoints/i/favorites.ts | 41 +-- src/server/api/endpoints/i/import-following.ts | 19 +- src/server/api/endpoints/i/import-user-lists.ts | 19 +- src/server/api/endpoints/i/notifications.ts | 81 ++---- src/server/api/endpoints/i/pin.ts | 9 +- .../api/endpoints/i/read-all-messaging-messages.ts | 25 +- .../api/endpoints/i/read-all-unread-notes.ts | 22 +- src/server/api/endpoints/i/regenerate-token.ts | 12 +- src/server/api/endpoints/i/signin-history.ts | 35 +-- src/server/api/endpoints/i/unpin.ts | 9 +- .../api/endpoints/i/update-client-setting.ts | 22 +- src/server/api/endpoints/i/update-email.ts | 24 +- src/server/api/endpoints/i/update-home.ts | 33 --- src/server/api/endpoints/i/update-mobile-home.ts | 32 --- src/server/api/endpoints/i/update-widget.ts | 88 ------- src/server/api/endpoints/i/update.ts | 138 +++------- src/server/api/endpoints/messaging/history.ts | 52 ++-- src/server/api/endpoints/messaging/messages.ts | 45 +--- .../api/endpoints/messaging/messages/create.ts | 56 ++-- .../api/endpoints/messaging/messages/delete.ts | 21 +- .../api/endpoints/messaging/messages/read.ts | 15 +- src/server/api/endpoints/meta.ts | 21 +- src/server/api/endpoints/mute/create.ts | 33 ++- src/server/api/endpoints/mute/delete.ts | 23 +- src/server/api/endpoints/mute/list.ts | 39 +-- src/server/api/endpoints/my/apps.ts | 19 +- src/server/api/endpoints/notes.ts | 58 ++--- src/server/api/endpoints/notes/children.ts | 112 ++------ src/server/api/endpoints/notes/conversation.ts | 12 +- src/server/api/endpoints/notes/create.ts | 67 ++--- src/server/api/endpoints/notes/delete.ts | 12 +- src/server/api/endpoints/notes/favorites/create.ts | 23 +- src/server/api/endpoints/notes/favorites/delete.ts | 19 +- src/server/api/endpoints/notes/featured.ts | 32 +-- src/server/api/endpoints/notes/global-timeline.ts | 89 ++----- src/server/api/endpoints/notes/hybrid-timeline.ts | 290 --------------------- src/server/api/endpoints/notes/local-timeline.ts | 111 +++----- src/server/api/endpoints/notes/mentions.ts | 112 ++------ .../api/endpoints/notes/polls/recommendation.ts | 82 +++--- src/server/api/endpoints/notes/polls/vote.ts | 98 +++---- src/server/api/endpoints/notes/reactions.ts | 35 +-- src/server/api/endpoints/notes/reactions/create.ts | 5 +- src/server/api/endpoints/notes/reactions/delete.ts | 5 +- src/server/api/endpoints/notes/renotes.ts | 39 +-- src/server/api/endpoints/notes/replies.ts | 83 ++---- src/server/api/endpoints/notes/search-by-tag.ts | 275 +++---------------- src/server/api/endpoints/notes/search.ts | 24 +- src/server/api/endpoints/notes/show.ts | 7 +- src/server/api/endpoints/notes/social-timeline.ts | 195 ++++++++++++++ src/server/api/endpoints/notes/state.ts | 24 +- src/server/api/endpoints/notes/timeline.ts | 156 +++-------- .../api/endpoints/notes/user-list-timeline.ts | 161 +++--------- src/server/api/endpoints/notes/watching/create.ts | 7 +- src/server/api/endpoints/notes/watching/delete.ts | 7 +- .../endpoints/notifications/mark-all-as-read.ts | 26 +- src/server/api/endpoints/stats.ts | 30 +-- src/server/api/endpoints/sw/register.ts | 13 +- src/server/api/endpoints/username/available.ts | 9 +- src/server/api/endpoints/users.ts | 84 +++--- src/server/api/endpoints/users/followers.ts | 98 ++----- src/server/api/endpoints/users/following.ts | 98 ++----- .../users/get-frequently-replied-users.ts | 52 ++-- src/server/api/endpoints/users/lists/create.ts | 18 +- src/server/api/endpoints/users/lists/delete.ts | 17 +- src/server/api/endpoints/users/lists/list.ts | 10 +- src/server/api/endpoints/users/lists/pull.ts | 23 +- src/server/api/endpoints/users/lists/push.ts | 21 +- src/server/api/endpoints/users/lists/show.ts | 15 +- src/server/api/endpoints/users/lists/update.ts | 23 +- src/server/api/endpoints/users/notes.ts | 130 +++------ src/server/api/endpoints/users/recommendation.ts | 99 ++----- src/server/api/endpoints/users/relation.ts | 7 +- src/server/api/endpoints/users/report-abuse.ts | 24 +- src/server/api/endpoints/users/search.ts | 40 ++- src/server/api/endpoints/users/show.ts | 29 +-- src/server/api/index.ts | 9 +- src/server/api/limiter.ts | 8 +- src/server/api/openapi/schemas.ts | 40 ++- src/server/api/private/signin.ts | 23 +- src/server/api/private/signup.ts | 99 ++++--- src/server/api/service/discord.ts | 86 +++--- src/server/api/service/github.ts | 55 ++-- src/server/api/service/twitter.ts | 58 +++-- src/server/api/stream/channel.ts | 8 + src/server/api/stream/channels/admin.ts | 2 +- src/server/api/stream/channels/drive.ts | 2 +- .../api/stream/channels/games/reversi-game.ts | 168 ++++++------ src/server/api/stream/channels/games/reversi.ts | 13 +- src/server/api/stream/channels/global-timeline.ts | 18 +- src/server/api/stream/channels/hashtag.ts | 54 ++-- src/server/api/stream/channels/home-timeline.ts | 45 ++-- src/server/api/stream/channels/hybrid-timeline.ts | 55 ---- src/server/api/stream/channels/index.ts | 4 +- src/server/api/stream/channels/local-timeline.ts | 45 ++-- src/server/api/stream/channels/main.ts | 9 +- src/server/api/stream/channels/messaging-index.ts | 2 +- src/server/api/stream/channels/messaging.ts | 4 +- src/server/api/stream/channels/social-timeline.ts | 64 +++++ src/server/api/stream/channels/user-list.ts | 72 ++++- src/server/api/stream/index.ts | 64 ++++- src/server/api/streaming.ts | 27 -- 210 files changed, 2811 insertions(+), 4917 deletions(-) create mode 100644 src/server/api/common/generate-mute-query.ts create mode 100644 src/server/api/common/generate-visibility-query.ts delete mode 100644 src/server/api/common/get-friends.ts delete mode 100644 src/server/api/common/get-hide-users.ts create mode 100644 src/server/api/common/make-pagination-query.ts delete mode 100644 src/server/api/endpoints/aggregation/hashtags.ts delete mode 100644 src/server/api/endpoints/i/clear-follow-request-notification.ts delete mode 100644 src/server/api/endpoints/i/update-home.ts delete mode 100644 src/server/api/endpoints/i/update-mobile-home.ts delete mode 100644 src/server/api/endpoints/i/update-widget.ts delete mode 100644 src/server/api/endpoints/notes/hybrid-timeline.ts create mode 100644 src/server/api/endpoints/notes/social-timeline.ts delete mode 100644 src/server/api/stream/channels/hybrid-timeline.ts create mode 100644 src/server/api/stream/channels/social-timeline.ts (limited to 'src/server/api') diff --git a/src/server/api/authenticate.ts b/src/server/api/authenticate.ts index 7781b87c88..e293e3fed0 100644 --- a/src/server/api/authenticate.ts +++ b/src/server/api/authenticate.ts @@ -1,37 +1,37 @@ -import App, { IApp } from '../../models/app'; -import { default as User, IUser } from '../../models/user'; -import AccessToken from '../../models/access-token'; import isNativeToken from './common/is-native-token'; +import { User } from '../../models/entities/user'; +import { App } from '../../models/entities/app'; +import { Users, AccessTokens, Apps } from '../../models'; -export default async (token: string): Promise<[IUser, IApp]> => { +export default async (token: string): Promise<[User, App]> => { if (token == null) { return [null, null]; } if (isNativeToken(token)) { // Fetch user - const user: IUser = await User + const user = await Users .findOne({ token }); - if (user === null) { + if (user == null) { throw 'user not found'; } return [user, null]; } else { - const accessToken = await AccessToken.findOne({ + const accessToken = await AccessTokens.findOne({ hash: token.toLowerCase() }); - if (accessToken === null) { + if (accessToken == null) { throw 'invalid signature'; } - const app = await App - .findOne({ _id: accessToken.appId }); + const app = await Apps + .findOne(accessToken.appId); - const user = await User - .findOne({ _id: accessToken.userId }); + const user = await Users + .findOne(accessToken.userId); return [user, app]; } diff --git a/src/server/api/call.ts b/src/server/api/call.ts index 89a44b3c65..885c622667 100644 --- a/src/server/api/call.ts +++ b/src/server/api/call.ts @@ -1,10 +1,10 @@ import { performance } from 'perf_hooks'; import limiter from './limiter'; -import { IUser } from '../../models/user'; -import { IApp } from '../../models/app'; +import { User } from '../../models/entities/user'; import endpoints from './endpoints'; import { ApiError } from './error'; import { apiLogger } from './logger'; +import { App } from '../../models/entities/app'; const accessDenied = { message: 'Access denied.', @@ -12,7 +12,7 @@ const accessDenied = { id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e' }; -export default async (endpoint: string, user: IUser, app: IApp, data: any, file?: any) => { +export default async (endpoint: string, user: User, app: App, data: any, file?: any) => { const isSecure = user != null && app == null; const ep = endpoints.find(e => e.name === endpoint); diff --git a/src/server/api/common/generate-mute-query.ts b/src/server/api/common/generate-mute-query.ts new file mode 100644 index 0000000000..090c14eb83 --- /dev/null +++ b/src/server/api/common/generate-mute-query.ts @@ -0,0 +1,36 @@ +import { User } from '../../../models/entities/user'; +import { Mutings } from '../../../models'; +import { SelectQueryBuilder, Brackets } from 'typeorm'; + +export function generateMuteQuery(q: SelectQueryBuilder, me: User) { + const mutingQuery = Mutings.createQueryBuilder('muting') + .select('muting.muteeId') + .where('muting.muterId = :muterId', { muterId: me.id }); + + // 投稿の作者をミュートしていない かつ + // 投稿の返信先の作者をミュートしていない かつ + // 投稿の引用元の作者をミュートしていない + q + .andWhere(`note.userId NOT IN (${ mutingQuery.getQuery() })`) + .andWhere(new Brackets(qb => { qb + .where(`note.replyUserId IS NULL`) + .orWhere(`note.replyUserId NOT IN (${ mutingQuery.getQuery() })`); + })) + .andWhere(new Brackets(qb => { qb + .where(`note.renoteUserId IS NULL`) + .orWhere(`note.renoteUserId NOT IN (${ mutingQuery.getQuery() })`); + })); + + q.setParameters(mutingQuery.getParameters()); +} + +export function generateMuteQueryForUsers(q: SelectQueryBuilder, me: User) { + const mutingQuery = Mutings.createQueryBuilder('muting') + .select('muting.muteeId') + .where('muting.muterId = :muterId', { muterId: me.id }); + + q + .andWhere(`user.id NOT IN (${ mutingQuery.getQuery() })`); + + q.setParameters(mutingQuery.getParameters()); +} diff --git a/src/server/api/common/generate-native-user-token.ts b/src/server/api/common/generate-native-user-token.ts index 2082b89a5a..92f8a3a0e8 100644 --- a/src/server/api/common/generate-native-user-token.ts +++ b/src/server/api/common/generate-native-user-token.ts @@ -1,3 +1,3 @@ import rndstr from 'rndstr'; -export default () => `!${rndstr('a-zA-Z0-9', 32)}`; +export default () => `!${rndstr('a-zA-Z0-9', 31)}`; diff --git a/src/server/api/common/generate-visibility-query.ts b/src/server/api/common/generate-visibility-query.ts new file mode 100644 index 0000000000..2807dc99dc --- /dev/null +++ b/src/server/api/common/generate-visibility-query.ts @@ -0,0 +1,40 @@ +import { User } from '../../../models/entities/user'; +import { Followings } from '../../../models'; +import { Brackets, SelectQueryBuilder } from 'typeorm'; + +export function generateVisibilityQuery(q: SelectQueryBuilder, me?: User) { + if (me == null) { + q.andWhere(new Brackets(qb => { qb + .where(`note.visibility = 'public'`) + .orWhere(`note.visibility = 'home'`); + })); + } else { + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); + + q.andWhere(new Brackets(qb => { qb + // 公開投稿である + .where(new Brackets(qb => { qb + .where(`note.visibility = 'public'`) + .orWhere(`note.visibility = 'home'`); + })) + // または 自分自身 + .orWhere('note.userId = :userId1', { userId1: me.id }) + // または 自分宛て + .orWhere(':userId2 = ANY(note.visibleUserIds)', { userId2: me.id }) + .orWhere(new Brackets(qb => { qb + // または フォロワー宛ての投稿であり、 + .where('note.visibility = \'followers\'') + .andWhere(new Brackets(qb => { qb + // 自分がフォロワーである + .where(`note.userId IN (${ followingQuery.getQuery() })`) + // または 自分の投稿へのリプライ + .orWhere('note.replyUserId = :userId3', { userId3: me.id }); + })); + })); + })); + + q.setParameters(followingQuery.getParameters()); + } +} diff --git a/src/server/api/common/get-friends.ts b/src/server/api/common/get-friends.ts deleted file mode 100644 index 876aa399f7..0000000000 --- a/src/server/api/common/get-friends.ts +++ /dev/null @@ -1,49 +0,0 @@ -import * as mongodb from 'mongodb'; -import Following from '../../../models/following'; - -export const getFriendIds = async (me: mongodb.ObjectID, includeMe = true) => { - // Fetch relation to other users who the I follows - // SELECT followee - const followings = await Following - .find({ - followerId: me - }, { - fields: { - followeeId: true - } - }); - - // ID list of other users who the I follows - const myfollowingIds = followings.map(following => following.followeeId); - - if (includeMe) { - myfollowingIds.push(me); - } - - return myfollowingIds; -}; - -export const getFriends = async (me: mongodb.ObjectID, includeMe = true, remoteOnly = false) => { - const q: any = remoteOnly ? { - followerId: me, - '_followee.host': { $ne: null } - } : { - followerId: me - }; - // Fetch relation to other users who the I follows - const followings = await Following - .find(q); - - // ID list of other users who the I follows - const myfollowings = followings.map(following => ({ - id: following.followeeId - })); - - if (includeMe) { - myfollowings.push({ - id: me - }); - } - - return myfollowings; -}; diff --git a/src/server/api/common/get-hide-users.ts b/src/server/api/common/get-hide-users.ts deleted file mode 100644 index 3cdf806751..0000000000 --- a/src/server/api/common/get-hide-users.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as mongo from 'mongodb'; -import Mute from '../../../models/mute'; -import User, { IUser } from '../../../models/user'; -import { unique } from '../../../prelude/array'; - -export async function getHideUserIds(me: IUser) { - return await getHideUserIdsById(me ? me._id : null); -} - -export async function getHideUserIdsById(meId?: mongo.ObjectID) { - const [suspended, muted] = await Promise.all([ - User.find({ - isSuspended: true - }, { - fields: { - _id: true - } - }), - meId ? Mute.find({ - muterId: meId - }) : Promise.resolve([]) - ]); - - return unique(suspended.map(user => user._id).concat(muted.map(mute => mute.muteeId))); -} diff --git a/src/server/api/common/getters.ts b/src/server/api/common/getters.ts index 7a72e6489a..b720840ebb 100644 --- a/src/server/api/common/getters.ts +++ b/src/server/api/common/getters.ts @@ -1,18 +1,15 @@ -import * as mongo from 'mongodb'; -import Note from '../../../models/note'; -import User, { isRemoteUser, isLocalUser } from '../../../models/user'; import { IdentifiableError } from '../../../misc/identifiable-error'; +import { User } from '../../../models/entities/user'; +import { Note } from '../../../models/entities/note'; +import { Notes, Users } from '../../../models'; /** * Get note for API processing */ -export async function getNote(noteId: mongo.ObjectID) { - const note = await Note.findOne({ - _id: noteId, - deletedAt: { $exists: false } - }); +export async function getNote(noteId: Note['id']) { + const note = await Notes.findOne(noteId); - if (note === null) { + if (note == null) { throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); } @@ -22,23 +19,10 @@ export async function getNote(noteId: mongo.ObjectID) { /** * Get user for API processing */ -export async function getUser(userId: mongo.ObjectID) { - const user = await User.findOne({ - _id: userId, - $or: [{ - isDeleted: { $exists: false } - }, { - isDeleted: false - }] - }, { - fields: { - data: false, - profile: false, - clientSettings: false - } - }); +export async function getUser(userId: User['id']) { + const user = await Users.findOne(userId); - if (user === null) { + if (user == null) { throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.'); } @@ -48,10 +32,10 @@ export async function getUser(userId: mongo.ObjectID) { /** * Get remote user for API processing */ -export async function getRemoteUser(userId: mongo.ObjectID) { +export async function getRemoteUser(userId: User['id']) { const user = await getUser(userId); - if (!isRemoteUser(user)) { + if (!Users.isRemoteUser(user)) { throw 'user is not a remote user'; } @@ -61,10 +45,10 @@ export async function getRemoteUser(userId: mongo.ObjectID) { /** * Get local user for API processing */ -export async function getLocalUser(userId: mongo.ObjectID) { +export async function getLocalUser(userId: User['id']) { const user = await getUser(userId); - if (!isLocalUser(user)) { + if (!Users.isLocalUser(user)) { throw 'user is not a local user'; } diff --git a/src/server/api/common/make-pagination-query.ts b/src/server/api/common/make-pagination-query.ts new file mode 100644 index 0000000000..0c859a4f8d --- /dev/null +++ b/src/server/api/common/make-pagination-query.ts @@ -0,0 +1,28 @@ +import { SelectQueryBuilder } from 'typeorm'; + +export function makePaginationQuery(q: SelectQueryBuilder, sinceId: string, untilId: string, sinceDate?: number, untilDate?: number) { + if (sinceId && untilId) { + q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); + q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); + q.orderBy(`${q.alias}.id`, 'DESC'); + } else if (sinceId) { + q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); + q.orderBy(`${q.alias}.id`, 'ASC'); + } else if (untilId) { + q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); + q.orderBy(`${q.alias}.id`, 'DESC'); + } else if (sinceDate && untilDate) { + q.andWhere(`${q.alias}.createdAt > :sinceDate`, { sinceDate: new Date(sinceDate) }); + q.andWhere(`${q.alias}.createdAt < :untilDate`, { untilDate: new Date(untilDate) }); + q.orderBy(`${q.alias}.createdAt`, 'DESC'); + } else if (sinceDate) { + q.andWhere(`${q.alias}.createdAt > :sinceDate`, { sinceDate: new Date(sinceDate) }); + q.orderBy(`${q.alias}.createdAt`, 'ASC'); + } else if (untilDate) { + q.andWhere(`${q.alias}.createdAt < :untilDate`, { untilDate: new Date(untilDate) }); + q.orderBy(`${q.alias}.createdAt`, 'DESC'); + } else { + q.orderBy(`${q.alias}.id`, 'DESC'); + } + return q; +} diff --git a/src/server/api/common/read-messaging-message.ts b/src/server/api/common/read-messaging-message.ts index 9f1e7e6ab4..2cb5a1f87f 100644 --- a/src/server/api/common/read-messaging-message.ts +++ b/src/server/api/common/read-messaging-message.ts @@ -1,77 +1,43 @@ -import * as mongo from 'mongodb'; -import isObjectId from '../../../misc/is-objectid'; -import Message from '../../../models/messaging-message'; -import { IMessagingMessage as IMessage } from '../../../models/messaging-message'; import { publishMainStream } from '../../../services/stream'; import { publishMessagingStream } from '../../../services/stream'; import { publishMessagingIndexStream } from '../../../services/stream'; -import User from '../../../models/user'; +import { User } from '../../../models/entities/user'; +import { MessagingMessage } from '../../../models/entities/messaging-message'; +import { MessagingMessages } from '../../../models'; +import { In } from 'typeorm'; /** * Mark messages as read */ -export default ( - user: string | mongo.ObjectID, - otherparty: string | mongo.ObjectID, - message: string | string[] | IMessage | IMessage[] | mongo.ObjectID | mongo.ObjectID[] -) => new Promise(async (resolve, reject) => { - - const userId = isObjectId(user) - ? user - : new mongo.ObjectID(user); - - const otherpartyId = isObjectId(otherparty) - ? otherparty - : new mongo.ObjectID(otherparty); - - const ids: mongo.ObjectID[] = Array.isArray(message) - ? isObjectId(message[0]) - ? (message as mongo.ObjectID[]) - : typeof message[0] === 'string' - ? (message as string[]).map(m => new mongo.ObjectID(m)) - : (message as IMessage[]).map(m => m._id) - : isObjectId(message) - ? [(message as mongo.ObjectID)] - : typeof message === 'string' - ? [new mongo.ObjectID(message)] - : [(message as IMessage)._id]; +export default async ( + userId: User['id'], + otherpartyId: User['id'], + messageIds: MessagingMessage['id'][] +) => { + if (messageIds.length === 0) return; // Update documents - await Message.update({ - _id: { $in: ids }, + await MessagingMessages.update({ + id: In(messageIds), userId: otherpartyId, recipientId: userId, isRead: false }, { - $set: { - isRead: true - } - }, { - multi: true - }); + isRead: true + }); // Publish event - publishMessagingStream(otherpartyId, userId, 'read', ids.map(id => id.toString())); - publishMessagingIndexStream(userId, 'read', ids.map(id => id.toString())); + publishMessagingStream(otherpartyId, userId, 'read', messageIds); + publishMessagingIndexStream(userId, 'read', messageIds); // Calc count of my unread messages - const count = await Message - .count({ - recipientId: userId, - isRead: false - }, { - limit: 1 - }); + const count = await MessagingMessages.count({ + recipientId: userId, + isRead: false + }); if (count == 0) { - // Update flag - User.update({ _id: userId }, { - $set: { - hasUnreadMessagingMessage: false - } - }); - // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 publishMainStream(userId, 'readAllMessagingMessages'); } -}); +}; diff --git a/src/server/api/common/read-notification.ts b/src/server/api/common/read-notification.ts index 4361305119..c8d43ba286 100644 --- a/src/server/api/common/read-notification.ts +++ b/src/server/api/common/read-notification.ts @@ -1,72 +1,38 @@ -import * as mongo from 'mongodb'; -import isObjectId from '../../../misc/is-objectid'; -import { default as Notification, INotification } from '../../../models/notification'; import { publishMainStream } from '../../../services/stream'; -import Mute from '../../../models/mute'; -import User from '../../../models/user'; +import { User } from '../../../models/entities/user'; +import { Notification } from '../../../models/entities/notification'; +import { Mutings, Notifications } from '../../../models'; +import { In, Not } from 'typeorm'; /** * Mark notifications as read */ -export default ( - user: string | mongo.ObjectID, - message: string | string[] | INotification | INotification[] | mongo.ObjectID | mongo.ObjectID[] -) => new Promise(async (resolve, reject) => { - - const userId = isObjectId(user) - ? user - : new mongo.ObjectID(user); - - const ids: mongo.ObjectID[] = Array.isArray(message) - ? isObjectId(message[0]) - ? (message as mongo.ObjectID[]) - : typeof message[0] === 'string' - ? (message as string[]).map(m => new mongo.ObjectID(m)) - : (message as INotification[]).map(m => m._id) - : isObjectId(message) - ? [(message as mongo.ObjectID)] - : typeof message === 'string' - ? [new mongo.ObjectID(message)] - : [(message as INotification)._id]; - - const mute = await Mute.find({ +export async function readNotification( + userId: User['id'], + notificationIds: Notification['id'][] +) { + const mute = await Mutings.find({ muterId: userId }); const mutedUserIds = mute.map(m => m.muteeId); // Update documents - await Notification.update({ - _id: { $in: ids }, + await Notifications.update({ + id: In(notificationIds), isRead: false }, { - $set: { - isRead: true - } - }, { - multi: true - }); + isRead: true + }); // Calc count of my unread notifications - const count = await Notification - .count({ - notifieeId: userId, - notifierId: { - $nin: mutedUserIds - }, - isRead: false - }, { - limit: 1 - }); - - if (count == 0) { - // Update flag - User.update({ _id: userId }, { - $set: { - hasUnreadNotification: false - } - }); + const count = await Notifications.count({ + notifieeId: userId, + ...(mutedUserIds.length > 0 ? { notifierId: Not(In(mutedUserIds)) } : {}), + isRead: false + }); + if (count === 0) { // 全ての(いままで未読だった)通知を(これで)読みましたよというイベントを発行 publishMainStream(userId, 'readAllNotifications'); } -}); +} diff --git a/src/server/api/common/signin.ts b/src/server/api/common/signin.ts index 84cad3a935..0f4ee4ca11 100644 --- a/src/server/api/common/signin.ts +++ b/src/server/api/common/signin.ts @@ -1,7 +1,7 @@ import * as Koa from 'koa'; import config from '../../../config'; -import { ILocalUser } from '../../../models/user'; +import { ILocalUser } from '../../../models/entities/user'; export default function(ctx: Koa.BaseContext, user: ILocalUser, redirect = false) { if (redirect) { diff --git a/src/server/api/define.ts b/src/server/api/define.ts index f2fababc32..a18419bcf6 100644 --- a/src/server/api/define.ts +++ b/src/server/api/define.ts @@ -1,8 +1,8 @@ import * as fs from 'fs'; -import { ILocalUser } from '../../models/user'; -import { IApp } from '../../models/app'; +import { ILocalUser } from '../../models/entities/user'; import { IEndpointMeta } from './endpoints'; import { ApiError } from './error'; +import { App } from '../../models/entities/app'; type Params = { [P in keyof T['params']]: T['params'][P]['transform'] extends Function @@ -12,8 +12,8 @@ type Params = { export type Response = Record | void; -export default function (meta: T, cb: (params: Params, user: ILocalUser, app: IApp, file?: any, cleanup?: Function) => Promise): (params: any, user: ILocalUser, app: IApp, file?: any) => Promise { - return (params: any, user: ILocalUser, app: IApp, file?: any) => { +export default function (meta: T, cb: (params: Params, user: ILocalUser, app: App, file?: any, cleanup?: Function) => Promise): (params: any, user: ILocalUser, app: App, file?: any) => Promise { + return (params: any, user: ILocalUser, app: App, file?: any) => { function cleanup() { fs.unlink(file.path, () => {}); } diff --git a/src/server/api/endpoints/admin/abuse-user-reports.ts b/src/server/api/endpoints/admin/abuse-user-reports.ts index d9fe3429ce..5c5a734c1d 100644 --- a/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Report, { packMany } from '../../../../models/abuse-user-report'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { AbuseUserReports } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['admin'], @@ -17,37 +18,18 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, } }; export default define(meta, async (ps) => { - const sort = { - _id: -1 - }; - const query = {} as any; - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } + const query = makePaginationQuery(AbuseUserReports.createQueryBuilder('report'), ps.sinceId, ps.untilId); - const reports = await Report - .find(query, { - limit: ps.limit, - sort: sort - }); + const reports = await query.take(ps.limit).getMany(); - return await packMany(reports); + return await AbuseUserReports.packMany(reports); }); diff --git a/src/server/api/endpoints/admin/drive/files.ts b/src/server/api/endpoints/admin/drive/files.ts index 8ed417a429..1ccabc92d9 100644 --- a/src/server/api/endpoints/admin/drive/files.ts +++ b/src/server/api/endpoints/admin/drive/files.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import File, { packMany } from '../../../../../models/drive-file'; import define from '../../../define'; import { fallback } from '../../../../../prelude/symbol'; +import { DriveFiles } from '../../../../../models'; export const meta = { tags: ['admin'], @@ -41,27 +41,25 @@ export const meta = { }; const sort: any = { // < https://github.com/Microsoft/TypeScript/issues/1863 - '+createdAt': { uploadDate: -1 }, - '-createdAt': { uploadDate: 1 }, - '+size': { length: -1 }, - '-size': { length: 1 }, - [fallback]: { _id: -1 } + '+createdAt': { createdAt: -1 }, + '-createdAt': { createdAt: 1 }, + '+size': { size: -1 }, + '-size': { size: 1 }, + [fallback]: { id: -1 } }; export default define(meta, async (ps, me) => { - const q = { - 'metadata.deletedAt': { $exists: false }, - } as any; + const q = {} as any; - if (ps.origin == 'local') q['metadata._user.host'] = null; - if (ps.origin == 'remote') q['metadata._user.host'] = { $ne: null }; + if (ps.origin == 'local') q['userHost'] = null; + if (ps.origin == 'remote') q['userHost'] = { $ne: null }; - const files = await File - .find(q, { - limit: ps.limit, - sort: sort[ps.sort] || sort[fallback], - skip: ps.offset - }); + const files = await DriveFiles.find({ + where: q, + take: ps.limit, + order: sort[ps.sort] || sort[fallback], + skip: ps.offset + }); - return await packMany(files, { detail: true, withUser: true, self: true }); + return await DriveFiles.packMany(files, { detail: true, withUser: true, self: true }); }); diff --git a/src/server/api/endpoints/admin/drive/show-file.ts b/src/server/api/endpoints/admin/drive/show-file.ts index 405b6d44ce..a2b6c158f0 100644 --- a/src/server/api/endpoints/admin/drive/show-file.ts +++ b/src/server/api/endpoints/admin/drive/show-file.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; -import DriveFile from '../../../../../models/drive-file'; import { ApiError } from '../../../error'; +import { DriveFiles } from '../../../../../models'; export const meta = { tags: ['admin'], @@ -13,7 +13,6 @@ export const meta = { params: { fileId: { validator: $.type(ID), - transform: transform, }, }, @@ -27,9 +26,7 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const file = await DriveFile.findOne({ - _id: ps.fileId - }); + const file = await DriveFiles.findOne(ps.fileId); if (file == null) { throw new ApiError(meta.errors.noSuchFile); diff --git a/src/server/api/endpoints/admin/emoji/add.ts b/src/server/api/endpoints/admin/emoji/add.ts index c126c8380f..c26e8dd04d 100644 --- a/src/server/api/endpoints/admin/emoji/add.ts +++ b/src/server/api/endpoints/admin/emoji/add.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import Emoji from '../../../../../models/emoji'; import define from '../../../define'; import { detectUrlMine } from '../../../../../misc/detect-url-mine'; +import { Emojis } from '../../../../../models'; +import { genId } from '../../../../../misc/gen-id'; export const meta = { desc: { @@ -32,7 +33,8 @@ export const meta = { export default define(meta, async (ps) => { const type = await detectUrlMine(ps.url); - const emoji = await Emoji.insert({ + const emoji = await Emojis.save({ + id: genId(), updatedAt: new Date(), name: ps.name, host: null, @@ -42,6 +44,6 @@ export default define(meta, async (ps) => { }); return { - id: emoji._id + id: emoji.id }; }); diff --git a/src/server/api/endpoints/admin/emoji/list.ts b/src/server/api/endpoints/admin/emoji/list.ts index 954f8f96c6..07174723b9 100644 --- a/src/server/api/endpoints/admin/emoji/list.ts +++ b/src/server/api/endpoints/admin/emoji/list.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; -import Emoji from '../../../../../models/emoji'; import define from '../../../define'; +import { Emojis } from '../../../../../models'; export const meta = { desc: { @@ -21,12 +21,12 @@ export const meta = { }; export default define(meta, async (ps) => { - const emojis = await Emoji.find({ + const emojis = await Emojis.find({ host: ps.host }); return emojis.map(e => ({ - id: e._id, + id: e.id, name: e.name, aliases: e.aliases, host: e.host, diff --git a/src/server/api/endpoints/admin/emoji/remove.ts b/src/server/api/endpoints/admin/emoji/remove.ts index 4c69dffbae..316834b884 100644 --- a/src/server/api/endpoints/admin/emoji/remove.ts +++ b/src/server/api/endpoints/admin/emoji/remove.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import Emoji from '../../../../../models/emoji'; import define from '../../../define'; -import ID from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; +import { Emojis } from '../../../../../models'; export const meta = { desc: { @@ -21,13 +21,9 @@ export const meta = { }; export default define(meta, async (ps) => { - const emoji = await Emoji.findOne({ - _id: ps.id - }); + const emoji = await Emojis.findOne(ps.id); if (emoji == null) throw new Error('emoji not found'); - await Emoji.remove({ _id: emoji._id }); - - return; + await Emojis.delete(emoji.id); }); diff --git a/src/server/api/endpoints/admin/emoji/update.ts b/src/server/api/endpoints/admin/emoji/update.ts index 8b1c07be9e..48b4a4ee23 100644 --- a/src/server/api/endpoints/admin/emoji/update.ts +++ b/src/server/api/endpoints/admin/emoji/update.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; -import Emoji from '../../../../../models/emoji'; import define from '../../../define'; -import ID from '../../../../../misc/cafy-id'; import { detectUrlMine } from '../../../../../misc/detect-url-mine'; +import { ID } from '../../../../../misc/cafy-id'; +import { Emojis } from '../../../../../models'; export const meta = { desc: { @@ -34,23 +34,17 @@ export const meta = { }; export default define(meta, async (ps) => { - const emoji = await Emoji.findOne({ - _id: ps.id - }); + const emoji = await Emojis.findOne(ps.id); if (emoji == null) throw new Error('emoji not found'); const type = await detectUrlMine(ps.url); - await Emoji.update({ _id: emoji._id }, { - $set: { - updatedAt: new Date(), - name: ps.name, - aliases: ps.aliases, - url: ps.url, - type, - } + await Emojis.update(emoji.id, { + updatedAt: new Date(), + name: ps.name, + aliases: ps.aliases, + url: ps.url, + type, }); - - return; }); diff --git a/src/server/api/endpoints/admin/federation/remove-all-following.ts b/src/server/api/endpoints/admin/federation/remove-all-following.ts index 98afdfc2a5..fca76e7086 100644 --- a/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -1,8 +1,7 @@ import $ from 'cafy'; import define from '../../../define'; -import Following from '../../../../../models/following'; -import User from '../../../../../models/user'; import deleteFollowing from '../../../../../services/following/delete'; +import { Followings, Users } from '../../../../../models'; export const meta = { tags: ['admin'], @@ -18,13 +17,13 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const followings = await Following.find({ - '_follower.host': ps.host + const followings = await Followings.find({ + followerHost: ps.host }); const pairs = await Promise.all(followings.map(f => Promise.all([ - User.findOne({ _id: f.followerId }), - User.findOne({ _id: f.followeeId }) + Users.findOne(f.followerId), + Users.findOne(f.followeeId) ]))); for (const pair of pairs) { diff --git a/src/server/api/endpoints/admin/federation/update-instance.ts b/src/server/api/endpoints/admin/federation/update-instance.ts index 0d127b53b3..d1abe95a5b 100644 --- a/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/src/server/api/endpoints/admin/federation/update-instance.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../../define'; -import Instance from '../../../../../models/instance'; +import { Instances } from '../../../../../models'; export const meta = { tags: ['admin'], @@ -13,10 +13,6 @@ export const meta = { validator: $.str }, - isBlocked: { - validator: $.bool - }, - isClosed: { validator: $.bool }, @@ -24,18 +20,13 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const instance = await Instance.findOne({ host: ps.host }); + const instance = await Instances.findOne({ host: ps.host }); if (instance == null) { throw new Error('instance not found'); } - Instance.update({ host: ps.host }, { - $set: { - isBlocked: ps.isBlocked, - isMarkedAsClosed: ps.isClosed - } + Instances.update({ host: ps.host }, { + isMarkedAsClosed: ps.isClosed }); - - return; }); diff --git a/src/server/api/endpoints/admin/invite.ts b/src/server/api/endpoints/admin/invite.ts index 28aa301957..4e264feef6 100644 --- a/src/server/api/endpoints/admin/invite.ts +++ b/src/server/api/endpoints/admin/invite.ts @@ -1,6 +1,7 @@ import rndstr from 'rndstr'; -import RegistrationTicket from '../../../../models/registration-tickets'; import define from '../../define'; +import { RegistrationTickets } from '../../../../models'; +import { genId } from '../../../../misc/gen-id'; export const meta = { desc: { @@ -18,7 +19,8 @@ export const meta = { export default define(meta, async (ps) => { const code = rndstr({ length: 5, chars: '0-9' }); - await RegistrationTicket.insert({ + await RegistrationTickets.save({ + id: genId(), createdAt: new Date(), code: code }); diff --git a/src/server/api/endpoints/admin/logs.ts b/src/server/api/endpoints/admin/logs.ts index 805a42b9e0..eee56a3939 100644 --- a/src/server/api/endpoints/admin/logs.ts +++ b/src/server/api/endpoints/admin/logs.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import Log from '../../../../models/log'; +import { Logs } from '../../../../models'; +import { Brackets } from 'typeorm'; export const meta = { tags: ['admin'], @@ -27,41 +28,44 @@ export const meta = { }; export default define(meta, async (ps) => { - const sort = { - _id: -1 - }; - const query = {} as any; + const query = Logs.createQueryBuilder('log'); + + if (ps.level) query.andWhere('log.level = :level', { level: ps.level }); - if (ps.level) query.level = ps.level; if (ps.domain) { - for (const d of ps.domain.split(' ')) { - const qs: any[] = []; - let i = 0; - for (const sd of (d.startsWith('-') ? d.substr(1) : d).split('.')) { - qs.push({ - [`domain.${i}`]: d.startsWith('-') ? { $ne: sd } : sd - }); - i++; - } - if (d.startsWith('-')) { - if (query['$and'] == null) query['$and'] = []; - query['$and'].push({ - $and: qs - }); - } else { - if (query['$or'] == null) query['$or'] = []; - query['$or'].push({ - $and: qs - }); - } + const whiteDomains = ps.domain.split(' ').filter(x => !x.startsWith('-')); + const blackDomains = ps.domain.split(' ').filter(x => x.startsWith('-')); + + if (whiteDomains.length > 0) { + query.andWhere(new Brackets(qb => { + for (const whiteDomain of whiteDomains) { + let i = 0; + for (const subDomain of whiteDomain.split('.')) { + const p = `whiteSubDomain_${subDomain}_${i}`; + // SQL is 1 based, so we need '+ 1' + qb.orWhere(`log.domain[${i + 1}] = :${p}`, { [p]: subDomain }); + i++; + } + } + })); + } + + if (blackDomains.length > 0) { + query.andWhere(new Brackets(qb => { + for (const blackDomain of blackDomains) { + let i = 0; + for (const subDomain of blackDomain.split('.')) { + const p = `blackSubDomain_${subDomain}_${i}`; + // SQL is 1 based, so we need '+ 1' + qb.andWhere(`log.domain[${i + 1}] != :${p}`, { [p]: subDomain }); + i++; + } + } + })); } } - const logs = await Log - .find(query, { - limit: ps.limit, - sort: sort - }); + const logs = await query.take(ps.limit).getMany(); return logs; }); diff --git a/src/server/api/endpoints/admin/moderators/add.ts b/src/server/api/endpoints/admin/moderators/add.ts index 2271bcd1a9..a15f0a17a2 100644 --- a/src/server/api/endpoints/admin/moderators/add.ts +++ b/src/server/api/endpoints/admin/moderators/add.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; -import User from '../../../../../models/user'; +import { Users } from '../../../../../models'; export const meta = { desc: { @@ -17,7 +17,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID' @@ -27,21 +26,13 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); } - await User.update({ - _id: user._id - }, { - $set: { - isModerator: true - } + await Users.update(user.id, { + isModerator: true }); - - return; }); diff --git a/src/server/api/endpoints/admin/moderators/remove.ts b/src/server/api/endpoints/admin/moderators/remove.ts index 84143d3e35..209cf0814f 100644 --- a/src/server/api/endpoints/admin/moderators/remove.ts +++ b/src/server/api/endpoints/admin/moderators/remove.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; -import User from '../../../../../models/user'; +import { Users } from '../../../../../models'; export const meta = { desc: { @@ -17,7 +17,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID' @@ -27,21 +26,13 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); } - await User.update({ - _id: user._id - }, { - $set: { - isModerator: false - } + await Users.update(user.id, { + isModerator: false }); - - return; }); diff --git a/src/server/api/endpoints/admin/remove-abuse-user-report.ts b/src/server/api/endpoints/admin/remove-abuse-user-report.ts index fa17e2c937..f293c00718 100644 --- a/src/server/api/endpoints/admin/remove-abuse-user-report.ts +++ b/src/server/api/endpoints/admin/remove-abuse-user-report.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import AbuseUserReport from '../../../../models/abuse-user-report'; +import { AbuseUserReports } from '../../../../models'; export const meta = { tags: ['admin'], @@ -12,23 +12,16 @@ export const meta = { params: { reportId: { validator: $.type(ID), - transform: transform }, } }; export default define(meta, async (ps) => { - const report = await AbuseUserReport.findOne({ - _id: ps.reportId - }); + const report = await AbuseUserReports.findOne(ps.reportId); if (report == null) { throw new Error('report not found'); } - await AbuseUserReport.remove({ - _id: report._id - }); - - return; + await AbuseUserReports.delete(report.id); }); diff --git a/src/server/api/endpoints/admin/reset-password.ts b/src/server/api/endpoints/admin/reset-password.ts index 73901d8358..07b8b6d938 100644 --- a/src/server/api/endpoints/admin/reset-password.ts +++ b/src/server/api/endpoints/admin/reset-password.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; import * as bcrypt from 'bcryptjs'; import rndstr from 'rndstr'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -18,7 +18,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to suspend' @@ -28,9 +27,7 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); @@ -45,12 +42,8 @@ export default define(meta, async (ps) => { // Generate hash of password const hash = bcrypt.hashSync(passwd); - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - password: hash - } + await Users.update(user.id, { + password: hash }); return { diff --git a/src/server/api/endpoints/admin/show-user.ts b/src/server/api/endpoints/admin/show-user.ts index 985f71a873..452125dea0 100644 --- a/src/server/api/endpoints/admin/show-user.ts +++ b/src/server/api/endpoints/admin/show-user.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -16,7 +16,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to suspend' @@ -26,9 +25,7 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); diff --git a/src/server/api/endpoints/admin/show-users.ts b/src/server/api/endpoints/admin/show-users.ts index 5feb1b4fd8..73976b9872 100644 --- a/src/server/api/endpoints/admin/show-users.ts +++ b/src/server/api/endpoints/admin/show-users.ts @@ -1,7 +1,6 @@ import $ from 'cafy'; -import User, { pack } from '../../../../models/user'; import define from '../../define'; -import { fallback } from '../../../../prelude/symbol'; +import { Users } from '../../../../models'; export const meta = { tags: ['admin'], @@ -55,51 +54,38 @@ export const meta = { } }; -const sort: any = { // < https://github.com/Microsoft/TypeScript/issues/1863 - '+follower': { followersCount: -1 }, - '-follower': { followersCount: 1 }, - '+createdAt': { createdAt: -1 }, - '-createdAt': { createdAt: 1 }, - '+updatedAt': { updatedAt: -1 }, - '-updatedAt': { updatedAt: 1 }, - [fallback]: { _id: -1 } -}; - export default define(meta, async (ps, me) => { - const q = { - $and: [] - } as any; + const query = Users.createQueryBuilder('user'); + + switch (ps.state) { + case 'admin': query.where('user.isAdmin = TRUE'); break; + case 'moderator': query.where('user.isModerator = TRUE'); break; + case 'adminOrModerator': query.where('user.isAdmin = TRUE OR isModerator = TRUE'); break; + case 'verified': query.where('user.isVerified = TRUE'); break; + case 'alive': query.where('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; + case 'silenced': query.where('user.isSilenced = TRUE'); break; + case 'suspended': query.where('user.isSuspended = TRUE'); break; + } - // state - q.$and.push( - ps.state == 'admin' ? { isAdmin: true } : - ps.state == 'moderator' ? { isModerator: true } : - ps.state == 'adminOrModerator' ? { - $or: [{ - isAdmin: true - }, { - isModerator: true - }] - } : - ps.state == 'verified' ? { isVerified: true } : - ps.state == 'silenced' ? { isSilenced: true } : - ps.state == 'suspended' ? { isSuspended: true } : - {} - ); + switch (ps.origin) { + case 'local': query.andWhere('user.host IS NULL'); break; + case 'remote': query.andWhere('user.host IS NOT NULL'); break; + } + + switch (ps.sort) { + case '+follower': query.orderBy('user.followersCount', 'DESC'); break; + case '-follower': query.orderBy('user.followersCount', 'ASC'); break; + case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; + case '+updatedAt': query.orderBy('user.updatedAt', 'DESC'); break; + case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break; + default: query.orderBy('user.id', 'ASC'); break; + } - // origin - q.$and.push( - ps.origin == 'local' ? { host: null } : - ps.origin == 'remote' ? { host: { $ne: null } } : - {} - ); + query.take(ps.limit); + query.skip(ps.offset); - const users = await User - .find(q, { - limit: ps.limit, - sort: sort[ps.sort] || sort[fallback], - skip: ps.offset - }); + const users = await query.getMany(); - return await Promise.all(users.map(user => pack(user, me, { detail: true }))); + return await Users.packMany(users, me, { detail: true }); }); diff --git a/src/server/api/endpoints/admin/silence-user.ts b/src/server/api/endpoints/admin/silence-user.ts index 2557d8de6a..83aa88012a 100644 --- a/src/server/api/endpoints/admin/silence-user.ts +++ b/src/server/api/endpoints/admin/silence-user.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -17,7 +17,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to make silence' @@ -27,9 +26,7 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); @@ -39,13 +36,7 @@ export default define(meta, async (ps) => { throw new Error('cannot silence admin'); } - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - isSilenced: true - } + await Users.update(user.id, { + isSilenced: true }); - - return; }); diff --git a/src/server/api/endpoints/admin/suspend-user.ts b/src/server/api/endpoints/admin/suspend-user.ts index 0a2d309530..fa4d378708 100644 --- a/src/server/api/endpoints/admin/suspend-user.ts +++ b/src/server/api/endpoints/admin/suspend-user.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User, { IUser } from '../../../../models/user'; -import Following from '../../../../models/following'; import deleteFollowing from '../../../../services/following/delete'; +import { Users, Followings } from '../../../../models'; +import { User } from '../../../../models/entities/user'; export const meta = { desc: { @@ -19,7 +19,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to suspend' @@ -29,9 +28,7 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); @@ -45,27 +42,21 @@ export default define(meta, async (ps) => { throw new Error('cannot suspend moderator'); } - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - isSuspended: true - } + await Users.update(user.id, { + isSuspended: true }); unFollowAll(user); - - return; }); -async function unFollowAll(follower: IUser) { - const followings = await Following.find({ - followerId: follower._id +async function unFollowAll(follower: User) { + const followings = await Followings.find({ + followerId: follower.id }); for (const following of followings) { - const followee = await User.findOne({ - _id: following.followeeId + const followee = await Users.findOne({ + id: following.followeeId }); if (followee == null) { diff --git a/src/server/api/endpoints/admin/unsilence-user.ts b/src/server/api/endpoints/admin/unsilence-user.ts index 01bf41aaef..f9b173366b 100644 --- a/src/server/api/endpoints/admin/unsilence-user.ts +++ b/src/server/api/endpoints/admin/unsilence-user.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -17,7 +17,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to unsilence' @@ -27,21 +26,13 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); } - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - isSilenced: false - } + await Users.update(user.id, { + isSilenced: false }); - - return; }); diff --git a/src/server/api/endpoints/admin/unsuspend-user.ts b/src/server/api/endpoints/admin/unsuspend-user.ts index 5da35f28e6..08dae034d3 100644 --- a/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/src/server/api/endpoints/admin/unsuspend-user.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -17,7 +17,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to unsuspend' @@ -27,21 +26,13 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); } - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - isSuspended: false - } + await Users.update(user.id, { + isSuspended: false }); - - return; }); diff --git a/src/server/api/endpoints/admin/unverify-user.ts b/src/server/api/endpoints/admin/unverify-user.ts index d3ca05cb39..b215dbf10d 100644 --- a/src/server/api/endpoints/admin/unverify-user.ts +++ b/src/server/api/endpoints/admin/unverify-user.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -17,7 +17,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to unverify' @@ -27,21 +26,13 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); } - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - isVerified: false - } + await Users.update(user.id, { + isVerified: false }); - - return; }); diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index f8f7cb5d9a..e242ac71a1 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; -import Meta from '../../../../models/meta'; import define from '../../define'; +import { Metas } from '../../../../models'; +import { Meta } from '../../../../models/entities/meta'; export const meta = { desc: { @@ -55,7 +56,7 @@ export const meta = { } }, - hidedTags: { + hiddenTags: { validator: $.optional.nullable.arr($.str), desc: { 'ja-JP': '統計などで無視するハッシュタグ' @@ -253,27 +254,6 @@ export const meta = { } }, - enableExternalUserRecommendation: { - validator: $.optional.bool, - desc: { - 'ja-JP': '外部ユーザーレコメンデーションを有効にする' - } - }, - - externalUserRecommendationEngine: { - validator: $.optional.nullable.str, - desc: { - 'ja-JP': '外部ユーザーレコメンデーションのサードパーティエンジン' - } - }, - - externalUserRecommendationTimeout: { - validator: $.optional.nullable.num.min(0), - desc: { - 'ja-JP': '外部ユーザーレコメンデーションのタイムアウト (ミリ秒)' - } - }, - enableEmail: { validator: $.optional.bool, desc: { @@ -347,7 +327,7 @@ export const meta = { }; export default define(meta, async (ps) => { - const set = {} as any; + const set = {} as Partial; if (ps.announcements) { set.announcements = ps.announcements; @@ -373,8 +353,8 @@ export default define(meta, async (ps) => { set.useStarForReactionFallback = ps.useStarForReactionFallback; } - if (Array.isArray(ps.hidedTags)) { - set.hidedTags = ps.hidedTags; + if (Array.isArray(ps.hiddenTags)) { + set.hiddenTags = ps.hiddenTags; } if (ps.mascotImageUrl !== undefined) { @@ -430,11 +410,11 @@ export default define(meta, async (ps) => { } if (ps.maintainerName !== undefined) { - set['maintainer.name'] = ps.maintainerName; + set.maintainerName = ps.maintainerName; } if (ps.maintainerEmail !== undefined) { - set['maintainer.email'] = ps.maintainerEmail; + set.maintainerEmail = ps.maintainerEmail; } if (ps.langs !== undefined) { @@ -481,18 +461,6 @@ export default define(meta, async (ps) => { set.discordClientSecret = ps.discordClientSecret; } - if (ps.enableExternalUserRecommendation !== undefined) { - set.enableExternalUserRecommendation = ps.enableExternalUserRecommendation; - } - - if (ps.externalUserRecommendationEngine !== undefined) { - set.externalUserRecommendationEngine = ps.externalUserRecommendationEngine; - } - - if (ps.externalUserRecommendationTimeout !== undefined) { - set.externalUserRecommendationTimeout = ps.externalUserRecommendationTimeout; - } - if (ps.enableEmail !== undefined) { set.enableEmail = ps.enableEmail; } @@ -537,9 +505,11 @@ export default define(meta, async (ps) => { set.swPrivateKey = ps.swPrivateKey; } - await Meta.update({}, { - $set: set - }, { upsert: true }); + const meta = await Metas.findOne(); - return; + if (meta) { + await Metas.update(meta.id, set); + } else { + await Metas.save(set); + } }); diff --git a/src/server/api/endpoints/admin/update-remote-user.ts b/src/server/api/endpoints/admin/update-remote-user.ts index a74685912c..0be9047d5a 100644 --- a/src/server/api/endpoints/admin/update-remote-user.ts +++ b/src/server/api/endpoints/admin/update-remote-user.ts @@ -1,6 +1,5 @@ -import * as mongo from 'mongodb'; import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { getRemoteUser } from '../../common/getters'; import { updatePerson } from '../../../../remote/activitypub/models/person'; @@ -19,7 +18,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to update' @@ -29,11 +27,6 @@ export const meta = { }; export default define(meta, async (ps) => { - await updatePersonById(ps.userId); - return; -}); - -async function updatePersonById(userId: mongo.ObjectID) { - const user = await getRemoteUser(userId); + const user = await getRemoteUser(ps.userId); await updatePerson(user.uri); -} +}); diff --git a/src/server/api/endpoints/admin/verify-user.ts b/src/server/api/endpoints/admin/verify-user.ts index f67b6c3bf0..c1b447a92b 100644 --- a/src/server/api/endpoints/admin/verify-user.ts +++ b/src/server/api/endpoints/admin/verify-user.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -17,7 +17,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to verify' @@ -27,21 +26,13 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); } - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - isVerified: true - } + await Users.update(user.id, { + isVerified: true }); - - return; }); diff --git a/src/server/api/endpoints/aggregation/hashtags.ts b/src/server/api/endpoints/aggregation/hashtags.ts deleted file mode 100644 index 978e9f64b7..0000000000 --- a/src/server/api/endpoints/aggregation/hashtags.ts +++ /dev/null @@ -1,72 +0,0 @@ -import Note from '../../../../models/note'; -import define from '../../define'; -import fetchMeta from '../../../../misc/fetch-meta'; - -export const meta = { - tags: ['hashtags'], - - requireCredential: false, -}; - -export default define(meta, async (ps) => { - const instance = await fetchMeta(); - const hidedTags = instance.hidedTags.map(t => t.toLowerCase()); - - // 重い - //const span = 1000 * 60 * 60 * 24 * 7; // 1週間 - const span = 1000 * 60 * 60 * 24; // 1日 - - //#region 1. 指定期間の内に投稿されたハッシュタグ(とユーザーのペア)を集計 - const data = await Note.aggregate([{ - $match: { - createdAt: { - $gt: new Date(Date.now() - span) - }, - tagsLower: { - $exists: true, - $ne: [] - } - } - }, { - $unwind: '$tagsLower' - }, { - $group: { - _id: { tag: '$tagsLower', userId: '$userId' } - } - }]) as { - _id: { - tag: string; - userId: any; - } - }[]; - //#endregion - - if (data.length == 0) { - return []; - } - - let tags: { - name: string; - count: number; - }[] = []; - - // カウント - for (const x of data.map(x => x._id).filter(x => !hidedTags.includes(x.tag))) { - const i = tags.findIndex(tag => tag.name == x.tag); - if (i != -1) { - tags[i].count++; - } else { - tags.push({ - name: x.tag, - count: 1 - }); - } - } - - // タグを人気順に並べ替え - tags.sort((a, b) => b.count - a.count); - - tags = tags.slice(0, 30); - - return tags; -}); diff --git a/src/server/api/endpoints/ap/show.ts b/src/server/api/endpoints/ap/show.ts index 7f4afa1f6e..5b2aaeadbb 100644 --- a/src/server/api/endpoints/ap/show.ts +++ b/src/server/api/endpoints/ap/show.ts @@ -1,15 +1,15 @@ import $ from 'cafy'; import define from '../../define'; import config from '../../../../config'; -import * as mongo from 'mongodb'; -import User, { pack as packUser, IUser } from '../../../../models/user'; import { createPerson } from '../../../../remote/activitypub/models/person'; -import Note, { pack as packNote, INote } from '../../../../models/note'; import { createNote } from '../../../../remote/activitypub/models/note'; import Resolver from '../../../../remote/activitypub/resolver'; import { ApiError } from '../../error'; -import Instance from '../../../../models/instance'; import { extractDbHost } from '../../../../misc/convert-host'; +import { Users, Notes } from '../../../../models'; +import { Note } from '../../../../models/entities/note'; +import { User } from '../../../../models/entities/user'; +import fetchMeta from '../../../../misc/fetch-meta'; export const meta = { tags: ['federation'], @@ -53,25 +53,40 @@ export default define(meta, async (ps) => { async function fetchAny(uri: string) { // URIがこのサーバーを指しているなら、ローカルユーザーIDとしてDBからフェッチ if (uri.startsWith(config.url + '/')) { - const id = new mongo.ObjectID(uri.split('/').pop()); - const [user, note] = await Promise.all([ - User.findOne({ _id: id }), - Note.findOne({ _id: id }) - ]); - - const packed = await mergePack(user, note); - if (packed !== null) return packed; + const parts = uri.split('/'); + const id = parts.pop(); + const type = parts.pop(); + + if (type === 'notes') { + const note = await Notes.findOne(id); + + if (note) { + return { + type: 'Note', + object: await Notes.pack(note, null, { detail: true }) + }; + } + } else if (type === 'users') { + const user = await Users.findOne(id); + + if (user) { + return { + type: 'User', + object: await Users.pack(user, null, { detail: true }) + }; + } + } } // ブロックしてたら中断 - const instance = await Instance.findOne({ host: extractDbHost(uri) }); - if (instance && instance.isBlocked) return null; + const meta = await fetchMeta(); + if (meta.blockedHosts.includes(extractDbHost(uri))) return null; // URI(AP Object id)としてDB検索 { const [user, note] = await Promise.all([ - User.findOne({ uri: uri }), - Note.findOne({ uri: uri }) + Users.findOne({ uri: uri }), + Notes.findOne({ uri: uri }) ]); const packed = await mergePack(user, note); @@ -86,8 +101,8 @@ async function fetchAny(uri: string) { // これはDBに存在する可能性があるため再度DB検索 if (uri !== object.id) { const [user, note] = await Promise.all([ - User.findOne({ uri: object.id }), - Note.findOne({ uri: object.id }) + Users.findOne({ uri: object.id }), + Notes.findOne({ uri: object.id }) ]); const packed = await mergePack(user, note); @@ -99,7 +114,7 @@ async function fetchAny(uri: string) { const user = await createPerson(object.id); return { type: 'User', - object: await packUser(user, null, { detail: true }) + object: await Users.pack(user, null, { detail: true }) }; } @@ -107,25 +122,25 @@ async function fetchAny(uri: string) { const note = await createNote(object.id); return { type: 'Note', - object: await packNote(note, null, { detail: true }) + object: await Notes.pack(note, null, { detail: true }) }; } return null; } -async function mergePack(user: IUser, note: INote) { +async function mergePack(user: User, note: Note) { if (user !== null) { return { type: 'User', - object: await packUser(user, null, { detail: true }) + object: await Users.pack(user, null, { detail: true }) }; } if (note !== null) { return { type: 'Note', - object: await packNote(note, null, { detail: true }) + object: await Notes.pack(note, null, { detail: true }) }; } diff --git a/src/server/api/endpoints/app/create.ts b/src/server/api/endpoints/app/create.ts index 67b1b8150a..c7e7e516ad 100644 --- a/src/server/api/endpoints/app/create.ts +++ b/src/server/api/endpoints/app/create.ts @@ -1,7 +1,8 @@ import rndstr from 'rndstr'; import $ from 'cafy'; -import App, { pack } from '../../../../models/app'; import define from '../../define'; +import { Apps } from '../../../../models'; +import { genId } from '../../../../misc/gen-id'; export const meta = { tags: ['app'], @@ -34,9 +35,10 @@ export default define(meta, async (ps, user) => { const secret = rndstr('a-zA-Z0-9', 32); // Create account - const app = await App.insert({ + const app = await Apps.save({ + id: genId(), createdAt: new Date(), - userId: user && user._id, + userId: user && user.id, name: ps.name, description: ps.description, permission: ps.permission, @@ -44,7 +46,7 @@ export default define(meta, async (ps, user) => { secret: secret }); - return await pack(app, null, { + return await Apps.pack(app, null, { detail: true, includeSecret: true }); diff --git a/src/server/api/endpoints/app/show.ts b/src/server/api/endpoints/app/show.ts index f3f5b843b3..ce9baed2ae 100644 --- a/src/server/api/endpoints/app/show.ts +++ b/src/server/api/endpoints/app/show.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import App, { pack } from '../../../../models/app'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { ApiError } from '../../error'; +import { Apps } from '../../../../models'; export const meta = { tags: ['app'], @@ -10,7 +10,6 @@ export const meta = { params: { appId: { validator: $.type(ID), - transform: transform }, }, @@ -27,14 +26,14 @@ export default define(meta, async (ps, user, app) => { const isSecure = user != null && app == null; // Lookup app - const ap = await App.findOne({ _id: ps.appId }); + const ap = await Apps.findOne(ps.appId); - if (ap === null) { + if (ap == null) { throw new ApiError(meta.errors.noSuchApp); } - return await pack(ap, user, { + return await Apps.pack(ap, user, { detail: true, - includeSecret: isSecure && ap.userId.equals(user._id) + includeSecret: isSecure && (ap.userId === user.id) }); }); diff --git a/src/server/api/endpoints/auth/accept.ts b/src/server/api/endpoints/auth/accept.ts index cedf7821fe..21a78011dc 100644 --- a/src/server/api/endpoints/auth/accept.ts +++ b/src/server/api/endpoints/auth/accept.ts @@ -1,11 +1,10 @@ import rndstr from 'rndstr'; import * as crypto from 'crypto'; import $ from 'cafy'; -import App from '../../../../models/app'; -import AuthSess from '../../../../models/auth-session'; -import AccessToken from '../../../../models/access-token'; import define from '../../define'; import { ApiError } from '../../error'; +import { AuthSessions, AccessTokens, Apps } from '../../../../models'; +import { genId } from '../../../../misc/gen-id'; export const meta = { tags: ['auth'], @@ -31,10 +30,10 @@ export const meta = { export default define(meta, async (ps, user) => { // Fetch token - const session = await AuthSess + const session = await AuthSessions .findOne({ token: ps.token }); - if (session === null) { + if (session == null) { throw new ApiError(meta.errors.noSuchSession); } @@ -42,16 +41,14 @@ export default define(meta, async (ps, user) => { const accessToken = rndstr('a-zA-Z0-9', 32); // Fetch exist access token - const exist = await AccessToken.findOne({ + const exist = await AccessTokens.findOne({ appId: session.appId, - userId: user._id, + userId: user.id, }); - if (exist === null) { + if (exist == null) { // Lookup app - const app = await App.findOne({ - _id: session.appId - }); + const app = await Apps.findOne(session.appId); // Generate Hash const sha256 = crypto.createHash('sha256'); @@ -59,20 +56,19 @@ export default define(meta, async (ps, user) => { const hash = sha256.digest('hex'); // Insert access token doc - await AccessToken.insert({ + await AccessTokens.save({ + id: genId(), createdAt: new Date(), appId: session.appId, - userId: user._id, + userId: user.id, token: accessToken, hash: hash }); } // Update session - await AuthSess.update(session._id, { - $set: { - userId: user._id - } + await AuthSessions.update(session.id, { + userId: user.id }); return; diff --git a/src/server/api/endpoints/auth/session/generate.ts b/src/server/api/endpoints/auth/session/generate.ts index e12bea7681..5a9bfe6451 100644 --- a/src/server/api/endpoints/auth/session/generate.ts +++ b/src/server/api/endpoints/auth/session/generate.ts @@ -1,10 +1,10 @@ import * as uuid from 'uuid'; import $ from 'cafy'; -import App from '../../../../../models/app'; -import AuthSess from '../../../../../models/auth-session'; import config from '../../../../../config'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { Apps, AuthSessions } from '../../../../../models'; +import { genId } from '../../../../../misc/gen-id'; export const meta = { tags: ['auth'], @@ -46,7 +46,7 @@ export const meta = { export default define(meta, async (ps) => { // Lookup app - const app = await App.findOne({ + const app = await Apps.findOne({ secret: ps.appSecret }); @@ -58,9 +58,10 @@ export default define(meta, async (ps) => { const token = uuid.v4(); // Create session token document - const doc = await AuthSess.insert({ + const doc = await AuthSessions.save({ + id: genId(), createdAt: new Date(), - appId: app._id, + appId: app.id, token: token }); diff --git a/src/server/api/endpoints/auth/session/show.ts b/src/server/api/endpoints/auth/session/show.ts index 992e0a499e..e6ecd8b839 100644 --- a/src/server/api/endpoints/auth/session/show.ts +++ b/src/server/api/endpoints/auth/session/show.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import AuthSess, { pack } from '../../../../../models/auth-session'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { AuthSessions } from '../../../../../models'; export const meta = { tags: ['auth'], @@ -29,7 +29,7 @@ export const meta = { export default define(meta, async (ps, user) => { // Lookup session - const session = await AuthSess.findOne({ + const session = await AuthSessions.findOne({ token: ps.token }); @@ -37,5 +37,5 @@ export default define(meta, async (ps, user) => { throw new ApiError(meta.errors.noSuchSession); } - return await pack(session, user); + return await AuthSessions.pack(session, user); }); diff --git a/src/server/api/endpoints/auth/session/userkey.ts b/src/server/api/endpoints/auth/session/userkey.ts index e09e16e658..8524b96f94 100644 --- a/src/server/api/endpoints/auth/session/userkey.ts +++ b/src/server/api/endpoints/auth/session/userkey.ts @@ -1,10 +1,7 @@ import $ from 'cafy'; -import App from '../../../../../models/app'; -import AuthSess from '../../../../../models/auth-session'; -import AccessToken from '../../../../../models/access-token'; -import { pack } from '../../../../../models/user'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { Apps, AuthSessions, AccessTokens, Users } from '../../../../../models'; export const meta = { tags: ['auth'], @@ -67,7 +64,7 @@ export const meta = { export default define(meta, async (ps) => { // Lookup app - const app = await App.findOne({ + const app = await Apps.findOne({ secret: ps.appSecret }); @@ -76,13 +73,12 @@ export default define(meta, async (ps) => { } // Fetch token - const session = await AuthSess - .findOne({ - token: ps.token, - appId: app._id - }); + const session = await AuthSessions.findOne({ + token: ps.token, + appId: app.id + }); - if (session === null) { + if (session == null) { throw new ApiError(meta.errors.noSuchSession); } @@ -91,25 +87,17 @@ export default define(meta, async (ps) => { } // Lookup access token - const accessToken = await AccessToken.findOne({ - appId: app._id, + const accessToken = await AccessTokens.findOne({ + appId: app.id, userId: session.userId }); // Delete session - - /* https://github.com/Automattic/monk/issues/178 - AuthSess.deleteOne({ - _id: session._id - }); - */ - AuthSess.remove({ - _id: session._id - }); + AuthSessions.delete(session.id); return { accessToken: accessToken.token, - user: await pack(session.userId, null, { + user: await Users.pack(session.userId, null, { detail: true }) }; diff --git a/src/server/api/endpoints/blocking/create.ts b/src/server/api/endpoints/blocking/create.ts index e723cb0386..0d6626b2d5 100644 --- a/src/server/api/endpoints/blocking/create.ts +++ b/src/server/api/endpoints/blocking/create.ts @@ -1,12 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import * as ms from 'ms'; -import { pack } from '../../../../models/user'; -import Blocking from '../../../../models/blocking'; import create from '../../../../services/blocking/create'; import define from '../../define'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { Blockings, NoteWatchings } from '../../../../models'; export const meta = { stability: 'stable', @@ -25,12 +24,11 @@ export const meta = { requireCredential: true, - kind: 'following-write', + kind: 'write:blocks', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -63,7 +61,7 @@ export default define(meta, async (ps, user) => { const blocker = user; // 自分自身 - if (user._id.equals(ps.userId)) { + if (user.id === ps.userId) { throw new ApiError(meta.errors.blockeeIsYourself); } @@ -74,19 +72,22 @@ export default define(meta, async (ps, user) => { }); // Check if already blocking - const exist = await Blocking.findOne({ - blockerId: blocker._id, - blockeeId: blockee._id + const exist = await Blockings.findOne({ + blockerId: blocker.id, + blockeeId: blockee.id }); - if (exist !== null) { + if (exist != null) { throw new ApiError(meta.errors.alreadyBlocking); } // Create blocking await create(blocker, blockee); - return await pack(blockee._id, user, { - detail: true + NoteWatchings.delete({ + userId: blocker.id, + noteUserId: blockee.id }); + + return await Blockings.pack(blockee.id, user); }); diff --git a/src/server/api/endpoints/blocking/delete.ts b/src/server/api/endpoints/blocking/delete.ts index 2a9fdc5e24..e304dca811 100644 --- a/src/server/api/endpoints/blocking/delete.ts +++ b/src/server/api/endpoints/blocking/delete.ts @@ -1,12 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import * as ms from 'ms'; -import { pack } from '../../../../models/user'; -import Blocking from '../../../../models/blocking'; import deleteBlocking from '../../../../services/blocking/delete'; import define from '../../define'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { Blockings } from '../../../../models'; export const meta = { stability: 'stable', @@ -25,12 +24,11 @@ export const meta = { requireCredential: true, - kind: 'following-write', + kind: 'write:blocks', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -63,7 +61,7 @@ export default define(meta, async (ps, user) => { const blocker = user; // Check if the blockee is yourself - if (user._id.equals(ps.userId)) { + if (user.id === ps.userId) { throw new ApiError(meta.errors.blockeeIsYourself); } @@ -74,19 +72,17 @@ export default define(meta, async (ps, user) => { }); // Check not blocking - const exist = await Blocking.findOne({ - blockerId: blocker._id, - blockeeId: blockee._id + const exist = await Blockings.findOne({ + blockerId: blocker.id, + blockeeId: blockee.id }); - if (exist === null) { + if (exist == null) { throw new ApiError(meta.errors.notBlocking); } // Delete blocking await deleteBlocking(blocker, blockee); - return await pack(blockee._id, user, { - detail: true - }); + return await Blockings.pack(blockee.id, user); }); diff --git a/src/server/api/endpoints/blocking/list.ts b/src/server/api/endpoints/blocking/list.ts index b9ad6e8a3f..a078891ab0 100644 --- a/src/server/api/endpoints/blocking/list.ts +++ b/src/server/api/endpoints/blocking/list.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Blocking, { packMany } from '../../../../models/blocking'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { Blockings } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { desc: { @@ -13,7 +14,7 @@ export const meta = { requireCredential: true, - kind: 'following-read', + kind: 'read:blocks', params: { limit: { @@ -23,12 +24,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, }, @@ -41,30 +40,12 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const query = { - blockerId: me._id - } as any; + const query = makePaginationQuery(Blockings.createQueryBuilder('blocking'), ps.sinceId, ps.untilId) + .andWhere(`blocking.blockerId = :meId`, { meId: me.id }); - const sort = { - _id: -1 - }; + const blockings = await query + .take(ps.limit) + .getMany(); - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } - - const blockings = await Blocking - .find(query, { - limit: ps.limit, - sort: sort - }); - - return await packMany(blockings, me); + return await Blockings.packMany(blockings, me); }); diff --git a/src/server/api/endpoints/charts/active-users.ts b/src/server/api/endpoints/charts/active-users.ts index 9dad942e06..60fa72c5c7 100644 --- a/src/server/api/endpoints/charts/active-users.ts +++ b/src/server/api/endpoints/charts/active-users.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import activeUsersChart from '../../../../services/chart/active-users'; +import { convertLog } from '../../../../services/chart/core'; +import { activeUsersChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -28,12 +29,7 @@ export const meta = { }, }, - res: { - type: 'array', - items: { - type: 'object', - }, - }, + res: convertLog(activeUsersChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/drive.ts b/src/server/api/endpoints/charts/drive.ts index 6bbb266f96..a9676e1586 100644 --- a/src/server/api/endpoints/charts/drive.ts +++ b/src/server/api/endpoints/charts/drive.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import driveChart, { driveLogSchema } from '../../../../services/chart/drive'; -import { convertLog } from '../../../../services/chart'; +import { convertLog } from '../../../../services/chart/core'; +import { driveChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -29,7 +29,7 @@ export const meta = { }, }, - res: convertLog(driveLogSchema), + res: convertLog(driveChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/federation.ts b/src/server/api/endpoints/charts/federation.ts index c7b34f1015..95f16c76a7 100644 --- a/src/server/api/endpoints/charts/federation.ts +++ b/src/server/api/endpoints/charts/federation.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import federationChart from '../../../../services/chart/federation'; +import { convertLog } from '../../../../services/chart/core'; +import { federationChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -28,12 +29,7 @@ export const meta = { }, }, - res: { - type: 'array', - items: { - type: 'object', - }, - }, + res: convertLog(federationChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/hashtag.ts b/src/server/api/endpoints/charts/hashtag.ts index 4db6e62408..a7ec12707e 100644 --- a/src/server/api/endpoints/charts/hashtag.ts +++ b/src/server/api/endpoints/charts/hashtag.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import hashtagChart from '../../../../services/chart/hashtag'; +import { convertLog } from '../../../../services/chart/core'; +import { hashtagChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -35,12 +36,7 @@ export const meta = { }, }, - res: { - type: 'array', - items: { - type: 'object', - }, - }, + res: convertLog(hashtagChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/instance.ts b/src/server/api/endpoints/charts/instance.ts index 3fe85f086a..cf3094f7e1 100644 --- a/src/server/api/endpoints/charts/instance.ts +++ b/src/server/api/endpoints/charts/instance.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import instanceChart from '../../../../services/chart/instance'; +import { convertLog } from '../../../../services/chart/core'; +import { instanceChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -36,12 +37,7 @@ export const meta = { } }, - res: { - type: 'array', - items: { - type: 'object', - }, - }, + res: convertLog(instanceChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/network.ts b/src/server/api/endpoints/charts/network.ts index 48b1d0f66f..c0fcd95fe9 100644 --- a/src/server/api/endpoints/charts/network.ts +++ b/src/server/api/endpoints/charts/network.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import networkChart from '../../../../services/chart/network'; +import { convertLog } from '../../../../services/chart/core'; +import { networkChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -28,12 +29,7 @@ export const meta = { }, }, - res: { - type: 'array', - items: { - type: 'object', - }, - }, + res: convertLog(networkChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/notes.ts b/src/server/api/endpoints/charts/notes.ts index cc0ca8bef7..86f30e4b89 100644 --- a/src/server/api/endpoints/charts/notes.ts +++ b/src/server/api/endpoints/charts/notes.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import notesChart, { notesLogSchema } from '../../../../services/chart/notes'; -import { convertLog } from '../../../../services/chart'; +import { convertLog } from '../../../../services/chart/core'; +import { notesChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -29,7 +29,7 @@ export const meta = { }, }, - res: convertLog(notesLogSchema), + res: convertLog(notesChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/user/drive.ts b/src/server/api/endpoints/charts/user/drive.ts index 064c7c7b72..e3696dfda1 100644 --- a/src/server/api/endpoints/charts/user/drive.ts +++ b/src/server/api/endpoints/charts/user/drive.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; import define from '../../../define'; -import perUserDriveChart, { perUserDriveLogSchema } from '../../../../../services/chart/per-user-drive'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import { convertLog } from '../../../../../services/chart'; +import { ID } from '../../../../../misc/cafy-id'; +import { convertLog } from '../../../../../services/chart/core'; +import { perUserDriveChart } from '../../../../../services/chart'; export const meta = { stability: 'stable', @@ -31,7 +31,6 @@ export const meta = { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -39,7 +38,7 @@ export const meta = { } }, - res: convertLog(perUserDriveLogSchema), + res: convertLog(perUserDriveChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/user/following.ts b/src/server/api/endpoints/charts/user/following.ts index f5b1355038..8feba0bd16 100644 --- a/src/server/api/endpoints/charts/user/following.ts +++ b/src/server/api/endpoints/charts/user/following.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; import define from '../../../define'; -import perUserFollowingChart, { perUserFollowingLogSchema } from '../../../../../services/chart/per-user-following'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import { convertLog } from '../../../../../services/chart'; +import { ID } from '../../../../../misc/cafy-id'; +import { convertLog } from '../../../../../services/chart/core'; +import { perUserFollowingChart } from '../../../../../services/chart'; export const meta = { stability: 'stable', @@ -31,7 +31,6 @@ export const meta = { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -39,9 +38,9 @@ export const meta = { } }, - res: convertLog(perUserFollowingLogSchema), + res: convertLog(perUserFollowingChart.schema), }; export default define(meta, async (ps) => { - return await perUserFollowingChart.getChart(ps.span as any, ps.limit, ps.userId); + return await perUserFollowingChart.getChart(ps.span as any, ps.limit, ps.userId); }); diff --git a/src/server/api/endpoints/charts/user/notes.ts b/src/server/api/endpoints/charts/user/notes.ts index 7e31978bf3..8c1db54f76 100644 --- a/src/server/api/endpoints/charts/user/notes.ts +++ b/src/server/api/endpoints/charts/user/notes.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; import define from '../../../define'; -import perUserNotesChart, { perUserNotesLogSchema } from '../../../../../services/chart/per-user-notes'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import { convertLog } from '../../../../../services/chart'; +import { ID } from '../../../../../misc/cafy-id'; +import { convertLog } from '../../../../../services/chart/core'; +import { perUserNotesChart } from '../../../../../services/chart'; export const meta = { stability: 'stable', @@ -31,7 +31,6 @@ export const meta = { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -39,7 +38,7 @@ export const meta = { } }, - res: convertLog(perUserNotesLogSchema), + res: convertLog(perUserNotesChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/user/reactions.ts b/src/server/api/endpoints/charts/user/reactions.ts index 51ff83f20e..7c9b2508ae 100644 --- a/src/server/api/endpoints/charts/user/reactions.ts +++ b/src/server/api/endpoints/charts/user/reactions.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; import define from '../../../define'; -import perUserReactionsChart from '../../../../../services/chart/per-user-reactions'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; +import { convertLog } from '../../../../../services/chart/core'; +import { perUserReactionsChart } from '../../../../../services/chart'; export const meta = { stability: 'stable', @@ -30,7 +31,6 @@ export const meta = { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -38,12 +38,7 @@ export const meta = { } }, - res: { - type: 'array', - items: { - type: 'object', - }, - }, + res: convertLog(perUserReactionsChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/users.ts b/src/server/api/endpoints/charts/users.ts index 9de54a630e..3ed5e09349 100644 --- a/src/server/api/endpoints/charts/users.ts +++ b/src/server/api/endpoints/charts/users.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import usersChart, { usersLogSchema } from '../../../../services/chart/users'; -import { convertLog } from '../../../../services/chart'; +import { convertLog } from '../../../../services/chart/core'; +import { usersChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -29,7 +29,7 @@ export const meta = { }, }, - res: convertLog(usersLogSchema), + res: convertLog(usersChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/drive.ts b/src/server/api/endpoints/drive.ts index 138adffad2..adf780301b 100644 --- a/src/server/api/endpoints/drive.ts +++ b/src/server/api/endpoints/drive.ts @@ -1,6 +1,6 @@ -import DriveFile from '../../../models/drive-file'; import define from '../define'; import fetchMeta from '../../../misc/fetch-meta'; +import { DriveFiles } from '../../../models'; export const meta = { desc: { @@ -12,7 +12,7 @@ export const meta = { requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', res: { type: 'object', @@ -31,27 +31,7 @@ export default define(meta, async (ps, user) => { const instance = await fetchMeta(); // Calculate drive usage - const usage = await DriveFile.aggregate([{ - $match: { - 'metadata.userId': user._id, - 'metadata.deletedAt': { $exists: false } - } - }, { - $project: { - length: true - } - }, { - $group: { - _id: null, - usage: { $sum: '$length' } - } - }]) - .then((aggregates: any[]) => { - if (aggregates.length > 0) { - return aggregates[0].usage; - } - return 0; - }); + const usage = await DriveFiles.clacDriveUsageOf(user); return { capacity: 1024 * 1024 * instance.localDriveCapacityMb, diff --git a/src/server/api/endpoints/drive/files.ts b/src/server/api/endpoints/drive/files.ts index f108e820e7..400b73d3b7 100644 --- a/src/server/api/endpoints/drive/files.ts +++ b/src/server/api/endpoints/drive/files.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import DriveFile, { packMany } from '../../../../models/drive-file'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { DriveFiles } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { desc: { @@ -13,7 +14,7 @@ export const meta = { requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { limit: { @@ -23,18 +24,15 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, folderId: { validator: $.optional.nullable.type(ID), default: null as any, - transform: transform, }, type: { @@ -51,36 +49,24 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const sort = { - _id: -1 - }; + const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId) + .andWhere('file.userId = :userId', { userId: user.id }); - const query = { - 'metadata.userId': user._id, - 'metadata.folderId': ps.folderId, - 'metadata.deletedAt': { $exists: false } - } as any; - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; + if (ps.folderId) { + query.andWhere('file.folderId = :folderId', { folderId: ps.folderId }); + } else { + query.andWhere('file.folderId IS NULL'); } if (ps.type) { - query.contentType = new RegExp(`^${ps.type.replace(/\*/g, '.+?')}$`); + if (ps.type.endsWith('/*')) { + query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); + } else { + query.andWhere('file.type = :type', { type: ps.type }); + } } - const files = await DriveFile - .find(query, { - limit: ps.limit, - sort: sort - }); + const files = await query.take(ps.limit).getMany(); - return await packMany(files, { detail: false, self: true }); + return await DriveFiles.packMany(files, { detail: false, self: true }); }); diff --git a/src/server/api/endpoints/drive/files/attached-notes.ts b/src/server/api/endpoints/drive/files/attached-notes.ts index c9eeab58c5..7214463dde 100644 --- a/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/src/server/api/endpoints/drive/files/attached-notes.ts @@ -1,9 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFile from '../../../../../models/drive-file'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; -import { packMany } from '../../../../../models/note'; import { ApiError } from '../../../error'; +import { DriveFiles } from '../../../../../models'; export const meta = { stability: 'stable', @@ -17,12 +16,11 @@ export const meta = { requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { fileId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のファイルID', 'en-US': 'Target file ID' @@ -48,18 +46,17 @@ export const meta = { export default define(meta, async (ps, user) => { // Fetch file - const file = await DriveFile - .findOne({ - _id: ps.fileId, - 'metadata.userId': user._id, - 'metadata.deletedAt': { $exists: false } - }); + const file = await DriveFiles.findOne({ + id: ps.fileId, + userId: user.id, + }); - if (file === null) { + if (file == null) { throw new ApiError(meta.errors.noSuchFile); } + /* v11 TODO return await packMany(file.metadata.attachedNoteIds || [], user, { detail: true - }); + });*/ }); diff --git a/src/server/api/endpoints/drive/files/check-existence.ts b/src/server/api/endpoints/drive/files/check-existence.ts index 926411c83a..3a87a9497f 100644 --- a/src/server/api/endpoints/drive/files/check-existence.ts +++ b/src/server/api/endpoints/drive/files/check-existence.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; -import DriveFile, { pack } from '../../../../../models/drive-file'; import define from '../../../define'; +import { DriveFiles } from '../../../../../models'; export const meta = { desc: { @@ -12,7 +12,7 @@ export const meta = { requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { md5: { @@ -29,11 +29,12 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const file = await DriveFile.findOne({ + const file = await DriveFiles.findOne({ md5: ps.md5, - 'metadata.userId': user._id, - 'metadata.deletedAt': { $exists: false } + userId: user.id, }); - return { file: file ? await pack(file, { self: true }) : null }; + return { + file: file ? await DriveFiles.pack(file, { self: true }) : null + }; }); diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts index b2979c4888..5702c70fc0 100644 --- a/src/server/api/endpoints/drive/files/create.ts +++ b/src/server/api/endpoints/drive/files/create.ts @@ -1,11 +1,11 @@ import * as ms from 'ms'; import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import { validateFileName, pack } from '../../../../../models/drive-file'; +import { ID } from '../../../../../misc/cafy-id'; import create from '../../../../../services/drive/add-file'; import define from '../../../define'; import { apiLogger } from '../../../logger'; import { ApiError } from '../../../error'; +import { DriveFiles } from '../../../../../models'; export const meta = { desc: { @@ -24,12 +24,11 @@ export const meta = { requireFile: true, - kind: 'drive-write', + kind: 'write:drive', params: { folderId: { validator: $.optional.nullable.type(ID), - transform: transform, default: null as any, desc: { 'ja-JP': 'フォルダID' @@ -78,7 +77,7 @@ export default define(meta, async (ps, user, app, file, cleanup) => { name = null; } else if (name === 'blob') { name = null; - } else if (!validateFileName(name)) { + } else if (!DriveFiles.validateFileName(name)) { throw new ApiError(meta.errors.invalidFileName); } } else { @@ -88,7 +87,7 @@ export default define(meta, async (ps, user, app, file, cleanup) => { try { // Create file const driveFile = await create(user, file.path, name, null, ps.folderId, ps.force, false, null, null, ps.isSensitive); - return pack(driveFile, { self: true }); + return DriveFiles.pack(driveFile, { self: true }); } catch (e) { apiLogger.error(e); throw new ApiError(); diff --git a/src/server/api/endpoints/drive/files/delete.ts b/src/server/api/endpoints/drive/files/delete.ts index dd4e187fcd..d8cc5ec0a1 100644 --- a/src/server/api/endpoints/drive/files/delete.ts +++ b/src/server/api/endpoints/drive/files/delete.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFile from '../../../../../models/drive-file'; +import { ID } from '../../../../../misc/cafy-id'; import del from '../../../../../services/drive/delete-file'; import { publishDriveStream } from '../../../../../services/stream'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { DriveFiles } from '../../../../../models'; export const meta = { stability: 'stable', @@ -18,12 +18,11 @@ export const meta = { requireCredential: true, - kind: 'drive-write', + kind: 'write:drive', params: { fileId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のファイルID', 'en-US': 'Target file ID' @@ -47,17 +46,13 @@ export const meta = { }; export default define(meta, async (ps, user) => { - // Fetch file - const file = await DriveFile - .findOne({ - _id: ps.fileId - }); + const file = await DriveFiles.findOne(ps.fileId); - if (file === null) { + if (file == null) { throw new ApiError(meta.errors.noSuchFile); } - if (!user.isAdmin && !user.isModerator && !file.metadata.userId.equals(user._id)) { + if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } @@ -65,7 +60,5 @@ export default define(meta, async (ps, user) => { await del(file); // Publish fileDeleted event - publishDriveStream(user._id, 'fileDeleted', file._id); - - return; + publishDriveStream(user.id, 'fileDeleted', file.id); }); diff --git a/src/server/api/endpoints/drive/files/find.ts b/src/server/api/endpoints/drive/files/find.ts index 0d4102a48f..265850f84c 100644 --- a/src/server/api/endpoints/drive/files/find.ts +++ b/src/server/api/endpoints/drive/files/find.ts @@ -1,14 +1,14 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFile, { pack } from '../../../../../models/drive-file'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; +import { DriveFiles } from '../../../../../models'; export const meta = { requireCredential: true, tags: ['drive'], - kind: 'drive-read', + kind: 'read:drive', params: { name: { @@ -17,7 +17,6 @@ export const meta = { folderId: { validator: $.optional.nullable.type(ID), - transform: transform, default: null as any, desc: { 'ja-JP': 'フォルダID' @@ -27,12 +26,11 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const files = await DriveFile - .find({ - filename: ps.name, - 'metadata.userId': user._id, - 'metadata.folderId': ps.folderId - }); + const files = await DriveFiles.find({ + name: ps.name, + userId: user.id, + folderId: ps.folderId + }); - return await Promise.all(files.map(file => pack(file, { self: true }))); + return await Promise.all(files.map(file => DriveFiles.pack(file, { self: true }))); }); diff --git a/src/server/api/endpoints/drive/files/show.ts b/src/server/api/endpoints/drive/files/show.ts index 6d63a8605c..b516ec2df6 100644 --- a/src/server/api/endpoints/drive/files/show.ts +++ b/src/server/api/endpoints/drive/files/show.ts @@ -1,10 +1,9 @@ import $ from 'cafy'; -import * as mongo from 'mongodb'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFile, { pack, IDriveFile } from '../../../../../models/drive-file'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; -import config from '../../../../../config'; import { ApiError } from '../../../error'; +import { DriveFile } from '../../../../../models/entities/drive-file'; +import { DriveFiles } from '../../../../../models'; export const meta = { stability: 'stable', @@ -18,12 +17,11 @@ export const meta = { requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { fileId: { validator: $.optional.type(ID), - transform: transform, desc: { 'ja-JP': '対象のファイルID', 'en-US': 'Target file ID' @@ -65,49 +63,33 @@ export const meta = { }; export default define(meta, async (ps, user) => { - let file: IDriveFile; + let file: DriveFile; if (ps.fileId) { - file = await DriveFile.findOne({ - _id: ps.fileId, - 'metadata.deletedAt': { $exists: false } - }); + file = await DriveFiles.findOne(ps.fileId); } else if (ps.url) { - const isInternalStorageUrl = ps.url.startsWith(config.driveUrl); - if (isInternalStorageUrl) { - // Extract file ID from url - // e.g. - // http://misskey.local/files/foo?original=bar --> foo - const fileId = new mongo.ObjectID(ps.url.replace(config.driveUrl, '').replace(/\?(.*)$/, '').replace(/\//g, '')); - file = await DriveFile.findOne({ - _id: fileId, - 'metadata.deletedAt': { $exists: false } - }); - } else { - file = await DriveFile.findOne({ - $or: [{ - 'metadata.url': ps.url - }, { - 'metadata.webpublicUrl': ps.url - }, { - 'metadata.thumbnailUrl': ps.url - }], - 'metadata.deletedAt': { $exists: false } - }); - } + file = await DriveFiles.findOne({ + where: [{ + url: ps.url + }, { + webpublicUrl: ps.url + }, { + thumbnailUrl: ps.url + }], + }); } else { throw new ApiError(meta.errors.fileIdOrUrlRequired); } - if (!user.isAdmin && !user.isModerator && !file.metadata.userId.equals(user._id)) { + if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } - if (file === null) { + if (file == null) { throw new ApiError(meta.errors.noSuchFile); } - return await pack(file, { + return await DriveFiles.pack(file, { detail: true, self: true }); diff --git a/src/server/api/endpoints/drive/files/update.ts b/src/server/api/endpoints/drive/files/update.ts index c8803bec3a..81e86a2734 100644 --- a/src/server/api/endpoints/drive/files/update.ts +++ b/src/server/api/endpoints/drive/files/update.ts @@ -1,11 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFolder from '../../../../../models/drive-folder'; -import DriveFile, { validateFileName, pack } from '../../../../../models/drive-file'; +import { ID } from '../../../../../misc/cafy-id'; import { publishDriveStream } from '../../../../../services/stream'; import define from '../../../define'; -import Note from '../../../../../models/note'; import { ApiError } from '../../../error'; +import { DriveFiles, DriveFolders } from '../../../../../models'; export const meta = { desc: { @@ -17,12 +15,11 @@ export const meta = { requireCredential: true, - kind: 'drive-write', + kind: 'write:drive', params: { fileId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のファイルID' } @@ -30,7 +27,6 @@ export const meta = { folderId: { validator: $.optional.nullable.type(ID), - transform: transform, default: undefined as any, desc: { 'ja-JP': 'フォルダID' @@ -38,7 +34,7 @@ export const meta = { }, name: { - validator: $.optional.str.pipe(validateFileName), + validator: $.optional.str.pipe(DriveFiles.validateFileName), default: undefined as any, desc: { 'ja-JP': 'ファイル名', @@ -78,69 +74,47 @@ export const meta = { }; export default define(meta, async (ps, user) => { - // Fetch file - const file = await DriveFile - .findOne({ - _id: ps.fileId - }); + const file = await DriveFiles.findOne(ps.fileId); - if (file === null) { + if (file == null) { throw new ApiError(meta.errors.noSuchFile); } - if (!user.isAdmin && !user.isModerator && !file.metadata.userId.equals(user._id)) { + if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } - if (ps.name) file.filename = ps.name; + if (ps.name) file.name = ps.name; - if (ps.isSensitive !== undefined) file.metadata.isSensitive = ps.isSensitive; + if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive; if (ps.folderId !== undefined) { if (ps.folderId === null) { - file.metadata.folderId = null; + file.folderId = null; } else { - // Fetch folder - const folder = await DriveFolder - .findOne({ - _id: ps.folderId, - userId: user._id - }); - - if (folder === null) { + const folder = await DriveFolders.findOne({ + id: ps.folderId, + userId: user.id + }); + + if (folder == null) { throw new ApiError(meta.errors.noSuchFolder); } - file.metadata.folderId = folder._id; + file.folderId = folder.id; } } - await DriveFile.update(file._id, { - $set: { - filename: file.filename, - 'metadata.folderId': file.metadata.folderId, - 'metadata.isSensitive': file.metadata.isSensitive - } - }); - - // ドライブのファイルが非正規化されているドキュメントも更新 - Note.find({ - '_files._id': file._id - }).then(notes => { - for (const note of notes) { - note._files[note._files.findIndex(f => f._id.equals(file._id))] = file; - Note.update({ _id: note._id }, { - $set: { - _files: note._files - } - }); - } + await DriveFiles.update(file.id, { + name: file.name, + folderId: file.folderId, + isSensitive: file.isSensitive }); - const fileObj = await pack(file, { self: true }); + const fileObj = await DriveFiles.pack(file, { self: true }); // Publish fileUpdated event - publishDriveStream(user._id, 'fileUpdated', fileObj); + publishDriveStream(user.id, 'fileUpdated', fileObj); return fileObj; }); diff --git a/src/server/api/endpoints/drive/files/upload-from-url.ts b/src/server/api/endpoints/drive/files/upload-from-url.ts index 93a9fa62fa..034ab10f19 100644 --- a/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import * as ms from 'ms'; -import { pack } from '../../../../../models/drive-file'; import uploadFromUrl from '../../../../../services/drive/upload-from-url'; import define from '../../../define'; +import { DriveFiles } from '../../../../../models'; export const meta = { desc: { @@ -19,7 +19,7 @@ export const meta = { requireCredential: true, - kind: 'drive-write', + kind: 'write:drive', params: { url: { @@ -30,7 +30,6 @@ export const meta = { folderId: { validator: $.optional.nullable.type(ID), default: null as any, - transform: transform }, isSensitive: { @@ -53,5 +52,5 @@ export const meta = { }; export default define(meta, async (ps, user) => { - return await pack(await uploadFromUrl(ps.url, user, ps.folderId, null, ps.isSensitive, ps.force), { self: true }); + return await DriveFiles.pack(await uploadFromUrl(ps.url, user, ps.folderId, null, ps.isSensitive, ps.force), { self: true }); }); diff --git a/src/server/api/endpoints/drive/folders.ts b/src/server/api/endpoints/drive/folders.ts index 73c179f7be..f5c3816407 100644 --- a/src/server/api/endpoints/drive/folders.ts +++ b/src/server/api/endpoints/drive/folders.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import DriveFolder, { pack } from '../../../../models/drive-folder'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { DriveFolders } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { desc: { @@ -13,7 +14,7 @@ export const meta = { requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { limit: { @@ -23,18 +24,15 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, folderId: { validator: $.optional.nullable.type(ID), default: null as any, - transform: transform, } }, @@ -47,29 +45,16 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const sort = { - _id: -1 - }; - const query = { - userId: user._id, - parentId: ps.folderId - } as any; - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; + const query = makePaginationQuery(DriveFolders.createQueryBuilder('folder'), ps.sinceId, ps.untilId) + .andWhere('folder.userId = :userId', { userId: user.id }); + + if (ps.folderId) { + query.andWhere('folder.parentId = :parentId', { parentId: ps.folderId }); + } else { + query.andWhere('folder.parentId IS NULL'); } - const folders = await DriveFolder - .find(query, { - limit: ps.limit, - sort: sort - }); + const folders = await query.take(ps.limit).getMany(); - return await Promise.all(folders.map(folder => pack(folder))); + return await Promise.all(folders.map(folder => DriveFolders.pack(folder))); }); diff --git a/src/server/api/endpoints/drive/folders/create.ts b/src/server/api/endpoints/drive/folders/create.ts index 5fab0b91a1..5530abf9dc 100644 --- a/src/server/api/endpoints/drive/folders/create.ts +++ b/src/server/api/endpoints/drive/folders/create.ts @@ -1,9 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFolder, { isValidFolderName, pack } from '../../../../../models/drive-folder'; +import { ID } from '../../../../../misc/cafy-id'; import { publishDriveStream } from '../../../../../services/stream'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { DriveFolders } from '../../../../../models'; +import { genId } from '../../../../../misc/gen-id'; export const meta = { stability: 'stable', @@ -17,11 +18,11 @@ export const meta = { requireCredential: true, - kind: 'drive-write', + kind: 'write:drive', params: { name: { - validator: $.optional.str.pipe(isValidFolderName), + validator: $.optional.str.pipe(DriveFolders.validateFolderName), default: 'Untitled', desc: { 'ja-JP': 'フォルダ名', @@ -31,7 +32,6 @@ export const meta = { parentId: { validator: $.optional.nullable.type(ID), - transform: transform, desc: { 'ja-JP': '親フォルダID', 'en-US': 'Parent folder ID' @@ -53,29 +53,29 @@ export default define(meta, async (ps, user) => { let parent = null; if (ps.parentId) { // Fetch parent folder - parent = await DriveFolder - .findOne({ - _id: ps.parentId, - userId: user._id - }); + parent = await DriveFolders.findOne({ + id: ps.parentId, + userId: user.id + }); - if (parent === null) { + if (parent == null) { throw new ApiError(meta.errors.noSuchFolder); } } // Create folder - const folder = await DriveFolder.insert({ + const folder = await DriveFolders.save({ + id: genId(), createdAt: new Date(), name: ps.name, - parentId: parent !== null ? parent._id : null, - userId: user._id + parentId: parent !== null ? parent.id : null, + userId: user.id }); - const folderObj = await pack(folder); + const folderObj = await DriveFolders.pack(folder); // Publish folderCreated event - publishDriveStream(user._id, 'folderCreated', folderObj); + publishDriveStream(user.id, 'folderCreated', folderObj); return folderObj; }); diff --git a/src/server/api/endpoints/drive/folders/delete.ts b/src/server/api/endpoints/drive/folders/delete.ts index 9f22bf9ea7..fe6c05ad07 100644 --- a/src/server/api/endpoints/drive/folders/delete.ts +++ b/src/server/api/endpoints/drive/folders/delete.ts @@ -1,10 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFolder from '../../../../../models/drive-folder'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { publishDriveStream } from '../../../../../services/stream'; -import DriveFile from '../../../../../models/drive-file'; import { ApiError } from '../../../error'; +import { DriveFolders, DriveFiles } from '../../../../../models'; export const meta = { stability: 'stable', @@ -18,12 +17,11 @@ export const meta = { requireCredential: true, - kind: 'drive-write', + kind: 'write:drive', params: { folderId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のフォルダID', 'en-US': 'Target folder ID' @@ -48,29 +46,26 @@ export const meta = { export default define(meta, async (ps, user) => { // Get folder - const folder = await DriveFolder - .findOne({ - _id: ps.folderId, - userId: user._id - }); + const folder = await DriveFolders.findOne({ + id: ps.folderId, + userId: user.id + }); - if (folder === null) { + if (folder == null) { throw new ApiError(meta.errors.noSuchFolder); } const [childFoldersCount, childFilesCount] = await Promise.all([ - DriveFolder.count({ parentId: folder._id }), - DriveFile.count({ 'metadata.folderId': folder._id }) + DriveFolders.count({ parentId: folder.id }), + DriveFiles.count({ folderId: folder.id }) ]); if (childFoldersCount !== 0 || childFilesCount !== 0) { throw new ApiError(meta.errors.hasChildFilesOrFolders); } - await DriveFolder.remove({ _id: folder._id }); + await DriveFolders.delete(folder.id); // Publish folderCreated event - publishDriveStream(user._id, 'folderDeleted', folder._id); - - return; + publishDriveStream(user.id, 'folderDeleted', folder.id); }); diff --git a/src/server/api/endpoints/drive/folders/find.ts b/src/server/api/endpoints/drive/folders/find.ts index 16b6c10633..f0989ec5ae 100644 --- a/src/server/api/endpoints/drive/folders/find.ts +++ b/src/server/api/endpoints/drive/folders/find.ts @@ -1,14 +1,14 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFolder, { pack } from '../../../../../models/drive-folder'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; +import { DriveFolders } from '../../../../../models'; export const meta = { tags: ['drive'], requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { name: { @@ -17,7 +17,6 @@ export const meta = { parentId: { validator: $.optional.nullable.type(ID), - transform: transform, default: null as any, desc: { 'ja-JP': 'フォルダID' @@ -34,12 +33,11 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const folders = await DriveFolder - .find({ - name: ps.name, - userId: user._id, - parentId: ps.parentId - }); - - return await Promise.all(folders.map(folder => pack(folder))); + const folders = await DriveFolders.find({ + name: ps.name, + userId: user.id, + parentId: ps.parentId + }); + + return await Promise.all(folders.map(folder => DriveFolders.pack(folder))); }); diff --git a/src/server/api/endpoints/drive/folders/show.ts b/src/server/api/endpoints/drive/folders/show.ts index bbfcbed51f..60507e7d7f 100644 --- a/src/server/api/endpoints/drive/folders/show.ts +++ b/src/server/api/endpoints/drive/folders/show.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFolder, { pack } from '../../../../../models/drive-folder'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { DriveFolders } from '../../../../../models'; export const meta = { stability: 'stable', @@ -16,12 +16,11 @@ export const meta = { requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { folderId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のフォルダID', 'en-US': 'Target folder ID' @@ -44,17 +43,16 @@ export const meta = { export default define(meta, async (ps, user) => { // Get folder - const folder = await DriveFolder - .findOne({ - _id: ps.folderId, - userId: user._id - }); + const folder = await DriveFolders.findOne({ + id: ps.folderId, + userId: user.id + }); - if (folder === null) { + if (folder == null) { throw new ApiError(meta.errors.noSuchFolder); } - return await pack(folder, { + return await DriveFolders.pack(folder, { detail: true }); }); diff --git a/src/server/api/endpoints/drive/folders/update.ts b/src/server/api/endpoints/drive/folders/update.ts index a1ee2669f0..90129bed63 100644 --- a/src/server/api/endpoints/drive/folders/update.ts +++ b/src/server/api/endpoints/drive/folders/update.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFolder, { isValidFolderName, pack } from '../../../../../models/drive-folder'; +import { ID } from '../../../../../misc/cafy-id'; import { publishDriveStream } from '../../../../../services/stream'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { DriveFolders } from '../../../../../models'; export const meta = { stability: 'stable', @@ -17,12 +17,11 @@ export const meta = { requireCredential: true, - kind: 'drive-write', + kind: 'write:drive', params: { folderId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のフォルダID', 'en-US': 'Target folder ID' @@ -30,7 +29,7 @@ export const meta = { }, name: { - validator: $.optional.str.pipe(isValidFolderName), + validator: $.optional.str.pipe(DriveFolders.validateFolderName), desc: { 'ja-JP': 'フォルダ名', 'en-US': 'Folder name' @@ -39,7 +38,6 @@ export const meta = { parentId: { validator: $.optional.nullable.type(ID), - transform: transform, desc: { 'ja-JP': '親フォルダID', 'en-US': 'Parent folder ID' @@ -70,46 +68,41 @@ export const meta = { export default define(meta, async (ps, user) => { // Fetch folder - const folder = await DriveFolder - .findOne({ - _id: ps.folderId, - userId: user._id - }); + const folder = await DriveFolders.findOne({ + id: ps.folderId, + userId: user.id + }); - if (folder === null) { + if (folder == null) { throw new ApiError(meta.errors.noSuchFolder); } if (ps.name) folder.name = ps.name; if (ps.parentId !== undefined) { - if (ps.parentId.equals(folder._id)) { + if (ps.parentId === folder.id) { throw new ApiError(meta.errors.recursiveNesting); } else if (ps.parentId === null) { folder.parentId = null; } else { // Get parent folder - const parent = await DriveFolder - .findOne({ - _id: ps.parentId, - userId: user._id - }); + const parent = await DriveFolders.findOne({ + id: ps.parentId, + userId: user.id + }); - if (parent === null) { + if (parent == null) { throw new ApiError(meta.errors.noSuchParentFolder); } // Check if the circular reference will occur async function checkCircle(folderId: any): Promise { // Fetch folder - const folder2 = await DriveFolder.findOne({ - _id: folderId - }, { - _id: true, - parentId: true + const folder2 = await DriveFolders.findOne({ + id: folderId }); - if (folder2._id.equals(folder._id)) { + if (folder2.id === folder.id) { return true; } else if (folder2.parentId) { return await checkCircle(folder2.parentId); @@ -124,22 +117,20 @@ export default define(meta, async (ps, user) => { } } - folder.parentId = parent._id; + folder.parentId = parent.id; } } // Update - DriveFolder.update(folder._id, { - $set: { - name: folder.name, - parentId: folder.parentId - } + DriveFolders.update(folder.id, { + name: folder.name, + parentId: folder.parentId }); - const folderObj = await pack(folder); + const folderObj = await DriveFolders.pack(folder); // Publish folderUpdated event - publishDriveStream(user._id, 'folderUpdated', folderObj); + publishDriveStream(user.id, 'folderUpdated', folderObj); return folderObj; }); diff --git a/src/server/api/endpoints/drive/stream.ts b/src/server/api/endpoints/drive/stream.ts index 916482be4d..9a84627767 100644 --- a/src/server/api/endpoints/drive/stream.ts +++ b/src/server/api/endpoints/drive/stream.ts @@ -1,14 +1,15 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import DriveFile, { packMany } from '../../../../models/drive-file'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { DriveFiles } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['drive'], requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { limit: { @@ -18,12 +19,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, type: { @@ -40,35 +39,18 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const sort = { - _id: -1 - }; - - const query = { - 'metadata.userId': user._id, - 'metadata.deletedAt': { $exists: false } - } as any; - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } + const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId) + .andWhere('file.userId = :userId', { userId: user.id }); if (ps.type) { - query.contentType = new RegExp(`^${ps.type.replace(/\*/g, '.+?')}$`); + if (ps.type.endsWith('/*')) { + query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); + } else { + query.andWhere('file.type = :type', { type: ps.type }); + } } - const files = await DriveFile - .find(query, { - limit: ps.limit, - sort: sort - }); + const files = await query.take(ps.limit).getMany(); - return await packMany(files, { self: true }); + return await DriveFiles.packMany(files, { detail: false, self: true }); }); diff --git a/src/server/api/endpoints/federation/instances.ts b/src/server/api/endpoints/federation/instances.ts index f81f81822e..1946d26dec 100644 --- a/src/server/api/endpoints/federation/instances.ts +++ b/src/server/api/endpoints/federation/instances.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import Instance from '../../../../models/instance'; +import { Instances } from '../../../../models'; +import fetchMeta from '../../../../misc/fetch-meta'; export const meta = { tags: ['federation'], @@ -37,92 +38,55 @@ export const meta = { }; export default define(meta, async (ps, me) => { - let sort; - - if (ps.sort) { - if (ps.sort == '+notes') { - sort = { - notesCount: -1 - }; - } else if (ps.sort == '-notes') { - sort = { - notesCount: 1 - }; - } else if (ps.sort == '+users') { - sort = { - usersCount: -1 - }; - } else if (ps.sort == '-users') { - sort = { - usersCount: 1 - }; - } else if (ps.sort == '+following') { - sort = { - followingCount: -1 - }; - } else if (ps.sort == '-following') { - sort = { - followingCount: 1 - }; - } else if (ps.sort == '+followers') { - sort = { - followersCount: -1 - }; - } else if (ps.sort == '-followers') { - sort = { - followersCount: 1 - }; - } else if (ps.sort == '+caughtAt') { - sort = { - caughtAt: -1 - }; - } else if (ps.sort == '-caughtAt') { - sort = { - caughtAt: 1 - }; - } else if (ps.sort == '+lastCommunicatedAt') { - sort = { - lastCommunicatedAt: -1 - }; - } else if (ps.sort == '-lastCommunicatedAt') { - sort = { - lastCommunicatedAt: 1 - }; - } else if (ps.sort == '+driveUsage') { - sort = { - driveUsage: -1 - }; - } else if (ps.sort == '-driveUsage') { - sort = { - driveUsage: 1 - }; - } else if (ps.sort == '+driveFiles') { - sort = { - driveFiles: -1 - }; - } else if (ps.sort == '-driveFiles') { - sort = { - driveFiles: 1 - }; + const query = Instances.createQueryBuilder('instance'); + + switch (ps.sort) { + case '+notes': query.orderBy('instance.notesCount', 'DESC'); break; + case '-notes': query.orderBy('instance.notesCount', 'ASC'); break; + case '+usersCount': query.orderBy('instance.usersCount', 'DESC'); break; + case '-usersCount': query.orderBy('instance.usersCount', 'ASC'); break; + case '+followingCount': query.orderBy('instance.followingCount', 'DESC'); break; + case '-followingCount': query.orderBy('instance.followingCount', 'ASC'); break; + case '+followersCount': query.orderBy('instance.followersCount', 'DESC'); break; + case '-followersCount': query.orderBy('instance.followersCount', 'ASC'); break; + case '+caughtAt': query.orderBy('instance.caughtAt', 'DESC'); break; + case '-caughtAt': query.orderBy('instance.caughtAt', 'ASC'); break; + case '+lastCommunicatedAt': query.orderBy('instance.lastCommunicatedAt', 'DESC'); break; + case '-lastCommunicatedAt': query.orderBy('instance.lastCommunicatedAt', 'ASC'); break; + case '+driveUsage': query.orderBy('instance.driveUsage', 'DESC'); break; + case '-driveUsage': query.orderBy('instance.driveUsage', 'ASC'); break; + case '+driveFiles': query.orderBy('instance.driveFiles', 'DESC'); break; + case '-driveFiles': query.orderBy('instance.driveFiles', 'ASC'); break; + + default: query.orderBy('instance.id', 'DESC'); break; + } + + if (typeof ps.blocked === 'boolean') { + const meta = await fetchMeta(); + if (ps.blocked) { + query.andWhere('instance.host IN (:...blocks)', { blocks: meta.blockedHosts }); + } else { + query.andWhere('instance.host NOT IN (:...blocks)', { blocks: meta.blockedHosts }); } - } else { - sort = { - _id: -1 - }; } - const q = {} as any; + if (typeof ps.notResponding === 'boolean') { + if (ps.notResponding) { + query.andWhere('instance.isNotResponding = TRUE'); + } else { + query.andWhere('instance.isNotResponding = FALSE'); + } + } - if (typeof ps.blocked === 'boolean') q.isBlocked = ps.blocked; - if (typeof ps.notResponding === 'boolean') q.isNotResponding = ps.notResponding; - if (typeof ps.markedAsClosed === 'boolean') q.isMarkedAsClosed = ps.markedAsClosed; + if (typeof ps.markedAsClosed === 'boolean') { + if (ps.markedAsClosed) { + query.andWhere('instance.isMarkedAsClosed = TRUE'); + } else { + query.andWhere('instance.isMarkedAsClosed = FALSE'); + } + } - const instances = await Instance - .find(q, { - limit: ps.limit, - sort: sort, - skip: ps.offset - }); + const instances = await query.take(ps.limit).skip(ps.offset).getMany(); return instances; }); diff --git a/src/server/api/endpoints/federation/show-instance.ts b/src/server/api/endpoints/federation/show-instance.ts index e7f68620af..875afa05b2 100644 --- a/src/server/api/endpoints/federation/show-instance.ts +++ b/src/server/api/endpoints/federation/show-instance.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../define'; -import Instance from '../../../../models/instance'; +import { Instances } from '../../../../models'; export const meta = { tags: ['federation'], @@ -15,7 +15,7 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const instance = await Instance + const instance = await Instances .findOne({ host: ps.host }); return instance; diff --git a/src/server/api/endpoints/following/create.ts b/src/server/api/endpoints/following/create.ts index 81b2399551..5b43815a5e 100644 --- a/src/server/api/endpoints/following/create.ts +++ b/src/server/api/endpoints/following/create.ts @@ -1,12 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import * as ms from 'ms'; -import { pack } from '../../../../models/user'; -import Following from '../../../../models/following'; import create from '../../../../services/following/create'; import define from '../../define'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { Followings, Users } from '../../../../models'; export const meta = { stability: 'stable', @@ -25,12 +24,11 @@ export const meta = { requireCredential: true, - kind: 'following-write', + kind: 'write:following', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -75,7 +73,7 @@ export default define(meta, async (ps, user) => { const follower = user; // 自分自身 - if (user._id.equals(ps.userId)) { + if (user.id === ps.userId) { throw new ApiError(meta.errors.followeeIsYourself); } @@ -86,12 +84,12 @@ export default define(meta, async (ps, user) => { }); // Check if already following - const exist = await Following.findOne({ - followerId: follower._id, - followeeId: followee._id + const exist = await Followings.findOne({ + followerId: follower.id, + followeeId: followee.id }); - if (exist !== null) { + if (exist != null) { throw new ApiError(meta.errors.alreadyFollowing); } @@ -103,5 +101,5 @@ export default define(meta, async (ps, user) => { throw e; } - return await pack(followee._id, user); + return await Users.pack(followee.id, user); }); diff --git a/src/server/api/endpoints/following/delete.ts b/src/server/api/endpoints/following/delete.ts index 8f8249b1e8..240a037c9e 100644 --- a/src/server/api/endpoints/following/delete.ts +++ b/src/server/api/endpoints/following/delete.ts @@ -1,12 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import * as ms from 'ms'; -import { pack } from '../../../../models/user'; -import Following from '../../../../models/following'; import deleteFollowing from '../../../../services/following/delete'; import define from '../../define'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { Followings, Users } from '../../../../models'; export const meta = { stability: 'stable', @@ -25,12 +24,11 @@ export const meta = { requireCredential: true, - kind: 'following-write', + kind: 'write:following', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -63,7 +61,7 @@ export default define(meta, async (ps, user) => { const follower = user; // Check if the followee is yourself - if (user._id.equals(ps.userId)) { + if (user.id === ps.userId) { throw new ApiError(meta.errors.followeeIsYourself); } @@ -74,16 +72,16 @@ export default define(meta, async (ps, user) => { }); // Check not following - const exist = await Following.findOne({ - followerId: follower._id, - followeeId: followee._id + const exist = await Followings.findOne({ + followerId: follower.id, + followeeId: followee.id }); - if (exist === null) { + if (exist == null) { throw new ApiError(meta.errors.notFollowing); } await deleteFollowing(follower, followee); - return await pack(followee._id, user); + return await Users.pack(followee.id, user); }); diff --git a/src/server/api/endpoints/following/requests/accept.ts b/src/server/api/endpoints/following/requests/accept.ts index 0975990c02..65c24f7be9 100644 --- a/src/server/api/endpoints/following/requests/accept.ts +++ b/src/server/api/endpoints/following/requests/accept.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import acceptFollowRequest from '../../../../../services/following/requests/accept'; import define from '../../../define'; import { ApiError } from '../../../error'; @@ -15,12 +15,11 @@ export const meta = { requireCredential: true, - kind: 'following-write', + kind: 'write:following', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' diff --git a/src/server/api/endpoints/following/requests/cancel.ts b/src/server/api/endpoints/following/requests/cancel.ts index 371f9f0ed3..79cdb776f2 100644 --- a/src/server/api/endpoints/following/requests/cancel.ts +++ b/src/server/api/endpoints/following/requests/cancel.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import cancelFollowRequest from '../../../../../services/following/requests/cancel'; -import { pack } from '../../../../../models/user'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getUser } from '../../../common/getters'; +import { Users } from '../../../../../models'; export const meta = { desc: { @@ -16,12 +16,11 @@ export const meta = { requireCredential: true, - kind: 'following-write', + kind: 'write:following', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -58,5 +57,5 @@ export default define(meta, async (ps, user) => { throw e; } - return await pack(followee._id, user); + return await Users.pack(followee.id, user); }); diff --git a/src/server/api/endpoints/following/requests/list.ts b/src/server/api/endpoints/following/requests/list.ts index c9bcedf929..13e4a39388 100644 --- a/src/server/api/endpoints/following/requests/list.ts +++ b/src/server/api/endpoints/following/requests/list.ts @@ -1,5 +1,5 @@ -import FollowRequest, { pack } from '../../../../../models/follow-request'; import define from '../../../define'; +import { FollowRequests } from '../../../../../models'; export const meta = { desc: { @@ -11,13 +11,13 @@ export const meta = { requireCredential: true, - kind: 'following-read' + kind: 'read:following' }; export default define(meta, async (ps, user) => { - const reqs = await FollowRequest.find({ - followeeId: user._id + const reqs = await FollowRequests.find({ + followeeId: user.id }); - return await Promise.all(reqs.map(req => pack(req))); + return await Promise.all(reqs.map(req => FollowRequests.pack(req))); }); diff --git a/src/server/api/endpoints/following/requests/reject.ts b/src/server/api/endpoints/following/requests/reject.ts index 5e59d4bc97..cccb60b243 100644 --- a/src/server/api/endpoints/following/requests/reject.ts +++ b/src/server/api/endpoints/following/requests/reject.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import rejectFollowRequest from '../../../../../services/following/requests/reject'; import define from '../../../define'; import { ApiError } from '../../../error'; @@ -15,12 +15,11 @@ export const meta = { requireCredential: true, - kind: 'following-write', + kind: 'write:following', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' diff --git a/src/server/api/endpoints/games/reversi/games.ts b/src/server/api/endpoints/games/reversi/games.ts index e3c22c7611..07736e0424 100644 --- a/src/server/api/endpoints/games/reversi/games.ts +++ b/src/server/api/endpoints/games/reversi/games.ts @@ -1,7 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import ReversiGame, { pack } from '../../../../../models/games/reversi/game'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; +import { ReversiGames } from '../../../../../models'; +import { makePaginationQuery } from '../../../common/make-pagination-query'; +import { Brackets } from 'typeorm'; export const meta = { tags: ['games'], @@ -14,12 +16,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, my: { @@ -30,39 +30,20 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const q: any = ps.my ? { - isStarted: true, - $or: [{ - user1Id: user._id - }, { - user2Id: user._id - }] - } : { - isStarted: true - }; - - 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 query = makePaginationQuery(ReversiGames.createQueryBuilder('game'), ps.sinceId, ps.untilId) + .andWhere('game.isStarted = TRUE'); + + if (ps.my) { + query.andWhere(new Brackets(qb => { qb + .where('game.user1Id = :userId', { userId: user.id }) + .orWhere('game.user2Id = :userId', { userId: user.id }); + })); } // Fetch games - const games = await ReversiGame.find(q, { - sort: sort, - limit: ps.limit - }); + const games = await query.take(ps.limit).getMany(); - return await Promise.all(games.map((g) => pack(g, user, { + return await Promise.all(games.map((g) => ReversiGames.pack(g, user, { detail: false }))); }); diff --git a/src/server/api/endpoints/games/reversi/games/show.ts b/src/server/api/endpoints/games/reversi/games/show.ts index 766ca90119..ea2776b16f 100644 --- a/src/server/api/endpoints/games/reversi/games/show.ts +++ b/src/server/api/endpoints/games/reversi/games/show.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../../misc/cafy-id'; -import ReversiGame, { pack } from '../../../../../../models/games/reversi/game'; +import { ID } from '../../../../../../misc/cafy-id'; import Reversi from '../../../../../../games/reversi/core'; import define from '../../../../define'; import { ApiError } from '../../../../error'; +import { ReversiGames } from '../../../../../../models'; export const meta = { tags: ['games'], @@ -11,7 +11,6 @@ export const meta = { params: { gameId: { validator: $.type(ID), - transform: transform, }, }, @@ -25,22 +24,23 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const game = await ReversiGame.findOne({ _id: ps.gameId }); + const game = await ReversiGames.findOne(ps.gameId); if (game == null) { throw new ApiError(meta.errors.noSuchGame); } - const o = new Reversi(game.settings.map, { - isLlotheo: game.settings.isLlotheo, - canPutEverywhere: game.settings.canPutEverywhere, - loopedBoard: game.settings.loopedBoard + const o = new Reversi(game.map, { + isLlotheo: game.isLlotheo, + canPutEverywhere: game.canPutEverywhere, + loopedBoard: game.loopedBoard }); - for (const log of game.logs) + for (const log of game.logs) { o.put(log.color, log.pos); + } - const packed = await pack(game, user); + const packed = await ReversiGames.pack(game, user); return Object.assign({ board: o.board, diff --git a/src/server/api/endpoints/games/reversi/games/surrender.ts b/src/server/api/endpoints/games/reversi/games/surrender.ts index 446210894d..56d66fb205 100644 --- a/src/server/api/endpoints/games/reversi/games/surrender.ts +++ b/src/server/api/endpoints/games/reversi/games/surrender.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../../misc/cafy-id'; -import ReversiGame, { pack } from '../../../../../../models/games/reversi/game'; +import { ID } from '../../../../../../misc/cafy-id'; import { publishReversiGameStream } from '../../../../../../services/stream'; import define from '../../../../define'; import { ApiError } from '../../../../error'; +import { ReversiGames } from '../../../../../../models'; export const meta = { tags: ['games'], @@ -17,7 +17,6 @@ export const meta = { params: { gameId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '投了したい対局' } @@ -46,7 +45,7 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const game = await ReversiGame.findOne({ _id: ps.gameId }); + const game = await ReversiGames.findOne(ps.gameId); if (game == null) { throw new ApiError(meta.errors.noSuchGame); @@ -56,26 +55,20 @@ export default define(meta, async (ps, user) => { throw new ApiError(meta.errors.alreadyEnded); } - if (!game.user1Id.equals(user._id) && !game.user2Id.equals(user._id)) { + if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) { throw new ApiError(meta.errors.accessDenied); } - const winnerId = game.user1Id.equals(user._id) ? game.user2Id : game.user1Id; + const winnerId = game.user1Id === user.id ? game.user2Id : game.user1Id; - await ReversiGame.update({ - _id: game._id - }, { - $set: { - surrendered: user._id, - isEnded: true, - winnerId: winnerId - } + await ReversiGames.update(game.id, { + surrendered: user.id, + isEnded: true, + winnerId: winnerId }); - publishReversiGameStream(game._id, 'ended', { + publishReversiGameStream(game.id, 'ended', { winnerId: winnerId, - game: await pack(game._id, user) + game: await ReversiGames.pack(game.id, user) }); - - return; }); diff --git a/src/server/api/endpoints/games/reversi/invitations.ts b/src/server/api/endpoints/games/reversi/invitations.ts index c204770578..71f5aca1d1 100644 --- a/src/server/api/endpoints/games/reversi/invitations.ts +++ b/src/server/api/endpoints/games/reversi/invitations.ts @@ -1,5 +1,5 @@ -import Matching, { pack as packMatching } from '../../../../../models/games/reversi/matching'; import define from '../../../define'; +import { ReversiMatchings } from '../../../../../models'; export const meta = { tags: ['games'], @@ -9,13 +9,9 @@ export const meta = { export default define(meta, async (ps, user) => { // Find session - const invitations = await Matching.find({ - childId: user._id - }, { - sort: { - _id: -1 - } + const invitations = await ReversiMatchings.find({ + childId: user.id }); - return await Promise.all(invitations.map((i) => packMatching(i, user))); + return await Promise.all(invitations.map((i) => ReversiMatchings.pack(i, user))); }); diff --git a/src/server/api/endpoints/games/reversi/match.ts b/src/server/api/endpoints/games/reversi/match.ts index e66765944d..e34d3c67f4 100644 --- a/src/server/api/endpoints/games/reversi/match.ts +++ b/src/server/api/endpoints/games/reversi/match.ts @@ -1,12 +1,14 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import Matching, { pack as packMatching } from '../../../../../models/games/reversi/matching'; -import ReversiGame, { pack as packGame } from '../../../../../models/games/reversi/game'; +import { ID } from '../../../../../misc/cafy-id'; import { publishMainStream, publishReversiStream } from '../../../../../services/stream'; import { eighteight } from '../../../../../games/reversi/maps'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getUser } from '../../../common/getters'; +import { genId } from '../../../../../misc/gen-id'; +import { ReversiMatchings, ReversiGames } from '../../../../../models'; +import { ReversiGame } from '../../../../../models/entities/games/reversi/game'; +import { ReversiMatching } from '../../../../../models/entities/games/reversi/matching'; export const meta = { tags: ['games'], @@ -16,7 +18,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -41,50 +42,47 @@ export const meta = { export default define(meta, async (ps, user) => { // Myself - if (ps.userId.equals(user._id)) { + if (ps.userId === user.id) { throw new ApiError(meta.errors.isYourself); } // Find session - const exist = await Matching.findOne({ + const exist = await ReversiMatchings.findOne({ parentId: ps.userId, - childId: user._id + childId: user.id }); if (exist) { // Destroy session - Matching.remove({ - _id: exist._id - }); + ReversiMatchings.delete(exist.id); // Create game - const game = await ReversiGame.insert({ + const game = await ReversiGames.save({ + id: genId(), createdAt: new Date(), user1Id: exist.parentId, - user2Id: user._id, + user2Id: user.id, user1Accepted: false, user2Accepted: false, isStarted: false, isEnded: false, logs: [], - settings: { - map: eighteight.data, - bw: 'random', - isLlotheo: false - } - }); + map: eighteight.data, + bw: 'random', + isLlotheo: false + } as ReversiGame); - publishReversiStream(exist.parentId, 'matched', await packGame(game, exist.parentId)); + publishReversiStream(exist.parentId, 'matched', await ReversiGames.pack(game, exist.parentId)); - const other = await Matching.count({ - childId: user._id + const other = await ReversiMatchings.count({ + childId: user.id }); if (other == 0) { - publishMainStream(user._id, 'reversiNoInvites'); + publishMainStream(user.id, 'reversiNoInvites'); } - return await packGame(game, user); + return await ReversiGames.pack(game, user); } else { // Fetch child const child = await getUser(ps.userId).catch(e => { @@ -93,21 +91,22 @@ export default define(meta, async (ps, user) => { }); // 以前のセッションはすべて削除しておく - await Matching.remove({ - parentId: user._id + await ReversiMatchings.delete({ + parentId: user.id }); // セッションを作成 - const matching = await Matching.insert({ + const matching = await ReversiMatchings.save({ + id: genId(), createdAt: new Date(), - parentId: user._id, - childId: child._id - }); + parentId: user.id, + childId: child.id + } as ReversiMatching); - const packed = await packMatching(matching, child); - publishReversiStream(child._id, 'invited', packed); - publishMainStream(child._id, 'reversiInvited', packed); + const packed = await ReversiMatchings.pack(matching, child); + publishReversiStream(child.id, 'invited', packed); + publishMainStream(child.id, 'reversiInvited', packed); - return; + return null; } }); diff --git a/src/server/api/endpoints/games/reversi/match/cancel.ts b/src/server/api/endpoints/games/reversi/match/cancel.ts index fb230032d8..71aaae5ee1 100644 --- a/src/server/api/endpoints/games/reversi/match/cancel.ts +++ b/src/server/api/endpoints/games/reversi/match/cancel.ts @@ -1,5 +1,5 @@ -import Matching from '../../../../../../models/games/reversi/matching'; import define from '../../../../define'; +import { ReversiMatchings } from '../../../../../../models'; export const meta = { tags: ['games'], @@ -8,9 +8,7 @@ export const meta = { }; export default define(meta, async (ps, user) => { - await Matching.remove({ - parentId: user._id + await ReversiMatchings.delete({ + parentId: user.id }); - - return; }); diff --git a/src/server/api/endpoints/hashtags/list.ts b/src/server/api/endpoints/hashtags/list.ts index f454d47fed..7996c81669 100644 --- a/src/server/api/endpoints/hashtags/list.ts +++ b/src/server/api/endpoints/hashtags/list.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../define'; -import Hashtag from '../../../../models/hashtag'; +import { Hashtags } from '../../../../models'; export const meta = { tags: ['hashtags'], @@ -54,40 +54,39 @@ export const meta = { }, }; -const sort: any = { - '+mentionedUsers': { mentionedUsersCount: -1 }, - '-mentionedUsers': { mentionedUsersCount: 1 }, - '+mentionedLocalUsers': { mentionedLocalUsersCount: -1 }, - '-mentionedLocalUsers': { mentionedLocalUsersCount: 1 }, - '+mentionedRemoteUsers': { mentionedRemoteUsersCount: -1 }, - '-mentionedRemoteUsers': { mentionedRemoteUsersCount: 1 }, - '+attachedUsers': { attachedUsersCount: -1 }, - '-attachedUsers': { attachedUsersCount: 1 }, - '+attachedLocalUsers': { attachedLocalUsersCount: -1 }, - '-attachedLocalUsers': { attachedLocalUsersCount: 1 }, - '+attachedRemoteUsers': { attachedRemoteUsersCount: -1 }, - '-attachedRemoteUsers': { attachedRemoteUsersCount: 1 }, -}; - export default define(meta, async (ps, me) => { - const q = {} as any; - if (ps.attachedToUserOnly) q.attachedUsersCount = { $ne: 0 }; - if (ps.attachedToLocalUserOnly) q.attachedLocalUsersCount = { $ne: 0 }; - if (ps.attachedToRemoteUserOnly) q.attachedRemoteUsersCount = { $ne: 0 }; - const tags = await Hashtag - .find(q, { - limit: ps.limit, - sort: sort[ps.sort], - fields: { - tag: true, - mentionedUsersCount: true, - mentionedLocalUsersCount: true, - mentionedRemoteUsersCount: true, - attachedUsersCount: true, - attachedLocalUsersCount: true, - attachedRemoteUsersCount: true - } - }); + const query = Hashtags.createQueryBuilder('tag'); + + if (ps.attachedToUserOnly) query.andWhere('tag.attachedUsersCount != 0'); + if (ps.attachedToLocalUserOnly) query.andWhere('tag.attachedLocalUsersCount != 0'); + if (ps.attachedToRemoteUserOnly) query.andWhere('tag.attachedRemoteUsersCount != 0'); + + switch (ps.sort) { + case '+mentionedUsers': query.orderBy('tag.mentionedUsersCount', 'DESC'); break; + case '-mentionedUsers': query.orderBy('tag.mentionedUsersCount', 'ASC'); break; + case '+mentionedLocalUsers': query.orderBy('tag.mentionedLocalUsersCount', 'DESC'); break; + case '-mentionedLocalUsers': query.orderBy('tag.mentionedLocalUsersCount', 'ASC'); break; + case '+mentionedRemoteUsers': query.orderBy('tag.mentionedRemoteUsersCount', 'DESC'); break; + case '-mentionedRemoteUsers': query.orderBy('tag.mentionedRemoteUsersCount', 'ASC'); break; + case '+attachedUsers': query.orderBy('tag.attachedUsersCount', 'DESC'); break; + case '-attachedUsers': query.orderBy('tag.attachedUsersCount', 'ASC'); break; + case '+attachedLocalUsers': query.orderBy('tag.attachedLocalUsersCount', 'DESC'); break; + case '-attachedLocalUsers': query.orderBy('tag.attachedLocalUsersCount', 'ASC'); break; + case '+attachedRemoteUsers': query.orderBy('tag.attachedRemoteUsersCount', 'DESC'); break; + case '-attachedRemoteUsers': query.orderBy('tag.attachedRemoteUsersCount', 'ASC'); break; + } + + query.select([ + 'tag.name', + 'tag.mentionedUsersCount', + 'tag.mentionedLocalUsersCount', + 'tag.mentionedRemoteUsersCount', + 'tag.attachedUsersCount', + 'tag.attachedLocalUsersCount', + 'tag.attachedRemoteUsersCount', + ]); + + const tags = await query.take(ps.limit).getMany(); return tags; }); diff --git a/src/server/api/endpoints/hashtags/search.ts b/src/server/api/endpoints/hashtags/search.ts index 19b2975adf..fd91a2ebcc 100644 --- a/src/server/api/endpoints/hashtags/search.ts +++ b/src/server/api/endpoints/hashtags/search.ts @@ -1,7 +1,6 @@ import $ from 'cafy'; -import Hashtag from '../../../../models/hashtag'; import define from '../../define'; -import * as escapeRegexp from 'escape-regexp'; +import { Hashtags } from '../../../../models'; export const meta = { desc: { @@ -46,16 +45,12 @@ export const meta = { }; export default define(meta, async (ps) => { - const hashtags = await Hashtag - .find({ - tag: new RegExp('^' + escapeRegexp(ps.query.toLowerCase())) - }, { - sort: { - count: -1 - }, - limit: ps.limit, - skip: ps.offset - }); - - return hashtags.map(tag => tag.tag); + const hashtags = await Hashtags.createQueryBuilder('tag') + .where('tag.name like :q', { q: ps.query.toLowerCase() + '%' }) + .orderBy('tag.count', 'DESC') + .take(ps.limit) + .skip(ps.offset) + .getMany(); + + return hashtags.map(tag => tag.name); }); diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts index 8b8dd70245..c750e72a15 100644 --- a/src/server/api/endpoints/hashtags/trend.ts +++ b/src/server/api/endpoints/hashtags/trend.ts @@ -1,17 +1,19 @@ -import Note from '../../../../models/note'; -import { erase } from '../../../../prelude/array'; import define from '../../define'; import fetchMeta from '../../../../misc/fetch-meta'; +import { Notes } from '../../../../models'; +import { Note } from '../../../../models/entities/note'; /* トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要 ユニーク投稿数とはそのハッシュタグと投稿ユーザーのペアのカウントで、例えば同じユーザーが複数回同じハッシュタグを投稿してもそのハッシュタグのユニーク投稿数は1とカウントされる + +..が理想だけどPostgreSQLでどうするのか分からないので単に「直近Aの内に投稿されたユニーク投稿数が多いハッシュタグ」で妥協する */ const rangeA = 1000 * 60 * 30; // 30分 -const rangeB = 1000 * 60 * 120; // 2時間 -const coefficient = 1.25; // 「n倍」の部分 -const requiredUsers = 3; // 最低何人がそのタグを投稿している必要があるか +//const rangeB = 1000 * 60 * 120; // 2時間 +//const coefficient = 1.25; // 「n倍」の部分 +//const requiredUsers = 3; // 最低何人がそのタグを投稿している必要があるか const max = 5; @@ -23,92 +25,47 @@ export const meta = { export default define(meta, async () => { const instance = await fetchMeta(); - const hidedTags = instance.hidedTags.map(t => t.toLowerCase()); - - //#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計 - const data = await Note.aggregate([{ - $match: { - createdAt: { - $gt: new Date(Date.now() - rangeA) - }, - tagsLower: { - $exists: true, - $ne: [] - } - } - }, { - $unwind: '$tagsLower' - }, { - $group: { - _id: { tag: '$tagsLower', userId: '$userId' } - } - }]) as { - _id: { - tag: string; - userId: any; - } - }[]; - //#endregion + const hiddenTags = instance.hiddenTags.map(t => t.toLowerCase()); + + const tagNotes = await Notes.createQueryBuilder('note') + .where(`note.createdAt > :date`, { date: new Date(Date.now() - rangeA) }) + .andWhere(`note.tags != '{}'`) + .select(['note.tags', 'note.userId']) + .getMany(); - if (data.length == 0) { + if (tagNotes.length === 0) { return []; } const tags: { name: string; - count: number; + users: Note['userId'][]; }[] = []; - // カウント - for (const x of data.map(x => x._id).filter(x => !hidedTags.includes(x.tag))) { - const i = tags.findIndex(tag => tag.name == x.tag); - if (i != -1) { - tags[i].count++; - } else { - tags.push({ - name: x.tag, - count: 1 - }); - } - } - - // 最低要求投稿者数を下回るならカットする - const limitedTags = tags.filter(tag => tag.count >= requiredUsers); - - //#region 2. 1で取得したそれぞれのタグについて、「直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上」かどうかを判定する - const hotsPromises = limitedTags.map(async tag => { - const passedCount = (await Note.distinct('userId', { - tagsLower: tag.name, - createdAt: { - $lt: new Date(Date.now() - rangeA), - $gt: new Date(Date.now() - rangeB) + for (const note of tagNotes) { + for (const tag of note.tags) { + if (hiddenTags.includes(tag)) continue; + + const x = tags.find(x => x.name === tag); + if (x) { + if (!x.users.includes(note.userId)) { + x.users.push(note.userId); + } + } else { + tags.push({ + name: tag, + users: [note.userId] + }); } - }) as any).length; - - if (tag.count >= (passedCount * coefficient)) { - return tag; - } else { - return null; } - }); - //#endregion + } // タグを人気順に並べ替え - let hots = erase(null, await Promise.all(hotsPromises)) - .sort((a, b) => b.count - a.count) + const hots = tags + .sort((a, b) => b.users.length - a.users.length) .map(tag => tag.name) .slice(0, max); - //#region 3. もし上記の方法でのトレンド抽出の結果、求められているタグ数に達しなければ「ただ単に現在投稿数が多いハッシュタグ」に切り替える - if (hots.length < max) { - hots = hots.concat(tags - .filter(tag => hots.indexOf(tag.name) == -1) - .sort((a, b) => b.count - a.count) - .map(tag => tag.name) - .slice(0, max - hots.length)); - } - //#endregion - //#region 2(または3)で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する const countPromises: Promise[] = []; @@ -118,23 +75,25 @@ export default define(meta, async () => { const interval = 1000 * 60 * 10; for (let i = 0; i < range; i++) { - countPromises.push(Promise.all(hots.map(tag => Note.distinct('userId', { - tagsLower: tag, - createdAt: { - $lt: new Date(Date.now() - (interval * i)), - $gt: new Date(Date.now() - (interval * (i + 1))) - } - })))); + countPromises.push(Promise.all(hots.map(tag => Notes.createQueryBuilder('note') + .select('count(distinct note.userId)') + .where(':tag = ANY(note.tags)', { tag: tag }) + .andWhere('note.createdAt < :lt', { lt: new Date(Date.now() - (interval * i)) }) + .andWhere('note.createdAt > :gt', { gt: new Date(Date.now() - (interval * (i + 1))) }) + .getRawOne() + .then(x => parseInt(x.count, 10)) + ))); } const countsLog = await Promise.all(countPromises); - const totalCounts: any = await Promise.all(hots.map(tag => Note.distinct('userId', { - tagsLower: tag, - createdAt: { - $gt: new Date(Date.now() - (interval * range)) - } - }))); + const totalCounts: any = await Promise.all(hots.map(tag => Notes.createQueryBuilder('note') + .select('count(distinct note.userId)') + .where(':tag = ANY(note.tags)', { tag: tag }) + .andWhere('note.createdAt > :gt', { gt: new Date(Date.now() - (interval * range)) }) + .getRawOne() + .then(x => parseInt(x.count, 10)) + )); //#endregion const stats = hots.map((tag, i) => ({ diff --git a/src/server/api/endpoints/hashtags/users.ts b/src/server/api/endpoints/hashtags/users.ts index 4b047aee95..20cba96d0e 100644 --- a/src/server/api/endpoints/hashtags/users.ts +++ b/src/server/api/endpoints/hashtags/users.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; -import User, { pack } from '../../../../models/user'; import define from '../../define'; +import { Users } from '../../../../models'; export const meta = { requireCredential: false, @@ -54,39 +54,32 @@ export const meta = { }, }; -const sort: any = { - '+follower': { followersCount: -1 }, - '-follower': { followersCount: 1 }, - '+createdAt': { createdAt: -1 }, - '-createdAt': { createdAt: 1 }, - '+updatedAt': { updatedAt: -1 }, - '-updatedAt': { updatedAt: 1 }, -}; - export default define(meta, async (ps, me) => { - const q = { - tags: ps.tag, - $and: [] - } as any; + const query = Users.createQueryBuilder('user') + .where(':tag = ANY(user.tags)', { tag: ps.tag }); + + const recent = new Date(Date.now() - (1000 * 60 * 60 * 24 * 5)); + + if (ps.state === 'alive') { + query.andWhere('user.updatedAt > :date', { date: recent }); + } - // state - q.$and.push( - ps.state == 'alive' ? { updatedAt: { $gt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 5)) } } : - {} - ); + if (ps.origin === 'local') { + query.andWhere('user.host IS NULL'); + } else if (ps.origin === 'remote') { + query.andWhere('user.host IS NOT NULL'); + } - // origin - q.$and.push( - ps.origin == 'local' ? { host: null } : - ps.origin == 'remote' ? { host: { $ne: null } } : - {} - ); + switch (ps.sort) { + case '+follower': query.orderBy('user.followersCount', 'DESC'); break; + case '-follower': query.orderBy('user.followersCount', 'ASC'); break; + case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; + case '+updatedAt': query.orderBy('user.updatedAt', 'DESC'); break; + case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break; + } - const users = await User - .find(q, { - limit: ps.limit, - sort: sort[ps.sort], - }); + const users = await query.take(ps.limit).getMany(); - return await Promise.all(users.map(user => pack(user, me, { detail: true }))); + return await Users.packMany(users, me, { detail: true }); }); diff --git a/src/server/api/endpoints/i.ts b/src/server/api/endpoints/i.ts index 7b50cc76c2..afad38c469 100644 --- a/src/server/api/endpoints/i.ts +++ b/src/server/api/endpoints/i.ts @@ -1,5 +1,5 @@ -import { pack } from '../../../models/user'; import define from '../define'; +import { Users } from '../../../models'; export const meta = { stability: 'stable', @@ -22,7 +22,7 @@ export const meta = { export default define(meta, async (ps, user, app) => { const isSecure = user != null && app == null; - return await pack(user, user, { + return await Users.pack(user, user, { detail: true, includeHasUnreadNotes: true, includeSecrets: isSecure diff --git a/src/server/api/endpoints/i/2fa/done.ts b/src/server/api/endpoints/i/2fa/done.ts index 556354c386..8ccb09b8b7 100644 --- a/src/server/api/endpoints/i/2fa/done.ts +++ b/src/server/api/endpoints/i/2fa/done.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import * as speakeasy from 'speakeasy'; -import User from '../../../../../models/user'; import define from '../../../define'; +import { Users } from '../../../../../models'; export const meta = { requireCredential: true, @@ -32,12 +32,8 @@ export default define(meta, async (ps, user) => { throw new Error('not verified'); } - await User.update(user._id, { - $set: { - 'twoFactorSecret': user.twoFactorTempSecret, - 'twoFactorEnabled': true - } + await Users.update(user.id, { + twoFactorSecret: user.twoFactorTempSecret, + twoFactorEnabled: true }); - - return; }); diff --git a/src/server/api/endpoints/i/2fa/register.ts b/src/server/api/endpoints/i/2fa/register.ts index 302b51ec0b..5efe77900a 100644 --- a/src/server/api/endpoints/i/2fa/register.ts +++ b/src/server/api/endpoints/i/2fa/register.ts @@ -2,9 +2,9 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; import * as QRCode from 'qrcode'; -import User from '../../../../../models/user'; import config from '../../../../../config'; import define from '../../../define'; +import { Users } from '../../../../../models'; export const meta = { requireCredential: true, @@ -31,10 +31,8 @@ export default define(meta, async (ps, user) => { length: 32 }); - await User.update(user._id, { - $set: { - twoFactorTempSecret: secret.base32 - } + await Users.update(user.id, { + twoFactorTempSecret: secret.base32 }); // Get the data URL of the authenticator URL diff --git a/src/server/api/endpoints/i/2fa/unregister.ts b/src/server/api/endpoints/i/2fa/unregister.ts index 37b2639198..fb3ecd4043 100644 --- a/src/server/api/endpoints/i/2fa/unregister.ts +++ b/src/server/api/endpoints/i/2fa/unregister.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; -import User from '../../../../../models/user'; import define from '../../../define'; +import { Users } from '../../../../../models'; export const meta = { requireCredential: true, @@ -23,11 +23,9 @@ export default define(meta, async (ps, user) => { throw new Error('incorrect password'); } - await User.update(user._id, { - $set: { - 'twoFactorSecret': null, - 'twoFactorEnabled': false - } + await Users.update(user.id, { + twoFactorSecret: null, + twoFactorEnabled: false }); return; diff --git a/src/server/api/endpoints/i/authorized-apps.ts b/src/server/api/endpoints/i/authorized-apps.ts index cb8be9ed97..ebf04fcb58 100644 --- a/src/server/api/endpoints/i/authorized-apps.ts +++ b/src/server/api/endpoints/i/authorized-apps.ts @@ -1,7 +1,6 @@ import $ from 'cafy'; -import AccessToken from '../../../../models/access-token'; -import { pack } from '../../../../models/app'; import define from '../../define'; +import { AccessTokens, Apps } from '../../../../models'; export const meta = { requireCredential: true, @@ -28,18 +27,18 @@ export const meta = { export default define(meta, async (ps, user) => { // Get tokens - const tokens = await AccessToken - .find({ - userId: user._id - }, { - limit: ps.limit, - skip: ps.offset, - sort: { - _id: ps.sort == 'asc' ? 1 : -1 - } - }); + const tokens = await AccessTokens.find({ + where: { + userId: user.id + }, + take: ps.limit, + skip: ps.offset, + order: { + id: ps.sort == 'asc' ? 1 : -1 + } + }); - return await Promise.all(tokens.map(token => pack(token.appId, user, { + return await Promise.all(tokens.map(token => Apps.pack(token.appId, user, { detail: true }))); }); diff --git a/src/server/api/endpoints/i/change-password.ts b/src/server/api/endpoints/i/change-password.ts index 8ab286b4bf..f8f977200f 100644 --- a/src/server/api/endpoints/i/change-password.ts +++ b/src/server/api/endpoints/i/change-password.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; -import User from '../../../../models/user'; import define from '../../define'; +import { Users } from '../../../../models'; export const meta = { requireCredential: true, @@ -31,11 +31,7 @@ export default define(meta, async (ps, user) => { const salt = await bcrypt.genSalt(8); const hash = await bcrypt.hash(ps.newPassword, salt); - await User.update(user._id, { - $set: { - 'password': hash - } + await Users.update(user.id, { + password: hash }); - - return; }); diff --git a/src/server/api/endpoints/i/clear-follow-request-notification.ts b/src/server/api/endpoints/i/clear-follow-request-notification.ts deleted file mode 100644 index 38c6ec1cef..0000000000 --- a/src/server/api/endpoints/i/clear-follow-request-notification.ts +++ /dev/null @@ -1,23 +0,0 @@ -import User from '../../../../models/user'; -import define from '../../define'; - -export const meta = { - tags: ['account', 'following'], - - requireCredential: true, - - kind: 'account-write', - - params: { - } -}; - -export default define(meta, async (ps, user) => { - await User.update({ _id: user._id }, { - $set: { - pendingReceivedFollowRequestsCount: 0 - } - }); - - return; -}); diff --git a/src/server/api/endpoints/i/delete-account.ts b/src/server/api/endpoints/i/delete-account.ts index fed38eab5a..5aff74e0cc 100644 --- a/src/server/api/endpoints/i/delete-account.ts +++ b/src/server/api/endpoints/i/delete-account.ts @@ -1,10 +1,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; -import User from '../../../../models/user'; import define from '../../define'; -import { createDeleteNotesJob, createDeleteDriveFilesJob } from '../../../../queue'; -import Message from '../../../../models/messaging-message'; -import Signin from '../../../../models/signin'; +import { Users } from '../../../../models'; export const meta = { requireCredential: true, @@ -26,27 +23,5 @@ export default define(meta, async (ps, user) => { throw new Error('incorrect password'); } - await User.update({ _id: user._id }, { - $set: { - isDeleted: true, - name: null, - description: null, - pinnedNoteIds: [], - password: null, - email: null, - twitter: null, - github: null, - discord: null, - profile: {}, - fields: [], - clientSettings: {}, - } - }); - - Message.remove({ userId: user._id }); - Signin.remove({ userId: user._id }); - createDeleteNotesJob(user); - createDeleteDriveFilesJob(user); - - return; + await Users.delete(user.id); }); diff --git a/src/server/api/endpoints/i/export-blocking.ts b/src/server/api/endpoints/i/export-blocking.ts index 346b29c79d..14d49487e8 100644 --- a/src/server/api/endpoints/i/export-blocking.ts +++ b/src/server/api/endpoints/i/export-blocking.ts @@ -13,6 +13,4 @@ export const meta = { export default define(meta, async (ps, user) => { createExportBlockingJob(user); - - return; }); diff --git a/src/server/api/endpoints/i/export-following.ts b/src/server/api/endpoints/i/export-following.ts index 5977b03105..50dd28837f 100644 --- a/src/server/api/endpoints/i/export-following.ts +++ b/src/server/api/endpoints/i/export-following.ts @@ -13,6 +13,4 @@ export const meta = { export default define(meta, async (ps, user) => { createExportFollowingJob(user); - - return; }); diff --git a/src/server/api/endpoints/i/export-mute.ts b/src/server/api/endpoints/i/export-mute.ts index 22ceff3631..1eb51cd77e 100644 --- a/src/server/api/endpoints/i/export-mute.ts +++ b/src/server/api/endpoints/i/export-mute.ts @@ -13,6 +13,4 @@ export const meta = { export default define(meta, async (ps, user) => { createExportMuteJob(user); - - return; }); diff --git a/src/server/api/endpoints/i/export-notes.ts b/src/server/api/endpoints/i/export-notes.ts index 2881aa2697..dd32c18d11 100644 --- a/src/server/api/endpoints/i/export-notes.ts +++ b/src/server/api/endpoints/i/export-notes.ts @@ -13,6 +13,4 @@ export const meta = { export default define(meta, async (ps, user) => { createExportNotesJob(user); - - return; }); diff --git a/src/server/api/endpoints/i/export-user-lists.ts b/src/server/api/endpoints/i/export-user-lists.ts index 9d7424ad89..7650ca7210 100644 --- a/src/server/api/endpoints/i/export-user-lists.ts +++ b/src/server/api/endpoints/i/export-user-lists.ts @@ -13,6 +13,4 @@ export const meta = { export default define(meta, async (ps, user) => { createExportUserListsJob(user); - - return; }); diff --git a/src/server/api/endpoints/i/favorites.ts b/src/server/api/endpoints/i/favorites.ts index 7ea6f7b966..d2d149b2d1 100644 --- a/src/server/api/endpoints/i/favorites.ts +++ b/src/server/api/endpoints/i/favorites.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Favorite, { packMany } from '../../../../models/favorite'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { NoteFavorites } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { desc: { @@ -23,42 +24,22 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, - } + }, } }; export default define(meta, async (ps, user) => { - const query = { - userId: user._id - } as any; - - const sort = { - _id: -1 - }; - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } + const query = makePaginationQuery(NoteFavorites.createQueryBuilder('favorite'), ps.sinceId, ps.untilId) + .andWhere(`favorite.userId = :meId`, { meId: user.id }) + .leftJoinAndSelect('favorite.note', 'note'); - // Get favorites - const favorites = await Favorite - .find(query, { - limit: ps.limit, - sort: sort - }); + const favorites = await query + .take(ps.limit) + .getMany(); - return await packMany(favorites, user); + return await NoteFavorites.packMany(favorites, user); }); diff --git a/src/server/api/endpoints/i/import-following.ts b/src/server/api/endpoints/i/import-following.ts index f188291bc2..deafec18ec 100644 --- a/src/server/api/endpoints/i/import-following.ts +++ b/src/server/api/endpoints/i/import-following.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { createImportFollowingJob } from '../../../../queue'; import ms = require('ms'); -import DriveFile from '../../../../models/drive-file'; import { ApiError } from '../../error'; +import { DriveFiles } from '../../../../models'; export const meta = { secure: true, @@ -17,7 +17,6 @@ export const meta = { params: { fileId: { validator: $.type(ID), - transform: transform, } }, @@ -49,16 +48,12 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const file = await DriveFile.findOne({ - _id: ps.fileId - }); + const file = await DriveFiles.findOne(ps.fileId); if (file == null) throw new ApiError(meta.errors.noSuchFile); - //if (!file.contentType.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); - if (file.length > 50000) throw new ApiError(meta.errors.tooBigFile); - if (file.length === 0) throw new ApiError(meta.errors.emptyFile); + //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); + if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile); + if (file.size === 0) throw new ApiError(meta.errors.emptyFile); - createImportFollowingJob(user, file._id); - - return; + createImportFollowingJob(user, file.id); }); diff --git a/src/server/api/endpoints/i/import-user-lists.ts b/src/server/api/endpoints/i/import-user-lists.ts index ed3085e5f8..b7d9d029b7 100644 --- a/src/server/api/endpoints/i/import-user-lists.ts +++ b/src/server/api/endpoints/i/import-user-lists.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { createImportUserListsJob } from '../../../../queue'; import ms = require('ms'); -import DriveFile from '../../../../models/drive-file'; import { ApiError } from '../../error'; +import { DriveFiles } from '../../../../models'; export const meta = { secure: true, @@ -17,7 +17,6 @@ export const meta = { params: { fileId: { validator: $.type(ID), - transform: transform, } }, @@ -49,16 +48,12 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const file = await DriveFile.findOne({ - _id: ps.fileId - }); + const file = await DriveFiles.findOne(ps.fileId); if (file == null) throw new ApiError(meta.errors.noSuchFile); - //if (!file.contentType.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); - if (file.length > 30000) throw new ApiError(meta.errors.tooBigFile); - if (file.length === 0) throw new ApiError(meta.errors.emptyFile); + //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); + if (file.size > 30000) throw new ApiError(meta.errors.tooBigFile); + if (file.size === 0) throw new ApiError(meta.errors.emptyFile); - createImportUserListsJob(user, file._id); - - return; + createImportUserListsJob(user, file.id); }); diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts index d3e3064abd..9b016e0a2d 100644 --- a/src/server/api/endpoints/i/notifications.ts +++ b/src/server/api/endpoints/i/notifications.ts @@ -1,11 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Notification from '../../../../models/notification'; -import { packMany } from '../../../../models/notification'; -import { getFriendIds } from '../../common/get-friends'; -import read from '../../common/read-notification'; +import { ID } from '../../../../misc/cafy-id'; +import { readNotification } from '../../common/read-notification'; import define from '../../define'; -import { getHideUserIds } from '../../common/get-hide-users'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Notifications, Followings, Mutings } from '../../../../models'; export const meta = { desc: { @@ -17,7 +15,7 @@ export const meta = { requireCredential: true, - kind: 'account-read', + kind: 'read:notifications', params: { limit: { @@ -27,12 +25,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, following: { @@ -46,12 +42,12 @@ export const meta = { }, includeTypes: { - validator: $.optional.arr($.str.or(['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'poll_vote', 'receiveFollowRequest'])), + validator: $.optional.arr($.str.or(['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest'])), default: [] as string[] }, excludeTypes: { - validator: $.optional.arr($.str.or(['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'poll_vote', 'receiveFollowRequest'])), + validator: $.optional.arr($.str.or(['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest'])), default: [] as string[] } }, @@ -65,63 +61,38 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const hideUserIds = await getHideUserIds(user); + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: user.id }); - const query = { - notifieeId: user._id, - $and: [{ - notifierId: { - $nin: hideUserIds - } - }] - } as any; + const mutingQuery = Mutings.createQueryBuilder('muting') + .select('muting.muteeId') + .where('muting.muterId = :muterId', { muterId: user.id }); - const sort = { - _id: -1 - }; + const query = makePaginationQuery(Notifications.createQueryBuilder('notification'), ps.sinceId, ps.untilId) + .andWhere(`notification.notifieeId = :meId`, { meId: user.id }) + .leftJoinAndSelect('notification.notifier', 'notifier'); - if (ps.following) { - // ID list of the user itself and other users who the user follows - const followingIds = await getFriendIds(user._id); - - query.$and.push({ - notifierId: { - $in: followingIds - } - }); - } + query.andWhere(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`); + query.setParameters(mutingQuery.getParameters()); - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; + if (ps.following) { + query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: user.id }); + query.setParameters(followingQuery.getParameters()); } if (ps.includeTypes.length > 0) { - query.type = { - $in: ps.includeTypes - }; + query.andWhere(`notification.type IN (:...includeTypes)`, { includeTypes: ps.includeTypes }); } else if (ps.excludeTypes.length > 0) { - query.type = { - $nin: ps.excludeTypes - }; + query.andWhere(`notification.type NOT IN (:...excludeTypes)`, { excludeTypes: ps.excludeTypes }); } - const notifications = await Notification - .find(query, { - limit: ps.limit, - sort: sort - }); + const notifications = await query.take(ps.limit).getMany(); // Mark all as read if (notifications.length > 0 && ps.markAsRead) { - read(user._id, notifications); + readNotification(user.id, notifications.map(x => x.id)); } - return await packMany(notifications); + return await Notifications.packMany(notifications); }); diff --git a/src/server/api/endpoints/i/pin.ts b/src/server/api/endpoints/i/pin.ts index 8d853d45c8..ac104b19f9 100644 --- a/src/server/api/endpoints/i/pin.ts +++ b/src/server/api/endpoints/i/pin.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import { pack } from '../../../../models/user'; +import { ID } from '../../../../misc/cafy-id'; import { addPinned } from '../../../../services/i/pin'; import define from '../../define'; import { ApiError } from '../../error'; +import { Users } from '../../../../models'; export const meta = { stability: 'stable', @@ -16,12 +16,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' @@ -58,7 +57,7 @@ export default define(meta, async (ps, user) => { throw e; }); - return await pack(user, user, { + return await Users.pack(user, user, { detail: true }); }); diff --git a/src/server/api/endpoints/i/read-all-messaging-messages.ts b/src/server/api/endpoints/i/read-all-messaging-messages.ts index bbbfa0d7b3..e8ada277e9 100644 --- a/src/server/api/endpoints/i/read-all-messaging-messages.ts +++ b/src/server/api/endpoints/i/read-all-messaging-messages.ts @@ -1,7 +1,6 @@ -import User from '../../../../models/user'; import { publishMainStream } from '../../../../services/stream'; -import Message from '../../../../models/messaging-message'; import define from '../../define'; +import { MessagingMessages } from '../../../../models'; export const meta = { desc: { @@ -13,7 +12,7 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { } @@ -21,24 +20,12 @@ export const meta = { export default define(meta, async (ps, user) => { // Update documents - await Message.update({ - recipientId: user._id, + await MessagingMessages.update({ + recipientId: user.id, isRead: false }, { - $set: { - isRead: true - } - }, { - multi: true - }); - - User.update({ _id: user._id }, { - $set: { - hasUnreadMessagingMessage: false - } + isRead: true }); - publishMainStream(user._id, 'readAllMessagingMessages'); - - return; + publishMainStream(user.id, 'readAllMessagingMessages'); }); diff --git a/src/server/api/endpoints/i/read-all-unread-notes.ts b/src/server/api/endpoints/i/read-all-unread-notes.ts index 742c2d9908..cc8ebf58ec 100644 --- a/src/server/api/endpoints/i/read-all-unread-notes.ts +++ b/src/server/api/endpoints/i/read-all-unread-notes.ts @@ -1,7 +1,6 @@ -import User from '../../../../models/user'; import { publishMainStream } from '../../../../services/stream'; -import NoteUnread from '../../../../models/note-unread'; import define from '../../define'; +import { NoteUnreads } from '../../../../models'; export const meta = { desc: { @@ -13,7 +12,7 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { } @@ -21,20 +20,11 @@ export const meta = { export default define(meta, async (ps, user) => { // Remove documents - await NoteUnread.remove({ - userId: user._id - }); - - User.update({ _id: user._id }, { - $set: { - hasUnreadMentions: false, - hasUnreadSpecifiedNotes: false - } + await NoteUnreads.delete({ + userId: user.id }); // 全て既読になったイベントを発行 - publishMainStream(user._id, 'readAllUnreadMentions'); - publishMainStream(user._id, 'readAllUnreadSpecifiedNotes'); - - return; + publishMainStream(user.id, 'readAllUnreadMentions'); + publishMainStream(user.id, 'readAllUnreadSpecifiedNotes'); }); diff --git a/src/server/api/endpoints/i/regenerate-token.ts b/src/server/api/endpoints/i/regenerate-token.ts index ad10b99b36..729c1a300a 100644 --- a/src/server/api/endpoints/i/regenerate-token.ts +++ b/src/server/api/endpoints/i/regenerate-token.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; -import User from '../../../../models/user'; import { publishMainStream } from '../../../../services/stream'; import generateUserToken from '../../common/generate-native-user-token'; import define from '../../define'; +import { Users } from '../../../../models'; export const meta = { requireCredential: true, @@ -28,14 +28,10 @@ export default define(meta, async (ps, user) => { // Generate secret const secret = generateUserToken(); - await User.update(user._id, { - $set: { - 'token': secret - } + await Users.update(user.id, { + token: secret }); // Publish event - publishMainStream(user._id, 'myTokenRegenerated'); - - return; + publishMainStream(user.id, 'myTokenRegenerated'); }); diff --git a/src/server/api/endpoints/i/signin-history.ts b/src/server/api/endpoints/i/signin-history.ts index 87160a9f91..e9ae19d734 100644 --- a/src/server/api/endpoints/i/signin-history.ts +++ b/src/server/api/endpoints/i/signin-history.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Signin, { pack } from '../../../../models/signin'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { Signins } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { requireCredential: true, @@ -16,41 +17,19 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, } } }; export default define(meta, async (ps, user) => { - const query = { - userId: user._id - } as any; - - const sort = { - _id: -1 - }; - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } + const query = makePaginationQuery(Signins.createQueryBuilder('signin'), ps.sinceId, ps.untilId) + .andWhere(`signin.userId = :meId`, { meId: user.id }); - const history = await Signin - .find(query, { - limit: ps.limit, - sort: sort - }); + const history = await query.take(ps.limit).getMany(); - return await Promise.all(history.map(record => pack(record))); + return await Promise.all(history.map(record => Signins.pack(record))); }); diff --git a/src/server/api/endpoints/i/unpin.ts b/src/server/api/endpoints/i/unpin.ts index 184d46f2c3..4688533578 100644 --- a/src/server/api/endpoints/i/unpin.ts +++ b/src/server/api/endpoints/i/unpin.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import { pack } from '../../../../models/user'; +import { ID } from '../../../../misc/cafy-id'; import { removePinned } from '../../../../services/i/pin'; import define from '../../define'; import { ApiError } from '../../error'; +import { Users } from '../../../../models'; export const meta = { stability: 'stable', @@ -16,12 +16,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' @@ -44,7 +43,7 @@ export default define(meta, async (ps, user) => { throw e; }); - return await pack(user, user, { + return await Users.pack(user, user, { detail: true }); }); diff --git a/src/server/api/endpoints/i/update-client-setting.ts b/src/server/api/endpoints/i/update-client-setting.ts index 79cd04e169..edbfe28f35 100644 --- a/src/server/api/endpoints/i/update-client-setting.ts +++ b/src/server/api/endpoints/i/update-client-setting.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import User from '../../../../models/user'; import { publishMainStream } from '../../../../services/stream'; import define from '../../define'; +import { Users } from '../../../../models'; export const meta = { requireCredential: true, @@ -10,7 +10,7 @@ export const meta = { params: { name: { - validator: $.str + validator: $.str.match(/^[a-zA-Z]+$/) }, value: { @@ -20,18 +20,18 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const x: any = {}; - x[`clientSettings.${ps.name}`] = ps.value; - - await User.update(user._id, { - $set: x - }); + await Users.createQueryBuilder().update() + .set({ + clientData: { + [ps.name]: ps.value + }, + }) + .where('id = :id', { id: user.id }) + .execute(); // Publish event - publishMainStream(user._id, 'clientSettingUpdated', { + publishMainStream(user.id, 'clientSettingUpdated', { key: ps.name, value: ps.value }); - - return; }); diff --git a/src/server/api/endpoints/i/update-email.ts b/src/server/api/endpoints/i/update-email.ts index c90462d850..253017535f 100644 --- a/src/server/api/endpoints/i/update-email.ts +++ b/src/server/api/endpoints/i/update-email.ts @@ -1,5 +1,4 @@ import $ from 'cafy'; -import User, { pack } from '../../../../models/user'; import { publishMainStream } from '../../../../services/stream'; import define from '../../define'; import * as nodemailer from 'nodemailer'; @@ -9,6 +8,7 @@ import config from '../../../../config'; import * as ms from 'ms'; import * as bcrypt from 'bcryptjs'; import { apiLogger } from '../../logger'; +import { Users } from '../../../../models'; export const meta = { requireCredential: true, @@ -39,29 +39,25 @@ export default define(meta, async (ps, user) => { throw new Error('incorrect password'); } - await User.update(user._id, { - $set: { - email: ps.email, - emailVerified: false, - emailVerifyCode: null - } + await Users.update(user.id, { + email: ps.email, + emailVerified: false, + emailVerifyCode: null }); - const iObj = await pack(user._id, user, { + const iObj = await Users.pack(user.id, user, { detail: true, includeSecrets: true }); // Publish meUpdated event - publishMainStream(user._id, 'meUpdated', iObj); + publishMainStream(user.id, 'meUpdated', iObj); if (ps.email != null) { const code = rndstr('a-z0-9', 16); - await User.update(user._id, { - $set: { - emailVerifyCode: code - } + await Users.update(user.id, { + emailVerifyCode: code }); const meta = await fetchMeta(); @@ -84,7 +80,7 @@ export default define(meta, async (ps, user) => { transporter.sendMail({ from: meta.email, to: ps.email, - subject: meta.name, + subject: meta.name || 'Misskey', text: `To verify email, please click this link: ${link}` }, (error, info) => { if (error) { diff --git a/src/server/api/endpoints/i/update-home.ts b/src/server/api/endpoints/i/update-home.ts deleted file mode 100644 index e2c319887f..0000000000 --- a/src/server/api/endpoints/i/update-home.ts +++ /dev/null @@ -1,33 +0,0 @@ -import $ from 'cafy'; -import User from '../../../../models/user'; -import { publishMainStream } from '../../../../services/stream'; -import define from '../../define'; - -export const meta = { - requireCredential: true, - - secure: true, - - params: { - home: { - validator: $.arr($.obj({ - name: $.str, - id: $.str, - place: $.str, - data: $.obj() - }).strict()) - } - } -}; - -export default define(meta, async (ps, user) => { - await User.update(user._id, { - $set: { - 'clientSettings.home': ps.home - } - }); - - publishMainStream(user._id, 'homeUpdated', ps.home); - - return; -}); diff --git a/src/server/api/endpoints/i/update-mobile-home.ts b/src/server/api/endpoints/i/update-mobile-home.ts deleted file mode 100644 index 642e2b3e09..0000000000 --- a/src/server/api/endpoints/i/update-mobile-home.ts +++ /dev/null @@ -1,32 +0,0 @@ -import $ from 'cafy'; -import User from '../../../../models/user'; -import { publishMainStream } from '../../../../services/stream'; -import define from '../../define'; - -export const meta = { - requireCredential: true, - - secure: true, - - params: { - home: { - validator: $.arr($.obj({ - name: $.str, - id: $.str, - data: $.obj() - }).strict()) - } - } -}; - -export default define(meta, async (ps, user) => { - await User.update(user._id, { - $set: { - 'clientSettings.mobileHome': ps.home - } - }); - - publishMainStream(user._id, 'mobileHomeUpdated', ps.home); - - return; -}); diff --git a/src/server/api/endpoints/i/update-widget.ts b/src/server/api/endpoints/i/update-widget.ts deleted file mode 100644 index 67d342278d..0000000000 --- a/src/server/api/endpoints/i/update-widget.ts +++ /dev/null @@ -1,88 +0,0 @@ -import $ from 'cafy'; -import User from '../../../../models/user'; -import { publishMainStream } from '../../../../services/stream'; -import define from '../../define'; - -export const meta = { - requireCredential: true, - - secure: true, - - params: { - id: { - validator: $.str - }, - - data: { - validator: $.obj() - } - } -}; - -export default define(meta, async (ps, user) => { - if (ps.id == null && ps.data == null) throw new Error('you need to set id and data params if home param unset'); - - let widget; - - //#region Desktop home - if (widget == null && user.clientSettings.home) { - const desktopHome = user.clientSettings.home; - widget = desktopHome.find((w: any) => w.id == ps.id); - if (widget) { - widget.data = ps.data; - - await User.update(user._id, { - $set: { - 'clientSettings.home': desktopHome - } - }); - } - } - //#endregion - - //#region Mobile home - if (widget == null && user.clientSettings.mobileHome) { - const mobileHome = user.clientSettings.mobileHome; - widget = mobileHome.find((w: any) => w.id == ps.id); - if (widget) { - widget.data = ps.data; - - await User.update(user._id, { - $set: { - 'clientSettings.mobileHome': mobileHome - } - }); - } - } - //#endregion - - //#region Deck - if (widget == null && user.clientSettings.deck && user.clientSettings.deck.columns) { - const deck = user.clientSettings.deck; - for (const c of deck.columns.filter((c: any) => c.type == 'widgets')) { - for (const w of c.widgets.filter((w: any) => w.id == ps.id)) { - widget = w; - } - } - if (widget) { - widget.data = ps.data; - - await User.update(user._id, { - $set: { - 'clientSettings.deck': deck - } - }); - } - } - //#endregion - - if (widget) { - publishMainStream(user._id, 'widgetUpdated', { - id: ps.id, data: ps.data - }); - - return; - } else { - throw new Error('widget not found'); - } -}); diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 099ef33990..f3e5d41021 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -1,18 +1,16 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import User, { isValidName, isValidDescription, isValidLocation, isValidBirthday, pack } from '../../../../models/user'; +import { ID } from '../../../../misc/cafy-id'; import { publishMainStream } from '../../../../services/stream'; -import DriveFile from '../../../../models/drive-file'; import acceptAllFollowRequests from '../../../../services/following/requests/accept-all'; import { publishToFollowers } from '../../../../services/i/update'; import define from '../../define'; -import getDriveFileUrl from '../../../../misc/get-drive-file-url'; import { parse, parsePlain } from '../../../../mfm/parse'; import extractEmojis from '../../../../misc/extract-emojis'; import extractHashtags from '../../../../misc/extract-hashtags'; import * as langmap from 'langmap'; import { updateHashtag } from '../../../../services/update-hashtag'; import { ApiError } from '../../error'; +import { Users, DriveFiles } from '../../../../models'; export const meta = { desc: { @@ -24,18 +22,18 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { name: { - validator: $.optional.nullable.str.pipe(isValidName), + validator: $.optional.nullable.str.pipe(Users.isValidName), desc: { 'ja-JP': '名前(ハンドルネームやニックネーム)' } }, description: { - validator: $.optional.nullable.str.pipe(isValidDescription), + validator: $.optional.nullable.str.pipe(Users.isValidDescription), desc: { 'ja-JP': 'アカウントの説明や自己紹介' } @@ -49,14 +47,14 @@ export const meta = { }, location: { - validator: $.optional.nullable.str.pipe(isValidLocation), + validator: $.optional.nullable.str.pipe(Users.isValidLocation), desc: { 'ja-JP': '住んでいる地域、所在' } }, birthday: { - validator: $.optional.nullable.str.pipe(isValidBirthday), + validator: $.optional.nullable.str.pipe(Users.isValidBirthday), desc: { 'ja-JP': '誕生日 (YYYY-MM-DD形式)' } @@ -64,7 +62,6 @@ export const meta = { avatarId: { validator: $.optional.nullable.type(ID), - transform: transform, desc: { 'ja-JP': 'アイコンに設定する画像のドライブファイルID' } @@ -72,20 +69,11 @@ export const meta = { bannerId: { validator: $.optional.nullable.type(ID), - transform: transform, desc: { 'ja-JP': 'バナーに設定する画像のドライブファイルID' } }, - wallpaperId: { - validator: $.optional.nullable.type(ID), - transform: transform, - desc: { - 'ja-JP': '壁紙に設定する画像のドライブファイルID' - } - }, - isLocked: { validator: $.optional.bool, desc: { @@ -171,116 +159,76 @@ export default define(meta, async (ps, user, app) => { if (ps.name !== undefined) updates.name = ps.name; if (ps.description !== undefined) updates.description = ps.description; if (ps.lang !== undefined) updates.lang = ps.lang; - if (ps.location !== undefined) updates['profile.location'] = ps.location; - if (ps.birthday !== undefined) updates['profile.birthday'] = ps.birthday; + if (ps.location !== undefined) updates.location = ps.location; + if (ps.birthday !== undefined) updates.birthday = ps.birthday; if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId; if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId; - if (ps.wallpaperId !== undefined) updates.wallpaperId = ps.wallpaperId; if (typeof ps.isLocked == 'boolean') updates.isLocked = ps.isLocked; if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot; if (typeof ps.carefulBot == 'boolean') updates.carefulBot = ps.carefulBot; if (typeof ps.autoAcceptFollowed == 'boolean') updates.autoAcceptFollowed = ps.autoAcceptFollowed; if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat; - if (typeof ps.autoWatch == 'boolean') updates['settings.autoWatch'] = ps.autoWatch; - if (typeof ps.alwaysMarkNsfw == 'boolean') updates['settings.alwaysMarkNsfw'] = ps.alwaysMarkNsfw; + if (typeof ps.autoWatch == 'boolean') updates.autoWatch = ps.autoWatch; + if (typeof ps.alwaysMarkNsfw == 'boolean') updates.alwaysMarkNsfw = ps.alwaysMarkNsfw; if (ps.avatarId) { - const avatar = await DriveFile.findOne({ - _id: ps.avatarId - }); + const avatar = await DriveFiles.findOne(ps.avatarId); - if (avatar == null) throw new ApiError(meta.errors.noSuchAvatar); - if (!avatar.contentType.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage); + if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar); + if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage); - if (avatar.metadata.deletedAt) { - updates.avatarUrl = null; - } else { - updates.avatarUrl = getDriveFileUrl(avatar, true); + updates.avatarUrl = avatar.thumbnailUrl; - if (avatar.metadata.properties.avgColor) { - updates.avatarColor = avatar.metadata.properties.avgColor; - } + if (avatar.properties.avgColor) { + updates.avatarColor = avatar.properties.avgColor; } } if (ps.bannerId) { - const banner = await DriveFile.findOne({ - _id: ps.bannerId - }); + const banner = await DriveFiles.findOne(ps.bannerId); - if (banner == null) throw new ApiError(meta.errors.noSuchBanner); - if (!banner.contentType.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage); + if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner); + if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage); - if (banner.metadata.deletedAt) { - updates.bannerUrl = null; - } else { - updates.bannerUrl = getDriveFileUrl(banner, false); + updates.bannerUrl = banner.webpublicUrl; - if (banner.metadata.properties.avgColor) { - updates.bannerColor = banner.metadata.properties.avgColor; - } - } - } - - if (ps.wallpaperId !== undefined) { - if (ps.wallpaperId === null) { - updates.wallpaperUrl = null; - updates.wallpaperColor = null; - } else { - const wallpaper = await DriveFile.findOne({ - _id: ps.wallpaperId - }); - - if (wallpaper == null) throw new Error('wallpaper not found'); - - if (wallpaper.metadata.deletedAt) { - updates.wallpaperUrl = null; - } else { - updates.wallpaperUrl = getDriveFileUrl(wallpaper); - - if (wallpaper.metadata.properties.avgColor) { - updates.wallpaperColor = wallpaper.metadata.properties.avgColor; - } - } + if (banner.properties.avgColor) { + updates.bannerColor = banner.properties.avgColor; } } //#region emojis/tags - if (updates.name != null || updates.description != null) { - let emojis = [] as string[]; - let tags = [] as string[]; + let emojis = [] as string[]; + let tags = [] as string[]; - if (updates.name != null) { - const tokens = parsePlain(updates.name); - emojis = emojis.concat(extractEmojis(tokens)); - } + if (updates.name != null) { + const tokens = parsePlain(updates.name); + emojis = emojis.concat(extractEmojis(tokens)); + } - if (updates.description != null) { - const tokens = parse(updates.description); - emojis = emojis.concat(extractEmojis(tokens)); - tags = extractHashtags(tokens).map(tag => tag.toLowerCase()); - } + if (updates.description != null) { + const tokens = parse(updates.description); + emojis = emojis.concat(extractEmojis(tokens)); + tags = extractHashtags(tokens).map(tag => tag.toLowerCase()); + } - updates.emojis = emojis; - updates.tags = tags; + updates.emojis = emojis; + updates.tags = tags; - // ハッシュタグ更新 - for (const tag of tags) updateHashtag(user, tag, true, true); - for (const tag of (user.tags || []).filter(x => !tags.includes(x))) updateHashtag(user, tag, true, false); - } + // ハッシュタグ更新 + for (const tag of tags) updateHashtag(user, tag, true, true); + for (const tag of user.tags.filter(x => !tags.includes(x))) updateHashtag(user, tag, true, false); //#endregion - await User.update(user._id, { - $set: updates - }); + await Users.update(user.id, updates); - const iObj = await pack(user._id, user, { + const iObj = await Users.pack(user.id, user, { detail: true, includeSecrets: isSecure }); // Publish meUpdated event - publishMainStream(user._id, 'meUpdated', iObj); + publishMainStream(user.id, 'meUpdated', iObj); // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 if (user.isLocked && ps.isLocked === false) { @@ -288,7 +236,7 @@ export default define(meta, async (ps, user, app) => { } // フォロワーにUpdateを配信 - publishToFollowers(user._id); + publishToFollowers(user.id); return iObj; }); diff --git a/src/server/api/endpoints/messaging/history.ts b/src/server/api/endpoints/messaging/history.ts index 699dc7c253..c0aec61212 100644 --- a/src/server/api/endpoints/messaging/history.ts +++ b/src/server/api/endpoints/messaging/history.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import Mute from '../../../../models/mute'; -import Message, { pack, IMessagingMessage } from '../../../../models/messaging-message'; import define from '../../define'; +import { MessagingMessage } from '../../../../models/entities/messaging-message'; +import { MessagingMessages, Mutings } from '../../../../models'; +import { Brackets } from 'typeorm'; export const meta = { desc: { @@ -31,34 +32,33 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const mute = await Mute.find({ - muterId: user._id, - deletedAt: { $exists: false } + const mute = await Mutings.find({ + muterId: user.id, }); - const history: IMessagingMessage[] = []; + const history: MessagingMessage[] = []; for (let i = 0; i < ps.limit; i++) { - const found = history.map(m => m.userId.equals(user._id) ? m.recipientId : m.userId); + const found = history.map(m => (m.userId === user.id) ? m.recipientId : m.userId); - const message = await Message.findOne({ - $or: [{ - userId: user._id - }, { - recipientId: user._id - }], - $and: [{ - userId: { $nin: found }, - recipientId: { $nin: found } - }, { - userId: { $nin: mute.map(m => m.muteeId) }, - recipientId: { $nin: mute.map(m => m.muteeId) } - }] - }, { - sort: { - createdAt: -1 - } - }); + const query = MessagingMessages.createQueryBuilder('message') + .where(new Brackets(qb => { qb + .where(`message.userId = :userId`, { userId: user.id }) + .orWhere(`message.recipientId = :userId`, { userId: user.id }); + })) + .orderBy('message.createdAt', 'DESC'); + + if (found.length > 0) { + query.andWhere(`message.userId NOT IN (:...found)`, { found: found }); + query.andWhere(`message.recipientId NOT IN (:...found)`, { found: found }); + } + + if (mute.length > 0) { + query.andWhere(`message.userId NOT IN (:...mute)`, { mute: mute.map(m => m.muteeId) }); + query.andWhere(`message.recipientId NOT IN (:...mute)`, { mute: mute.map(m => m.muteeId) }); + } + + const message = await query.getOne(); if (message) { history.push(message); @@ -67,5 +67,5 @@ export default define(meta, async (ps, user) => { } } - return await Promise.all(history.map(h => pack(h._id, user))); + return await Promise.all(history.map(h => MessagingMessages.pack(h.id, user))); }); diff --git a/src/server/api/endpoints/messaging/messages.ts b/src/server/api/endpoints/messaging/messages.ts index c19db45f1f..02c57b8d03 100644 --- a/src/server/api/endpoints/messaging/messages.ts +++ b/src/server/api/endpoints/messaging/messages.ts @@ -1,11 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Message from '../../../../models/messaging-message'; -import { pack } from '../../../../models/messaging-message'; +import { ID } from '../../../../misc/cafy-id'; import read from '../../common/read-messaging-message'; import define from '../../define'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { MessagingMessages } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { desc: { @@ -22,7 +22,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -36,12 +35,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, markAsRead: { @@ -73,43 +70,17 @@ export default define(meta, async (ps, user) => { throw e; }); - const query = { - $or: [{ - userId: user._id, - recipientId: recipient._id - }, { - userId: recipient._id, - recipientId: user._id - }] - } as any; - - const sort = { - _id: -1 - }; - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } + const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId) + .andWhere(`(message.userId = :meId AND message.recipientId = :recipientId) OR (message.userId = :recipientId AND message.recipientId = :meId)`, { meId: user.id, recipientId: recipient.id }); - const messages = await Message - .find(query, { - limit: ps.limit, - sort: sort - }); + const messages = await query.getMany(); // Mark all as read if (ps.markAsRead) { - read(user._id, recipient._id, messages); + read(user.id, recipient.id, messages.map(x => x.id)); } - return await Promise.all(messages.map(message => pack(message, user, { + return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, { populateRecipient: false }))); }); diff --git a/src/server/api/endpoints/messaging/messages/create.ts b/src/server/api/endpoints/messaging/messages/create.ts index fc048e6edd..2c7e5ad2d9 100644 --- a/src/server/api/endpoints/messaging/messages/create.ts +++ b/src/server/api/endpoints/messaging/messages/create.ts @@ -1,17 +1,14 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import Message from '../../../../../models/messaging-message'; -import { isValidText } from '../../../../../models/messaging-message'; -import User from '../../../../../models/user'; -import Mute from '../../../../../models/mute'; -import DriveFile from '../../../../../models/drive-file'; -import { pack } from '../../../../../models/messaging-message'; +import { ID } from '../../../../../misc/cafy-id'; import { publishMainStream } from '../../../../../services/stream'; import { publishMessagingStream, publishMessagingIndexStream } from '../../../../../services/stream'; import pushSw from '../../../../../services/push-notification'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getUser } from '../../../common/getters'; +import { MessagingMessages, DriveFiles, Mutings } from '../../../../../models'; +import { MessagingMessage } from '../../../../../models/entities/messaging-message'; +import { genId } from '../../../../../misc/gen-id'; export const meta = { desc: { @@ -28,7 +25,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -36,12 +32,11 @@ export const meta = { }, text: { - validator: $.optional.str.pipe(isValidText) + validator: $.optional.str.pipe(MessagingMessages.isValidText) }, fileId: { validator: $.optional.type(ID), - transform: transform, } }, @@ -78,7 +73,7 @@ export const meta = { export default define(meta, async (ps, user) => { // Myself - if (ps.userId.equals(user._id)) { + if (ps.userId === user.id) { throw new ApiError(meta.errors.recipientIsYourself); } @@ -90,12 +85,12 @@ export default define(meta, async (ps, user) => { let file = null; if (ps.fileId != null) { - file = await DriveFile.findOne({ - _id: ps.fileId, - 'metadata.userId': user._id + file = await DriveFiles.findOne({ + id: ps.fileId, + userId: user.id }); - if (file === null) { + if (file == null) { throw new ApiError(meta.errors.noSuchFile); } } @@ -105,16 +100,17 @@ export default define(meta, async (ps, user) => { throw new ApiError(meta.errors.contentRequired); } - const message = await Message.insert({ + const message = await MessagingMessages.save({ + id: genId(), createdAt: new Date(), - fileId: file ? file._id : undefined, - recipientId: recipient._id, - text: ps.text ? ps.text.trim() : undefined, - userId: user._id, + fileId: file ? file.id : null, + recipientId: recipient.id, + text: ps.text ? ps.text.trim() : null, + userId: user.id, isRead: false - }); + } as MessagingMessage); - const messageObj = await pack(message); + const messageObj = await MessagingMessages.pack(message); // 自分のストリーム publishMessagingStream(message.userId, message.recipientId, 'message', messageObj); @@ -126,25 +122,17 @@ export default define(meta, async (ps, user) => { publishMessagingIndexStream(message.recipientId, 'message', messageObj); publishMainStream(message.recipientId, 'messagingMessage', messageObj); - // Update flag - User.update({ _id: recipient._id }, { - $set: { - hasUnreadMessagingMessage: true - } - }); - // 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する setTimeout(async () => { - const freshMessage = await Message.findOne({ _id: message._id }, { isRead: true }); + const freshMessage = await MessagingMessages.findOne({ id: message.id }); if (freshMessage == null) return; // メッセージが削除されている場合もある if (!freshMessage.isRead) { //#region ただしミュートされているなら発行しない - const mute = await Mute.find({ - muterId: recipient._id, - deletedAt: { $exists: false } + const mute = await Mutings.find({ + muterId: recipient.id, }); const mutedUserIds = mute.map(m => m.muteeId.toString()); - if (mutedUserIds.indexOf(user._id.toString()) != -1) { + if (mutedUserIds.indexOf(user.id) != -1) { return; } //#endregion diff --git a/src/server/api/endpoints/messaging/messages/delete.ts b/src/server/api/endpoints/messaging/messages/delete.ts index 0ca12846c1..9f55caba62 100644 --- a/src/server/api/endpoints/messaging/messages/delete.ts +++ b/src/server/api/endpoints/messaging/messages/delete.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import Message from '../../../../../models/messaging-message'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { publishMessagingStream } from '../../../../../services/stream'; import * as ms from 'ms'; import { ApiError } from '../../../error'; +import { MessagingMessages } from '../../../../../models'; export const meta = { stability: 'stable', @@ -29,7 +29,6 @@ export const meta = { params: { messageId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のメッセージのID', 'en-US': 'Target message ID.' @@ -47,19 +46,17 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const message = await Message.findOne({ - _id: ps.messageId, - userId: user._id + const message = await MessagingMessages.findOne({ + id: ps.messageId, + userId: user.id }); - if (message === null) { + if (message == null) { throw new ApiError(meta.errors.noSuchMessage); } - await Message.remove({ _id: message._id }); + await MessagingMessages.delete(message.id); - publishMessagingStream(message.userId, message.recipientId, 'deleted', message._id); - publishMessagingStream(message.recipientId, message.userId, 'deleted', message._id); - - return; + publishMessagingStream(message.userId, message.recipientId, 'deleted', message.id); + publishMessagingStream(message.recipientId, message.userId, 'deleted', message.id); }); diff --git a/src/server/api/endpoints/messaging/messages/read.ts b/src/server/api/endpoints/messaging/messages/read.ts index aa8ecdc4ff..24a28285bf 100644 --- a/src/server/api/endpoints/messaging/messages/read.ts +++ b/src/server/api/endpoints/messaging/messages/read.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import Message from '../../../../../models/messaging-message'; +import { ID } from '../../../../../misc/cafy-id'; import read from '../../../common/read-messaging-message'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { MessagingMessages } from '../../../../../models'; export const meta = { desc: { @@ -20,7 +20,6 @@ export const meta = { params: { messageId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '既読にするメッセージのID', 'en-US': 'The ID of a message that you want to mark as read' @@ -38,16 +37,14 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const message = await Message.findOne({ - _id: ps.messageId, - recipientId: user._id + const message = await MessagingMessages.findOne({ + id: ps.messageId, + recipientId: user.id }); if (message == null) { throw new ApiError(meta.errors.noSuchMessage); } - read(user._id, message.userId, message); - - return; + read(user.id, message.userId, [message.id]); }); diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index a297f47e0e..785f21f22b 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; import * as os from 'os'; import config from '../../../config'; -import Emoji from '../../../models/emoji'; import define from '../define'; import fetchMeta from '../../../misc/fetch-meta'; import * as pkg from '../../../../package.json'; +import { Emojis } from '../../../models'; export const meta = { stability: 'stable', @@ -81,14 +81,11 @@ export const meta = { export default define(meta, async (ps, me) => { const instance = await fetchMeta(); - const emojis = await Emoji.find({ host: null }, { - fields: { - _id: false - } - }); + const emojis = await Emojis.find({ host: null }); const response: any = { - maintainer: instance.maintainer, + maintainerName: instance.maintainerName, + maintainerEmail: instance.maintainerEmail, version: pkg.version, @@ -145,17 +142,12 @@ export default define(meta, async (ps, me) => { github: instance.enableGithubIntegration, discord: instance.enableDiscordIntegration, serviceWorker: instance.enableServiceWorker, - userRecommendation: { - external: instance.enableExternalUserRecommendation, - engine: instance.externalUserRecommendationEngine, - timeout: instance.externalUserRecommendationTimeout - } }; } if (me && (me.isAdmin || me.isModerator)) { response.useStarForReactionFallback = instance.useStarForReactionFallback; - response.hidedTags = instance.hidedTags; + response.hiddenTags = instance.hiddenTags; response.recaptchaSecretKey = instance.recaptchaSecretKey; response.proxyAccount = instance.proxyAccount; response.twitterConsumerKey = instance.twitterConsumerKey; @@ -164,9 +156,6 @@ export default define(meta, async (ps, me) => { response.githubClientSecret = instance.githubClientSecret; response.discordClientId = instance.discordClientId; response.discordClientSecret = instance.discordClientSecret; - response.enableExternalUserRecommendation = instance.enableExternalUserRecommendation; - response.externalUserRecommendationEngine = instance.externalUserRecommendationEngine; - response.externalUserRecommendationTimeout = instance.externalUserRecommendationTimeout; response.summalyProxy = instance.summalyProxy; response.email = instance.email; response.smtpSecure = instance.smtpSecure; diff --git a/src/server/api/endpoints/mute/create.ts b/src/server/api/endpoints/mute/create.ts index 7eaee90a05..d13c546fdc 100644 --- a/src/server/api/endpoints/mute/create.ts +++ b/src/server/api/endpoints/mute/create.ts @@ -1,9 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Mute from '../../../../models/mute'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { genId } from '../../../../misc/gen-id'; +import { Mutings, NoteWatchings } from '../../../../models'; +import { Muting } from '../../../../models/entities/muting'; export const meta = { desc: { @@ -15,12 +17,11 @@ export const meta = { requireCredential: true, - kind: 'account/write', + kind: 'write:mutes', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -53,7 +54,7 @@ export default define(meta, async (ps, user) => { const muter = user; // 自分自身 - if (user._id.equals(ps.userId)) { + if (user.id === ps.userId) { throw new ApiError(meta.errors.muteeIsYourself); } @@ -64,21 +65,25 @@ export default define(meta, async (ps, user) => { }); // Check if already muting - const exist = await Mute.findOne({ - muterId: muter._id, - muteeId: mutee._id + const exist = await Mutings.findOne({ + muterId: muter.id, + muteeId: mutee.id }); - if (exist !== null) { + if (exist != null) { throw new ApiError(meta.errors.alreadyMuting); } // Create mute - await Mute.insert({ + await Mutings.save({ + id: genId(), createdAt: new Date(), - muterId: muter._id, - muteeId: mutee._id, - }); + muterId: muter.id, + muteeId: mutee.id, + } as Muting); - return; + NoteWatchings.delete({ + userId: muter.id, + noteUserId: mutee.id + }); }); diff --git a/src/server/api/endpoints/mute/delete.ts b/src/server/api/endpoints/mute/delete.ts index 1a03f6371b..1aae15af91 100644 --- a/src/server/api/endpoints/mute/delete.ts +++ b/src/server/api/endpoints/mute/delete.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Mute from '../../../../models/mute'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { Mutings } from '../../../../models'; export const meta = { desc: { @@ -15,12 +15,11 @@ export const meta = { requireCredential: true, - kind: 'account/write', + kind: 'write:mutes', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -53,7 +52,7 @@ export default define(meta, async (ps, user) => { const muter = user; // Check if the mutee is yourself - if (user._id.equals(ps.userId)) { + if (user.id === ps.userId) { throw new ApiError(meta.errors.muteeIsYourself); } @@ -64,19 +63,17 @@ export default define(meta, async (ps, user) => { }); // Check not muting - const exist = await Mute.findOne({ - muterId: muter._id, - muteeId: mutee._id + const exist = await Mutings.findOne({ + muterId: muter.id, + muteeId: mutee.id }); - if (exist === null) { + if (exist == null) { throw new ApiError(meta.errors.notMuting); } // Delete mute - await Mute.remove({ - _id: exist._id + await Mutings.delete({ + id: exist.id }); - - return; }); diff --git a/src/server/api/endpoints/mute/list.ts b/src/server/api/endpoints/mute/list.ts index 1b8f759496..5f2d184472 100644 --- a/src/server/api/endpoints/mute/list.ts +++ b/src/server/api/endpoints/mute/list.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Mute, { packMany } from '../../../../models/mute'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Mutings } from '../../../../models'; export const meta = { desc: { @@ -13,7 +14,7 @@ export const meta = { requireCredential: true, - kind: 'account/read', + kind: 'read:mutes', params: { limit: { @@ -23,12 +24,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, }, @@ -41,30 +40,12 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const query = { - muterId: me._id - } as any; + const query = makePaginationQuery(Mutings.createQueryBuilder('muting'), ps.sinceId, ps.untilId) + .andWhere(`muting.muterId = :meId`, { meId: me.id }); - const sort = { - _id: -1 - }; + const mutings = await query + .take(ps.limit) + .getMany(); - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } - - const mutes = await Mute - .find(query, { - limit: ps.limit, - sort: sort - }); - - return await packMany(mutes, me); + return await Mutings.packMany(mutings, me); }); diff --git a/src/server/api/endpoints/my/apps.ts b/src/server/api/endpoints/my/apps.ts index 1a936c918b..d205d1674c 100644 --- a/src/server/api/endpoints/my/apps.ts +++ b/src/server/api/endpoints/my/apps.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; -import App, { pack } from '../../../../models/app'; import define from '../../define'; +import { Apps } from '../../../../models'; export const meta = { tags: ['account', 'app'], @@ -27,19 +27,16 @@ export const meta = { export default define(meta, async (ps, user) => { const query = { - userId: user._id + userId: user.id }; - const apps = await App - .find(query, { - limit: ps.limit, - skip: ps.offset, - sort: { - _id: -1 - } - }); + const apps = await Apps.find({ + where: query, + take: ps.limit, + skip: ps.offset, + }); - return await Promise.all(apps.map(app => pack(app, user, { + return await Promise.all(apps.map(app => Apps.pack(app, user, { detail: true }))); }); diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts index 835c515cfe..10f6e39845 100644 --- a/src/server/api/endpoints/notes.ts +++ b/src/server/api/endpoints/notes.ts @@ -1,7 +1,8 @@ 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 { makePaginationQuery } from '../common/make-pagination-query'; +import { Notes } from '../../../models'; export const meta = { desc: { @@ -39,14 +40,6 @@ export const meta = { } }, - media: { - validator: $.optional.bool, - deprecated: true, - desc: { - 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, - poll: { validator: $.optional.bool, desc: { @@ -61,12 +54,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, }, @@ -79,43 +70,29 @@ export const meta = { }; export default define(meta, async (ps) => { - const sort = { - _id: -1 - }; - const query = { - deletedAt: null, - visibility: 'public', - localOnly: { $ne: true }, - } as any; - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere(`note.visibility = 'public'`) + .andWhere(`note.localOnly = FALSE`) + .leftJoinAndSelect('note.user', 'user'); if (ps.local) { - query['_user.host'] = null; + query.andWhere('note.userHost IS NULL'); } if (ps.reply != undefined) { - query.replyId = ps.reply ? { $exists: true, $ne: null } : null; + query.andWhere(ps.reply ? 'note.replyId IS NOT NULL' : 'note.replyId IS NULL'); } if (ps.renote != undefined) { - query.renoteId = ps.renote ? { $exists: true, $ne: null } : null; + query.andWhere(ps.renote ? 'note.renoteId IS NOT NULL' : 'note.renoteId IS NULL'); } - const withFiles = ps.withFiles != undefined ? ps.withFiles : ps.media; - - if (withFiles) query.fileIds = { $exists: true, $ne: null }; + if (ps.withFiles != undefined) { + query.andWhere(ps.withFiles ? `note.fileIds != '{}'` : `note.fileIds = '{}'`); + } if (ps.poll != undefined) { - query.poll = ps.poll ? { $exists: true, $ne: null } : null; + query.andWhere(ps.poll ? 'note.hasPoll = TRUE' : 'note.hasPoll = FALSE'); } // TODO @@ -123,10 +100,7 @@ export default define(meta, async (ps) => { // query.isBot = bot; //} - const notes = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const notes = await query.take(ps.limit).getMany(); - return await packMany(notes); + return await Notes.packMany(notes); }); 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) : [], - - // 隠すユーザーを取得 - 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 = { - $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); + 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'); + + if (user) generateVisibilityQuery(query, user); + if (user) generateMuteQuery(query, user); + + const notes = await query.take(ps.limit).getMany(); + + 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/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts deleted file mode 100644 index 9695547f04..0000000000 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ /dev/null @@ -1,290 +0,0 @@ -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 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'; - -export const meta = { - desc: { - 'ja-JP': 'ハイブリッドタイムラインを取得します。' - }, - - tags: ['notes'], - - params: { - limit: { - validator: $.optional.num.range(1, 100), - default: 10, - desc: { - 'ja-JP': '最大数' - } - }, - - sinceId: { - validator: $.optional.type(ID), - transform: transform, - desc: { - 'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します' - } - }, - - untilId: { - validator: $.optional.type(ID), - transform: transform, - desc: { - 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' - } - }, - - sinceDate: { - validator: $.optional.num, - desc: { - 'ja-JP': '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' - } - }, - - untilDate: { - validator: $.optional.num, - desc: { - 'ja-JP': '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' - } - }, - - includeMyRenotes: { - validator: $.optional.bool, - default: true, - desc: { - 'ja-JP': '自分の行ったRenoteを含めるかどうか' - } - }, - - includeRenotedMyNotes: { - validator: $.optional.bool, - default: true, - desc: { - 'ja-JP': 'Renoteされた自分の投稿を含めるかどうか' - } - }, - - includeLocalRenotes: { - validator: $.optional.bool, - default: true, - desc: { - 'ja-JP': 'Renoteされたローカルの投稿を含めるかどうか' - } - }, - - withFiles: { - validator: $.optional.bool, - desc: { - 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' - } - }, - - mediaOnly: { - validator: $.optional.bool, - deprecated: true, - desc: { - 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, - }, - - res: { - type: 'array', - items: { - type: 'Note', - }, - }, - - errors: { - stlDisabled: { - message: 'Social timeline has been disabled.', - code: 'STL_DISABLED', - id: '620763f4-f621-4533-ab33-0577a1a3c342' - }, - } -}; - -export default define(meta, async (ps, user) => { - 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, - - /*// リプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める) - $or: [{ - // リプライでない - replyId: null - }, { // または - // リプライだが返信先が投稿者自身の投稿 - $expr: { - $eq: ['$_reply.userId', '$userId'] - } - }, { // または - // リプライだが返信先が自分(フォロワー)の投稿 - '_reply.userId': user._id - }, { // または - // 自分(フォロワー)が送信したリプライ - userId: 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 = { - $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; - - // MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。 - // つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。 - // for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws - - if (ps.includeMyRenotes === false) { - query.$and.push({ - $or: [{ - userId: { $ne: user._id } - }, { - renoteId: null - }, { - text: { $ne: null } - }, { - fileIds: { $ne: [] } - }, { - poll: { $ne: null } - }] - }); - } - - if (ps.includeRenotedMyNotes === false) { - query.$and.push({ - $or: [{ - '_renote.userId': { $ne: user._id } - }, { - renoteId: null - }, { - text: { $ne: null } - }, { - fileIds: { $ne: [] } - }, { - poll: { $ne: null } - }] - }); - } - - if (ps.includeLocalRenotes === false) { - query.$and.push({ - $or: [{ - '_renote.user.host': { $ne: null } - }, { - renoteId: null - }, { - text: { $ne: null } - }, { - fileIds: { $ne: [] } - }, { - poll: { $ne: null } - }] - }); - } - - 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) - }; - } - //#endregion - - const timeline = await Note.find(query, { - limit: ps.limit, - sort: sort - }); - - activeUsersChart.update(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 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 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 = { - $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 followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: 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 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); - - 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 != 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 + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .leftJoinAndSelect('note.user', 'user'); + + if (me) generateVisibilityQuery(query, me); + if (me) generateMuteQuery(query, me); + + 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 'mute_direct': - push({ - userId: { - $nin: hideUserIds - } - }); - 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/social-timeline.ts b/src/server/api/endpoints/notes/social-timeline.ts new file mode 100644 index 0000000000..10e215d6c4 --- /dev/null +++ b/src/server/api/endpoints/notes/social-timeline.ts @@ -0,0 +1,195 @@ +import $ from 'cafy'; +import { ID } from '../../../../misc/cafy-id'; +import define from '../../define'; +import fetchMeta from '../../../../misc/fetch-meta'; +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': 'ソーシャルタイムラインを取得します。' + }, + + tags: ['notes'], + + params: { + limit: { + validator: $.optional.num.range(1, 100), + default: 10, + desc: { + 'ja-JP': '最大数' + } + }, + + sinceId: { + validator: $.optional.type(ID), + desc: { + 'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します' + } + }, + + untilId: { + validator: $.optional.type(ID), + desc: { + 'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します' + } + }, + + sinceDate: { + validator: $.optional.num, + desc: { + 'ja-JP': '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' + } + }, + + untilDate: { + validator: $.optional.num, + desc: { + 'ja-JP': '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' + } + }, + + includeMyRenotes: { + validator: $.optional.bool, + default: true, + desc: { + 'ja-JP': '自分の行ったRenoteを含めるかどうか' + } + }, + + includeRenotedMyNotes: { + validator: $.optional.bool, + default: true, + desc: { + 'ja-JP': 'Renoteされた自分の投稿を含めるかどうか' + } + }, + + includeLocalRenotes: { + validator: $.optional.bool, + default: true, + desc: { + 'ja-JP': 'Renoteされたローカルの投稿を含めるかどうか' + } + }, + + withFiles: { + validator: $.optional.bool, + desc: { + 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' + } + }, + }, + + res: { + type: 'array', + items: { + type: 'Note', + }, + }, + + errors: { + stlDisabled: { + message: 'Social timeline has been disabled.', + code: 'STL_DISABLED', + id: '620763f4-f621-4533-ab33-0577a1a3c342' + }, + } +}; + +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); + } + + //#region Construct query + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: 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()); + + generateVisibilityQuery(query, user); + generateMuteQuery(query, user); + + /* TODO + // MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。 + // つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。 + // for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws + + if (ps.includeMyRenotes === false) { + query.$and.push({ + $or: [{ + userId: { $ne: user.id } + }, { + renoteId: null + }, { + text: { $ne: null } + }, { + fileIds: { $ne: [] } + }, { + poll: { $ne: null } + }] + }); + } + + if (ps.includeRenotedMyNotes === false) { + query.$and.push({ + $or: [{ + '_renote.userId': { $ne: user.id } + }, { + renoteId: null + }, { + text: { $ne: null } + }, { + fileIds: { $ne: [] } + }, { + poll: { $ne: null } + }] + }); + } + + if (ps.includeLocalRenotes === false) { + query.$and.push({ + $or: [{ + '_renote.user.host': { $ne: null } + }, { + renoteId: null + }, { + text: { $ne: null } + }, { + fileIds: { $ne: [] } + }, { + poll: { $ne: null } + }] + }); + } + */ + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + //#endregion + + const timeline = await query.take(ps.limit).getMany(); + + if (user) { + activeUsersChart.update(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 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, - - $and: [{ - // フォローしている人の投稿 - $or: followQuery - }, { - // visible for me - $or: visibleQuery - }], - - // mute - userId: { - $nin: hideUserIds - }, - '_reply.userId': { - $nin: hideUserIds - }, - '_renote.userId': { - $nin: hideUserIds - }, - }] - } as any; - + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: 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()); + + 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); }); diff --git a/src/server/api/endpoints/notifications/mark-all-as-read.ts b/src/server/api/endpoints/notifications/mark-all-as-read.ts index e5df648285..9f34a32e80 100644 --- a/src/server/api/endpoints/notifications/mark-all-as-read.ts +++ b/src/server/api/endpoints/notifications/mark-all-as-read.ts @@ -1,7 +1,6 @@ -import Notification from '../../../../models/notification'; import { publishMainStream } from '../../../../services/stream'; -import User from '../../../../models/user'; import define from '../../define'; +import { Notifications } from '../../../../models'; export const meta = { desc: { @@ -13,29 +12,18 @@ export const meta = { requireCredential: true, - kind: 'notification-write' + kind: 'write:notifications' }; export default define(meta, async (ps, user) => { // Update documents - await Notification.update({ - notifieeId: user._id, - isRead: false + await Notifications.update({ + notifieeId: user.id, + isRead: false, }, { - $set: { - isRead: true - } - }, { - multi: true - }); - - // Update flag - User.update({ _id: user._id }, { - $set: { - hasUnreadNotification: false - } + isRead: true }); // 全ての通知を読みましたよというイベントを発行 - publishMainStream(user._id, 'readAllNotifications'); + publishMainStream(user.id, 'readAllNotifications'); }); diff --git a/src/server/api/endpoints/stats.ts b/src/server/api/endpoints/stats.ts index 30c49cdd86..f3ebaa16ad 100644 --- a/src/server/api/endpoints/stats.ts +++ b/src/server/api/endpoints/stats.ts @@ -1,7 +1,6 @@ import define from '../define'; -import driveChart from '../../../services/chart/drive'; -import federationChart from '../../../services/chart/federation'; -import fetchMeta from '../../../misc/fetch-meta'; +import { Notes, Users } from '../../../models'; +import { federationChart, driveChart } from '../../../services/chart'; export const meta = { requireCredential: false, @@ -43,16 +42,17 @@ export const meta = { }; export default define(meta, async () => { - const instance = await fetchMeta(); - - const stats: any = instance.stats; - - const driveStats = await driveChart.getChart('hour', 1); - stats.driveUsageLocal = driveStats.local.totalSize[0]; - stats.driveUsageRemote = driveStats.remote.totalSize[0]; - - const federationStats = await federationChart.getChart('hour', 1); - stats.instances = federationStats.instance.total[0]; - - return stats; + const [notesCount, originalNotesCount, usersCount, originalUsersCount, instances, driveUsageLocal, driveUsageRemote] = await Promise.all([ + Notes.count(), + Notes.count({ userHost: null }), + Users.count(), + Users.count({ host: null }), + federationChart.getChart('hour', 1).then(chart => chart.instance.total[0]), + driveChart.getChart('hour', 1).then(chart => chart.local.totalSize[0]), + driveChart.getChart('hour', 1).then(chart => chart.remote.totalSize[0]), + ]); + + return { + notesCount, originalNotesCount, usersCount, originalUsersCount, instances, driveUsageLocal, driveUsageRemote + }; }); diff --git a/src/server/api/endpoints/sw/register.ts b/src/server/api/endpoints/sw/register.ts index 0b81b06abe..cb0572aa90 100644 --- a/src/server/api/endpoints/sw/register.ts +++ b/src/server/api/endpoints/sw/register.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import Subscription from '../../../../models/sw-subscription'; import define from '../../define'; import fetchMeta from '../../../../misc/fetch-meta'; +import { genId } from '../../../../misc/gen-id'; +import { SwSubscriptions } from '../../../../models'; export const meta = { tags: ['account'], @@ -25,12 +26,11 @@ export const meta = { export default define(meta, async (ps, user) => { // if already subscribed - const exist = await Subscription.findOne({ - userId: user._id, + const exist = await SwSubscriptions.findOne({ + userId: user.id, endpoint: ps.endpoint, auth: ps.auth, publickey: ps.publickey, - deletedAt: { $exists: false } }); const instance = await fetchMeta(); @@ -42,8 +42,9 @@ export default define(meta, async (ps, user) => { }; } - await Subscription.insert({ - userId: user._id, + await SwSubscriptions.save({ + id: genId(), + userId: user.id, endpoint: ps.endpoint, auth: ps.auth, publickey: ps.publickey diff --git a/src/server/api/endpoints/username/available.ts b/src/server/api/endpoints/username/available.ts index 1d098eb399..42ab176652 100644 --- a/src/server/api/endpoints/username/available.ts +++ b/src/server/api/endpoints/username/available.ts @@ -1,7 +1,6 @@ import $ from 'cafy'; -import User from '../../../../models/user'; -import { validateUsername } from '../../../../models/user'; import define from '../../define'; +import { Users } from '../../../../models'; export const meta = { tags: ['users'], @@ -10,18 +9,16 @@ export const meta = { params: { username: { - validator: $.str.pipe(validateUsername) + validator: $.str.pipe(Users.validateUsername) } } }; export default define(meta, async (ps) => { // Get exist - const exist = await User.count({ + const exist = await Users.count({ host: null, usernameLower: ps.username.toLowerCase() - }, { - limit: 1 }); return { diff --git a/src/server/api/endpoints/users.ts b/src/server/api/endpoints/users.ts index be83dcd9cc..f99165f3d5 100644 --- a/src/server/api/endpoints/users.ts +++ b/src/server/api/endpoints/users.ts @@ -1,10 +1,7 @@ import $ from 'cafy'; -import User, { pack } from '../../../models/user'; import define from '../define'; -import { fallback } from '../../../prelude/symbol'; -import { getHideUserIds } from '../common/get-hide-users'; - -const nonnull = { $ne: null as any }; +import { Users } from '../../../models'; +import { generateMuteQueryForUsers } from '../common/generate-mute-query'; export const meta = { tags: ['users'], @@ -63,53 +60,38 @@ export const meta = { }, }; -const state: any = { // < https://github.com/Microsoft/TypeScript/issues/1863 - 'admin': { isAdmin: true }, - 'moderator': { isModerator: true }, - 'adminOrModerator': { - $or: [ - { isAdmin: true }, - { isModerator: true } - ] - }, - 'verified': { isVerified: true }, - 'alive': { - updatedAt: { $gt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) } - }, - [fallback]: {} -}; +export default define(meta, async (ps, me) => { + const query = Users.createQueryBuilder('user'); -const origin: any = { // < https://github.com/Microsoft/TypeScript/issues/1863 - 'local': { host: null }, - 'remote': { host: nonnull }, - [fallback]: {} -}; + switch (ps.state) { + case 'admin': query.where('user.isAdmin = TRUE'); break; + case 'moderator': query.where('user.isModerator = TRUE'); break; + case 'adminOrModerator': query.where('user.isAdmin = TRUE OR isModerator = TRUE'); break; + case 'verified': query.where('user.isVerified = TRUE'); break; + case 'alive': query.where('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; + } -const sort: any = { // < https://github.com/Microsoft/TypeScript/issues/1863 - '+follower': { followersCount: -1 }, - '-follower': { followersCount: 1 }, - '+createdAt': { createdAt: -1 }, - '-createdAt': { createdAt: 1 }, - '+updatedAt': { updatedAt: -1 }, - '-updatedAt': { updatedAt: 1 }, - [fallback]: { _id: -1 } -}; + switch (ps.origin) { + case 'local': query.andWhere('user.host IS NULL'); break; + case 'remote': query.andWhere('user.host IS NOT NULL'); break; + } -export default define(meta, async (ps, me) => { - const hideUserIds = await getHideUserIds(me); - - const users = await User - .find({ - $and: [ - state[ps.state] || state[fallback], - origin[ps.origin] || origin[fallback] - ], - ...(hideUserIds && hideUserIds.length > 0 ? { _id: { $nin: hideUserIds } } : {}) - }, { - limit: ps.limit, - sort: sort[ps.sort] || sort[fallback], - skip: ps.offset - }); - - return await Promise.all(users.map(user => pack(user, me, { detail: true }))); + switch (ps.sort) { + case '+follower': query.orderBy('user.followersCount', 'DESC'); break; + case '-follower': query.orderBy('user.followersCount', 'ASC'); break; + case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; + case '+updatedAt': query.orderBy('user.updatedAt', 'DESC'); break; + case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break; + default: query.orderBy('user.id', 'ASC'); break; + } + + if (me) generateMuteQueryForUsers(query, me); + + query.take(ps.limit); + query.skip(ps.offset); + + const users = await query.getMany(); + + return await Users.packMany(users, me, { detail: true }); }); diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts index 3c8290a8b1..51b007ddaa 100644 --- a/src/server/api/endpoints/users/followers.ts +++ b/src/server/api/endpoints/users/followers.ts @@ -1,11 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import User from '../../../../models/user'; -import Following from '../../../../models/following'; -import { pack } from '../../../../models/user'; -import { getFriendIds } from '../../common/get-friends'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { ApiError } from '../../error'; +import { Users, Followings } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { desc: { @@ -20,7 +18,6 @@ export const meta = { params: { userId: { validator: $.optional.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -35,38 +32,25 @@ export const meta = { validator: $.optional.nullable.str }, - limit: { - validator: $.optional.num.range(1, 100), - default: 10 + sinceId: { + validator: $.optional.type(ID), }, - cursor: { + untilId: { validator: $.optional.type(ID), - default: null as any, - transform: transform, }, - iknow: { - validator: $.optional.bool, - default: false, - } + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, }, res: { - type: 'object', - properties: { - users: { - type: 'array', - items: { - type: 'User', - } - }, - next: { - type: 'string', - format: 'id', - nullable: true - } - } + type: 'array', + items: { + type: 'Following', + }, }, errors: { @@ -79,54 +63,20 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const q: any = ps.userId != null - ? { _id: ps.userId } - : { usernameLower: ps.username.toLowerCase(), host: ps.host }; - - const user = await User.findOne(q); + const user = await Users.findOne(ps.userId != null + ? { id: ps.userId } + : { usernameLower: ps.username.toLowerCase(), host: ps.host }); - if (user === null) { + if (user == null) { throw new ApiError(meta.errors.noSuchUser); } - const query = { - followeeId: user._id - } as any; - - // ログインしていてかつ iknow フラグがあるとき - if (me && ps.iknow) { - // Get my friends - const myFriends = await getFriendIds(me._id); - - query.followerId = { - $in: myFriends - }; - } - - // カーソルが指定されている場合 - if (ps.cursor) { - query._id = { - $lt: ps.cursor - }; - } - - // Get followers - const following = await Following - .find(query, { - limit: ps.limit + 1, - sort: { _id: -1 } - }); - - // 「次のページ」があるかどうか - const inStock = following.length === ps.limit + 1; - if (inStock) { - following.pop(); - } + const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) + .andWhere(`following.followeeId = :userId`, { userId: user.id }); - const users = await Promise.all(following.map(f => pack(f.followerId, me, { detail: true }))); + const followings = await query + .take(ps.limit) + .getMany(); - return { - users: users, - next: inStock ? following[following.length - 1]._id : null, - }; + return await Followings.packMany(followings, me, { populateFollower: true }); }); diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts index 4bc740cad9..46550f0f77 100644 --- a/src/server/api/endpoints/users/following.ts +++ b/src/server/api/endpoints/users/following.ts @@ -1,11 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import User from '../../../../models/user'; -import Following from '../../../../models/following'; -import { pack } from '../../../../models/user'; -import { getFriendIds } from '../../common/get-friends'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { ApiError } from '../../error'; +import { Users, Followings } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { desc: { @@ -20,7 +18,6 @@ export const meta = { params: { userId: { validator: $.optional.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -35,38 +32,25 @@ export const meta = { validator: $.optional.nullable.str }, - limit: { - validator: $.optional.num.range(1, 100), - default: 10 + sinceId: { + validator: $.optional.type(ID), }, - cursor: { + untilId: { validator: $.optional.type(ID), - default: null as any, - transform: transform, }, - iknow: { - validator: $.optional.bool, - default: false, - } + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, }, res: { - type: 'object', - properties: { - users: { - type: 'array', - items: { - type: 'User', - } - }, - next: { - type: 'string', - format: 'id', - nullable: true - } - } + type: 'array', + items: { + type: 'Following', + }, }, errors: { @@ -79,54 +63,20 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const q: any = ps.userId != null - ? { _id: ps.userId } - : { usernameLower: ps.username.toLowerCase(), host: ps.host }; - - const user = await User.findOne(q); + const user = await Users.findOne(ps.userId != null + ? { id: ps.userId } + : { usernameLower: ps.username.toLowerCase(), host: ps.host }); - if (user === null) { + if (user == null) { throw new ApiError(meta.errors.noSuchUser); } - const query = { - followerId: user._id - } as any; - - // ログインしていてかつ iknow フラグがあるとき - if (me && ps.iknow) { - // Get my friends - const myFriends = await getFriendIds(me._id); - - query.followeeId = { - $in: myFriends - }; - } - - // カーソルが指定されている場合 - if (ps.cursor) { - query._id = { - $lt: ps.cursor - }; - } - - // Get followers - const following = await Following - .find(query, { - limit: ps.limit + 1, - sort: { _id: -1 } - }); - - // 「次のページ」があるかどうか - const inStock = following.length === ps.limit + 1; - if (inStock) { - following.pop(); - } + const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) + .andWhere(`following.followerId = :userId`, { userId: user.id }); - const users = await Promise.all(following.map(f => pack(f.followeeId, me, { detail: true }))); + const followings = await query + .take(ps.limit) + .getMany(); - return { - users: users, - next: inStock ? following[following.length - 1]._id : null, - }; + return await Followings.packMany(followings, me, { populateFollowee: true }); }); diff --git a/src/server/api/endpoints/users/get-frequently-replied-users.ts b/src/server/api/endpoints/users/get-frequently-replied-users.ts index 46c7fba2f6..f82f437629 100644 --- a/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -1,12 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { pack } from '../../../../models/user'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { maximum } from '../../../../prelude/array'; -import { getHideUserIds } from '../../common/get-hide-users'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { Not, In } from 'typeorm'; +import { Notes, Users } from '../../../../models'; export const meta = { tags: ['users'], @@ -16,7 +15,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -53,21 +51,16 @@ export default define(meta, async (ps, me) => { }); // Fetch recent notes - const recentNotes = await Note.find({ - userId: user._id, - replyId: { - $exists: true, - $ne: null - } - }, { - sort: { - _id: -1 + const recentNotes = await Notes.find({ + where: { + userId: user.id, + replyId: Not(null) }, - limit: 1000, - fields: { - _id: false, - replyId: true - } + order: { + id: -1 + }, + take: 1000, + select: ['replyId'] }); // 投稿が少なかったら中断 @@ -75,21 +68,12 @@ export default define(meta, async (ps, me) => { return []; } - const hideUserIds = await getHideUserIds(me); - hideUserIds.push(user._id); - - const replyTargetNotes = await Note.find({ - _id: { - $in: recentNotes.map(p => p.replyId) + // TODO ミュートを考慮 + const replyTargetNotes = await Notes.find({ + where: { + id: In(recentNotes.map(p => p.replyId)), }, - userId: { - $nin: hideUserIds - } - }, { - fields: { - _id: false, - userId: true - } + select: ['userId'] }); const repliedUsers: any = {}; @@ -114,7 +98,7 @@ export default define(meta, async (ps, me) => { // Make replies object (includes weights) const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ - user: await pack(user, me, { detail: true }), + user: await Users.pack(user, me, { detail: true }), weight: repliedUsers[user] / peak }))); diff --git a/src/server/api/endpoints/users/lists/create.ts b/src/server/api/endpoints/users/lists/create.ts index 00d2538c9f..21dc6d331d 100644 --- a/src/server/api/endpoints/users/lists/create.ts +++ b/src/server/api/endpoints/users/lists/create.ts @@ -1,6 +1,8 @@ import $ from 'cafy'; -import UserList, { pack } from '../../../../../models/user-list'; import define from '../../../define'; +import { UserLists } from '../../../../../models'; +import { genId } from '../../../../../misc/gen-id'; +import { UserList } from '../../../../../models/entities/user-list'; export const meta = { desc: { @@ -12,7 +14,7 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { title: { @@ -22,12 +24,12 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const userList = await UserList.insert({ + const userList = await UserLists.save({ + id: genId(), createdAt: new Date(), - userId: user._id, - title: ps.title, - userIds: [] - }); + userId: user.id, + name: ps.title, + } as UserList); - return await pack(userList); + return await UserLists.pack(userList); }); diff --git a/src/server/api/endpoints/users/lists/delete.ts b/src/server/api/endpoints/users/lists/delete.ts index d8faaa928c..0634bca4e3 100644 --- a/src/server/api/endpoints/users/lists/delete.ts +++ b/src/server/api/endpoints/users/lists/delete.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import UserList from '../../../../../models/user-list'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { UserLists } from '../../../../../models'; export const meta = { desc: { @@ -14,12 +14,11 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { listId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象となるユーザーリストのID', 'en-US': 'ID of target user list' @@ -37,16 +36,14 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const userList = await UserList.findOne({ - _id: ps.listId, - userId: user._id + const userList = await UserLists.findOne({ + id: ps.listId, + userId: user.id }); if (userList == null) { throw new ApiError(meta.errors.noSuchList); } - await UserList.remove({ - _id: userList._id - }); + await UserLists.delete(userList.id); }); diff --git a/src/server/api/endpoints/users/lists/list.ts b/src/server/api/endpoints/users/lists/list.ts index ece2af5603..b05fc45527 100644 --- a/src/server/api/endpoints/users/lists/list.ts +++ b/src/server/api/endpoints/users/lists/list.ts @@ -1,5 +1,5 @@ -import UserList, { pack } from '../../../../../models/user-list'; import define from '../../../define'; +import { UserLists } from '../../../../../models'; export const meta = { desc: { @@ -10,7 +10,7 @@ export const meta = { requireCredential: true, - kind: 'account-read', + kind: 'read:account', res: { type: 'array', @@ -21,9 +21,9 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const userLists = await UserList.find({ - userId: me._id, + const userLists = await UserLists.find({ + userId: me.id, }); - return await Promise.all(userLists.map(x => pack(x))); + return await Promise.all(userLists.map(x => UserLists.pack(x))); }); diff --git a/src/server/api/endpoints/users/lists/pull.ts b/src/server/api/endpoints/users/lists/pull.ts index 0eee1975db..524670b341 100644 --- a/src/server/api/endpoints/users/lists/pull.ts +++ b/src/server/api/endpoints/users/lists/pull.ts @@ -1,11 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import UserList from '../../../../../models/user-list'; -import { pack as packUser } from '../../../../../models/user'; +import { ID } from '../../../../../misc/cafy-id'; import { publishUserListStream } from '../../../../../services/stream'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getUser } from '../../../common/getters'; +import { UserLists, UserListJoinings, Users } from '../../../../../models'; export const meta = { desc: { @@ -17,17 +16,15 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { listId: { validator: $.type(ID), - transform: transform, }, userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -52,9 +49,9 @@ export const meta = { export default define(meta, async (ps, me) => { // Fetch the list - const userList = await UserList.findOne({ - _id: ps.listId, - userId: me._id, + const userList = await UserLists.findOne({ + id: ps.listId, + userId: me.id, }); if (userList == null) { @@ -68,11 +65,7 @@ export default define(meta, async (ps, me) => { }); // Pull the user - await UserList.update({ _id: userList._id }, { - $pull: { - userIds: user._id - } - }); + await UserListJoinings.delete({ userId: user.id }); - publishUserListStream(userList._id, 'userRemoved', await packUser(user)); + publishUserListStream(userList.id, 'userRemoved', await Users.pack(user)); }); diff --git a/src/server/api/endpoints/users/lists/push.ts b/src/server/api/endpoints/users/lists/push.ts index eea2f39a8c..2763b3a19c 100644 --- a/src/server/api/endpoints/users/lists/push.ts +++ b/src/server/api/endpoints/users/lists/push.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import UserList from '../../../../../models/user-list'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getUser } from '../../../common/getters'; import { pushUserToUserList } from '../../../../../services/user-list/push'; +import { UserLists, UserListJoinings } from '../../../../../models'; export const meta = { desc: { @@ -16,17 +16,15 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { listId: { validator: $.type(ID), - transform: transform, }, userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -57,9 +55,9 @@ export const meta = { export default define(meta, async (ps, me) => { // Fetch the list - const userList = await UserList.findOne({ - _id: ps.listId, - userId: me._id, + const userList = await UserLists.findOne({ + id: ps.listId, + userId: me.id, }); if (userList == null) { @@ -72,7 +70,12 @@ export default define(meta, async (ps, me) => { throw e; }); - if (userList.userIds.map(id => id.toHexString()).includes(user._id.toHexString())) { + const exist = await UserListJoinings.findOne({ + userListId: userList.id, + userId: user.id + }); + + if (exist) { throw new ApiError(meta.errors.alreadyAdded); } diff --git a/src/server/api/endpoints/users/lists/show.ts b/src/server/api/endpoints/users/lists/show.ts index 0fab2fa499..1a997ec7c5 100644 --- a/src/server/api/endpoints/users/lists/show.ts +++ b/src/server/api/endpoints/users/lists/show.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import UserList, { pack } from '../../../../../models/user-list'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { UserLists } from '../../../../../models'; export const meta = { desc: { @@ -14,12 +14,11 @@ export const meta = { requireCredential: true, - kind: 'account-read', + kind: 'read:account', params: { listId: { validator: $.type(ID), - transform: transform, }, }, @@ -38,14 +37,14 @@ export const meta = { export default define(meta, async (ps, me) => { // Fetch the list - const userList = await UserList.findOne({ - _id: ps.listId, - userId: me._id, + const userList = await UserLists.findOne({ + id: ps.listId, + userId: me.id, }); if (userList == null) { throw new ApiError(meta.errors.noSuchList); } - return await pack(userList); + return await UserLists.pack(userList); }); diff --git a/src/server/api/endpoints/users/lists/update.ts b/src/server/api/endpoints/users/lists/update.ts index 5897693144..dc08d59f6a 100644 --- a/src/server/api/endpoints/users/lists/update.ts +++ b/src/server/api/endpoints/users/lists/update.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import UserList, { pack } from '../../../../../models/user-list'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { UserLists } from '../../../../../models'; export const meta = { desc: { @@ -14,19 +14,18 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { listId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象となるユーザーリストのID', 'en-US': 'ID of target user list' } }, - title: { + name: { validator: $.str.range(1, 100), desc: { 'ja-JP': 'このユーザーリストの名前', @@ -46,20 +45,18 @@ export const meta = { export default define(meta, async (ps, user) => { // Fetch the list - const userList = await UserList.findOne({ - _id: ps.listId, - userId: user._id + const userList = await UserLists.findOne({ + id: ps.listId, + userId: user.id }); if (userList == null) { throw new ApiError(meta.errors.noSuchList); } - await UserList.update({ _id: userList._id }, { - $set: { - title: ps.title - } + await UserLists.update(userList.id, { + name: ps.name }); - return await pack(userList._id); + return await UserLists.pack(userList.id); }); diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index 10d2f37fc2..6df394cbb1 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -1,10 +1,13 @@ 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 Following from '../../../../models/following'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { Notes } from '../../../../models'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { Brackets } from 'typeorm'; export const meta = { desc: { @@ -16,7 +19,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -42,17 +44,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': '指定すると、その投稿を基点としてより古い投稿を取得します' } }, @@ -102,15 +102,6 @@ export const meta = { } }, - mediaOnly: { - validator: $.optional.bool, - default: false, - deprecated: true, - desc: { - 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, - fileType: { validator: $.optional.arr($.str), desc: { @@ -150,67 +141,44 @@ export default define(meta, async (ps, me) => { throw e; }); - const isFollowing = me == null ? false : ((await Following.findOne({ - followerId: me._id, - followeeId: user._id - })) != null); - //#region Construct query - const sort = { } as any; + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere('note.userId = :userId', { userId: user.id }) + .leftJoinAndSelect('note.user', 'user'); - const visibleQuery = me == null ? [{ - visibility: { $in: ['public', 'home'] } - }] : [{ - visibility: { - $in: isFollowing ? ['public', 'home', 'followers'] : ['public', 'home'] - } - }, { - // myself (for specified/private) - userId: me._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ me._id ] } - }]; + if (me) generateVisibilityQuery(query, me); + if (me) generateMuteQuery(query, me); + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } - const query = { - $and: [ {} ], - deletedAt: null, - userId: user._id, - $or: visibleQuery - } as any; + if (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.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - sort._id = -1; - query._id = { - $lt: ps.untilId - }; - } else if (ps.sinceDate) { - sort.createdAt = 1; - query.createdAt = { - $gt: new Date(ps.sinceDate) - }; - } else if (ps.untilDate) { - sort.createdAt = -1; - query.createdAt = { - $lt: new Date(ps.untilDate) - }; - } else { - sort._id = -1; + if (ps.excludeNsfw) { + // v11 TODO + /*query['_files.isSensitive'] = { + $ne: true + };*/ + } } if (!ps.includeReplies) { - query.replyId = null; + query.andWhere('note.replyId IS NULL'); } + /* TODO if (ps.includeMyRenotes === false) { query.$and.push({ $or: [{ - userId: { $ne: user._id } + userId: { $ne: user.id } }, { renoteId: null }, { @@ -222,35 +190,11 @@ export default define(meta, async (ps, me) => { }] }); } + */ - 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 - }; - } - } //#endregion - const notes = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const timeline = await query.take(ps.limit).getMany(); - return await packMany(notes, me); + return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index 60710fffca..2c82d6613e 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -1,14 +1,8 @@ import * as ms from 'ms'; import $ from 'cafy'; -import User, { pack, ILocalUser } from '../../../../models/user'; -import { getFriendIds } from '../../common/get-friends'; -import * as request from 'request-promise-native'; -import config from '../../../../config'; import define from '../../define'; -import fetchMeta from '../../../../misc/fetch-meta'; -import resolveUser from '../../../../remote/resolve-user'; -import { getHideUserIds } from '../../common/get-hide-users'; -import { apiLogger } from '../../logger'; +import { Users, Followings } from '../../../../models'; +import { generateMuteQueryForUsers } from '../../common/generate-mute-query'; export const meta = { desc: { @@ -19,7 +13,7 @@ export const meta = { requireCredential: true, - kind: 'account-read', + kind: 'read:account', params: { limit: { @@ -42,83 +36,24 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const instance = await fetchMeta(); + const query = Users.createQueryBuilder('user') + .where('user.isLocked = FALSE') + .where('user.host IS NULL') + .where('user.updatedAt >= :date', { date: new Date(Date.now() - ms('7days')) }) + .orderBy('user.followersCount', 'DESC'); - if (instance.enableExternalUserRecommendation) { - const userName = me.username; - const hostName = config.hostname; - const limit = ps.limit; - const offset = ps.offset; - const timeout = instance.externalUserRecommendationTimeout; - const engine = instance.externalUserRecommendationEngine; - const url = engine - .replace('{{host}}', hostName) - .replace('{{user}}', userName) - .replace('{{limit}}', limit.toString()) - .replace('{{offset}}', offset.toString()); + generateMuteQueryForUsers(query, me); - const users = await request({ - url: url, - proxy: config.proxy, - timeout: timeout, - json: true, - followRedirect: true, - followAllRedirects: true - }) - .then(body => convertUsers(body, me)); + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); - return users; - } else { - // ID list of the user itself and other users who the user follows - const followingIds = await getFriendIds(me._id); + query + .andWhere(`user.id NOT IN (${ followingQuery.getQuery() })`); - // 隠すユーザーを取得 - const hideUserIds = await getHideUserIds(me); + query.setParameters(followingQuery.getParameters()); - const users = await User.find({ - _id: { - $nin: followingIds.concat(hideUserIds) - }, - isLocked: { $ne: true }, - updatedAt: { - $gte: new Date(Date.now() - ms('7days')) - }, - host: null - }, { - limit: ps.limit, - skip: ps.offset, - sort: { - followersCount: -1 - } - }); + const users = await query.take(ps.limit).skip(ps.offset).getMany(); - return await Promise.all(users.map(user => pack(user, me, { detail: true }))); - } + return await Users.packMany(users, me, { detail: true }); }); - -type IRecommendUser = { - name: string; - username: string; - host: string; - description: string; - avatarUrl: string; -}; - -/** - * Resolve/Pack dummy users - */ -async function convertUsers(src: IRecommendUser[], me: ILocalUser) { - const packed = await Promise.all(src.map(async x => { - const user = await resolveUser(x.username, x.host) - .catch(() => { - apiLogger.warn(`Can't resolve ${x.username}@${x.host}`); - return null; - }); - - if (user == null) return x; - - return await pack(user, me, { detail: true }); - })); - - return packed; -} diff --git a/src/server/api/endpoints/users/relation.ts b/src/server/api/endpoints/users/relation.ts index f4121aa0d0..4971738d32 100644 --- a/src/server/api/endpoints/users/relation.ts +++ b/src/server/api/endpoints/users/relation.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform, ObjectId } from '../../../../misc/cafy-id'; -import { getRelation } from '../../../../models/user'; import define from '../../define'; +import { ID } from '../../../../misc/cafy-id'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -15,7 +15,6 @@ export const meta = { params: { userId: { validator: $.either($.type(ID), $.arr($.type(ID)).unique()), - transform: (v: any): ObjectId | ObjectId[] => Array.isArray(v) ? v.map(x => transform(x)) : transform(v), desc: { 'ja-JP': 'ユーザーID (配列でも可)' } @@ -26,7 +25,7 @@ export const meta = { export default define(meta, async (ps, me) => { const ids = Array.isArray(ps.userId) ? ps.userId : [ps.userId]; - const relations = await Promise.all(ids.map(id => getRelation(me._id, id))); + const relations = await Promise.all(ids.map(id => Users.getRelation(me.id, id))); return Array.isArray(ps.userId) ? relations : relations[0]; }); diff --git a/src/server/api/endpoints/users/report-abuse.ts b/src/server/api/endpoints/users/report-abuse.ts index 0f23f8f0c3..2ee28c9002 100644 --- a/src/server/api/endpoints/users/report-abuse.ts +++ b/src/server/api/endpoints/users/report-abuse.ts @@ -1,11 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; -import AbuseUserReport from '../../../../models/abuse-user-report'; import { publishAdminStream } from '../../../../services/stream'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { AbuseUserReports, Users } from '../../../../models'; +import { genId } from '../../../../misc/gen-id'; export const meta = { desc: { @@ -19,7 +19,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -62,7 +61,7 @@ export default define(meta, async (ps, me) => { throw e; }); - if (user._id.equals(me._id)) { + if (user.id === me.id) { throw new ApiError(meta.errors.cannotReportYourself); } @@ -70,17 +69,18 @@ export default define(meta, async (ps, me) => { throw new ApiError(meta.errors.cannotReportAdmin); } - const report = await AbuseUserReport.insert({ + const report = await AbuseUserReports.save({ + id: genId(), createdAt: new Date(), - userId: user._id, - reporterId: me._id, + userId: user.id, + reporterId: me.id, comment: ps.comment }); // Publish event to moderators setTimeout(async () => { - const moderators = await User.find({ - $or: [{ + const moderators = await Users.find({ + where: [{ isAdmin: true }, { isModerator: true @@ -88,8 +88,8 @@ export default define(meta, async (ps, me) => { }); for (const moderator of moderators) { - publishAdminStream(moderator._id, 'newAbuseUserReport', { - id: report._id, + publishAdminStream(moderator.id, 'newAbuseUserReport', { + id: report.id, userId: report.userId, reporterId: report.reporterId, comment: report.comment diff --git a/src/server/api/endpoints/users/search.ts b/src/server/api/endpoints/users/search.ts index a95f6df6de..2e76546ade 100644 --- a/src/server/api/endpoints/users/search.ts +++ b/src/server/api/endpoints/users/search.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import * as escapeRegexp from 'escape-regexp'; -import User, { pack, validateUsername, IUser } from '../../../../models/user'; import define from '../../define'; +import { Users } from '../../../../models'; +import { User } from '../../../../models/entities/user'; export const meta = { desc: { @@ -62,34 +62,30 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const isUsername = validateUsername(ps.query.replace('@', ''), !ps.localOnly); + const isUsername = Users.validateUsername(ps.query.replace('@', ''), !ps.localOnly); - let users: IUser[] = []; + let users: User[] = []; if (isUsername) { - users = await User - .find({ - host: null, - usernameLower: new RegExp('^' + escapeRegexp(ps.query.replace('@', '').toLowerCase())), - isSuspended: { $ne: true } - }, { - limit: ps.limit, - skip: ps.offset - }); + users = await Users.createQueryBuilder('user') + .where('user.host IS NULL') + .where('user.isSuspended = FALSE') + .where('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) + .take(ps.limit) + .skip(ps.offset) + .getMany(); if (users.length < ps.limit && !ps.localOnly) { - const otherUsers = await User - .find({ - host: { $ne: null }, - usernameLower: new RegExp('^' + escapeRegexp(ps.query.replace('@', '').toLowerCase())), - isSuspended: { $ne: true } - }, { - limit: ps.limit - users.length - }); + const otherUsers = await Users.createQueryBuilder('user') + .where('user.host IS NOT NULL') + .where('user.isSuspended = FALSE') + .where('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) + .take(ps.limit - users.length) + .getMany(); users = users.concat(otherUsers); } } - return await Promise.all(users.map(user => pack(user, me, { detail: ps.detail }))); + return await Users.packMany(users, me, { detail: ps.detail }); }); diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts index 4e59945eba..a605eaf30a 100644 --- a/src/server/api/endpoints/users/show.ts +++ b/src/server/api/endpoints/users/show.ts @@ -1,12 +1,11 @@ import $ from 'cafy'; -import ID, { transform, transformMany } from '../../../../misc/cafy-id'; -import User, { pack, isRemoteUser } from '../../../../models/user'; import resolveRemoteUser from '../../../../remote/resolve-user'; import define from '../../define'; import { apiLogger } from '../../logger'; import { ApiError } from '../../error'; - -const cursorOption = { fields: { data: false } }; +import { ID } from '../../../../misc/cafy-id'; +import { Users } from '../../../../models'; +import { In } from 'typeorm'; export const meta = { desc: { @@ -20,7 +19,6 @@ export const meta = { params: { userId: { validator: $.optional.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -29,7 +27,6 @@ export const meta = { userIds: { validator: $.optional.arr($.type(ID)).unique(), - transform: transformMany, desc: { 'ja-JP': 'ユーザーID (配列)' } @@ -68,42 +65,40 @@ export default define(meta, async (ps, me) => { let user; if (ps.userIds) { - const users = await User.find({ - _id: { - $in: ps.userIds - } + const users = await Users.find({ + id: In(ps.userIds) }); - return await Promise.all(users.map(u => pack(u, me, { + return await Promise.all(users.map(u => Users.pack(u, me, { detail: true }))); } else { // Lookup user if (typeof ps.host === 'string') { - user = await resolveRemoteUser(ps.username, ps.host, cursorOption).catch(e => { + user = await resolveRemoteUser(ps.username, ps.host).catch(e => { apiLogger.warn(`failed to resolve remote user: ${e}`); throw new ApiError(meta.errors.failedToResolveRemoteUser); }); } else { const q: any = ps.userId != null - ? { _id: ps.userId } + ? { id: ps.userId } : { usernameLower: ps.username.toLowerCase(), host: null }; - user = await User.findOne(q, cursorOption); + user = await Users.findOne(q); } - if (user === null) { + if (user == null) { throw new ApiError(meta.errors.noSuchUser); } // ユーザー情報更新 - if (isRemoteUser(user)) { + if (Users.isRemoteUser(user)) { if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { resolveRemoteUser(ps.username, ps.host, { }, true); } } - return await pack(user, me, { + return await Users.pack(user, me, { detail: true }); } diff --git a/src/server/api/index.ts b/src/server/api/index.ts index fac57ca06e..7858efd927 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -15,8 +15,8 @@ import signin from './private/signin'; import discord from './service/discord'; import github from './service/github'; import twitter from './service/twitter'; -import Instance from '../../models/instance'; import { toASCII } from 'punycode'; +import { Instances } from '../../models'; // Init app const app = new Koa(); @@ -67,10 +67,9 @@ router.use(github.routes()); router.use(twitter.routes()); router.get('/v1/instance/peers', async ctx => { - const instances = await Instance.find({ - }, { - host: 1 - }); + const instances = await Instances.find({ + select: ['host'] + }); const punyCodes = instances.map(instance => toASCII(instance.host)); diff --git a/src/server/api/limiter.ts b/src/server/api/limiter.ts index 3d66172fd8..e29c061337 100644 --- a/src/server/api/limiter.ts +++ b/src/server/api/limiter.ts @@ -2,12 +2,12 @@ import * as Limiter from 'ratelimiter'; import limiterDB from '../../db/redis'; import { IEndpoint } from './endpoints'; import getAcct from '../../misc/acct/render'; -import { IUser } from '../../models/user'; +import { User } from '../../models/entities/user'; import Logger from '../../services/logger'; const logger = new Logger('limiter'); -export default (endpoint: IEndpoint, user: IUser) => new Promise((ok, reject) => { +export default (endpoint: IEndpoint, user: User) => new Promise((ok, reject) => { // Redisがインストールされてない場合は常に許可 if (limiterDB == null) { ok(); @@ -38,7 +38,7 @@ export default (endpoint: IEndpoint, user: IUser) => new Promise((ok, reject) => // Short-term limit function min() { const minIntervalLimiter = new Limiter({ - id: `${user._id}:${key}:min`, + id: `${user.id}:${key}:min`, duration: limitation.minInterval, max: 1, db: limiterDB @@ -66,7 +66,7 @@ export default (endpoint: IEndpoint, user: IUser) => new Promise((ok, reject) => // Long term limit function max() { const limiter = new Limiter({ - id: `${user._id}:${key}`, + id: `${user.id}:${key}`, duration: limitation.duration, max: limitation.max, db: limiterDB diff --git a/src/server/api/openapi/schemas.ts b/src/server/api/openapi/schemas.ts index 70a0d6faf0..5992fee835 100644 --- a/src/server/api/openapi/schemas.ts +++ b/src/server/api/openapi/schemas.ts @@ -221,7 +221,7 @@ export const schemas = { }, type: { type: 'string', - enum: ['follow', 'receiveFollowRequest', 'mention', 'reply', 'renote', 'quote', 'reaction', 'poll_vote'], + enum: ['follow', 'receiveFollowRequest', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote'], description: 'The type of the notification.' }, }, @@ -258,7 +258,7 @@ export const schemas = { description: 'The MD5 hash of this Drive file.', example: '15eca7fba0480996e2245f5185bf39f2' }, - datasize: { + size: { type: 'number', description: 'The size of this Drive file. (bytes)', example: 51469 @@ -275,7 +275,7 @@ export const schemas = { description: 'Whether this Drive file is sensitive.', }, }, - required: ['id', 'createdAt', 'name', 'type', 'datasize', 'md5'] + required: ['id', 'createdAt', 'name', 'type', 'size', 'md5'] }, DriveFolder: { @@ -318,6 +318,40 @@ export const schemas = { required: ['id', 'createdAt', 'name'] }, + Following: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'id', + description: 'The unique identifier for this following.', + example: 'xxxxxxxxxxxxxxxxxxxxxxxx', + }, + createdAt: { + type: 'string', + format: 'date-time', + description: 'The date that the following was created.' + }, + followeeId: { + type: 'string', + format: 'id', + }, + followee: { + $ref: '#/components/schemas/User', + description: 'The followee.' + }, + followerId: { + type: 'string', + format: 'id', + }, + follower: { + $ref: '#/components/schemas/User', + description: 'The follower.' + }, + }, + required: ['id', 'createdAt', 'followeeId', 'followerId'] + }, + Muting: { type: 'object', properties: { diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts index 40bcd2c5d6..c1fd908d8a 100644 --- a/src/server/api/private/signin.ts +++ b/src/server/api/private/signin.ts @@ -1,11 +1,12 @@ import * as Koa from 'koa'; import * as bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; -import User, { ILocalUser } from '../../../models/user'; -import Signin, { pack } from '../../../models/signin'; import { publishMainStream } from '../../../services/stream'; import signin from '../common/signin'; import config from '../../../config'; +import { Users, Signins } from '../../../models'; +import { ILocalUser } from '../../../models/entities/user'; +import { genId } from '../../../misc/gen-id'; export default async (ctx: Koa.BaseContext) => { ctx.set('Access-Control-Allow-Origin', config.url); @@ -32,17 +33,12 @@ export default async (ctx: Koa.BaseContext) => { } // Fetch user - const user = await User.findOne({ + const user = await Users.findOne({ usernameLower: username.toLowerCase(), host: null - }, { - fields: { - data: false, - profile: false - } - }) as ILocalUser; + }) as ILocalUser; - if (user === null) { + if (user == null) { ctx.throw(404, { error: 'user not found' }); @@ -77,14 +73,15 @@ export default async (ctx: Koa.BaseContext) => { } // Append signin history - const record = await Signin.insert({ + const record = await Signins.save({ + id: genId(), createdAt: new Date(), - userId: user._id, + userId: user.id, ip: ctx.ip, headers: ctx.headers, success: same }); // Publish signin event - publishMainStream(user._id, 'signin', await pack(record)); + publishMainStream(user.id, 'signin', await Signins.pack(record)); }; diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 89b7b330d2..1d304b8e11 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -1,14 +1,15 @@ import * as Koa from 'koa'; import * as bcrypt from 'bcryptjs'; -import { generate as generateKeypair } from '../../../crypto_key'; -import User, { IUser, validateUsername, validatePassword, pack } from '../../../models/user'; +import { generateKeyPair } from 'crypto'; import generateUserToken from '../common/generate-native-user-token'; import config from '../../../config'; -import Meta from '../../../models/meta'; -import RegistrationTicket from '../../../models/registration-tickets'; -import usersChart from '../../../services/chart/users'; import fetchMeta from '../../../misc/fetch-meta'; import * as recaptcha from 'recaptcha-promise'; +import { Users, RegistrationTickets, UserServiceLinkings, UserKeypairs } from '../../../models'; +import { genId } from '../../../misc/gen-id'; +import { usersChart } from '../../../services/chart'; +import { UserServiceLinking } from '../../../models/entities/user-service-linking'; +import { User } from '../../../models/entities/user'; export default async (ctx: Koa.BaseContext) => { const body = ctx.request.body as any; @@ -32,6 +33,7 @@ export default async (ctx: Koa.BaseContext) => { const username = body['username']; const password = body['password']; + const host = process.env.NODE_ENV === 'test' ? (body['host'] || null) : null; const invitationCode = body['invitationCode']; if (instance && instance.disableRegistration) { @@ -40,7 +42,7 @@ export default async (ctx: Koa.BaseContext) => { return; } - const ticket = await RegistrationTicket.findOne({ + const ticket = await RegistrationTickets.findOne({ code: invitationCode }); @@ -49,39 +51,22 @@ export default async (ctx: Koa.BaseContext) => { return; } - RegistrationTicket.remove({ - _id: ticket._id - }); + RegistrationTickets.delete(ticket.id); } // Validate username - if (!validateUsername(username)) { + if (!Users.validateUsername(username)) { ctx.status = 400; return; } // Validate password - if (!validatePassword(password)) { + if (!Users.validatePassword(password)) { ctx.status = 400; return; } - const usersCount = await User.count({}); - - // Fetch exist user that same username - const usernameExist = await User - .count({ - usernameLower: username.toLowerCase(), - host: null - }, { - limit: 1 - }); - - // Check username already used - if (usernameExist !== 0) { - ctx.status = 400; - return; - } + const usersCount = await Users.count({}); // Generate hash of password const salt = await bcrypt.genSalt(8); @@ -90,46 +75,50 @@ export default async (ctx: Koa.BaseContext) => { // Generate secret const secret = generateUserToken(); - // Create account - const account: IUser = await User.insert({ - avatarId: null, - bannerId: null, + if (await Users.findOne({ usernameLower: username.toLowerCase(), host: null })) { + ctx.status = 400; + return; + } + + const account = await Users.save({ + id: genId(), createdAt: new Date(), - description: null, - followersCount: 0, - followingCount: 0, - name: null, - notesCount: 0, username: username, usernameLower: username.toLowerCase(), - host: null, - keypair: generateKeypair(), + host: host, token: secret, password: hash, isAdmin: config.autoAdmin && usersCount === 0, autoAcceptFollowed: true, - profile: { - bio: null, - birthday: null, - location: null - }, - settings: { - autoWatch: false - } + autoWatch: false + } as User); + + await UserKeypairs.save({ + id: genId(), + keyPem: await new Promise((s, j) => generateKeyPair('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem', + cipher: undefined, + passphrase: undefined + } + }, (e, _, x) => e ? j(e) : s(x))), + userId: account.id }); - //#region Increment users count - Meta.update({}, { - $inc: { - 'stats.usersCount': 1, - 'stats.originalUsersCount': 1 - } - }, { upsert: true }); - //#endregion + await UserServiceLinkings.save({ + id: genId(), + userId: account.id + } as UserServiceLinking); usersChart.update(account, true); - const res = await pack(account, account, { + const res = await Users.pack(account, account, { detail: true, includeSecrets: true }); diff --git a/src/server/api/service/discord.ts b/src/server/api/service/discord.ts index 92f5bbf72d..4290e1ff9d 100644 --- a/src/server/api/service/discord.ts +++ b/src/server/api/service/discord.ts @@ -2,13 +2,14 @@ import * as Koa from 'koa'; import * as Router from 'koa-router'; import * as request from 'request'; import { OAuth2 } from 'oauth'; -import User, { pack, ILocalUser } from '../../../models/user'; import config from '../../../config'; import { publishMainStream } from '../../../services/stream'; import redis from '../../../db/redis'; import * as uuid from 'uuid'; import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; +import { Users, UserServiceLinkings } from '../../../models'; +import { ILocalUser } from '../../../models/entities/user'; function getUserToken(ctx: Koa.BaseContext) { return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1]; @@ -39,19 +40,27 @@ router.get('/disconnect/discord', async ctx => { return; } - const user = await User.findOneAndUpdate({ + const user = await Users.findOne({ host: null, - 'token': userToken + token: userToken + }); + + await UserServiceLinkings.update({ + userId: user.id }, { - $set: { - 'discord': null - } + discord: false, + discordAccessToken: null, + discordRefreshToken: null, + discordExpiresDate: null, + discordId: null, + discordUsername: null, + discordDiscriminator: null, }); ctx.body = `Discordの連携を解除しました :v:`; // Publish i updated event - publishMainStream(user._id, 'meUpdated', await pack(user, user, { + publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { detail: true, includeSecrets: true })); @@ -193,32 +202,30 @@ router.get('/dc/cb', async ctx => { return; } - let user = await User.findOne({ - host: null, - 'discord.id': id - }) as ILocalUser; + const link = await UserServiceLinkings.createQueryBuilder() + .where('discord @> :discord', { + discord: { + id: id, + }, + }) + .andWhere('userHost IS NULL') + .getOne(); - if (!user) { + if (link == null) { ctx.throw(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`); return; } - user = await User.findOneAndUpdate({ - host: null, - 'discord.id': id - }, { - $set: { - discord: { - accessToken, - refreshToken, - expiresDate, - username, - discriminator - } - } - }) as ILocalUser; + await UserServiceLinkings.update(link.id, { + discord: true, + discordAccessToken: accessToken, + discordRefreshToken: refreshToken, + discordExpiresDate: expiresDate, + discordUsername: username, + discordDiscriminator: discriminator + }); - signin(ctx, user, true); + signin(ctx, await Users.findOne(link.userId) as ILocalUser, true); } else { const code = ctx.query.code; @@ -277,26 +284,25 @@ router.get('/dc/cb', async ctx => { return; } - const user = await User.findOneAndUpdate({ + const user = await Users.findOne({ host: null, token: userToken - }, { - $set: { - discord: { - accessToken, - refreshToken, - expiresDate, - id, - username, - discriminator - } - } + }); + + await UserServiceLinkings.update({ userId: user.id }, { + discord: true, + discordAccessToken: accessToken, + discordRefreshToken: refreshToken, + discordExpiresDate: expiresDate, + discordId: id, + discordUsername: username, + discordDiscriminator: discriminator }); ctx.body = `Discord: @${username}#${discriminator} を、Misskey: @${user.username} に接続しました!`; // Publish i updated event - publishMainStream(user._id, 'meUpdated', await pack(user, user, { + publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { detail: true, includeSecrets: true })); diff --git a/src/server/api/service/github.ts b/src/server/api/service/github.ts index cf3589a4b7..e59b149d19 100644 --- a/src/server/api/service/github.ts +++ b/src/server/api/service/github.ts @@ -2,13 +2,14 @@ import * as Koa from 'koa'; import * as Router from 'koa-router'; import * as request from 'request'; import { OAuth2 } from 'oauth'; -import User, { pack, ILocalUser } from '../../../models/user'; import config from '../../../config'; import { publishMainStream } from '../../../services/stream'; import redis from '../../../db/redis'; import * as uuid from 'uuid'; import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; +import { Users, UserServiceLinkings } from '../../../models'; +import { ILocalUser } from '../../../models/entities/user'; function getUserToken(ctx: Koa.BaseContext) { return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1]; @@ -39,19 +40,24 @@ router.get('/disconnect/github', async ctx => { return; } - const user = await User.findOneAndUpdate({ + const user = await Users.findOne({ host: null, - 'token': userToken + token: userToken + }); + + await UserServiceLinkings.update({ + userId: user.id }, { - $set: { - 'github': null - } + github: false, + githubAccessToken: null, + githubId: null, + githubLogin: null, }); ctx.body = `GitHubの連携を解除しました :v:`; // Publish i updated event - publishMainStream(user._id, 'meUpdated', await pack(user, user, { + publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { detail: true, includeSecrets: true })); @@ -185,17 +191,21 @@ router.get('/gh/cb', async ctx => { return; } - const user = await User.findOne({ - host: null, - 'github.id': id - }) as ILocalUser; + const link = await UserServiceLinkings.createQueryBuilder() + .where('github @> :github', { + github: { + id: id, + }, + }) + .andWhere('userHost IS NULL') + .getOne(); - if (!user) { + if (link == null) { ctx.throw(404, `@${login}と連携しているMisskeyアカウントはありませんでした...`); return; } - signin(ctx, user, true); + signin(ctx, await Users.findOne(link.userId) as ILocalUser, true); } else { const code = ctx.query.code; @@ -248,23 +258,22 @@ router.get('/gh/cb', async ctx => { return; } - const user = await User.findOneAndUpdate({ + const user = await Users.findOne({ host: null, token: userToken - }, { - $set: { - github: { - accessToken, - id, - login - } - } + }); + + await UserServiceLinkings.update({ userId: user.id }, { + github: true, + githubAccessToken: accessToken, + githubId: id, + githubLogin: login, }); ctx.body = `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`; // Publish i updated event - publishMainStream(user._id, 'meUpdated', await pack(user, user, { + publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { detail: true, includeSecrets: true })); diff --git a/src/server/api/service/twitter.ts b/src/server/api/service/twitter.ts index fc23808e21..77cf71395b 100644 --- a/src/server/api/service/twitter.ts +++ b/src/server/api/service/twitter.ts @@ -3,11 +3,12 @@ import * as Router from 'koa-router'; import * as uuid from 'uuid'; import autwh from 'autwh'; import redis from '../../../db/redis'; -import User, { pack, ILocalUser } from '../../../models/user'; import { publishMainStream } from '../../../services/stream'; import config from '../../../config'; import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; +import { Users, UserServiceLinkings } from '../../../models'; +import { ILocalUser } from '../../../models/entities/user'; function getUserToken(ctx: Koa.BaseContext) { return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1]; @@ -38,19 +39,25 @@ router.get('/disconnect/twitter', async ctx => { return; } - const user = await User.findOneAndUpdate({ + const user = await Users.findOne({ host: null, - 'token': userToken + token: userToken + }); + + await UserServiceLinkings.update({ + userId: user.id }, { - $set: { - 'twitter': null - } + twitter: false, + twitterAccessToken: null, + twitterAccessTokenSecret: null, + twitterUserId: null, + twitterScreenName: null, }); ctx.body = `Twitterの連携を解除しました :v:`; // Publish i updated event - publishMainStream(user._id, 'meUpdated', await pack(user, user, { + publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { detail: true, includeSecrets: true })); @@ -132,17 +139,21 @@ router.get('/tw/cb', async ctx => { const result = await twAuth.done(JSON.parse(twCtx), ctx.query.oauth_verifier); - const user = await User.findOne({ - host: null, - 'twitter.userId': result.userId - }) as ILocalUser; + const link = await UserServiceLinkings.createQueryBuilder() + .where('twitter @> :twitter', { + twitter: { + userId: result.userId, + }, + }) + .andWhere('userHost IS NULL') + .getOne(); - if (user == null) { + if (link == null) { ctx.throw(404, `@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`); return; } - signin(ctx, user, true); + signin(ctx, await Users.findOne(link.userId) as ILocalUser, true); } else { const verifier = ctx.query.oauth_verifier; @@ -161,24 +172,23 @@ router.get('/tw/cb', async ctx => { const result = await twAuth.done(JSON.parse(twCtx), verifier); - const user = await User.findOneAndUpdate({ + const user = await Users.findOne({ host: null, token: userToken - }, { - $set: { - twitter: { - accessToken: result.accessToken, - accessTokenSecret: result.accessTokenSecret, - userId: result.userId, - screenName: result.screenName - } - } + }); + + await UserServiceLinkings.update({ userId: user.id }, { + twitter: true, + twitterAccessToken: result.accessToken, + twitterAccessTokenSecret: result.accessTokenSecret, + twitterUserId: result.userId, + twitterScreenName: result.screenName, }); ctx.body = `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`; // Publish i updated event - publishMainStream(user._id, 'meUpdated', await pack(user, user, { + publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { detail: true, includeSecrets: true })); diff --git a/src/server/api/stream/channel.ts b/src/server/api/stream/channel.ts index bdbe4605cf..18fa651820 100644 --- a/src/server/api/stream/channel.ts +++ b/src/server/api/stream/channel.ts @@ -15,6 +15,14 @@ export default abstract class Channel { return this.connection.user; } + protected get following() { + return this.connection.following; + } + + protected get muting() { + return this.connection.muting; + } + protected get subscriber() { return this.connection.subscriber; } diff --git a/src/server/api/stream/channels/admin.ts b/src/server/api/stream/channels/admin.ts index 6bcd1a7e0b..e2eba10f78 100644 --- a/src/server/api/stream/channels/admin.ts +++ b/src/server/api/stream/channels/admin.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe admin stream - this.subscriber.on(`adminStream:${this.user._id}`, data => { + this.subscriber.on(`adminStream:${this.user.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/drive.ts b/src/server/api/stream/channels/drive.ts index 391c4b5c32..671aad4366 100644 --- a/src/server/api/stream/channels/drive.ts +++ b/src/server/api/stream/channels/drive.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe drive stream - this.subscriber.on(`driveStream:${this.user._id}`, data => { + this.subscriber.on(`driveStream:${this.user.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/games/reversi-game.ts b/src/server/api/stream/channels/games/reversi-game.ts index 87df9e194c..158f108c4e 100644 --- a/src/server/api/stream/channels/games/reversi-game.ts +++ b/src/server/api/stream/channels/games/reversi-game.ts @@ -1,22 +1,22 @@ import autobind from 'autobind-decorator'; import * as CRC32 from 'crc-32'; -import * as mongo from 'mongodb'; -import ReversiGame, { pack } from '../../../../../models/games/reversi/game'; import { publishReversiGameStream } from '../../../../../services/stream'; import Reversi from '../../../../../games/reversi/core'; import * as maps from '../../../../../games/reversi/maps'; import Channel from '../../channel'; +import { ReversiGame } from '../../../../../models/entities/games/reversi/game'; +import { ReversiGames } from '../../../../../models'; export default class extends Channel { public readonly chName = 'gamesReversiGame'; public static shouldShare = false; public static requireCredential = false; - private gameId: mongo.ObjectID; + private gameId: ReversiGame['id']; @autobind public async init(params: any) { - this.gameId = new mongo.ObjectID(params.gameId as string); + this.gameId = params.gameId; // Subscribe game stream this.subscriber.on(`reversiGameStream:${this.gameId}`, data => { @@ -29,7 +29,7 @@ export default class extends Channel { switch (type) { case 'accept': this.accept(true); break; case 'cancelAccept': this.accept(false); break; - case 'updateSettings': this.updateSettings(body.settings); break; + case 'updateSettings': this.updateSettings(body.key, body.value); break; case 'initForm': this.initForm(body); break; case 'updateForm': this.updateForm(body.id, body.value); break; case 'message': this.message(body); break; @@ -39,54 +39,55 @@ export default class extends Channel { } @autobind - private async updateSettings(settings: any) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + private async updateSettings(key: string, value: any) { + const game = await ReversiGames.findOne(this.gameId); if (game.isStarted) return; - if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return; - if (game.user1Id.equals(this.user._id) && game.user1Accepted) return; - if (game.user2Id.equals(this.user._id) && game.user2Accepted) return; + if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; + if ((game.user1Id === this.user.id) && game.user1Accepted) return; + if ((game.user2Id === this.user.id) && game.user2Accepted) return; - await ReversiGame.update({ _id: this.gameId }, { - $set: { - settings - } + if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard'].includes(key)) return; + + await ReversiGames.update({ id: this.gameId }, { + [key]: value }); - publishReversiGameStream(this.gameId, 'updateSettings', settings); + publishReversiGameStream(this.gameId, 'updateSettings', { + key: key, + value: value + }); } @autobind private async initForm(form: any) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne(this.gameId); if (game.isStarted) return; - if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return; + if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - const set = game.user1Id.equals(this.user._id) ? { + const set = game.user1Id === this.user.id ? { form1: form } : { - form2: form - }; + form2: form + }; - await ReversiGame.update({ _id: this.gameId }, { - $set: set - }); + await ReversiGames.update({ id: this.gameId }, set); publishReversiGameStream(this.gameId, 'initForm', { - userId: this.user._id, + userId: this.user.id, form }); } @autobind private async updateForm(id: string, value: any) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne({ id: this.gameId }); if (game.isStarted) return; - if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return; + if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - const form = game.user1Id.equals(this.user._id) ? game.form2 : game.form1; + const form = game.user1Id === this.user.id ? game.form2 : game.form1; const item = form.find((i: any) => i.id == id); @@ -94,18 +95,16 @@ export default class extends Channel { item.value = value; - const set = game.user1Id.equals(this.user._id) ? { + const set = game.user1Id === this.user.id ? { form2: form } : { form1: form }; - await ReversiGame.update({ _id: this.gameId }, { - $set: set - }); + await ReversiGames.update({ id: this.gameId }, set); publishReversiGameStream(this.gameId, 'updateForm', { - userId: this.user._id, + userId: this.user.id, id, value }); @@ -115,24 +114,22 @@ export default class extends Channel { private async message(message: any) { message.id = Math.random(); publishReversiGameStream(this.gameId, 'message', { - userId: this.user._id, + userId: this.user.id, message }); } @autobind private async accept(accept: boolean) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne(this.gameId); if (game.isStarted) return; let bothAccepted = false; - if (game.user1Id.equals(this.user._id)) { - await ReversiGame.update({ _id: this.gameId }, { - $set: { - user1Accepted: accept - } + if (game.user1Id === this.user.id) { + await ReversiGames.update({ id: this.gameId }, { + user1Accepted: accept }); publishReversiGameStream(this.gameId, 'changeAccepts', { @@ -141,11 +138,9 @@ export default class extends Channel { }); if (accept && game.user2Accepted) bothAccepted = true; - } else if (game.user2Id.equals(this.user._id)) { - await ReversiGame.update({ _id: this.gameId }, { - $set: { - user2Accepted: accept - } + } else if (game.user2Id === this.user.id) { + await ReversiGames.update({ id: this.gameId }, { + user2Accepted: accept }); publishReversiGameStream(this.gameId, 'changeAccepts', { @@ -161,15 +156,15 @@ export default class extends Channel { if (bothAccepted) { // 3秒後、まだacceptされていたらゲーム開始 setTimeout(async () => { - const freshGame = await ReversiGame.findOne({ _id: this.gameId }); + const freshGame = await ReversiGames.findOne(this.gameId); if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return; if (!freshGame.user1Accepted || !freshGame.user2Accepted) return; let bw: number; - if (freshGame.settings.bw == 'random') { + if (freshGame.bw == 'random') { bw = Math.random() > 0.5 ? 1 : 2; } else { - bw = freshGame.settings.bw as number; + bw = parseInt(freshGame.bw, 10); } function getRandomMap() { @@ -178,22 +173,20 @@ export default class extends Channel { return Object.values(maps)[rnd].data; } - const map = freshGame.settings.map != null ? freshGame.settings.map : getRandomMap(); + const map = freshGame.map != null ? freshGame.map : getRandomMap(); - await ReversiGame.update({ _id: this.gameId }, { - $set: { - startedAt: new Date(), - isStarted: true, - black: bw, - 'settings.map': map - } + await ReversiGames.update({ id: this.gameId }, { + startedAt: new Date(), + isStarted: true, + black: bw, + map: map }); //#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理 const o = new Reversi(map, { - isLlotheo: freshGame.settings.isLlotheo, - canPutEverywhere: freshGame.settings.canPutEverywhere, - loopedBoard: freshGame.settings.loopedBoard + isLlotheo: freshGame.isLlotheo, + canPutEverywhere: freshGame.canPutEverywhere, + loopedBoard: freshGame.loopedBoard }); if (o.isEnded) { @@ -206,23 +199,22 @@ export default class extends Channel { winner = null; } - await ReversiGame.update({ - _id: this.gameId + await ReversiGames.update({ + id: this.gameId }, { - $set: { - isEnded: true, - winnerId: winner - } - }); + isEnded: true, + winnerId: winner + }); publishReversiGameStream(this.gameId, 'ended', { winnerId: winner, - game: await pack(this.gameId, this.user) + game: await ReversiGames.pack(this.gameId, this.user) }); } //#endregion - publishReversiGameStream(this.gameId, 'started', await pack(this.gameId, this.user)); + publishReversiGameStream(this.gameId, 'started', + await ReversiGames.pack(this.gameId, this.user)); }, 3000); } } @@ -230,16 +222,16 @@ export default class extends Channel { // 石を打つ @autobind private async set(pos: number) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne(this.gameId); if (!game.isStarted) return; if (game.isEnded) return; - if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return; + if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - const o = new Reversi(game.settings.map, { - isLlotheo: game.settings.isLlotheo, - canPutEverywhere: game.settings.canPutEverywhere, - loopedBoard: game.settings.loopedBoard + const o = new Reversi(game.map, { + isLlotheo: game.isLlotheo, + canPutEverywhere: game.canPutEverywhere, + loopedBoard: game.loopedBoard }); for (const log of game.logs) { @@ -247,7 +239,7 @@ export default class extends Channel { } const myColor = - (game.user1Id.equals(this.user._id) && game.black == 1) || (game.user2Id.equals(this.user._id) && game.black == 2) + ((game.user1Id === this.user.id) && game.black == 1) || ((game.user2Id === this.user.id) && game.black == 2) ? true : false; @@ -271,20 +263,18 @@ export default class extends Channel { pos }; - const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()); + const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()).toString(); - await ReversiGame.update({ - _id: this.gameId + game.logs.push(log); + + await ReversiGames.update({ + id: this.gameId }, { - $set: { - crc32, - isEnded: o.isEnded, - winnerId: winner - }, - $push: { - logs: log - } - }); + crc32, + isEnded: o.isEnded, + winnerId: winner, + logs: game.logs + }); publishReversiGameStream(this.gameId, 'set', Object.assign(log, { next: o.turn @@ -293,14 +283,14 @@ export default class extends Channel { if (o.isEnded) { publishReversiGameStream(this.gameId, 'ended', { winnerId: winner, - game: await pack(this.gameId, this.user) + game: await ReversiGames.pack(this.gameId, this.user) }); } } @autobind private async check(crc32: string) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne(this.gameId); if (!game.isStarted) return; @@ -308,7 +298,7 @@ export default class extends Channel { if (game.crc32 == null) return; if (crc32 !== game.crc32) { - this.send('rescue', await pack(game, this.user)); + this.send('rescue', await ReversiGames.pack(game, this.user)); } } } diff --git a/src/server/api/stream/channels/games/reversi.ts b/src/server/api/stream/channels/games/reversi.ts index 1b1ad187a3..0498e5e017 100644 --- a/src/server/api/stream/channels/games/reversi.ts +++ b/src/server/api/stream/channels/games/reversi.ts @@ -1,8 +1,7 @@ import autobind from 'autobind-decorator'; -import * as mongo from 'mongodb'; -import Matching, { pack } from '../../../../../models/games/reversi/matching'; import { publishMainStream } from '../../../../../services/stream'; import Channel from '../../channel'; +import { ReversiMatchings } from '../../../../../models'; export default class extends Channel { public readonly chName = 'gamesReversi'; @@ -12,7 +11,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe reversi stream - this.subscriber.on(`reversiStream:${this.user._id}`, data => { + this.subscriber.on(`reversiStream:${this.user.id}`, data => { this.send(data); }); } @@ -22,12 +21,12 @@ export default class extends Channel { switch (type) { case 'ping': if (body.id == null) return; - const matching = await Matching.findOne({ - parentId: this.user._id, - childId: new mongo.ObjectID(body.id) + const matching = await ReversiMatchings.findOne({ + parentId: this.user.id, + childId: body.id }); if (matching == null) return; - publishMainStream(matching.childId, 'reversiInvited', await pack(matching, matching.childId)); + publishMainStream(matching.childId, 'reversiInvited', await ReversiMatchings.pack(matching, matching.childId)); break; } } diff --git a/src/server/api/stream/channels/global-timeline.ts b/src/server/api/stream/channels/global-timeline.ts index b3689d47f5..bfb7697ba7 100644 --- a/src/server/api/stream/channels/global-timeline.ts +++ b/src/server/api/stream/channels/global-timeline.ts @@ -1,17 +1,14 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; import fetchMeta from '../../../../misc/fetch-meta'; +import { Notes } from '../../../../models'; export default class extends Channel { public readonly chName = 'globalTimeline'; public static shouldShare = true; public static requireCredential = false; - private mutedUserIds: string[] = []; - @autobind public async init(params: any) { const meta = await fetchMeta(); @@ -20,29 +17,26 @@ export default class extends Channel { } // Subscribe events - this.subscriber.on('globalTimeline', this.onNote); - - const mute = await Mute.find({ muterId: this.user._id }); - this.mutedUserIds = mute.map(m => m.muteeId.toString()); + this.subscriber.on('notesStream', this.onNote); } @autobind private async onNote(note: any) { // リプライなら再pack if (note.replyId != null) { - note.reply = await pack(note.replyId, this.user, { + note.reply = await Notes.pack(note.replyId, this.user, { detail: true }); } // Renoteなら再pack if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { + note.renote = await Notes.pack(note.renoteId, this.user, { detail: true }); } // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, this.mutedUserIds)) return; + if (shouldMuteThisNote(note, this.muting)) return; this.send('note', note); } @@ -50,6 +44,6 @@ export default class extends Channel { @autobind public dispose() { // Unsubscribe events - this.subscriber.off('globalTimeline', this.onNote); + this.subscriber.off('notesStream', this.onNote); } } diff --git a/src/server/api/stream/channels/hashtag.ts b/src/server/api/stream/channels/hashtag.ts index 586ce02f06..36c56c7ab6 100644 --- a/src/server/api/stream/channels/hashtag.ts +++ b/src/server/api/stream/channels/hashtag.ts @@ -1,40 +1,46 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; +import { Notes } from '../../../../models'; export default class extends Channel { public readonly chName = 'hashtag'; public static shouldShare = false; public static requireCredential = false; + private q: string[][]; @autobind public async init(params: any) { - const mute = this.user ? await Mute.find({ muterId: this.user._id }) : null; - const mutedUserIds = mute ? mute.map(m => m.muteeId.toString()) : []; + this.q = params.q; - const q: string[][] = params.q; - - if (q == null) return; + if (this.q == null) return; // Subscribe stream - this.subscriber.on('hashtag', async note => { - const noteTags = note.tags.map((t: string) => t.toLowerCase()); - const matched = q.some(tags => tags.every(tag => noteTags.includes(tag.toLowerCase()))); - if (!matched) return; - - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { - detail: true - }); - } - - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, mutedUserIds)) return; - - this.send('note', note); - }); + this.subscriber.on('notesStream', this.onNote); + } + + @autobind + private async onNote(note: any) { + const noteTags = note.tags.map((t: string) => t.toLowerCase()); + const matched = this.q.some(tags => tags.every(tag => noteTags.includes(tag.toLowerCase()))); + if (!matched) return; + + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } + + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する + if (shouldMuteThisNote(note, this.muting)) return; + + this.send('note', note); + } + + @autobind + public dispose() { + // Unsubscribe events + this.subscriber.off('notesStream', this.onNote); } } diff --git a/src/server/api/stream/channels/home-timeline.ts b/src/server/api/stream/channels/home-timeline.ts index 3c0b238720..2cece0947f 100644 --- a/src/server/api/stream/channels/home-timeline.ts +++ b/src/server/api/stream/channels/home-timeline.ts @@ -1,42 +1,49 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; +import { Notes } from '../../../../models'; export default class extends Channel { public readonly chName = 'homeTimeline'; public static shouldShare = true; public static requireCredential = true; - private mutedUserIds: string[] = []; - @autobind public async init(params: any) { // Subscribe events - this.subscriber.on(`homeTimeline:${this.user._id}`, this.onNote); - - const mute = await Mute.find({ muterId: this.user._id }); - this.mutedUserIds = mute.map(m => m.muteeId.toString()); + this.subscriber.on('notesStream', this.onNote); } @autobind private async onNote(note: any) { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await pack(note.replyId, this.user, { - detail: true - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { + // その投稿のユーザーをフォローしていなかったら弾く + if (this.user.id !== note.userId && !this.following.includes(note.userId)) return; + + if (['followers', 'specified'].includes(note.visibility)) { + note = await Notes.pack(note.id, this.user, { detail: true }); + + if (note.isHidden) { + return; + } + } else { + // リプライなら再pack + if (note.replyId != null) { + note.reply = await Notes.pack(note.replyId, this.user, { + detail: true + }); + } + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } } // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, this.mutedUserIds)) return; + if (shouldMuteThisNote(note, this.muting)) return; this.send('note', note); } @@ -44,6 +51,6 @@ export default class extends Channel { @autobind public dispose() { // Unsubscribe events - this.subscriber.off(`homeTimeline:${this.user._id}`, this.onNote); + this.subscriber.off('notesStream', this.onNote); } } diff --git a/src/server/api/stream/channels/hybrid-timeline.ts b/src/server/api/stream/channels/hybrid-timeline.ts deleted file mode 100644 index 35ef17b56b..0000000000 --- a/src/server/api/stream/channels/hybrid-timeline.ts +++ /dev/null @@ -1,55 +0,0 @@ -import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; -import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; -import Channel from '../channel'; -import fetchMeta from '../../../../misc/fetch-meta'; - -export default class extends Channel { - public readonly chName = 'hybridTimeline'; - public static shouldShare = true; - public static requireCredential = true; - - private mutedUserIds: string[] = []; - - @autobind - public async init(params: any) { - const meta = await fetchMeta(); - if (meta.disableLocalTimeline && !this.user.isAdmin && !this.user.isModerator) return; - - // Subscribe events - this.subscriber.on('hybridTimeline', this.onNewNote); - this.subscriber.on(`hybridTimeline:${this.user._id}`, this.onNewNote); - - const mute = await Mute.find({ muterId: this.user._id }); - this.mutedUserIds = mute.map(m => m.muteeId.toString()); - } - - @autobind - private async onNewNote(note: any) { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await pack(note.replyId, this.user, { - detail: true - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { - detail: true - }); - } - - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, this.mutedUserIds)) return; - - this.send('note', note); - } - - @autobind - public dispose() { - // Unsubscribe events - this.subscriber.off('hybridTimeline', this.onNewNote); - this.subscriber.off(`hybridTimeline:${this.user._id}`, this.onNewNote); - } -} diff --git a/src/server/api/stream/channels/index.ts b/src/server/api/stream/channels/index.ts index 4527fb1e46..199ab0a809 100644 --- a/src/server/api/stream/channels/index.ts +++ b/src/server/api/stream/channels/index.ts @@ -1,7 +1,7 @@ import main from './main'; import homeTimeline from './home-timeline'; import localTimeline from './local-timeline'; -import hybridTimeline from './hybrid-timeline'; +import socialTimeline from './social-timeline'; import globalTimeline from './global-timeline'; import notesStats from './notes-stats'; import serverStats from './server-stats'; @@ -20,7 +20,7 @@ export default { main, homeTimeline, localTimeline, - hybridTimeline, + socialTimeline, globalTimeline, notesStats, serverStats, diff --git a/src/server/api/stream/channels/local-timeline.ts b/src/server/api/stream/channels/local-timeline.ts index 3402023192..4aec2d66b4 100644 --- a/src/server/api/stream/channels/local-timeline.ts +++ b/src/server/api/stream/channels/local-timeline.ts @@ -1,17 +1,14 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; import fetchMeta from '../../../../misc/fetch-meta'; +import { Notes } from '../../../../models'; export default class extends Channel { public readonly chName = 'localTimeline'; public static shouldShare = true; public static requireCredential = false; - private mutedUserIds: string[] = []; - @autobind public async init(params: any) { const meta = await fetchMeta(); @@ -20,29 +17,39 @@ export default class extends Channel { } // Subscribe events - this.subscriber.on('localTimeline', this.onNote); - - const mute = this.user ? await Mute.find({ muterId: this.user._id }) : null; - this.mutedUserIds = mute ? mute.map(m => m.muteeId.toString()) : []; + this.subscriber.on('notesStream', this.onNote); } @autobind private async onNote(note: any) { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await pack(note.replyId, this.user, { - detail: true - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { + if (note.user.host !== null) return; + if (note.visibility === 'home') return; + + if (['followers', 'specified'].includes(note.visibility)) { + note = await Notes.pack(note.id, this.user, { detail: true }); + + if (note.isHidden) { + return; + } + } else { + // リプライなら再pack + if (note.replyId != null) { + note.reply = await Notes.pack(note.replyId, this.user, { + detail: true + }); + } + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } } // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, this.mutedUserIds)) return; + if (shouldMuteThisNote(note, this.muting)) return; this.send('note', note); } @@ -50,6 +57,6 @@ export default class extends Channel { @autobind public dispose() { // Unsubscribe events - this.subscriber.off('localTimeline', this.onNote); + this.subscriber.off('notesStream', this.onNote); } } diff --git a/src/server/api/stream/channels/main.ts b/src/server/api/stream/channels/main.ts index 175d914fa5..0d9bf3149d 100644 --- a/src/server/api/stream/channels/main.ts +++ b/src/server/api/stream/channels/main.ts @@ -1,6 +1,6 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; import Channel from '../channel'; +import { Mutings } from '../../../../models'; export default class extends Channel { public readonly chName = 'main'; @@ -9,16 +9,15 @@ export default class extends Channel { @autobind public async init(params: any) { - const mute = await Mute.find({ muterId: this.user._id }); - const mutedUserIds = mute.map(m => m.muteeId.toString()); + const mute = await Mutings.find({ muterId: this.user.id }); // Subscribe main stream channel - this.subscriber.on(`mainStream:${this.user._id}`, async data => { + this.subscriber.on(`mainStream:${this.user.id}`, async data => { const { type, body } = data; switch (type) { case 'notification': { - if (mutedUserIds.includes(body.userId)) return; + if (mute.map(m => m.muteeId).includes(body.userId)) return; if (body.note && body.note.isHidden) return; break; } diff --git a/src/server/api/stream/channels/messaging-index.ts b/src/server/api/stream/channels/messaging-index.ts index 148ff7f935..648badc1dc 100644 --- a/src/server/api/stream/channels/messaging-index.ts +++ b/src/server/api/stream/channels/messaging-index.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe messaging index stream - this.subscriber.on(`messagingIndexStream:${this.user._id}`, data => { + this.subscriber.on(`messagingIndexStream:${this.user.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/messaging.ts b/src/server/api/stream/channels/messaging.ts index 0d81b4e45c..b81fbb9d4c 100644 --- a/src/server/api/stream/channels/messaging.ts +++ b/src/server/api/stream/channels/messaging.ts @@ -14,7 +14,7 @@ export default class extends Channel { this.otherpartyId = params.otherparty as string; // Subscribe messaging stream - this.subscriber.on(`messagingStream:${this.user._id}-${this.otherpartyId}`, data => { + this.subscriber.on(`messagingStream:${this.user.id}-${this.otherpartyId}`, data => { this.send(data); }); } @@ -23,7 +23,7 @@ export default class extends Channel { public onMessage(type: string, body: any) { switch (type) { case 'read': - read(this.user._id, this.otherpartyId, body.id); + read(this.user.id, this.otherpartyId, body.id); break; } } diff --git a/src/server/api/stream/channels/social-timeline.ts b/src/server/api/stream/channels/social-timeline.ts new file mode 100644 index 0000000000..1d76eed297 --- /dev/null +++ b/src/server/api/stream/channels/social-timeline.ts @@ -0,0 +1,64 @@ +import autobind from 'autobind-decorator'; +import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; +import Channel from '../channel'; +import fetchMeta from '../../../../misc/fetch-meta'; +import { Notes } from '../../../../models'; + +export default class extends Channel { + public readonly chName = 'socialTimeline'; + public static shouldShare = true; + public static requireCredential = true; + + @autobind + public async init(params: any) { + const meta = await fetchMeta(); + if (meta.disableLocalTimeline && !this.user.isAdmin && !this.user.isModerator) return; + + // Subscribe events + this.subscriber.on('notesStream', this.onNote); + } + + @autobind + private async onNote(note: any) { + // 自分自身の投稿 または その投稿のユーザーをフォローしている または ローカルの投稿 の場合だけ + if (!( + this.user.id === note.userId || + this.following.includes(note.userId) || + note.user.host === null + )) return; + + if (['followers', 'specified'].includes(note.visibility)) { + note = await Notes.pack(note.id, this.user, { + detail: true + }); + + if (note.isHidden) { + return; + } + } else { + // リプライなら再pack + if (note.replyId != null) { + note.reply = await Notes.pack(note.replyId, this.user, { + detail: true + }); + } + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } + } + + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する + if (shouldMuteThisNote(note, this.muting)) return; + + this.send('note', note); + } + + @autobind + public dispose() { + // Unsubscribe events + this.subscriber.off('notesStream', this.onNote); + } +} diff --git a/src/server/api/stream/channels/user-list.ts b/src/server/api/stream/channels/user-list.ts index 5debf41770..f5434b8f08 100644 --- a/src/server/api/stream/channels/user-list.ts +++ b/src/server/api/stream/channels/user-list.ts @@ -1,23 +1,81 @@ import autobind from 'autobind-decorator'; import Channel from '../channel'; -import { pack } from '../../../../models/note'; +import { Notes, UserListJoinings } from '../../../../models'; +import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; +import { User } from '../../../../models/entities/user'; export default class extends Channel { public readonly chName = 'userList'; public static shouldShare = false; public static requireCredential = false; + private listId: string; + public listUsers: User['id'][] = []; + private listUsersClock: NodeJS.Timer; @autobind public async init(params: any) { - const listId = params.listId as string; + this.listId = params.listId as string; // Subscribe stream - this.subscriber.on(`userListStream:${listId}`, async data => { - // 再パック - if (data.type == 'note') data.body = await pack(data.body.id, this.user, { + this.subscriber.on(`userListStream:${this.listId}`, this.send); + + this.subscriber.on('notesStream', this.onNote); + + this.updateListUsers(); + this.listUsersClock = setInterval(this.updateListUsers, 5000); + } + + @autobind + private async updateListUsers() { + const users = await UserListJoinings.find({ + where: { + userListId: this.listId, + }, + select: ['userId'] + }); + + this.listUsers = users.map(x => x.userId); + } + + @autobind + private async onNote(note: any) { + if (!this.listUsers.includes(note.userId)) return; + + if (['followers', 'specified'].includes(note.visibility)) { + note = await Notes.pack(note.id, this.user, { detail: true }); - this.send(data); - }); + + if (note.isHidden) { + return; + } + } else { + // リプライなら再pack + if (note.replyId != null) { + note.reply = await Notes.pack(note.replyId, this.user, { + detail: true + }); + } + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } + } + + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する + if (shouldMuteThisNote(note, this.muting)) return; + + this.send('note', note); + } + + @autobind + public dispose() { + // Unsubscribe events + this.subscriber.off(`userListStream:${this.listId}`, this.send); + this.subscriber.off('notesStream', this.onNote); + + clearInterval(this.listUsersClock); } } diff --git a/src/server/api/stream/index.ts b/src/server/api/stream/index.ts index 22f7646cb9..abbd91ec81 100644 --- a/src/server/api/stream/index.ts +++ b/src/server/api/stream/index.ts @@ -1,33 +1,35 @@ import autobind from 'autobind-decorator'; import * as websocket from 'websocket'; - -import User, { IUser } from '../../../models/user'; -import readNotification from '../common/read-notification'; +import { readNotification } from '../common/read-notification'; import call from '../call'; -import { IApp } from '../../../models/app'; import readNote from '../../../services/note/read'; - import Channel from './channel'; import channels from './channels'; import { EventEmitter } from 'events'; +import { User } from '../../../models/entities/user'; +import { App } from '../../../models/entities/app'; +import { Users, Followings, Mutings } from '../../../models'; /** * Main stream connection */ export default class Connection { - public user?: IUser; - public app: IApp; + public user?: User; + public following: User['id'][] = []; + public muting: User['id'][] = []; + public app: App; private wsConnection: websocket.connection; public subscriber: EventEmitter; private channels: Channel[] = []; private subscribingNotes: any = {}; - public sendMessageToWsOverride: any = null; // 後方互換性のため + private followingClock: NodeJS.Timer; + private mutingClock: NodeJS.Timer; constructor( wsConnection: websocket.connection, subscriber: EventEmitter, - user: IUser, - app: IApp + user: User, + app: App ) { this.wsConnection = wsConnection; this.user = user; @@ -35,6 +37,14 @@ export default class Connection { this.subscriber = subscriber; this.wsConnection.on('message', this.onWsConnectionMessage); + + if (this.user) { + this.updateFollowing(); + this.followingClock = setInterval(this.updateFollowing, 5000); + + this.updateMuting(); + this.mutingClock = setInterval(this.updateMuting, 5000); + } } /** @@ -64,7 +74,7 @@ export default class Connection { @autobind private async onApiRequest(payload: any) { // 新鮮なデータを利用するためにユーザーをフェッチ - const user = this.user ? await User.findOne({ _id: this.user._id }) : null; + const user = this.user ? await Users.findOne(this.user.id) : null; const endpoint = payload.endpoint || payload.ep; // alias @@ -79,7 +89,7 @@ export default class Connection { @autobind private onReadNotification(payload: any) { if (!payload.id) return; - readNotification(this.user._id, payload.id); + readNotification(this.user.id, [payload.id]); } /** @@ -100,7 +110,7 @@ export default class Connection { } if (payload.read) { - readNote(this.user._id, payload.id); + readNote(this.user.id, payload.id); } } @@ -150,7 +160,6 @@ export default class Connection { */ @autobind public sendMessageToWs(type: string, payload: any) { - if (this.sendMessageToWsOverride) return this.sendMessageToWsOverride(type, payload); // 後方互換性のため this.wsConnection.send(JSON.stringify({ type: type, body: payload @@ -208,6 +217,30 @@ export default class Connection { } } + @autobind + private async updateFollowing() { + const followings = await Followings.find({ + where: { + followerId: this.user.id + }, + select: ['followeeId'] + }); + + this.following = followings.map(x => x.followeeId); + } + + @autobind + private async updateMuting() { + const mutings = await Mutings.find({ + where: { + muterId: this.user.id + }, + select: ['muteeId'] + }); + + this.muting = mutings.map(x => x.muteeId); + } + /** * ストリームが切れたとき */ @@ -216,5 +249,8 @@ export default class Connection { for (const c of this.channels.filter(c => c.dispose)) { c.dispose(); } + + if (this.followingClock) clearInterval(this.followingClock); + if (this.mutingClock) clearInterval(this.mutingClock); } } diff --git a/src/server/api/streaming.ts b/src/server/api/streaming.ts index f8f3c0ff4a..ab66f2b6d9 100644 --- a/src/server/api/streaming.ts +++ b/src/server/api/streaming.ts @@ -48,33 +48,6 @@ module.exports = (server: http.Server) => { const main = new MainStreamConnection(connection, ev, user, app); - // 後方互換性のため - if (request.resourceURL.pathname !== '/streaming') { - main.sendMessageToWsOverride = (type: string, payload: any) => { - if (type == 'channel') { - type = payload.type; - payload = payload.body; - } - if (type.startsWith('api:')) { - type = type.replace('api:', 'api-res:'); - } - connection.send(JSON.stringify({ - type: type, - body: payload - })); - }; - - main.connectChannel(Math.random().toString().substr(2, 8), null, - request.resourceURL.pathname === '/' ? 'homeTimeline' : - request.resourceURL.pathname === '/local-timeline' ? 'localTimeline' : - request.resourceURL.pathname === '/hybrid-timeline' ? 'hybridTimeline' : - request.resourceURL.pathname === '/global-timeline' ? 'globalTimeline' : null); - - if (request.resourceURL.pathname === '/') { - main.connectChannel(Math.random().toString().substr(2, 8), null, 'main'); - } - } - connection.once('close', () => { ev.removeAllListeners(); main.dispose(); -- cgit v1.2.3-freya From fffa32df48d9caec3ae568931d482f00c20806eb Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Apr 2019 23:05:57 +0900 Subject: Fix bug --- src/server/api/endpoints/ap/show.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/ap/show.ts b/src/server/api/endpoints/ap/show.ts index 5b2aaeadbb..7378e3edc2 100644 --- a/src/server/api/endpoints/ap/show.ts +++ b/src/server/api/endpoints/ap/show.ts @@ -130,14 +130,14 @@ async function fetchAny(uri: string) { } async function mergePack(user: User, note: Note) { - if (user !== null) { + if (user != null) { return { type: 'User', object: await Users.pack(user, null, { detail: true }) }; } - if (note !== null) { + if (note != null) { return { type: 'Note', object: await Notes.pack(note, null, { detail: true }) -- cgit v1.2.3-freya From d9780606b3f69fc7fff96245c032c2e443dc3432 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Apr 2019 23:06:07 +0900 Subject: Refactor --- src/queue/processors/db/import-following.ts | 2 +- src/queue/processors/db/import-user-lists.ts | 2 +- src/remote/resolve-user.ts | 4 ++-- src/server/api/endpoints/users/show.ts | 6 +++--- src/services/note/create.ts | 2 +- src/tools/resync-remote-user.ts | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/server/api') diff --git a/src/queue/processors/db/import-following.ts b/src/queue/processors/db/import-following.ts index 2e646c1869..07a1639494 100644 --- a/src/queue/processors/db/import-following.ts +++ b/src/queue/processors/db/import-following.ts @@ -3,7 +3,7 @@ import * as Bull from 'bull'; import { queueLogger } from '../../logger'; import follow from '../../../services/following/create'; import parseAcct from '../../../misc/acct/parse'; -import resolveUser from '../../../remote/resolve-user'; +import { resolveUser } from '../../../remote/resolve-user'; import { downloadTextFile } from '../../../misc/download-text-file'; import { isSelfHost, toDbHost } from '../../../misc/convert-host'; import { Users, DriveFiles } from '../../../models'; diff --git a/src/queue/processors/db/import-user-lists.ts b/src/queue/processors/db/import-user-lists.ts index 8be5785896..ff896c1bef 100644 --- a/src/queue/processors/db/import-user-lists.ts +++ b/src/queue/processors/db/import-user-lists.ts @@ -2,7 +2,7 @@ import * as Bull from 'bull'; import { queueLogger } from '../../logger'; import parseAcct from '../../../misc/acct/parse'; -import resolveUser from '../../../remote/resolve-user'; +import { resolveUser } from '../../../remote/resolve-user'; import { pushUserToUserList } from '../../../services/user-list/push'; import { downloadTextFile } from '../../../misc/download-text-file'; import { isSelfHost, toDbHost } from '../../../misc/convert-host'; diff --git a/src/remote/resolve-user.ts b/src/remote/resolve-user.ts index be846ab279..a10d3c2d84 100644 --- a/src/remote/resolve-user.ts +++ b/src/remote/resolve-user.ts @@ -10,7 +10,7 @@ import { Users } from '../models'; const logger = remoteLogger.createSubLogger('resolve-user'); -export default async (username: string, _host: string, option?: any, resync = false): Promise => { +export async function resolveUser(username: string, _host: string, option?: any, resync = false): Promise { const usernameLower = username.toLowerCase(); if (_host == null) { @@ -73,7 +73,7 @@ export default async (username: string, _host: string, option?: any, resync = fa logger.info(`return existing remote user: ${acctLower}`); return user; -}; +} async function resolveSelf(acctLower: string) { logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts index a605eaf30a..ae93e40eaa 100644 --- a/src/server/api/endpoints/users/show.ts +++ b/src/server/api/endpoints/users/show.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import resolveRemoteUser from '../../../../remote/resolve-user'; +import { resolveUser } from '../../../../remote/resolve-user'; import define from '../../define'; import { apiLogger } from '../../logger'; import { ApiError } from '../../error'; @@ -75,7 +75,7 @@ export default define(meta, async (ps, me) => { } else { // Lookup user if (typeof ps.host === 'string') { - user = await resolveRemoteUser(ps.username, ps.host).catch(e => { + user = await resolveUser(ps.username, ps.host).catch(e => { apiLogger.warn(`failed to resolve remote user: ${e}`); throw new ApiError(meta.errors.failedToResolveRemoteUser); }); @@ -94,7 +94,7 @@ export default define(meta, async (ps, me) => { // ユーザー情報更新 if (Users.isRemoteUser(user)) { if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { - resolveRemoteUser(ps.username, ps.host, { }, true); + resolveUser(ps.username, ps.host, { }, true); } } diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 9ac9223d3c..305e173e16 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -7,7 +7,7 @@ import renderAnnounce from '../../remote/activitypub/renderer/announce'; import { renderActivity } from '../../remote/activitypub/renderer'; import watch from './watch'; import { parse } from '../../mfm/parse'; -import resolveUser from '../../remote/resolve-user'; +import { resolveUser } from '../../remote/resolve-user'; import config from '../../config'; import { updateHashtag } from '../update-hashtag'; import { erase, concat } from '../../prelude/array'; diff --git a/src/tools/resync-remote-user.ts b/src/tools/resync-remote-user.ts index 7db5fe82ef..52f63cf1f1 100644 --- a/src/tools/resync-remote-user.ts +++ b/src/tools/resync-remote-user.ts @@ -1,5 +1,5 @@ import parseAcct from '../misc/acct/parse'; -import resolveUser from '../remote/resolve-user'; +import { resolveUser } from '../remote/resolve-user'; async function main(acct: string): Promise { const { username, host } = parseAcct(acct); -- cgit v1.2.3-freya From 6a30d32e7d7aced5138ce64ca93103649c387c20 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Apr 2019 23:31:58 +0900 Subject: Fix bug --- src/client/app/common/views/components/trends.vue | 4 ++-- src/server/api/endpoints/hashtags/trend.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/server/api') diff --git a/src/client/app/common/views/components/trends.vue b/src/client/app/common/views/components/trends.vue index cd67cc0092..536d55247c 100644 --- a/src/client/app/common/views/components/trends.vue +++ b/src/client/app/common/views/components/trends.vue @@ -4,9 +4,9 @@

{{ $t('empty') }}

-
+
- #{{ stat.name }} + #{{ stat.tag }}

{{ $t('count').replace('{}', stat.usersCount) }}

diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts index c750e72a15..e01e9d698f 100644 --- a/src/server/api/endpoints/hashtags/trend.ts +++ b/src/server/api/endpoints/hashtags/trend.ts @@ -67,7 +67,7 @@ export default define(meta, async () => { .slice(0, max); //#region 2(または3)で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する - const countPromises: Promise[] = []; + const countPromises: Promise[] = []; const range = 20; @@ -87,7 +87,7 @@ export default define(meta, async () => { const countsLog = await Promise.all(countPromises); - const totalCounts: any = await Promise.all(hots.map(tag => Notes.createQueryBuilder('note') + const totalCounts = await Promise.all(hots.map(tag => Notes.createQueryBuilder('note') .select('count(distinct note.userId)') .where(':tag = ANY(note.tags)', { tag: tag }) .andWhere('note.createdAt > :gt', { gt: new Date(Date.now() - (interval * range)) }) @@ -98,8 +98,8 @@ export default define(meta, async () => { const stats = hots.map((tag, i) => ({ tag, - chart: countsLog.map(counts => counts[i].length), - usersCount: totalCounts[i].length + chart: countsLog.map(counts => counts[i]), + usersCount: totalCounts[i] })); return stats; -- cgit v1.2.3-freya From d9a5e06b5b658d6191917ac5edb1f6add9d949bc Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 8 Apr 2019 02:27:06 +0900 Subject: Fix bug --- src/server/api/endpoints/admin/logs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/admin/logs.ts b/src/server/api/endpoints/admin/logs.ts index eee56a3939..907b1fdc14 100644 --- a/src/server/api/endpoints/admin/logs.ts +++ b/src/server/api/endpoints/admin/logs.ts @@ -65,7 +65,7 @@ export default define(meta, async (ps) => { } } - const logs = await query.take(ps.limit).getMany(); + const logs = await query.orderBy('log.createdAt', 'DESC').take(ps.limit).getMany(); return logs; }); -- cgit v1.2.3-freya From 142d59be85cd06b11d9df1375bcd0a84c0425e22 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 8 Apr 2019 03:35:02 +0900 Subject: Fix bug --- src/models/entities/user-keypair.ts | 7 ++++++- src/remote/activitypub/renderer/key.ts | 3 +-- src/remote/activitypub/request.ts | 2 +- src/server/api/private/signup.ts | 35 ++++++++++++++++++++-------------- 4 files changed, 29 insertions(+), 18 deletions(-) (limited to 'src/server/api') diff --git a/src/models/entities/user-keypair.ts b/src/models/entities/user-keypair.ts index 06b98d2536..be264641f7 100644 --- a/src/models/entities/user-keypair.ts +++ b/src/models/entities/user-keypair.ts @@ -20,5 +20,10 @@ export class UserKeypair { @Column('varchar', { length: 4096, }) - public keyPem: string; + public publicKey: string; + + @Column('varchar', { + length: 4096, + }) + public privateKey: string; } diff --git a/src/remote/activitypub/renderer/key.ts b/src/remote/activitypub/renderer/key.ts index fb5975a6c4..334e5e00cd 100644 --- a/src/remote/activitypub/renderer/key.ts +++ b/src/remote/activitypub/renderer/key.ts @@ -1,4 +1,3 @@ -import { createPublicKey } from 'crypto'; import config from '../../../config'; import { ILocalUser } from '../../../models/entities/user'; import { UserKeypair } from '../../../models/entities/user-keypair'; @@ -7,5 +6,5 @@ export default (user: ILocalUser, key: UserKeypair) => ({ id: `${config.url}/users/${user.id}/publickey`, type: 'Key', owner: `${config.url}/users/${user.id}`, - publicKeyPem: createPublicKey(key.keyPem) + publicKeyPem: key.publicKey }); diff --git a/src/remote/activitypub/request.ts b/src/remote/activitypub/request.ts index a089ed371c..c50d05e2a6 100644 --- a/src/remote/activitypub/request.ts +++ b/src/remote/activitypub/request.ts @@ -67,7 +67,7 @@ export default async (user: ILocalUser, url: string, object: any) => { sign(req, { authorizationHeaderName: 'Signature', - key: keypair.keyPem, + key: keypair.privateKey, keyId: `${config.url}/users/${user.id}/publickey`, headers: ['date', 'host', 'digest'] }); diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 1d304b8e11..8ab702bd8a 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -10,6 +10,7 @@ import { genId } from '../../../misc/gen-id'; import { usersChart } from '../../../services/chart'; import { UserServiceLinking } from '../../../models/entities/user-service-linking'; import { User } from '../../../models/entities/user'; +import { UserKeypair } from '../../../models/entities/user-keypair'; export default async (ctx: Koa.BaseContext) => { const body = ctx.request.body as any; @@ -80,6 +81,23 @@ export default async (ctx: Koa.BaseContext) => { return; } + const keyPair = await new Promise((s, j) => + generateKeyPair('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem', + cipher: undefined, + passphrase: undefined + } + }, (e, publicKey, privateKey) => + e ? j(e) : s([publicKey, privateKey]) + )); + const account = await Users.save({ id: genId(), createdAt: new Date(), @@ -95,21 +113,10 @@ export default async (ctx: Koa.BaseContext) => { await UserKeypairs.save({ id: genId(), - keyPem: await new Promise((s, j) => generateKeyPair('rsa', { - modulusLength: 4096, - publicKeyEncoding: { - type: 'pkcs1', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs1', - format: 'pem', - cipher: undefined, - passphrase: undefined - } - }, (e, _, x) => e ? j(e) : s(x))), + publicKey: keyPair[0], + privateKey: keyPair[1], userId: account.id - }); + } as UserKeypair); await UserServiceLinkings.save({ id: genId(), -- cgit v1.2.3-freya From 6c9d80d4e8d2f9a7256fd3640a04d0e8172e3f71 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 8 Apr 2019 16:39:02 +0900 Subject: Fix bug --- src/server/api/endpoints/users/search.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/users/search.ts b/src/server/api/endpoints/users/search.ts index 2e76546ade..443bd507f3 100644 --- a/src/server/api/endpoints/users/search.ts +++ b/src/server/api/endpoints/users/search.ts @@ -69,8 +69,8 @@ export default define(meta, async (ps, me) => { if (isUsername) { users = await Users.createQueryBuilder('user') .where('user.host IS NULL') - .where('user.isSuspended = FALSE') - .where('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) + .andWhere('user.isSuspended = FALSE') + .andWhere('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) .take(ps.limit) .skip(ps.offset) .getMany(); @@ -78,8 +78,8 @@ export default define(meta, async (ps, me) => { if (users.length < ps.limit && !ps.localOnly) { const otherUsers = await Users.createQueryBuilder('user') .where('user.host IS NOT NULL') - .where('user.isSuspended = FALSE') - .where('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) + .andWhere('user.isSuspended = FALSE') + .andWhere('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) .take(ps.limit - users.length) .getMany(); -- cgit v1.2.3-freya From 735687be21b08a11bf167663b9336a56eeb05313 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 8 Apr 2019 20:29:52 +0900 Subject: update token generation --- src/models/entities/user.ts | 4 ++-- src/server/api/common/generate-native-user-token.ts | 2 +- src/server/api/common/is-native-token.ts | 2 +- src/server/api/endpoints/auth/accept.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/server/api') diff --git a/src/models/entities/user.ts b/src/models/entities/user.ts index 1ef98cadc2..0a2878c0c9 100644 --- a/src/models/entities/user.ts +++ b/src/models/entities/user.ts @@ -255,8 +255,8 @@ export class User { public password: string | null; @Index({ unique: true }) - @Column('varchar', { - length: 32, nullable: true, unique: true, + @Column('char', { + length: 16, nullable: true, unique: true, comment: 'The native access token of the User. It will be null if the origin of the user is local.' }) public token: string | null; diff --git a/src/server/api/common/generate-native-user-token.ts b/src/server/api/common/generate-native-user-token.ts index 92f8a3a0e8..9d44885630 100644 --- a/src/server/api/common/generate-native-user-token.ts +++ b/src/server/api/common/generate-native-user-token.ts @@ -1,3 +1,3 @@ import rndstr from 'rndstr'; -export default () => `!${rndstr('a-zA-Z0-9', 31)}`; +export default () => `0${rndstr('a-zA-Z0-9', 15)}`; diff --git a/src/server/api/common/is-native-token.ts b/src/server/api/common/is-native-token.ts index 6afbc99ab5..22af84aad2 100644 --- a/src/server/api/common/is-native-token.ts +++ b/src/server/api/common/is-native-token.ts @@ -1 +1 @@ -export default (token: string) => token.startsWith('!'); +export default (token: string) => token.startsWith('0'); diff --git a/src/server/api/endpoints/auth/accept.ts b/src/server/api/endpoints/auth/accept.ts index 21a78011dc..0baa6b111c 100644 --- a/src/server/api/endpoints/auth/accept.ts +++ b/src/server/api/endpoints/auth/accept.ts @@ -38,7 +38,7 @@ export default define(meta, async (ps, user) => { } // Generate access token - const accessToken = rndstr('a-zA-Z0-9', 32); + const accessToken = '1' + rndstr('a-zA-Z0-9', 15); // Fetch exist access token const exist = await AccessTokens.findOne({ -- cgit v1.2.3-freya From 87d3a06dcd6a76120789c050dd6a0c7111bf7224 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 8 Apr 2019 23:05:41 +0900 Subject: revert rename --- locales/ja-JP.yml | 6 +- .../app/common/views/deck/deck.column-core.vue | 2 +- .../app/common/views/deck/deck.tl-column.vue | 4 +- src/client/app/common/views/deck/deck.tl.vue | 6 +- src/client/app/common/views/deck/deck.vue | 6 +- .../app/desktop/views/home/timeline.core.vue | 6 +- src/client/app/desktop/views/home/timeline.vue | 6 +- .../app/mobile/views/pages/home.timeline.vue | 6 +- src/client/app/mobile/views/pages/home.vue | 10 +- src/docs/stream.ja-JP.md | 2 +- src/server/api/endpoints/notes/hybrid-timeline.ts | 195 +++++++++++++++++++++ src/server/api/endpoints/notes/social-timeline.ts | 195 --------------------- src/server/api/stream/channels/hybrid-timeline.ts | 64 +++++++ src/server/api/stream/channels/index.ts | 4 +- src/server/api/stream/channels/social-timeline.ts | 64 ------- test/streaming.ts | 14 +- 16 files changed, 295 insertions(+), 295 deletions(-) create mode 100644 src/server/api/endpoints/notes/hybrid-timeline.ts delete mode 100644 src/server/api/endpoints/notes/social-timeline.ts create mode 100644 src/server/api/stream/channels/hybrid-timeline.ts delete mode 100644 src/server/api/stream/channels/social-timeline.ts (limited to 'src/server/api') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index a36817688c..927eefb5ba 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1088,7 +1088,7 @@ desktop/views/components/taskmanager.vue: desktop/views/components/timeline.vue: home: "ホーム" local: "ローカル" - social: "ソーシャル" + hybrid: "ソーシャル" global: "グローバル" mentions: "あなた宛て" messages: "ダイレクト投稿" @@ -1665,7 +1665,7 @@ mobile/views/pages/following.vue: mobile/views/pages/home.vue: home: "ホーム" local: "ローカル" - social: "ソーシャル" + hybrid: "ソーシャル" global: "グローバル" mentions: "あなた宛て" messages: "ダイレクト投稿" @@ -1735,7 +1735,7 @@ deck: widgets: "ウィジェット" home: "ホーム" local: "ローカル" - social: "ソーシャル" + hybrid: "ソーシャル" hashtag: "ハッシュタグ" global: "グローバル" mentions: "あなた宛て" diff --git a/src/client/app/common/views/deck/deck.column-core.vue b/src/client/app/common/views/deck/deck.column-core.vue index e3f92dea16..974c58235d 100644 --- a/src/client/app/common/views/deck/deck.column-core.vue +++ b/src/client/app/common/views/deck/deck.column-core.vue @@ -3,7 +3,7 @@ - + diff --git a/src/client/app/common/views/deck/deck.tl-column.vue b/src/client/app/common/views/deck/deck.tl-column.vue index f6a9ee5286..5ab8ccb12f 100644 --- a/src/client/app/common/views/deck/deck.tl-column.vue +++ b/src/client/app/common/views/deck/deck.tl-column.vue @@ -3,7 +3,7 @@

{{ $t('@.user-suspended') }}

-

{{ $t('@.is-remote-user') }}{{ $t('@.view-on-remote') }}

+

{{ $t('@.is-remote-user') }}{{ $t('@.view-on-remote') }}

diff --git a/src/models/entities/user-keypair.ts b/src/models/entities/user-keypair.ts index 808985f473..603321d758 100644 --- a/src/models/entities/user-keypair.ts +++ b/src/models/entities/user-keypair.ts @@ -22,4 +22,12 @@ export class UserKeypair { length: 4096, }) public privateKey: string; + + constructor(data: Partial) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } } diff --git a/src/models/entities/user-profile.ts b/src/models/entities/user-profile.ts index 421d17c59e..a2d7b8d2c2 100644 --- a/src/models/entities/user-profile.ts +++ b/src/models/entities/user-profile.ts @@ -39,6 +39,12 @@ export class UserProfile { value: string; }[]; + @Column('varchar', { + length: 512, nullable: true, + comment: 'Remote URL of the user.' + }) + public url: string | null; + @Column('varchar', { length: 128, nullable: true, comment: 'The email address of the User.' @@ -192,4 +198,12 @@ export class UserProfile { }) public userHost: string | null; //#endregion + + constructor(data: Partial) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } } diff --git a/src/models/entities/user-publickey.ts b/src/models/entities/user-publickey.ts index 26b694407d..21edc3e9e2 100644 --- a/src/models/entities/user-publickey.ts +++ b/src/models/entities/user-publickey.ts @@ -23,4 +23,12 @@ export class UserPublickey { length: 4096, }) public keyPem: string; + + constructor(data: Partial) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } } diff --git a/src/models/entities/user.ts b/src/models/entities/user.ts index ebf07ff3ff..e40c32a76f 100644 --- a/src/models/entities/user.ts +++ b/src/models/entities/user.ts @@ -205,6 +205,14 @@ export class User { comment: 'The native access token of the User. It will be null if the origin of the user is local.' }) public token: string | null; + + constructor(data: Partial) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } } export interface ILocalUser extends User { diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index ac18b18996..d7f2c3d040 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -87,7 +87,6 @@ export class UserRepository extends Repository { name: user.name, username: user.username, host: user.host, - uri: user.uri, avatarUrl: user.avatarUrl, bannerUrl: user.bannerUrl, avatarColor: user.avatarColor, @@ -118,6 +117,7 @@ export class UserRepository extends Repository { } : {}), ...(opts.detail ? { + url: profile.url, createdAt: user.createdAt, updatedAt: user.updatedAt, description: profile.description, diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index b2edf39734..58aca48eb0 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -25,6 +25,7 @@ import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-e import { toPuny } from '../../../misc/convert-host'; import { UserProfile } from '../../../models/entities/user-profile'; import { validActor } from '../../../remote/activitypub/type'; +import { getConnection } from 'typeorm'; const logger = apLogger; /** @@ -136,27 +137,42 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise) as IRemoteUser; + // Start transaction + await getConnection().transaction(async transactionalEntityManager => { + user = await transactionalEntityManager.save(new User({ + id: genId(), + avatarId: null, + bannerId: null, + createdAt: new Date(person.published) || new Date(), + lastFetchedAt: new Date(), + name: person.name, + isLocked: person.manuallyApprovesFollowers, + username: person.preferredUsername, + usernameLower: person.preferredUsername.toLowerCase(), + host, + inbox: person.inbox, + sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined), + featured: person.featured, + uri: person.id, + tags, + isBot, + isCat: (person as any).isCat === true + })) as IRemoteUser; + + await transactionalEntityManager.save(new UserProfile({ + userId: user.id, + description: fromHtml(person.summary), + url: person.url, + fields, + userHost: host + })); + + await transactionalEntityManager.save(new UserPublickey({ + userId: user.id, + keyId: person.publicKey.id, + keyPem: person.publicKey.publicKeyPem + })); + }); } catch (e) { // duplicate key error if (isDuplicateKeyValueError(e)) { @@ -167,19 +183,6 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise); - - await UserPublickeys.save({ - userId: user.id, - keyId: person.publicKey.id, - keyPem: person.publicKey.publicKeyPem - } as UserPublickey); - // Register host registerOrFetchInstanceDoc(host).then(i => { Instances.increment({ id: i.id }, 'usersCount', 1); diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 5ed25fa411..ed4a533dec 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -12,6 +12,7 @@ import { User } from '../../../models/entities/user'; import { UserKeypair } from '../../../models/entities/user-keypair'; import { toPuny } from '../../../misc/convert-host'; import { UserProfile } from '../../../models/entities/user-profile'; +import { getConnection } from 'typeorm'; export default async (ctx: Koa.BaseContext) => { const body = ctx.request.body as any; @@ -99,28 +100,33 @@ export default async (ctx: Koa.BaseContext) => { e ? j(e) : s([publicKey, privateKey]) )); - const account = await Users.save({ - id: genId(), - createdAt: new Date(), - username: username, - usernameLower: username.toLowerCase(), - host: toPuny(host), - token: secret, - isAdmin: config.autoAdmin && usersCount === 0, - } as User); - - await UserKeypairs.save({ - publicKey: keyPair[0], - privateKey: keyPair[1], - userId: account.id - } as UserKeypair); - - await UserProfiles.save({ - userId: account.id, - autoAcceptFollowed: true, - autoWatch: false, - password: hash, - } as Partial); + let account: User; + + // Start transaction + await getConnection().transaction(async transactionalEntityManager => { + account = await transactionalEntityManager.save(new User({ + id: genId(), + createdAt: new Date(), + username: username, + usernameLower: username.toLowerCase(), + host: toPuny(host), + token: secret, + isAdmin: config.autoAdmin && usersCount === 0, + })); + + await transactionalEntityManager.save(new UserKeypair({ + publicKey: keyPair[0], + privateKey: keyPair[1], + userId: account.id + })); + + await transactionalEntityManager.save(new UserProfile({ + userId: account.id, + autoAcceptFollowed: true, + autoWatch: false, + password: hash, + })); + }); usersChart.update(account, true); diff --git a/src/server/web/index.ts b/src/server/web/index.ts index de0d65cf33..d1a15e3929 100644 --- a/src/server/web/index.ts +++ b/src/server/web/index.ts @@ -16,7 +16,7 @@ import fetchMeta from '../../misc/fetch-meta'; import * as pkg from '../../../package.json'; import { genOpenapiSpec } from '../api/openapi/gen-spec'; import config from '../../config'; -import { Users, Notes, Emojis } from '../../models'; +import { Users, Notes, Emojis, UserProfiles } from '../../models'; import parseAcct from '../../misc/acct/parse'; import getNoteSummary from '../../misc/get-note-summary'; @@ -149,11 +149,14 @@ router.get('/@:user', async (ctx, next) => { usernameLower: username.toLowerCase(), host }); + const profile = await UserProfiles.findOne({ + userId: user.id + }); if (user != null) { const meta = await fetchMeta(); await ctx.render('user', { - user, + user, profile, instanceName: meta.name || 'Misskey' }); ctx.set('Cache-Control', 'public, max-age=180'); diff --git a/src/server/web/views/user.pug b/src/server/web/views/user.pug index 3f32933f52..bff98ba80f 100644 --- a/src/server/web/views/user.pug +++ b/src/server/web/views/user.pug @@ -9,12 +9,12 @@ block title = `${title} | ${instanceName}` block desc - meta(name='description' content= user.description) + meta(name='description' content= profile.description) block og meta(property='og:type' content='blog') meta(property='og:title' content= title) - meta(property='og:description' content= user.description) + meta(property='og:description' content= profile.description) meta(property='og:url' content= url) meta(property='og:image' content= img) @@ -24,12 +24,12 @@ block meta meta(name='twitter:card' content='summary') - if user.twitter - meta(name='twitter:creator' content=`@${user.twitter.screenName}`) + if profile.twitter + meta(name='twitter:creator' content=`@${profile.twitter.screenName}`) if !user.host - link(rel='alternate' href=`${config.url}/users/${user._id}` type='application/activity+json') + link(rel='alternate' href=`${config.url}/users/${user.id}` type='application/activity+json') if user.uri link(rel='alternate' href=user.uri type='application/activity+json') - if user.url - link(rel='alternate' href=user.url type='text/html') + if profile.url + link(rel='alternate' href=profile.url type='text/html') -- cgit v1.2.3-freya From 3c29027ca303190f197f259cc9bc1580d7b32cc6 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 12 Apr 2019 01:54:28 +0900 Subject: Clean up --- src/server/api/private/signup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/server/api') diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index ed4a533dec..03d83efd94 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -5,7 +5,7 @@ import generateUserToken from '../common/generate-native-user-token'; import config from '../../../config'; import fetchMeta from '../../../misc/fetch-meta'; import * as recaptcha from 'recaptcha-promise'; -import { Users, RegistrationTickets, UserProfiles, UserKeypairs } from '../../../models'; +import { Users, RegistrationTickets } from '../../../models'; import { genId } from '../../../misc/gen-id'; import { usersChart } from '../../../services/chart'; import { User } from '../../../models/entities/user'; -- cgit v1.2.3-freya From 987168b863c52d0548050ffbac569782bb9a8cef Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 01:43:22 +0900 Subject: strictNullChecks (#4666) * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip --- gulpfile.ts | 2 +- package.json | 2 +- src/boot/master.ts | 2 +- src/boot/worker.ts | 2 +- src/config/load.ts | 2 +- src/daemons/notes-stats-child.ts | 2 +- src/db/postgre.ts | 4 +- src/games/reversi/core.ts | 16 +-- src/mfm/fromHtml.ts | 2 - src/mfm/parse.ts | 4 +- src/mfm/toHtml.ts | 2 +- src/misc/acct/type.ts | 2 +- src/misc/convert-host.ts | 3 +- src/misc/detect-mine.ts | 2 +- src/misc/donwload-url.ts | 3 +- src/misc/fetch-meta.ts | 1 - src/misc/fetch-proxy-account.ts | 3 +- src/misc/identifiable-error.ts | 2 +- src/misc/nyaize.ts | 4 +- src/misc/schema.ts | 4 +- src/models/entities/poll.ts | 2 +- src/models/repositories/abuse-user-report.ts | 3 +- src/models/repositories/app.ts | 3 +- src/models/repositories/auth-session.ts | 3 +- src/models/repositories/blocking.ts | 3 +- src/models/repositories/drive-file.ts | 5 +- src/models/repositories/drive-folder.ts | 3 +- src/models/repositories/follow-request.ts | 3 +- src/models/repositories/following.ts | 43 ++++++- src/models/repositories/games/reversi/game.ts | 3 +- src/models/repositories/games/reversi/matching.ts | 3 +- src/models/repositories/messaging-message.ts | 3 +- src/models/repositories/muting.ts | 3 +- src/models/repositories/note-favorite.ts | 3 +- src/models/repositories/note-reaction.ts | 3 +- src/models/repositories/note.ts | 17 +-- src/models/repositories/notification.ts | 15 +-- src/models/repositories/user-list.ts | 5 +- src/models/repositories/user.ts | 29 ++--- src/prelude/ensure.ts | 7 ++ src/queue/index.ts | 4 +- src/queue/processors/db/delete-drive-files.ts | 8 +- src/queue/processors/db/export-blocking.ts | 12 +- src/queue/processors/db/export-following.ts | 12 +- src/queue/processors/db/export-mute.ts | 12 +- src/queue/processors/db/export-notes.ts | 15 ++- src/queue/processors/db/export-user-lists.ts | 8 +- src/queue/processors/db/import-following.ts | 16 ++- src/queue/processors/db/import-user-lists.ts | 22 ++-- src/queue/processors/deliver.ts | 2 +- src/queue/processors/inbox.ts | 9 +- src/remote/activitypub/kernel/accept/follow.ts | 3 +- src/remote/activitypub/kernel/add/index.ts | 1 + src/remote/activitypub/kernel/announce/note.ts | 9 +- src/remote/activitypub/kernel/block/index.ts | 3 +- src/remote/activitypub/kernel/follow.ts | 3 +- src/remote/activitypub/kernel/index.ts | 2 +- src/remote/activitypub/kernel/like.ts | 1 + src/remote/activitypub/kernel/reject/follow.ts | 3 +- src/remote/activitypub/kernel/remove/index.ts | 1 + src/remote/activitypub/kernel/undo/block.ts | 3 +- src/remote/activitypub/kernel/undo/follow.ts | 3 +- src/remote/activitypub/kernel/undo/index.ts | 2 - src/remote/activitypub/kernel/undo/like.ts | 1 + src/remote/activitypub/models/image.ts | 17 +-- src/remote/activitypub/models/note.ts | 130 +++++++++++---------- src/remote/activitypub/models/person.ts | 81 ++++++------- src/remote/activitypub/models/question.ts | 8 +- src/remote/activitypub/models/tag.ts | 8 +- src/remote/activitypub/renderer/block.ts | 2 +- src/remote/activitypub/renderer/follow-user.ts | 3 +- src/remote/activitypub/renderer/note.ts | 19 ++- .../renderer/ordered-collection-page.ts | 2 +- src/remote/activitypub/renderer/person.ts | 15 ++- src/remote/activitypub/request.ts | 9 +- src/remote/resolve-user.ts | 31 ++++- src/remote/webfinger.ts | 2 +- src/server/activitypub.ts | 13 +-- src/server/activitypub/featured.ts | 6 +- src/server/activitypub/followers.ts | 6 +- src/server/activitypub/following.ts | 6 +- src/server/activitypub/outbox.ts | 9 +- src/server/api/api-handler.ts | 14 +-- src/server/api/authenticate.ts | 2 +- src/server/api/call.ts | 10 +- src/server/api/common/make-pagination-query.ts | 2 +- src/server/api/define.ts | 10 +- .../api/endpoints/admin/abuse-user-reports.ts | 2 +- src/server/api/endpoints/admin/drive/files.ts | 4 +- src/server/api/endpoints/admin/emoji/list.ts | 2 +- .../admin/federation/remove-all-following.ts | 7 +- src/server/api/endpoints/admin/logs.ts | 2 +- src/server/api/endpoints/admin/queue/jobs.ts | 4 +- src/server/api/endpoints/admin/show-users.ts | 2 +- .../api/endpoints/admin/update-remote-user.ts | 2 +- src/server/api/endpoints/ap/show.ts | 4 +- src/server/api/endpoints/auth/accept.ts | 3 +- src/server/api/endpoints/auth/session/userkey.ts | 3 +- src/server/api/endpoints/blocking/list.ts | 2 +- src/server/api/endpoints/charts/active-users.ts | 2 +- src/server/api/endpoints/charts/drive.ts | 2 +- src/server/api/endpoints/charts/federation.ts | 2 +- src/server/api/endpoints/charts/hashtag.ts | 2 +- src/server/api/endpoints/charts/instance.ts | 2 +- src/server/api/endpoints/charts/network.ts | 2 +- src/server/api/endpoints/charts/notes.ts | 2 +- src/server/api/endpoints/charts/user/drive.ts | 2 +- src/server/api/endpoints/charts/user/following.ts | 2 +- src/server/api/endpoints/charts/user/notes.ts | 2 +- src/server/api/endpoints/charts/user/reactions.ts | 2 +- src/server/api/endpoints/charts/users.ts | 2 +- src/server/api/endpoints/drive/files.ts | 2 +- src/server/api/endpoints/drive/files/create.ts | 2 +- src/server/api/endpoints/drive/files/show.ts | 10 +- src/server/api/endpoints/drive/folders.ts | 2 +- src/server/api/endpoints/drive/folders/update.ts | 6 +- src/server/api/endpoints/drive/stream.ts | 2 +- src/server/api/endpoints/federation/instances.ts | 2 +- src/server/api/endpoints/games/reversi/games.ts | 2 +- src/server/api/endpoints/games/reversi/match.ts | 4 +- src/server/api/endpoints/hashtags/list.ts | 2 +- src/server/api/endpoints/hashtags/search.ts | 2 +- src/server/api/endpoints/hashtags/users.ts | 2 +- src/server/api/endpoints/i/2fa/done.ts | 3 +- src/server/api/endpoints/i/2fa/register.ts | 5 +- src/server/api/endpoints/i/2fa/unregister.ts | 5 +- src/server/api/endpoints/i/authorized-apps.ts | 2 +- src/server/api/endpoints/i/change-password.ts | 5 +- src/server/api/endpoints/i/delete-account.ts | 5 +- src/server/api/endpoints/i/favorites.ts | 2 +- src/server/api/endpoints/i/notifications.ts | 6 +- src/server/api/endpoints/i/regenerate-token.ts | 5 +- src/server/api/endpoints/i/signin-history.ts | 2 +- src/server/api/endpoints/i/update-email.ts | 5 +- src/server/api/endpoints/i/update.ts | 6 +- src/server/api/endpoints/messaging/history.ts | 2 +- src/server/api/endpoints/mute/list.ts | 2 +- src/server/api/endpoints/my/apps.ts | 2 +- src/server/api/endpoints/notes.ts | 2 +- src/server/api/endpoints/notes/children.ts | 2 +- src/server/api/endpoints/notes/conversation.ts | 5 +- src/server/api/endpoints/notes/create.ts | 27 ++--- src/server/api/endpoints/notes/delete.ts | 3 +- src/server/api/endpoints/notes/featured.ts | 2 +- src/server/api/endpoints/notes/global-timeline.ts | 2 +- src/server/api/endpoints/notes/hybrid-timeline.ts | 2 +- src/server/api/endpoints/notes/local-timeline.ts | 8 +- src/server/api/endpoints/notes/mentions.ts | 2 +- .../api/endpoints/notes/polls/recommendation.ts | 2 +- src/server/api/endpoints/notes/polls/vote.ts | 7 +- src/server/api/endpoints/notes/reactions.ts | 2 +- src/server/api/endpoints/notes/renotes.ts | 2 +- src/server/api/endpoints/notes/replies.ts | 2 +- src/server/api/endpoints/notes/search-by-tag.ts | 4 +- src/server/api/endpoints/notes/search.ts | 2 +- src/server/api/endpoints/notes/timeline.ts | 2 +- .../api/endpoints/notes/user-list-timeline.ts | 2 +- src/server/api/endpoints/users.ts | 2 +- src/server/api/endpoints/users/followers.ts | 4 +- src/server/api/endpoints/users/following.ts | 4 +- .../users/get-frequently-replied-users.ts | 2 +- src/server/api/endpoints/users/notes.ts | 8 +- src/server/api/endpoints/users/recommendation.ts | 2 +- src/server/api/endpoints/users/search.ts | 6 +- src/server/api/endpoints/users/show.ts | 6 +- src/server/api/error.ts | 4 +- src/server/api/limiter.ts | 6 +- src/server/api/private/signin.ts | 5 +- src/server/api/private/signup.ts | 4 +- src/server/api/service/discord.ts | 91 ++++++++------- src/server/api/service/github.ts | 45 ++++--- src/server/api/service/twitter.ts | 25 ++-- src/server/api/stream/channels/admin.ts | 2 +- src/server/api/stream/channels/drive.ts | 2 +- .../api/stream/channels/games/reversi-game.ts | 80 +++++++------ src/server/api/stream/channels/games/reversi.ts | 4 +- src/server/api/stream/channels/home-timeline.ts | 8 +- src/server/api/stream/channels/hybrid-timeline.ts | 10 +- src/server/api/stream/channels/main.ts | 4 +- src/server/api/stream/channels/messaging-index.ts | 2 +- src/server/api/stream/channels/messaging.ts | 4 +- src/server/api/stream/index.ts | 20 ++-- src/server/file/send-drive-file.ts | 2 +- src/server/index.ts | 2 +- src/server/web/docs.ts | 6 +- src/server/web/feed.ts | 9 +- src/server/web/index.ts | 5 +- src/server/web/url-preview.ts | 2 +- src/server/well-known.ts | 4 +- src/services/blocking/create.ts | 2 +- src/services/chart/core.ts | 36 +++--- src/services/create-notification.ts | 1 + src/services/drive/add-file.ts | 36 +++--- src/services/drive/delete-file.ts | 16 +-- src/services/drive/image-processor.ts | 2 +- src/services/drive/upload-from-url.ts | 11 +- src/services/following/create.ts | 3 +- src/services/following/requests/accept-all.ts | 3 +- src/services/following/requests/accept.ts | 2 +- src/services/following/requests/reject.ts | 2 +- src/services/i/pin.ts | 15 +-- src/services/i/update.ts | 7 +- src/services/logger.ts | 16 +-- src/services/note/create.ts | 101 ++++++++-------- src/services/note/polls/update.ts | 11 +- src/services/note/polls/vote.ts | 16 +-- src/services/note/reaction/create.ts | 4 +- src/services/note/reaction/delete.ts | 2 +- src/services/push-notification.ts | 23 ++-- src/services/register-or-fetch-instance-doc.ts | 2 - src/services/stream.ts | 6 +- src/tools/clean-remote-files.ts | 3 +- src/tools/show-signin-history.ts | 4 +- tsconfig.json | 3 +- 214 files changed, 940 insertions(+), 786 deletions(-) create mode 100644 src/prelude/ensure.ts (limited to 'src/server/api') diff --git a/gulpfile.ts b/gulpfile.ts index b2956c2403..2242843db1 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -120,7 +120,7 @@ gulp.task('copy:client', () => ]) .pipe(isProduction ? (imagemin as any)() : gutil.noop()) .pipe(rename(path => { - path.dirname = path.dirname.replace('assets', '.'); + path.dirname = path.dirname!.replace('assets', '.'); })) .pipe(gulp.dest('./built/client/assets/')) ); diff --git a/package.json b/package.json index e89f98043c..e08a49462d 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "bcryptjs": "2.4.3", "bootstrap-vue": "2.0.0-rc.13", "bull": "3.7.0", - "cafy": "15.1.0", + "cafy": "15.1.1", "chai": "4.2.0", "chai-http": "4.2.1", "chalk": "2.4.2", diff --git a/src/boot/master.ts b/src/boot/master.ts index 2d4080fdb0..4d360c7265 100644 --- a/src/boot/master.ts +++ b/src/boot/master.ts @@ -44,7 +44,7 @@ function greet() { export async function masterMain() { greet(); - let config: Config; + let config!: Config; try { // initialize app diff --git a/src/boot/worker.ts b/src/boot/worker.ts index ca3716972a..362fa3f26b 100644 --- a/src/boot/worker.ts +++ b/src/boot/worker.ts @@ -15,6 +15,6 @@ export async function workerMain() { if (cluster.isWorker) { // Send a 'ready' message to parent process - process.send('ready'); + process.send!('ready'); } } diff --git a/src/config/load.ts b/src/config/load.ts index 6200faf12a..26b25eab4e 100644 --- a/src/config/load.ts +++ b/src/config/load.ts @@ -29,7 +29,7 @@ export default function load() { config.url = url.origin; - config.port = config.port || parseInt(process.env.PORT, 10); + config.port = config.port || parseInt(process.env.PORT || '', 10); mixin.host = url.host; mixin.hostname = url.hostname; diff --git a/src/daemons/notes-stats-child.ts b/src/daemons/notes-stats-child.ts index c491aed4cd..b60f5badfd 100644 --- a/src/daemons/notes-stats-child.ts +++ b/src/daemons/notes-stats-child.ts @@ -19,7 +19,7 @@ initDb().then(() => { all, local }; - process.send(stats); + process.send!(stats); } tick(); diff --git a/src/db/postgre.ts b/src/db/postgre.ts index e5726e9c87..641a552c09 100644 --- a/src/db/postgre.ts +++ b/src/db/postgre.ts @@ -76,7 +76,7 @@ class MyCustomLogger implements Logger { } export function initDb(justBorrow = false, sync = false, log = false) { - const enableLogging = log || !['production', 'test'].includes(process.env.NODE_ENV); + const enableLogging = log || !['production', 'test'].includes(process.env.NODE_ENV || ''); try { const conn = getConnection(); @@ -93,7 +93,7 @@ export function initDb(justBorrow = false, sync = false, log = false) { synchronize: process.env.NODE_ENV === 'test' || sync, dropSchema: process.env.NODE_ENV === 'test' && !justBorrow, logging: enableLogging, - logger: enableLogging ? new MyCustomLogger() : null, + logger: enableLogging ? new MyCustomLogger() : undefined, entities: [ Meta, Instance, diff --git a/src/games/reversi/core.ts b/src/games/reversi/core.ts index bb27d6f803..cf8986263b 100644 --- a/src/games/reversi/core.ts +++ b/src/games/reversi/core.ts @@ -37,7 +37,7 @@ export type Undo = { /** * ターン */ - turn: Color; + turn: Color | null; }; /** @@ -47,12 +47,12 @@ export default class Reversi { public map: MapPixel[]; public mapWidth: number; public mapHeight: number; - public board: Color[]; - public turn: Color = BLACK; + public board: (Color | null | undefined)[]; + public turn: Color | null = BLACK; public opts: Options; public prevPos = -1; - public prevColor: Color = null; + public prevColor: Color | null = null; private logs: Undo[] = []; @@ -145,12 +145,12 @@ export default class Reversi { // ターン計算 this.turn = this.canPutSomewhere(!this.prevColor) ? !this.prevColor : - this.canPutSomewhere(this.prevColor) ? this.prevColor : + this.canPutSomewhere(this.prevColor!) ? this.prevColor : null; } public undo() { - const undo = this.logs.pop(); + const undo = this.logs.pop()!; this.prevColor = undo.color; this.prevPos = undo.pos; this.board[undo.pos] = null; @@ -254,10 +254,10 @@ export default class Reversi { /** * ゲームの勝者 (null = 引き分け) */ - public get winner(): Color { + public get winner(): Color | null { return this.isEnded ? this.blackCount == this.whiteCount ? null : this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK : - undefined; + undefined as never; } } diff --git a/src/mfm/fromHtml.ts b/src/mfm/fromHtml.ts index 369f9de0ee..5fc4a16416 100644 --- a/src/mfm/fromHtml.ts +++ b/src/mfm/fromHtml.ts @@ -3,8 +3,6 @@ import { URL } from 'url'; import { urlRegex } from './prelude'; export function fromHtml(html: string): string { - if (html == null) return null; - const dom = parseFragment(html) as DefaultTreeDocumentFragment; let text = ''; diff --git a/src/mfm/parse.ts b/src/mfm/parse.ts index 9d60771708..f8464121f3 100644 --- a/src/mfm/parse.ts +++ b/src/mfm/parse.ts @@ -2,7 +2,7 @@ import { mfmLanguage } from './language'; import { MfmForest } from './prelude'; import { normalize } from './normalize'; -export function parse(source: string): MfmForest { +export function parse(source: string | null): MfmForest | null { if (source == null || source == '') { return null; } @@ -10,7 +10,7 @@ export function parse(source: string): MfmForest { return normalize(mfmLanguage.root.tryParse(source)); } -export function parsePlain(source: string): MfmForest { +export function parsePlain(source: string | null): MfmForest | null { if (source == null || source == '') { return null; } diff --git a/src/mfm/toHtml.ts b/src/mfm/toHtml.ts index 3cd7987624..58976fc2c3 100644 --- a/src/mfm/toHtml.ts +++ b/src/mfm/toHtml.ts @@ -4,7 +4,7 @@ import { intersperse } from '../prelude/array'; import { MfmForest, MfmTree } from './prelude'; import { IMentionedRemoteUsers } from '../models/entities/note'; -export function toHtml(tokens: MfmForest, mentionedRemoteUsers: IMentionedRemoteUsers = []) { +export function toHtml(tokens: MfmForest | null, mentionedRemoteUsers: IMentionedRemoteUsers = []) { if (tokens == null) { return null; } diff --git a/src/misc/acct/type.ts b/src/misc/acct/type.ts index c88a920c69..7f31257400 100644 --- a/src/misc/acct/type.ts +++ b/src/misc/acct/type.ts @@ -1,6 +1,6 @@ type Acct = { username: string; - host: string; + host: string | null; }; export default Acct; diff --git a/src/misc/convert-host.ts b/src/misc/convert-host.ts index f7feebd55c..dbf7864550 100644 --- a/src/misc/convert-host.ts +++ b/src/misc/convert-host.ts @@ -2,7 +2,7 @@ import config from '../config'; import { toASCII } from 'punycode'; import { URL } from 'url'; -export function getFullApAccount(username: string, host: string) { +export function getFullApAccount(username: string, host: string | null) { return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.host)}`; } @@ -17,6 +17,5 @@ export function extractDbHost(uri: string) { } export function toPuny(host: string) { - if (host == null) return null; return toASCII(host.toLowerCase()); } diff --git a/src/misc/detect-mine.ts b/src/misc/detect-mine.ts index bbf49efc10..70d58ffe21 100644 --- a/src/misc/detect-mine.ts +++ b/src/misc/detect-mine.ts @@ -3,7 +3,7 @@ import fileType from 'file-type'; import checkSvg from '../misc/check-svg'; export async function detectMine(path: string) { - return new Promise<[string, string]>((res, rej) => { + return new Promise<[string, string | null]>((res, rej) => { const readable = fs.createReadStream(path); readable .on('error', rej) diff --git a/src/misc/donwload-url.ts b/src/misc/donwload-url.ts index 0dd4e4ef5d..167e01fdd1 100644 --- a/src/misc/donwload-url.ts +++ b/src/misc/donwload-url.ts @@ -1,5 +1,4 @@ import * as fs from 'fs'; -import * as URL from 'url'; import * as request from 'request'; import config from '../config'; import chalk from 'chalk'; @@ -26,7 +25,7 @@ export async function downloadUrl(url: string, path: string) { rej(error); }); - const requestUrl = URL.parse(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url; + const requestUrl = new URL(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url; const req = request({ url: requestUrl, diff --git a/src/misc/fetch-meta.ts b/src/misc/fetch-meta.ts index d1483e9edb..cf1fc474ce 100644 --- a/src/misc/fetch-meta.ts +++ b/src/misc/fetch-meta.ts @@ -9,7 +9,6 @@ export default async function(): Promise { } else { return Metas.save({ id: genId(), - hiddenTags: [] } as Meta); } } diff --git a/src/misc/fetch-proxy-account.ts b/src/misc/fetch-proxy-account.ts index d60fa9b313..17b021e91e 100644 --- a/src/misc/fetch-proxy-account.ts +++ b/src/misc/fetch-proxy-account.ts @@ -1,8 +1,9 @@ import fetchMeta from './fetch-meta'; import { ILocalUser } from '../models/entities/user'; import { Users } from '../models'; +import { ensure } from '../prelude/ensure'; export async function fetchProxyAccount(): Promise { const meta = await fetchMeta(); - return await Users.findOne({ username: meta.proxyAccount, host: null }) as ILocalUser; + return await Users.findOne({ username: meta.proxyAccount!, host: null }).then(ensure) as ILocalUser; } diff --git a/src/misc/identifiable-error.ts b/src/misc/identifiable-error.ts index 1edd26cd18..2d7c6bd0c6 100644 --- a/src/misc/identifiable-error.ts +++ b/src/misc/identifiable-error.ts @@ -7,7 +7,7 @@ export class IdentifiableError extends Error { constructor(id: string, message?: string) { super(message); - this.message = message; + this.message = message || ''; this.id = id; } } diff --git a/src/misc/nyaize.ts b/src/misc/nyaize.ts index 8b06300eab..918e7d63fd 100644 --- a/src/misc/nyaize.ts +++ b/src/misc/nyaize.ts @@ -3,7 +3,7 @@ export function nyaize(text: string): string { // ja-JP .replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ') // ko-KR - .replace(/[나-낳]/g, (match: string) => String.fromCharCode( - match.codePointAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0) + .replace(/[나-낳]/g, match => String.fromCharCode( + match.codePointAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0) )); } diff --git a/src/misc/schema.ts b/src/misc/schema.ts index e5c24dd468..7c17953d97 100644 --- a/src/misc/schema.ts +++ b/src/misc/schema.ts @@ -19,8 +19,8 @@ type MyType = { export type SchemaType

= p['type'] extends 'number' ? number : p['type'] extends 'string' ? string : - p['type'] extends 'array' ? MyType[] : - p['type'] extends 'object' ? ObjType : + p['type'] extends 'array' ? MyType>[] : + p['type'] extends 'object' ? ObjType> : any; export function convertOpenApiSchema(schema: Schema) { diff --git a/src/models/entities/poll.ts b/src/models/entities/poll.ts index c0ad5547bd..6bb67163a2 100644 --- a/src/models/entities/poll.ts +++ b/src/models/entities/poll.ts @@ -67,5 +67,5 @@ export type IPoll = { choices: string[]; votes?: number[]; multiple: boolean; - expiresAt: Date; + expiresAt: Date | null; }; diff --git a/src/models/repositories/abuse-user-report.ts b/src/models/repositories/abuse-user-report.ts index c72a582c04..f619d6e37f 100644 --- a/src/models/repositories/abuse-user-report.ts +++ b/src/models/repositories/abuse-user-report.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { Users } from '..'; import rap from '@prezzemolo/rap'; import { AbuseUserReport } from '../entities/abuse-user-report'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(AbuseUserReport) export class AbuseUserReportRepository extends Repository { @@ -14,7 +15,7 @@ export class AbuseUserReportRepository extends Repository { public async pack( src: AbuseUserReport['id'] | AbuseUserReport, ) { - const report = typeof src === 'object' ? src : await this.findOne(src); + const report = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: report.id, diff --git a/src/models/repositories/app.ts b/src/models/repositories/app.ts index 2e3323baf8..a0c0cf68cb 100644 --- a/src/models/repositories/app.ts +++ b/src/models/repositories/app.ts @@ -1,6 +1,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { App } from '../entities/app'; import { AccessTokens } from '..'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(App) export class AppRepository extends Repository { @@ -19,7 +20,7 @@ export class AppRepository extends Repository { includeProfileImageIds: false }, options); - const app = typeof src === 'object' ? src : await this.findOne(src); + const app = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return { id: app.id, diff --git a/src/models/repositories/auth-session.ts b/src/models/repositories/auth-session.ts index 76e3ddf9ab..540c5466f5 100644 --- a/src/models/repositories/auth-session.ts +++ b/src/models/repositories/auth-session.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { Apps } from '..'; import rap from '@prezzemolo/rap'; import { AuthSession } from '../entities/auth-session'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(AuthSession) export class AuthSessionRepository extends Repository { @@ -9,7 +10,7 @@ export class AuthSessionRepository extends Repository { src: AuthSession['id'] | AuthSession, me?: any ) { - const session = typeof src === 'object' ? src : await this.findOne(src); + const session = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: session.id, diff --git a/src/models/repositories/blocking.ts b/src/models/repositories/blocking.ts index 81f3866131..e18aa591f3 100644 --- a/src/models/repositories/blocking.ts +++ b/src/models/repositories/blocking.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { Users } from '..'; import rap from '@prezzemolo/rap'; import { Blocking } from '../entities/blocking'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(Blocking) export class BlockingRepository extends Repository { @@ -16,7 +17,7 @@ export class BlockingRepository extends Repository { src: Blocking['id'] | Blocking, me?: any ) { - const blocking = typeof src === 'object' ? src : await this.findOne(src); + const blocking = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: blocking.id, diff --git a/src/models/repositories/drive-file.ts b/src/models/repositories/drive-file.ts index 817677fa3b..003e350713 100644 --- a/src/models/repositories/drive-file.ts +++ b/src/models/repositories/drive-file.ts @@ -4,6 +4,7 @@ import { Users, DriveFolders } from '..'; import rap from '@prezzemolo/rap'; import { User } from '../entities/user'; import { toPuny } from '../../misc/convert-host'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(DriveFile) export class DriveFileRepository extends Repository { @@ -91,7 +92,7 @@ export class DriveFileRepository extends Repository { self: false }, options); - const file = typeof src === 'object' ? src : await this.findOne(src); + const file = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: file.id, @@ -108,7 +109,7 @@ export class DriveFileRepository extends Repository { folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, { detail: true }) : null, - user: opts.withUser ? Users.pack(file.userId) : null + user: opts.withUser ? Users.pack(file.userId!) : null }); } } diff --git a/src/models/repositories/drive-folder.ts b/src/models/repositories/drive-folder.ts index faf0f353aa..ce88adefa4 100644 --- a/src/models/repositories/drive-folder.ts +++ b/src/models/repositories/drive-folder.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { DriveFolders, DriveFiles } from '..'; import rap from '@prezzemolo/rap'; import { DriveFolder } from '../entities/drive-folder'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(DriveFolder) export class DriveFolderRepository extends Repository { @@ -22,7 +23,7 @@ export class DriveFolderRepository extends Repository { detail: false }, options); - const folder = typeof src === 'object' ? src : await this.findOne(src); + const folder = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: folder.id, diff --git a/src/models/repositories/follow-request.ts b/src/models/repositories/follow-request.ts index bead093b21..451ed8e2d5 100644 --- a/src/models/repositories/follow-request.ts +++ b/src/models/repositories/follow-request.ts @@ -1,6 +1,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { FollowRequest } from '../entities/follow-request'; import { Users } from '..'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(FollowRequest) export class FollowRequestRepository extends Repository { @@ -8,7 +9,7 @@ export class FollowRequestRepository extends Repository { src: FollowRequest['id'] | FollowRequest, me?: any ) { - const request = typeof src === 'object' ? src : await this.findOne(src); + const request = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return { id: request.id, diff --git a/src/models/repositories/following.ts b/src/models/repositories/following.ts index 02253d272d..3fff57866f 100644 --- a/src/models/repositories/following.ts +++ b/src/models/repositories/following.ts @@ -2,9 +2,50 @@ import { EntityRepository, Repository } from 'typeorm'; import { Users } from '..'; import rap from '@prezzemolo/rap'; import { Following } from '../entities/following'; +import { ensure } from '../../prelude/ensure'; + +type LocalFollowerFollowing = Following & { + followerHost: null; + followerInbox: null; + followerSharedInbox: null; +}; + +type RemoteFollowerFollowing = Following & { + followerHost: string; + followerInbox: string; + followerSharedInbox: string; +}; + +type LocalFolloweeFollowing = Following & { + followeeHost: null; + followeeInbox: null; + followeeSharedInbox: null; +}; + +type RemoteFolloweeFollowing = Following & { + followeeHost: string; + followeeInbox: string; + followeeSharedInbox: string; +}; @EntityRepository(Following) export class FollowingRepository extends Repository { + public isLocalFollower(following: Following): following is LocalFollowerFollowing { + return following.followerHost == null; + } + + public isRemoteFollower(following: Following): following is RemoteFollowerFollowing { + return following.followerHost != null; + } + + public isLocalFollowee(following: Following): following is LocalFolloweeFollowing { + return following.followeeHost == null; + } + + public isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing { + return following.followeeHost != null; + } + public packMany( followings: any[], me?: any, @@ -24,7 +65,7 @@ export class FollowingRepository extends Repository { populateFollower?: boolean; } ) { - const following = typeof src === 'object' ? src : await this.findOne(src); + const following = typeof src === 'object' ? src : await this.findOne(src).then(ensure); if (opts == null) opts = {}; diff --git a/src/models/repositories/games/reversi/game.ts b/src/models/repositories/games/reversi/game.ts index f0cb6ff905..c380f5251e 100644 --- a/src/models/repositories/games/reversi/game.ts +++ b/src/models/repositories/games/reversi/game.ts @@ -1,6 +1,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { Users } from '../../..'; import { ReversiGame } from '../../../entities/games/reversi/game'; +import { ensure } from '../../../../prelude/ensure'; @EntityRepository(ReversiGame) export class ReversiGameRepository extends Repository { @@ -15,7 +16,7 @@ export class ReversiGameRepository extends Repository { detail: true }, options); - const game = typeof src === 'object' ? src : await this.findOne(src); + const game = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const meId = me ? typeof me === 'string' ? me : me.id : null; return { diff --git a/src/models/repositories/games/reversi/matching.ts b/src/models/repositories/games/reversi/matching.ts index 3612ac5c47..4d99c6ef76 100644 --- a/src/models/repositories/games/reversi/matching.ts +++ b/src/models/repositories/games/reversi/matching.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import rap from '@prezzemolo/rap'; import { ReversiMatching } from '../../../entities/games/reversi/matching'; import { Users } from '../../..'; +import { ensure } from '../../../../prelude/ensure'; @EntityRepository(ReversiMatching) export class ReversiMatchingRepository extends Repository { @@ -9,7 +10,7 @@ export class ReversiMatchingRepository extends Repository { src: ReversiMatching['id'] | ReversiMatching, me: any ) { - const matching = typeof src === 'object' ? src : await this.findOne(src); + const matching = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: matching.id, diff --git a/src/models/repositories/messaging-message.ts b/src/models/repositories/messaging-message.ts index b87b30388a..6659273539 100644 --- a/src/models/repositories/messaging-message.ts +++ b/src/models/repositories/messaging-message.ts @@ -1,6 +1,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { MessagingMessage } from '../entities/messaging-message'; import { Users, DriveFiles } from '..'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(MessagingMessage) export class MessagingMessageRepository extends Repository { @@ -19,7 +20,7 @@ export class MessagingMessageRepository extends Repository { populateRecipient: true }; - const message = typeof src === 'object' ? src : await this.findOne(src); + const message = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return { id: message.id, diff --git a/src/models/repositories/muting.ts b/src/models/repositories/muting.ts index cd98cb4fec..1812e2e713 100644 --- a/src/models/repositories/muting.ts +++ b/src/models/repositories/muting.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { Users } from '..'; import rap from '@prezzemolo/rap'; import { Muting } from '../entities/muting'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(Muting) export class MutingRepository extends Repository { @@ -16,7 +17,7 @@ export class MutingRepository extends Repository { src: Muting['id'] | Muting, me?: any ) { - const muting = typeof src === 'object' ? src : await this.findOne(src); + const muting = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: muting.id, diff --git a/src/models/repositories/note-favorite.ts b/src/models/repositories/note-favorite.ts index 4526461e69..f428903c13 100644 --- a/src/models/repositories/note-favorite.ts +++ b/src/models/repositories/note-favorite.ts @@ -1,6 +1,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { NoteFavorite } from '../entities/note-favorite'; import { Notes } from '..'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(NoteFavorite) export class NoteFavoriteRepository extends Repository { @@ -15,7 +16,7 @@ export class NoteFavoriteRepository extends Repository { src: NoteFavorite['id'] | NoteFavorite, me?: any ) { - const favorite = typeof src === 'object' ? src : await this.findOne(src); + const favorite = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return { id: favorite.id, diff --git a/src/models/repositories/note-reaction.ts b/src/models/repositories/note-reaction.ts index 7189da8e20..28191d4ab0 100644 --- a/src/models/repositories/note-reaction.ts +++ b/src/models/repositories/note-reaction.ts @@ -1,6 +1,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { NoteReaction } from '../entities/note-reaction'; import { Users } from '..'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(NoteReaction) export class NoteReactionRepository extends Repository { @@ -8,7 +9,7 @@ export class NoteReactionRepository extends Repository { src: NoteReaction['id'] | NoteReaction, me?: any ) { - const reaction = typeof src === 'object' ? src : await this.findOne(src); + const reaction = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return { id: reaction.id, diff --git a/src/models/repositories/note.ts b/src/models/repositories/note.ts index 01bf8cf87c..77cf00849f 100644 --- a/src/models/repositories/note.ts +++ b/src/models/repositories/note.ts @@ -5,6 +5,7 @@ import { unique, concat } from '../../prelude/array'; import { nyaize } from '../../misc/nyaize'; import { Emojis, Users, Apps, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..'; import rap from '@prezzemolo/rap'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(Note) export class NoteRepository extends Repository { @@ -12,7 +13,7 @@ export class NoteRepository extends Repository { return x.trim().length <= 100; } - private async hideNote(packedNote: any, meId: User['id']) { + private async hideNote(packedNote: any, meId: User['id'] | null) { let hide = false; // visibility が specified かつ自分が指定されていなかったら非表示 @@ -75,7 +76,7 @@ export class NoteRepository extends Repository { public packMany( notes: (Note['id'] | Note)[], - me?: User['id'] | User, + me?: User['id'] | User | null | undefined, options?: { detail?: boolean; skipHide?: boolean; @@ -86,7 +87,7 @@ export class NoteRepository extends Repository { public async pack( src: Note['id'] | Note, - me?: User['id'] | User, + me?: User['id'] | User | null | undefined, options?: { detail?: boolean; skipHide?: boolean; @@ -98,11 +99,11 @@ export class NoteRepository extends Repository { }, options); const meId = me ? typeof me === 'string' ? me : me.id : null; - const note = typeof src === 'object' ? src : await this.findOne(src); + const note = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const host = note.userHost; async function populatePoll() { - const poll = await Polls.findOne({ noteId: note.id }); + const poll = await Polls.findOne({ noteId: note.id }).then(ensure); const choices = poll.choices.map(c => ({ text: c, votes: poll.votes[poll.choices.indexOf(c)], @@ -111,7 +112,7 @@ export class NoteRepository extends Repository { if (poll.multiple) { const votes = await PollVotes.find({ - userId: meId, + userId: meId!, noteId: note.id }); @@ -121,7 +122,7 @@ export class NoteRepository extends Repository { } } else { const vote = await PollVotes.findOne({ - userId: meId, + userId: meId!, noteId: note.id }); @@ -139,7 +140,7 @@ export class NoteRepository extends Repository { async function populateMyReaction() { const reaction = await NoteReactions.findOne({ - userId: meId, + userId: meId!, noteId: note.id, }); diff --git a/src/models/repositories/notification.ts b/src/models/repositories/notification.ts index 9bc569cd3f..4781d4c065 100644 --- a/src/models/repositories/notification.ts +++ b/src/models/repositories/notification.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { Users, Notes } from '..'; import rap from '@prezzemolo/rap'; import { Notification } from '../entities/notification'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(Notification) export class NotificationRepository extends Repository { @@ -14,7 +15,7 @@ export class NotificationRepository extends Repository { public async pack( src: Notification['id'] | Notification, ) { - const notification = typeof src === 'object' ? src : await this.findOne(src); + const notification = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: notification.id, @@ -23,23 +24,23 @@ export class NotificationRepository extends Repository { userId: notification.notifierId, user: Users.pack(notification.notifier || notification.notifierId), ...(notification.type === 'mention' ? { - note: Notes.pack(notification.note || notification.noteId), + note: Notes.pack(notification.note || notification.noteId!), } : {}), ...(notification.type === 'reply' ? { - note: Notes.pack(notification.note || notification.noteId), + note: Notes.pack(notification.note || notification.noteId!), } : {}), ...(notification.type === 'renote' ? { - note: Notes.pack(notification.note || notification.noteId), + note: Notes.pack(notification.note || notification.noteId!), } : {}), ...(notification.type === 'quote' ? { - note: Notes.pack(notification.note || notification.noteId), + note: Notes.pack(notification.note || notification.noteId!), } : {}), ...(notification.type === 'reaction' ? { - note: Notes.pack(notification.note || notification.noteId), + note: Notes.pack(notification.note || notification.noteId!), reaction: notification.reaction } : {}), ...(notification.type === 'pollVote' ? { - note: Notes.pack(notification.note || notification.noteId), + note: Notes.pack(notification.note || notification.noteId!), choice: notification.choice } : {}) }); diff --git a/src/models/repositories/user-list.ts b/src/models/repositories/user-list.ts index 921c18ca7a..fbf81b8886 100644 --- a/src/models/repositories/user-list.ts +++ b/src/models/repositories/user-list.ts @@ -1,12 +1,13 @@ import { EntityRepository, Repository } from 'typeorm'; import { UserList } from '../entities/user-list'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(UserList) export class UserListRepository extends Repository { public async pack( - src: any, + src: UserList['id'] | UserList, ) { - const userList = typeof src === 'object' ? src : await this.findOne(src); + const userList = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return { id: userList.id, diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index d7f2c3d040..cddb77ffae 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository, In } from 'typeorm'; import { User, ILocalUser, IRemoteUser } from '../entities/user'; import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles } from '..'; import rap from '@prezzemolo/rap'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(User) export class UserRepository extends Repository { @@ -51,7 +52,7 @@ export class UserRepository extends Repository { public packMany( users: (User['id'] | User)[], - me?: User['id'] | User, + me?: User['id'] | User | null | undefined, options?: { detail?: boolean, includeSecrets?: boolean, @@ -63,7 +64,7 @@ export class UserRepository extends Repository { public async pack( src: User['id'] | User, - me?: User['id'] | User, + me?: User['id'] | User | null | undefined, options?: { detail?: boolean, includeSecrets?: boolean, @@ -75,12 +76,12 @@ export class UserRepository extends Repository { includeSecrets: false }, options); - const user = typeof src === 'object' ? src : await this.findOne(src); + const user = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const meId = me ? typeof me === 'string' ? me : me.id : null; const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null; const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : []; - const profile = opts.detail ? await UserProfiles.findOne({ userId: user.id }) : null; + const profile = opts.detail ? await UserProfiles.findOne({ userId: user.id }).then(ensure) : null; return await rap({ id: user.id, @@ -117,12 +118,12 @@ export class UserRepository extends Repository { } : {}), ...(opts.detail ? { - url: profile.url, + url: profile!.url, createdAt: user.createdAt, updatedAt: user.updatedAt, - description: profile.description, - location: profile.location, - birthday: profile.birthday, + description: profile!.description, + location: profile!.location, + birthday: profile!.birthday, followersCount: user.followersCount, followingCount: user.followingCount, notesCount: user.notesCount, @@ -135,9 +136,9 @@ export class UserRepository extends Repository { ...(opts.detail && meId === user.id ? { avatarId: user.avatarId, bannerId: user.bannerId, - autoWatch: profile.autoWatch, - alwaysMarkNsfw: profile.alwaysMarkNsfw, - carefulBot: profile.carefulBot, + autoWatch: profile!.autoWatch, + alwaysMarkNsfw: profile!.alwaysMarkNsfw, + carefulBot: profile!.carefulBot, hasUnreadMessagingMessage: MessagingMessages.count({ where: { recipientId: user.id, @@ -158,9 +159,9 @@ export class UserRepository extends Repository { } : {}), ...(opts.includeSecrets ? { - clientData: profile.clientData, - email: profile.email, - emailVerified: profile.emailVerified, + clientData: profile!.clientData, + email: profile!.email, + emailVerified: profile!.emailVerified, } : {}), ...(relation ? { diff --git a/src/prelude/ensure.ts b/src/prelude/ensure.ts new file mode 100644 index 0000000000..90bf05538a --- /dev/null +++ b/src/prelude/ensure.ts @@ -0,0 +1,7 @@ +export function ensure(x: T): NonNullable { + if (x == null) { + throw 'ぬるぽ'; + } else { + return x!; + } +} diff --git a/src/queue/index.ts b/src/queue/index.ts index 728c43c6ac..1ab59fd18f 100644 --- a/src/queue/index.ts +++ b/src/queue/index.ts @@ -12,7 +12,7 @@ import { queueLogger } from './logger'; import { DriveFile } from '../models/entities/drive-file'; function initializeQueue(name: string) { - return new Queue(name, config.redis != null ? { + return new Queue(name, { redis: { port: config.redis.port, host: config.redis.host, @@ -20,7 +20,7 @@ function initializeQueue(name: string) { db: config.redis.db || 0, }, prefix: config.redis.prefix ? `${config.redis.prefix}:queue` : 'queue' - } : null); + }); } export const deliverQueue = initializeQueue('deliver'); diff --git a/src/queue/processors/db/delete-drive-files.ts b/src/queue/processors/db/delete-drive-files.ts index 5f347fb588..491734acc6 100644 --- a/src/queue/processors/db/delete-drive-files.ts +++ b/src/queue/processors/db/delete-drive-files.ts @@ -10,9 +10,11 @@ const logger = queueLogger.createSubLogger('delete-drive-files'); export async function deleteDriveFiles(job: Bull.Job, done: any): Promise { logger.info(`Deleting drive files of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } let deletedCount = 0; let ended = false; diff --git a/src/queue/processors/db/export-blocking.ts b/src/queue/processors/db/export-blocking.ts index c12aa4fca3..44025ec960 100644 --- a/src/queue/processors/db/export-blocking.ts +++ b/src/queue/processors/db/export-blocking.ts @@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-blocking'); export async function exportBlocking(job: Bull.Job, done: any): Promise { logger.info(`Exporting blocking of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } // Create temp file const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { @@ -56,6 +58,10 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise { for (const block of blockings) { const u = await Users.findOne({ id: block.blockeeId }); + if (u == null) { + exportedCount++; continue; + } + const content = getFullApAccount(u.username, u.host); await new Promise((res, rej) => { stream.write(content + '\n', err => { diff --git a/src/queue/processors/db/export-following.ts b/src/queue/processors/db/export-following.ts index fb30df79fe..81dcf8f93e 100644 --- a/src/queue/processors/db/export-following.ts +++ b/src/queue/processors/db/export-following.ts @@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-following'); export async function exportFollowing(job: Bull.Job, done: any): Promise { logger.info(`Exporting following of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } // Create temp file const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { @@ -56,6 +58,10 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise { for (const following of followings) { const u = await Users.findOne({ id: following.followeeId }); + if (u == null) { + exportedCount++; continue; + } + const content = getFullApAccount(u.username, u.host); await new Promise((res, rej) => { stream.write(content + '\n', err => { diff --git a/src/queue/processors/db/export-mute.ts b/src/queue/processors/db/export-mute.ts index 3aed526dc5..f810b6ee8d 100644 --- a/src/queue/processors/db/export-mute.ts +++ b/src/queue/processors/db/export-mute.ts @@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-mute'); export async function exportMute(job: Bull.Job, done: any): Promise { logger.info(`Exporting mute of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } // Create temp file const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { @@ -56,6 +58,10 @@ export async function exportMute(job: Bull.Job, done: any): Promise { for (const mute of mutes) { const u = await Users.findOne({ id: mute.muteeId }); + if (u == null) { + exportedCount++; continue; + } + const content = getFullApAccount(u.username, u.host); await new Promise((res, rej) => { stream.write(content + '\n', err => { diff --git a/src/queue/processors/db/export-notes.ts b/src/queue/processors/db/export-notes.ts index 92867ad82e..eaa5caf63f 100644 --- a/src/queue/processors/db/export-notes.ts +++ b/src/queue/processors/db/export-notes.ts @@ -9,15 +9,18 @@ import { Users, Notes, Polls } from '../../../models'; import { MoreThan } from 'typeorm'; import { Note } from '../../../models/entities/note'; import { Poll } from '../../../models/entities/poll'; +import { ensure } from '../../../prelude/ensure'; const logger = queueLogger.createSubLogger('export-notes'); export async function exportNotes(job: Bull.Job, done: any): Promise { logger.info(`Exporting notes of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } // Create temp file const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { @@ -67,9 +70,9 @@ export async function exportNotes(job: Bull.Job, done: any): Promise { cursor = notes[notes.length - 1].id; for (const note of notes) { - let poll: Poll; + let poll: Poll | undefined; if (note.hasPoll) { - poll = await Polls.findOne({ noteId: note.id }); + poll = await Polls.findOne({ noteId: note.id }).then(ensure); } const content = JSON.stringify(serialize(note, poll)); await new Promise((res, rej) => { @@ -114,7 +117,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise { done(); } -function serialize(note: Note, poll: Poll): any { +function serialize(note: Note, poll: Poll | null = null): any { return { id: note.id, text: note.text, diff --git a/src/queue/processors/db/export-user-lists.ts b/src/queue/processors/db/export-user-lists.ts index f3987cb0d2..5cd978c1aa 100644 --- a/src/queue/processors/db/export-user-lists.ts +++ b/src/queue/processors/db/export-user-lists.ts @@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-user-lists'); export async function exportUserLists(job: Bull.Job, done: any): Promise { logger.info(`Exporting user lists of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } const lists = await UserLists.find({ userId: user.id diff --git a/src/queue/processors/db/import-following.ts b/src/queue/processors/db/import-following.ts index aae24b22d6..8de3193e46 100644 --- a/src/queue/processors/db/import-following.ts +++ b/src/queue/processors/db/import-following.ts @@ -13,13 +13,19 @@ const logger = queueLogger.createSubLogger('import-following'); export async function importFollowing(job: Bull.Job, done: any): Promise { logger.info(`Importing following of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } const file = await DriveFiles.findOne({ id: job.data.fileId }); + if (file == null) { + done(); + return; + } const csv = await downloadTextFile(file.url); @@ -31,11 +37,11 @@ export async function importFollowing(job: Bull.Job, done: any): Promise { try { const { username, host } = parseAcct(line.trim()); - let target = isSelfHost(host) ? await Users.findOne({ + let target = isSelfHost(host!) ? await Users.findOne({ host: null, usernameLower: username.toLowerCase() }) : await Users.findOne({ - host: toPuny(host), + host: toPuny(host!), usernameLower: username.toLowerCase() }); diff --git a/src/queue/processors/db/import-user-lists.ts b/src/queue/processors/db/import-user-lists.ts index c7273ea6b4..1e852be945 100644 --- a/src/queue/processors/db/import-user-lists.ts +++ b/src/queue/processors/db/import-user-lists.ts @@ -14,13 +14,19 @@ const logger = queueLogger.createSubLogger('import-user-lists'); export async function importUserLists(job: Bull.Job, done: any): Promise { logger.info(`Importing user lists of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } const file = await DriveFiles.findOne({ id: job.data.fileId }); + if (file == null) { + done(); + return; + } const csv = await downloadTextFile(file.url); @@ -43,22 +49,20 @@ export async function importUserLists(job: Bull.Job, done: any): Promise { }); } - let target = isSelfHost(host) ? await Users.findOne({ + let target = isSelfHost(host!) ? await Users.findOne({ host: null, usernameLower: username.toLowerCase() }) : await Users.findOne({ - host: toPuny(host), + host: toPuny(host!), usernameLower: username.toLowerCase() }); - if (host == null && target == null) continue; - - if (await UserListJoinings.findOne({ userListId: list.id, userId: target.id }) != null) continue; - if (target == null) { target = await resolveUser(username, host); } + if (await UserListJoinings.findOne({ userListId: list.id, userId: target.id }) != null) continue; + pushUserToUserList(target, list); } diff --git a/src/queue/processors/deliver.ts b/src/queue/processors/deliver.ts index b9701c0c65..8837c80d87 100644 --- a/src/queue/processors/deliver.ts +++ b/src/queue/processors/deliver.ts @@ -7,7 +7,7 @@ import { instanceChart } from '../../services/chart'; const logger = new Logger('deliver'); -let latest: string = null; +let latest: string | null = null; export default async (job: Bull.Job) => { const { host } = new URL(job.data.to); diff --git a/src/queue/processors/inbox.ts b/src/queue/processors/inbox.ts index 35b0ce5386..4deaef2ae3 100644 --- a/src/queue/processors/inbox.ts +++ b/src/queue/processors/inbox.ts @@ -14,6 +14,7 @@ import { UserPublickey } from '../../models/entities/user-publickey'; import fetchMeta from '../../misc/fetch-meta'; import { toPuny } from '../../misc/convert-host'; import { validActor } from '../../remote/activitypub/type'; +import { ensure } from '../../prelude/ensure'; const logger = new Logger('inbox'); @@ -35,7 +36,7 @@ export default async (job: Bull.Job): Promise => { if (keyIdLower.startsWith('acct:')) { const acct = parseAcct(keyIdLower.slice('acct:'.length)); - const host = toPuny(acct.host); + const host = acct.host ? toPuny(acct.host) : null; const username = toPuny(acct.username); if (host === null) { @@ -64,9 +65,7 @@ export default async (job: Bull.Job): Promise => { host: host }) as IRemoteUser; - key = await UserPublickeys.findOne({ - userId: user.id - }); + key = await UserPublickeys.findOne(user.id).then(ensure); } else { // アクティビティ内のホストの検証 const host = toPuny(new URL(signature.keyId).hostname); @@ -87,7 +86,7 @@ export default async (job: Bull.Job): Promise => { key = await UserPublickeys.findOne({ keyId: signature.keyId - }); + }).then(ensure); user = await Users.findOne(key.userId) as IRemoteUser; } diff --git a/src/remote/activitypub/kernel/accept/follow.ts b/src/remote/activitypub/kernel/accept/follow.ts index 816fcbadbf..f3e517ad9f 100644 --- a/src/remote/activitypub/kernel/accept/follow.ts +++ b/src/remote/activitypub/kernel/accept/follow.ts @@ -6,9 +6,10 @@ import { Users } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id; + if (id == null) throw 'missing id'; if (!id.startsWith(config.url + '/')) { - return null; + return; } const follower = await Users.findOne({ diff --git a/src/remote/activitypub/kernel/add/index.ts b/src/remote/activitypub/kernel/add/index.ts index d16f0a4a0d..a5b2687416 100644 --- a/src/remote/activitypub/kernel/add/index.ts +++ b/src/remote/activitypub/kernel/add/index.ts @@ -14,6 +14,7 @@ export default async (actor: IRemoteUser, activity: IAdd): Promise => { if (activity.target === actor.featured) { const note = await resolveNote(activity.object); + if (note == null) throw new Error('note not found'); await addPinned(actor, note.id); return; } diff --git a/src/remote/activitypub/kernel/announce/note.ts b/src/remote/activitypub/kernel/announce/note.ts index 403fc66bed..f9822c5187 100644 --- a/src/remote/activitypub/kernel/announce/note.ts +++ b/src/remote/activitypub/kernel/announce/note.ts @@ -53,16 +53,16 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity: logger.info(`Creating the (Re)Note: ${uri}`); //#region Visibility - const visibility = getVisibility(activity.to, activity.cc, actor); + const visibility = getVisibility(activity.to || [], activity.cc || [], actor); let visibleUsers: User[] = []; if (visibility == 'specified') { - visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri))); + visibleUsers = await Promise.all((note.to || []).map(uri => resolvePerson(uri))); } //#endergion await post(actor, { - createdAt: new Date(activity.published), + createdAt: activity.published ? new Date(activity.published) : null, renote, visibility, visibleUsers, @@ -75,9 +75,6 @@ type visibility = 'public' | 'home' | 'followers' | 'specified'; function getVisibility(to: string[], cc: string[], actor: IRemoteUser): visibility { const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public'; - to = to || []; - cc = cc || []; - if (to.includes(PUBLIC)) { return 'public'; } else if (cc.includes(PUBLIC)) { diff --git a/src/remote/activitypub/kernel/block/index.ts b/src/remote/activitypub/kernel/block/index.ts index 48e251dd9b..19e33eb7dd 100644 --- a/src/remote/activitypub/kernel/block/index.ts +++ b/src/remote/activitypub/kernel/block/index.ts @@ -9,13 +9,14 @@ const logger = apLogger; export default async (actor: IRemoteUser, activity: IBlock): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; const uri = activity.id || activity; logger.info(`Block: ${uri}`); if (!id.startsWith(config.url + '/')) { - return null; + return; } const blockee = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/follow.ts b/src/remote/activitypub/kernel/follow.ts index e6c8833f3a..d37404502f 100644 --- a/src/remote/activitypub/kernel/follow.ts +++ b/src/remote/activitypub/kernel/follow.ts @@ -6,9 +6,10 @@ import { Users } from '../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; if (!id.startsWith(config.url + '/')) { - return null; + return; } const followee = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/index.ts b/src/remote/activitypub/kernel/index.ts index 4a57d0675e..d1251817fa 100644 --- a/src/remote/activitypub/kernel/index.ts +++ b/src/remote/activitypub/kernel/index.ts @@ -71,7 +71,7 @@ const self = async (actor: IRemoteUser, activity: Object): Promise => { default: apLogger.warn(`unknown activity type: ${(activity as any).type}`); - return null; + return; } }; diff --git a/src/remote/activitypub/kernel/like.ts b/src/remote/activitypub/kernel/like.ts index 86dd8fb33d..d4fa7bf387 100644 --- a/src/remote/activitypub/kernel/like.ts +++ b/src/remote/activitypub/kernel/like.ts @@ -5,6 +5,7 @@ import { Notes } from '../../../models'; export default async (actor: IRemoteUser, activity: ILike) => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; // Transform: // https://misskey.ex/notes/xxxx to diff --git a/src/remote/activitypub/kernel/reject/follow.ts b/src/remote/activitypub/kernel/reject/follow.ts index b06ae6fb96..91689339ab 100644 --- a/src/remote/activitypub/kernel/reject/follow.ts +++ b/src/remote/activitypub/kernel/reject/follow.ts @@ -6,9 +6,10 @@ import { Users } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id; + if (id == null) throw 'missing id'; if (!id.startsWith(config.url + '/')) { - return null; + return; } const follower = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/remove/index.ts b/src/remote/activitypub/kernel/remove/index.ts index ae33be59dc..32b8d66471 100644 --- a/src/remote/activitypub/kernel/remove/index.ts +++ b/src/remote/activitypub/kernel/remove/index.ts @@ -14,6 +14,7 @@ export default async (actor: IRemoteUser, activity: IRemove): Promise => { if (activity.target === actor.featured) { const note = await resolveNote(activity.object); + if (note == null) throw new Error('note not found'); await removePinned(actor, note.id); return; } diff --git a/src/remote/activitypub/kernel/undo/block.ts b/src/remote/activitypub/kernel/undo/block.ts index c916a00737..9c277ed7d2 100644 --- a/src/remote/activitypub/kernel/undo/block.ts +++ b/src/remote/activitypub/kernel/undo/block.ts @@ -9,13 +9,14 @@ const logger = apLogger; export default async (actor: IRemoteUser, activity: IBlock): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; const uri = activity.id || activity; logger.info(`UnBlock: ${uri}`); if (!id.startsWith(config.url + '/')) { - return null; + return; } const blockee = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/undo/follow.ts b/src/remote/activitypub/kernel/undo/follow.ts index cc63a740b1..ce84d0c791 100644 --- a/src/remote/activitypub/kernel/undo/follow.ts +++ b/src/remote/activitypub/kernel/undo/follow.ts @@ -7,9 +7,10 @@ import { Users, FollowRequests, Followings } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; if (!id.startsWith(config.url + '/')) { - return null; + return; } const followee = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/undo/index.ts b/src/remote/activitypub/kernel/undo/index.ts index 6376ab93a8..5f2e58c3bf 100644 --- a/src/remote/activitypub/kernel/undo/index.ts +++ b/src/remote/activitypub/kernel/undo/index.ts @@ -39,6 +39,4 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise => { undoLike(actor, object as ILike); break; } - - return null; }; diff --git a/src/remote/activitypub/kernel/undo/like.ts b/src/remote/activitypub/kernel/undo/like.ts index f337a0173e..75879d697a 100644 --- a/src/remote/activitypub/kernel/undo/like.ts +++ b/src/remote/activitypub/kernel/undo/like.ts @@ -8,6 +8,7 @@ import { Notes } from '../../../../models'; */ export default async (actor: IRemoteUser, activity: ILike): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; const noteId = id.split('/').pop(); diff --git a/src/remote/activitypub/models/image.ts b/src/remote/activitypub/models/image.ts index c9991dba3b..f8b35ea21c 100644 --- a/src/remote/activitypub/models/image.ts +++ b/src/remote/activitypub/models/image.ts @@ -5,6 +5,7 @@ import fetchMeta from '../../../misc/fetch-meta'; import { apLogger } from '../logger'; import { DriveFile } from '../../../models/entities/drive-file'; import { DriveFiles } from '../../../models'; +import { ensure } from '../../../prelude/ensure'; const logger = apLogger; @@ -14,7 +15,7 @@ const logger = apLogger; export async function createImage(actor: IRemoteUser, value: any): Promise { // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { - return null; + throw new Error('actor has been suspended'); } const image = await new Resolver().resolve(value) as any; @@ -28,17 +29,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise= 400 && e < 500) { - logger.warn(`Ignored image: ${image.url} - ${e}`); - return null; - } - throw e; - } + let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache); if (file.isLink) { // URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、 @@ -49,7 +40,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise { +export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise { const uri = typeof value == 'string' ? value : value.id; + if (uri == null) throw 'missing uri'; // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { const id = uri.split('/').pop(); - return await Notes.findOne(id); + return await Notes.findOne(id).then(x => x || null); } //#region このサーバーに既に登録されていたらそれを返す @@ -52,7 +54,7 @@ export async function fetchNote(value: string | IObject, resolver?: Resolver): P /** * Noteを作成します。 */ -export async function createNote(value: any, resolver?: Resolver, silent = false): Promise { +export async function createNote(value: any, resolver?: Resolver, silent = false): Promise { if (resolver == null) resolver = new Resolver(); const object: any = await resolver.resolve(value); @@ -65,7 +67,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false value: value, object: object }); - return null; + throw 'invalid note'; } const note: INote = object; @@ -75,11 +77,11 @@ export async function createNote(value: any, resolver?: Resolver, silent = false logger.info(`Creating the Note: ${note.id}`); // 投稿者をフェッチ - const actor = await resolvePerson(note.attributedTo, null, resolver) as IRemoteUser; + const actor = await resolvePerson(note.attributedTo, resolver) as IRemoteUser; // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { - return null; + throw 'actor has been suspended'; } //#region Visibility @@ -95,9 +97,9 @@ export async function createNote(value: any, resolver?: Resolver, silent = false visibility = 'followers'; } else { visibility = 'specified'; - visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri, null, resolver))); + visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri, resolver))); } -} + } //#endergion const apMentions = await extractMentionedUsers(actor, note.to, note.cc, resolver); @@ -118,7 +120,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false : []; // リプライ - const reply: Note = note.inReplyTo + const reply: Note | undefined | null = note.inReplyTo ? await resolveNote(note.inReplyTo, resolver).catch(e => { // 4xxの場合はリプライしてないことにする if (e.statusCode >= 400 && e.statusCode < 500) { @@ -131,7 +133,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false : null; // 引用 - let quote: Note; + let quote: Note | undefined | null; if (note._misskey_quote && typeof note._misskey_quote == 'string') { quote = await resolveNote(note._misskey_quote).catch(e => { @@ -152,7 +154,8 @@ export async function createNote(value: any, resolver?: Resolver, silent = false // vote if (reply && reply.hasPoll) { - const poll = await Polls.findOne({ noteId: reply.id }); + const poll = await Polls.findOne({ noteId: reply.id }).then(ensure); + const tryCreateVote = async (name: string, index: number): Promise => { if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); @@ -180,7 +183,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false } } - const emojis = await extractEmojis(note.tag, actor.host).catch(e => { + const emojis = await extractEmojis(note.tag || [], actor.host).catch(e => { logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); @@ -196,7 +199,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false } return await post(actor, { - createdAt: new Date(note.published), + createdAt: note.published ? new Date(note.published) : null, files, reply, renote: quote, @@ -223,8 +226,9 @@ export async function createNote(value: any, resolver?: Resolver, silent = false * Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ -export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise { +export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise { const uri = typeof value == 'string' ? value : value.id; + if (uri == null) throw 'missing uri'; // ブロックしてたら中断 // TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく @@ -244,75 +248,79 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver): // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 return await createNote(uri, resolver).catch(e => { if (e.name === 'duplicated') { - return fetchNote(uri); + return fetchNote(uri).then(note => { + if (note == null) { + throw 'something happened'; + } else { + return note; + } + }); } else { throw e; } }); } -export async function extractEmojis(tags: ITag[], host: string) { +export async function extractEmojis(tags: ITag[], host: string): Promise { host = toPuny(host); if (!tags) return []; - const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url); - - return await Promise.all( - eomjiTags.map(async tag => { - const name = tag.name.replace(/^:/, '').replace(/:$/, ''); + const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url && tag.name); - const exists = await Emojis.findOne({ - host, - name - }); + return await Promise.all(eomjiTags.map(async tag => { + const name = tag.name!.replace(/^:/, '').replace(/:$/, ''); - if (exists) { - if ((tag.updated != null && exists.updatedAt == null) - || (tag.id != null && exists.uri == null) - || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt) - ) { - await Emojis.update({ - host, - name, - }, { - uri: tag.id, - url: tag.icon.url, - updatedAt: new Date(tag.updated), - }); - - return await Emojis.findOne({ - host, - name - }); - } + const exists = await Emojis.findOne({ + host, + name + }); - return exists; + if (exists) { + if ((tag.updated != null && exists.updatedAt == null) + || (tag.id != null && exists.uri == null) + || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt) + ) { + await Emojis.update({ + host, + name, + }, { + uri: tag.id, + url: tag.icon!.url, + updatedAt: new Date(tag.updated!), + }); + + return await Emojis.findOne({ + host, + name + }) as Emoji; } - logger.info(`register emoji host=${host}, name=${name}`); - - return await Emojis.save({ - id: genId(), - host, - name, - uri: tag.id, - url: tag.icon.url, - updatedAt: tag.updated ? new Date(tag.updated) : undefined, - aliases: [] - } as Emoji); - }) - ); + return exists; + } + + logger.info(`register emoji host=${host}, name=${name}`); + + return await Emojis.save({ + id: genId(), + host, + name, + uri: tag.id, + url: tag.icon!.url, + updatedAt: tag.updated ? new Date(tag.updated) : undefined, + aliases: [] + } as Partial); + })); } async function extractMentionedUsers(actor: IRemoteUser, to: string[], cc: string[], resolver: Resolver) { const ignoreUris = ['https://www.w3.org/ns/activitystreams#Public', `${actor.uri}/followers`]; const uris = difference(unique(concat([to || [], cc || []])), ignoreUris); - const limit = promiseLimit(2); + const limit = promiseLimit(2); const users = await Promise.all( - uris.map(uri => limit(() => resolvePerson(uri, null, resolver).catch(() => null)) as Promise) + uris.map(uri => limit(() => resolvePerson(uri, resolver).catch(() => null)) as Promise) ); - return users.filter(x => x != null); + return users.filter(x => x != null) as User[]; } diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index e7021956de..9465cf0cd0 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -26,6 +26,7 @@ import { toPuny } from '../../../misc/convert-host'; import { UserProfile } from '../../../models/entities/user-profile'; import { validActor } from '../../../remote/activitypub/type'; import { getConnection } from 'typeorm'; +import { ensure } from '../../../prelude/ensure'; const logger = apLogger; /** @@ -86,13 +87,13 @@ function validatePerson(x: any, uri: string) { * * Misskeyに対象のPersonが登録されていればそれを返します。 */ -export async function fetchPerson(uri: string, resolver?: Resolver): Promise { +export async function fetchPerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw 'uri is not string'; // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { const id = uri.split('/').pop(); - return await Users.findOne(id); + return await Users.findOne(id).then(x => x || null); } //#region このサーバーに既に登録されていたらそれを返す @@ -128,7 +129,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise tag.toLowerCase()); @@ -161,7 +162,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise !tags.includes(x))) updateHashtag(user, tag, true, false); + for (const tag of tags) updateHashtag(user!, tag, true, true); + for (const tag of (user!.tags || []).filter(x => !tags.includes(x))) updateHashtag(user!, tag, true, false); //#region アイコンとヘッダー画像をフェッチ - const [avatar, banner] = (await Promise.all([ + const [avatar, banner] = (await Promise.all([ person.icon, person.image ].map(img => img == null ? Promise.resolve(null) - : resolveImage(user, img).catch(() => null) + : resolveImage(user!, img).catch(() => null) ))); const avatarId = avatar ? avatar.id : null; @@ -210,9 +211,9 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { + const emojis = await extractEmojis(person.tag || [], host).catch(e => { logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); const emojiNames = emojis.map(emoji => emoji.name); - await Users.update(user.id, { + await Users.update(user!.id, { emojis: emojiNames }); //#endregion - await updateFeatured(user.id).catch(err => logger.error(err)); + await updateFeatured(user!.id).catch(err => logger.error(err)); - return user; + return user!; } /** @@ -254,7 +255,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { +export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: object): Promise { if (typeof uri !== 'string') throw 'uri is not string'; // URIがこのサーバーを指しているならスキップ @@ -290,7 +291,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje logger.info(`Updating the Person: ${person.id}`); // アイコンとヘッダー画像をフェッチ - const [avatar, banner] = (await Promise.all([ + const [avatar, banner] = (await Promise.all([ person.icon, person.image ].map(img => @@ -300,14 +301,14 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje ))); // カスタム絵文字取得 - const emojis = await extractEmojis(person.tag, exist.host).catch(e => { + const emojis = await extractEmojis(person.tag || [], exist.host).catch(e => { logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); const emojiNames = emojis.map(emoji => emoji.name); - const { fields, services } = analyzeAttachments(person.attachment); + const { fields, services } = analyzeAttachments(person.attachment || []); const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase()); @@ -317,7 +318,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined), featured: person.featured, emojis: emojiNames, - description: fromHtml(person.summary), + description: person.summary ? fromHtml(person.summary) : null, name: person.name, url: person.url, endpoints: person.endpoints, @@ -326,7 +327,6 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje isBot: object.type == 'Service', isCat: (person as any).isCat === true, isLocked: person.manuallyApprovesFollowers, - createdAt: new Date(Date.parse(person.published)) || null, } as Partial; if (avatar) { @@ -379,7 +379,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje * Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ -export async function resolvePerson(uri: string, verifier?: string, resolver?: Resolver): Promise { +export async function resolvePerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw 'uri is not string'; //#region このサーバーに既に登録されていたらそれを返す @@ -439,21 +439,24 @@ export function analyzeAttachments(attachments: ITag[]) { }[] = []; const services: { [x: string]: any } = {}; - if (Array.isArray(attachments)) - for (const attachment of attachments.filter(isPropertyValue)) - if (isPropertyValue(attachment.identifier)) - addService(services, attachment.identifier); - else + if (Array.isArray(attachments)) { + for (const attachment of attachments.filter(isPropertyValue)) { + if (isPropertyValue(attachment.identifier!)) { + addService(services, attachment.identifier!); + } else { fields.push({ - name: attachment.name, - value: fromHtml(attachment.value) + name: attachment.name!, + value: fromHtml(attachment.value!) }); + } + } + } return { fields, services }; } export async function updateFeatured(userId: User['id']) { - const user = await Users.findOne(userId); + const user = await Users.findOne(userId).then(ensure); if (!Users.isRemoteUser(user)) return; if (!user.featured) return; @@ -471,18 +474,18 @@ export async function updateFeatured(userId: User['id']) { if (!Array.isArray(items)) throw new Error(`Collection items is not an array`); // Resolve and regist Notes - const limit = promiseLimit(2); + const limit = promiseLimit(2); const featuredNotes = await Promise.all(items .filter(item => item.type === 'Note') .slice(0, 5) - .map(item => limit(() => resolveNote(item, resolver)) as Promise)); + .map(item => limit(() => resolveNote(item, resolver)))); for (const note of featuredNotes.filter(note => note != null)) { UserNotePinings.save({ id: genId(), createdAt: new Date(), userId: user.id, - noteId: note.id + noteId: note!.id } as UserNotePining); } } diff --git a/src/remote/activitypub/models/question.ts b/src/remote/activitypub/models/question.ts index 2ff8e21ab5..708cdc2a66 100644 --- a/src/remote/activitypub/models/question.ts +++ b/src/remote/activitypub/models/question.ts @@ -14,10 +14,10 @@ export async function extractPollFromQuestion(source: string | IQuestion): Promi throw 'invalid question'; } - const choices = question[multiple ? 'anyOf' : 'oneOf'] - .map((x, i) => x.name); + const choices = question[multiple ? 'anyOf' : 'oneOf']! + .map((x, i) => x.name!); - const votes = question[multiple ? 'anyOf' : 'oneOf'] + const votes = question[multiple ? 'anyOf' : 'oneOf']! .map((x, i) => x.replies && x.replies.totalItems || x._misskey_votes || 0); return { @@ -60,7 +60,7 @@ export async function updateQuestion(value: any) { for (const choice of poll.choices) { const oldCount = poll.votes[poll.choices.indexOf(choice)]; - const newCount = apChoices.filter(ap => ap.name === choice)[0].replies.totalItems; + const newCount = apChoices!.filter(ap => ap.name === choice)[0].replies!.totalItems; if (oldCount != newCount) { changed = true; diff --git a/src/remote/activitypub/models/tag.ts b/src/remote/activitypub/models/tag.ts index 0a1e6e29f9..8d2008d1d9 100644 --- a/src/remote/activitypub/models/tag.ts +++ b/src/remote/activitypub/models/tag.ts @@ -14,13 +14,13 @@ export type ITag = { identifier?: IIdentifier; }; -export function extractHashtags(tags: ITag[]) { - if (!tags) return []; +export function extractHashtags(tags: ITag[] | null | undefined): string[] { + if (tags == null) return []; const hashtags = tags.filter(tag => tag.type === 'Hashtag' && typeof tag.name == 'string'); return hashtags.map(tag => { - const m = tag.name.match(/^#(.+)/); + const m = tag.name ? tag.name.match(/^#(.+)/) : null; return m ? m[1] : null; - }).filter(x => x != null); + }).filter(x => x != null) as string[]; } diff --git a/src/remote/activitypub/renderer/block.ts b/src/remote/activitypub/renderer/block.ts index 946c45a813..c29a9aea82 100644 --- a/src/remote/activitypub/renderer/block.ts +++ b/src/remote/activitypub/renderer/block.ts @@ -1,7 +1,7 @@ import config from '../../../config'; import { ILocalUser, IRemoteUser } from '../../../models/entities/user'; -export default (blocker?: ILocalUser, blockee?: IRemoteUser) => ({ +export default (blocker: ILocalUser, blockee: IRemoteUser) => ({ type: 'Block', actor: `${config.url}/users/${blocker.id}`, object: blockee.uri diff --git a/src/remote/activitypub/renderer/follow-user.ts b/src/remote/activitypub/renderer/follow-user.ts index 9446be3c86..6d354803e5 100644 --- a/src/remote/activitypub/renderer/follow-user.ts +++ b/src/remote/activitypub/renderer/follow-user.ts @@ -1,12 +1,13 @@ import config from '../../../config'; import { Users } from '../../../models'; import { User } from '../../../models/entities/user'; +import { ensure } from '../../../prelude/ensure'; /** * Convert (local|remote)(Follower|Followee)ID to URL * @param id Follower|Followee ID */ export default async function renderFollowUser(id: User['id']): Promise { - const user = await Users.findOne(id); + const user = await Users.findOne(id).then(ensure); return Users.isLocalUser(user) ? `${config.url}/users/${user.id}` : user.uri; } diff --git a/src/remote/activitypub/renderer/note.ts b/src/remote/activitypub/renderer/note.ts index 58ee4fb52c..c66af2667b 100644 --- a/src/remote/activitypub/renderer/note.ts +++ b/src/remote/activitypub/renderer/note.ts @@ -10,6 +10,7 @@ import { DriveFiles, Notes, Users, Emojis, Polls } from '../../../models'; import { In } from 'typeorm'; import { Emoji } from '../../../models/entities/emoji'; import { Poll } from '../../../models/entities/poll'; +import { ensure } from '../../../prelude/ensure'; export default async function renderNote(note: Note, dive = true): Promise { const promisedFiles: Promise = note.fileIds.length > 0 @@ -17,15 +18,15 @@ export default async function renderNote(note: Note, dive = true): Promise : Promise.resolve([]); let inReplyTo; - let inReplyToNote: Note; + let inReplyToNote: Note | undefined; if (note.replyId) { inReplyToNote = await Notes.findOne(note.replyId); - if (inReplyToNote !== null) { + if (inReplyToNote != null) { const inReplyToUser = await Users.findOne(inReplyToNote.userId); - if (inReplyToUser !== null) { + if (inReplyToUser != null) { if (inReplyToNote.uri) { inReplyTo = inReplyToNote.uri; } else { @@ -51,9 +52,7 @@ export default async function renderNote(note: Note, dive = true): Promise } } - const user = await Users.findOne({ - id: note.userId - }); + const user = await Users.findOne(note.userId).then(ensure); const attributedTo = `${config.url}/users/${user.id}`; @@ -85,13 +84,13 @@ export default async function renderNote(note: Note, dive = true): Promise const files = await promisedFiles; let text = note.text; - let poll: Poll; + let poll: Poll | undefined; if (note.hasPoll) { poll = await Polls.findOne({ noteId: note.id }); } - let question: string; + let question: string | undefined; if (poll) { if (text == null) text = ''; const url = `${config.url}/notes/${note.id}`; @@ -144,7 +143,7 @@ export default async function renderNote(note: Note, dive = true): Promise name: text, replies: { type: 'Collection', - totalItems: poll.votes[i] + totalItems: poll!.votes[i] } })) } : {}; @@ -179,5 +178,5 @@ export async function getEmojis(names: string[]): Promise { })) ); - return emojis.filter(emoji => emoji != null); + return emojis.filter(emoji => emoji != null) as Emoji[]; } diff --git a/src/remote/activitypub/renderer/ordered-collection-page.ts b/src/remote/activitypub/renderer/ordered-collection-page.ts index 83af07870e..2433358646 100644 --- a/src/remote/activitypub/renderer/ordered-collection-page.ts +++ b/src/remote/activitypub/renderer/ordered-collection-page.ts @@ -7,7 +7,7 @@ * @param prev URL of prev page (optional) * @param next URL of next page (optional) */ -export default function(id: string, totalItems: any, orderedItems: any, partOf: string, prev: string, next: string) { +export default function(id: string, totalItems: any, orderedItems: any, partOf: string, prev?: string, next?: string) { const page = { id, partOf, diff --git a/src/remote/activitypub/renderer/person.ts b/src/remote/activitypub/renderer/person.ts index e561e47c68..3fb164ef4e 100644 --- a/src/remote/activitypub/renderer/person.ts +++ b/src/remote/activitypub/renderer/person.ts @@ -9,14 +9,15 @@ import renderEmoji from './emoji'; import { IIdentifier } from '../models/identifier'; import renderHashtag from './hashtag'; import { DriveFiles, UserProfiles, UserKeypairs } from '../../../models'; +import { ensure } from '../../../prelude/ensure'; export async function renderPerson(user: ILocalUser) { const id = `${config.url}/users/${user.id}`; const [avatar, banner, profile] = await Promise.all([ - DriveFiles.findOne(user.avatarId), - DriveFiles.findOne(user.bannerId), - UserProfiles.findOne({ userId: user.id }) + user.avatarId ? DriveFiles.findOne(user.avatarId) : Promise.resolve(undefined), + user.bannerId ? DriveFiles.findOne(user.bannerId) : Promise.resolve(undefined), + UserProfiles.findOne({ userId: user.id }).then(ensure) ]); const attachment: { @@ -76,9 +77,7 @@ export async function renderPerson(user: ILocalUser) { ...hashtagTags, ]; - const keypair = await UserKeypairs.findOne({ - userId: user.id - }); + const keypair = await UserKeypairs.findOne(user.id).then(ensure); return { type: user.isBot ? 'Service' : 'Person', @@ -94,8 +93,8 @@ export async function renderPerson(user: ILocalUser) { preferredUsername: user.username, name: user.name, summary: toHtml(parse(profile.description)), - icon: user.avatarId && renderImage(avatar), - image: user.bannerId && renderImage(banner), + icon: avatar ? renderImage(avatar) : null, + image: banner ? renderImage(banner) : null, tag, manuallyApprovesFollowers: user.isLocked, publicKey: renderKey(user, keypair), diff --git a/src/remote/activitypub/request.ts b/src/remote/activitypub/request.ts index 8aca5e8102..7dc48c15e3 100644 --- a/src/remote/activitypub/request.ts +++ b/src/remote/activitypub/request.ts @@ -12,6 +12,7 @@ import { apLogger } from './logger'; import { UserKeypairs } from '../../models'; import fetchMeta from '../../misc/fetch-meta'; import { toPuny } from '../../misc/convert-host'; +import { ensure } from '../../prelude/ensure'; export const logger = apLogger.createSubLogger('deliver'); @@ -38,7 +39,7 @@ export default async (user: ILocalUser, url: string, object: any) => { const keypair = await UserKeypairs.findOne({ userId: user.id - }); + }).then(ensure); const _ = new Promise((resolve, reject) => { const req = request({ @@ -56,7 +57,7 @@ export default async (user: ILocalUser, url: string, object: any) => { 'Digest': `SHA-256=${hash}` } }, res => { - if (res.statusCode >= 400) { + if (res.statusCode! >= 400) { logger.warn(`${url} --> ${res.statusCode}`); reject(res); } else { @@ -73,7 +74,7 @@ export default async (user: ILocalUser, url: string, object: any) => { }); // Signature: Signature ... => Signature: ... - let sig = req.getHeader('Signature').toString(); + let sig = req.getHeader('Signature')!.toString(); sig = sig.replace(/^Signature /, ''); req.setHeader('Signature', sig); @@ -112,7 +113,7 @@ async function resolveAddr(domain: string) { function resolveAddrInner(domain: string, options: IRunOptions = {}): Promise { return new Promise((res, rej) => { - lookup(domain, options, (error: any, address: string | string[]) => { + lookup(domain, options, (error, address) => { if (error) return rej(error); return res(Array.isArray(address) ? address[0] : address); }); diff --git a/src/remote/resolve-user.ts b/src/remote/resolve-user.ts index 6a8ce45c91..9b518f5e81 100644 --- a/src/remote/resolve-user.ts +++ b/src/remote/resolve-user.ts @@ -10,18 +10,31 @@ import { toPuny } from '../misc/convert-host'; const logger = remoteLogger.createSubLogger('resolve-user'); -export async function resolveUser(username: string, host: string, option?: any, resync = false): Promise { +export async function resolveUser(username: string, host: string | null, option?: any, resync = false): Promise { const usernameLower = username.toLowerCase(); - host = toPuny(host); if (host == null) { logger.info(`return local user: ${usernameLower}`); - return await Users.findOne({ usernameLower, host: null }); + return await Users.findOne({ usernameLower, host: null }).then(u => { + if (u == null) { + throw 'user not found'; + } else { + return u; + } + }); } + host = toPuny(host); + if (config.host == host) { logger.info(`return local user: ${usernameLower}`); - return await Users.findOne({ usernameLower, host: null }); + return await Users.findOne({ usernameLower, host: null }).then(u => { + if (u == null) { + throw 'user not found'; + } else { + return u; + } + }); } const user = await Users.findOne({ usernameLower, host }, option); @@ -63,7 +76,13 @@ export async function resolveUser(username: string, host: string, option?: any, await updatePerson(self.href); logger.info(`return resynced remote user: ${acctLower}`); - return await Users.findOne({ uri: self.href }); + return await Users.findOne({ uri: self.href }).then(u => { + if (u == null) { + throw 'user not found'; + } else { + return u; + } + }); } logger.info(`return existing remote user: ${acctLower}`); @@ -76,7 +95,7 @@ async function resolveSelf(acctLower: string) { logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ e.statusCode || e.message }`); throw new Error(`Failed to WebFinger for ${acctLower}: ${ e.statusCode || e.message }`); }); - const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self'); + const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self'); if (!self) { logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`); throw new Error('self link not found'); diff --git a/src/remote/webfinger.ts b/src/remote/webfinger.ts index 67535e37db..800673943b 100644 --- a/src/remote/webfinger.ts +++ b/src/remote/webfinger.ts @@ -5,7 +5,7 @@ import { query as urlQuery } from '../prelude/url'; type ILink = { href: string; - rel: string; + rel?: string; }; type IWebFinger = { diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts index 3b39977d47..12fccbfa7d 100644 --- a/src/server/activitypub.ts +++ b/src/server/activitypub.ts @@ -17,6 +17,7 @@ import { isSelfHost } from '../misc/convert-host'; import { Notes, Users, Emojis, UserKeypairs, Polls } from '../models'; import { ILocalUser, User } from '../models/entities/user'; import { In } from 'typeorm'; +import { ensure } from '../prelude/ensure'; // Init router const router = new Router(); @@ -123,8 +124,8 @@ router.get('/questions/:question', async (ctx, next) => { return; } - const user = await Users.findOne(pollNote.userId); - const poll = await Polls.findOne({ noteId: pollNote.id }); + const user = await Users.findOne(pollNote.userId).then(ensure); + const poll = await Polls.findOne({ noteId: pollNote.id }).then(ensure); ctx.body = renderActivity(await renderQuestion(user as ILocalUser, pollNote, poll)); setResponseType(ctx); @@ -156,9 +157,7 @@ router.get('/users/:user/publickey', async ctx => { return; } - const keypair = await UserKeypairs.findOne({ - userId: user.id - }); + const keypair = await UserKeypairs.findOne(user.id).then(ensure); if (Users.isLocalUser(user)) { ctx.body = renderActivity(renderKey(user, keypair)); @@ -189,7 +188,7 @@ router.get('/users/:user', async (ctx, next) => { const user = await Users.findOne({ id: userId, host: null - }); + }).then(ensure); await userInfo(ctx, user); }); @@ -200,7 +199,7 @@ router.get('/@:user', async (ctx, next) => { const user = await Users.findOne({ usernameLower: ctx.params.user.toLowerCase(), host: null - }); + }).then(ensure); await userInfo(ctx, user); }); diff --git a/src/server/activitypub/featured.ts b/src/server/activitypub/featured.ts index f43312d79a..86ec1000c7 100644 --- a/src/server/activitypub/featured.ts +++ b/src/server/activitypub/featured.ts @@ -5,6 +5,7 @@ import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-c import { setResponseType } from '../activitypub'; import renderNote from '../../remote/activitypub/renderer/note'; import { Users, Notes, UserNotePinings } from '../../models'; +import { ensure } from '../../prelude/ensure'; export default async (ctx: Router.IRouterContext) => { const userId = ctx.params.user; @@ -22,13 +23,14 @@ export default async (ctx: Router.IRouterContext) => { const pinings = await UserNotePinings.find({ userId: user.id }); - const pinnedNotes = await Promise.all(pinings.map(pining => Notes.findOne(pining.noteId))); + const pinnedNotes = await Promise.all(pinings.map(pining => + Notes.findOne(pining.noteId).then(ensure))); const renderedNotes = await Promise.all(pinnedNotes.map(note => renderNote(note))); const rendered = renderOrderedCollection( `${config.url}/users/${userId}/collections/featured`, - renderedNotes.length, null, null, renderedNotes + renderedNotes.length, undefined, undefined, renderedNotes ); ctx.body = renderActivity(rendered); diff --git a/src/server/activitypub/followers.ts b/src/server/activitypub/followers.ts index 62c54399ed..e48dc57f7a 100644 --- a/src/server/activitypub/followers.ts +++ b/src/server/activitypub/followers.ts @@ -69,18 +69,18 @@ export default async (ctx: Router.IRouterContext) => { cursor })}`, user.followersCount, renderedFollowers, partOf, - null, + undefined, inStock ? `${partOf}?${url.query({ page: 'true', cursor: followings[followings.length - 1].id - })}` : null + })}` : undefined ); ctx.body = renderActivity(rendered); setResponseType(ctx); } else { // index page - const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`, null); + const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`); ctx.body = renderActivity(rendered); ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); setResponseType(ctx); diff --git a/src/server/activitypub/following.ts b/src/server/activitypub/following.ts index 4894aac1f8..4a7314e0ce 100644 --- a/src/server/activitypub/following.ts +++ b/src/server/activitypub/following.ts @@ -70,18 +70,18 @@ export default async (ctx: Router.IRouterContext) => { cursor })}`, user.followingCount, renderedFollowees, partOf, - null, + undefined, inStock ? `${partOf}?${url.query({ page: 'true', cursor: followings[followings.length - 1].id - })}` : null + })}` : undefined ); ctx.body = renderActivity(rendered); setResponseType(ctx); } else { // index page - const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`, null); + const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`); ctx.body = renderActivity(rendered); ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); setResponseType(ctx); diff --git a/src/server/activitypub/outbox.ts b/src/server/activitypub/outbox.ts index 377f43c986..118d8f00a9 100644 --- a/src/server/activitypub/outbox.ts +++ b/src/server/activitypub/outbox.ts @@ -15,6 +15,7 @@ import { Users, Notes } from '../../models'; import { makePaginationQuery } from '../api/common/make-pagination-query'; import { Brackets } from 'typeorm'; import { Note } from '../../models/entities/note'; +import { ensure } from '../../prelude/ensure'; export default async (ctx: Router.IRouterContext) => { const userId = ctx.params.user; @@ -73,11 +74,11 @@ export default async (ctx: Router.IRouterContext) => { notes.length ? `${partOf}?${url.query({ page: 'true', since_id: notes[0].id - })}` : null, + })}` : undefined, notes.length ? `${partOf}?${url.query({ page: 'true', until_id: notes[notes.length - 1].id - })}` : null + })}` : undefined ); ctx.body = renderActivity(rendered); @@ -99,9 +100,9 @@ export default async (ctx: Router.IRouterContext) => { * Pack Create or Announce Activity * @param note Note */ -export async function packActivity(note: Note): Promise { +export async function packActivity(note: Note): Promise { if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length == 0)) { - const renote = await Notes.findOne(note.renoteId); + const renote = await Notes.findOne(note.renoteId).then(ensure); return renderAnnounce(renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`, note); } diff --git a/src/server/api/api-handler.ts b/src/server/api/api-handler.ts index 827aecdf25..2de6994f32 100644 --- a/src/server/api/api-handler.ts +++ b/src/server/api/api-handler.ts @@ -15,11 +15,11 @@ export default (endpoint: IEndpoint, ctx: Koa.BaseContext) => new Promise((res) ctx.status = x; ctx.body = { error: { - message: y.message, - code: y.code, - id: y.id, - kind: y.kind, - ...(y.info ? { info: y.info } : {}) + message: y!.message, + code: y!.code, + id: y!.id, + kind: y!.kind, + ...(y!.info ? { info: y!.info } : {}) } }; } else { @@ -31,9 +31,9 @@ export default (endpoint: IEndpoint, ctx: Koa.BaseContext) => new Promise((res) // Authentication authenticate(body['i']).then(([user, app]) => { // API invoking - call(endpoint.name, user, app, body, (ctx.req as any).file).then(res => { + call(endpoint.name, user, app, body, (ctx.req as any).file).then((res: any) => { reply(res); - }).catch(e => { + }).catch((e: ApiError) => { reply(e.httpStatusCode ? e.httpStatusCode : e.kind == 'client' ? 400 : 500, e); }); }).catch(() => { diff --git a/src/server/api/authenticate.ts b/src/server/api/authenticate.ts index e293e3fed0..5c2b7e45cf 100644 --- a/src/server/api/authenticate.ts +++ b/src/server/api/authenticate.ts @@ -3,7 +3,7 @@ import { User } from '../../models/entities/user'; import { App } from '../../models/entities/app'; import { Users, AccessTokens, Apps } from '../../models'; -export default async (token: string): Promise<[User, App]> => { +export default async (token: string): Promise<[User | null | undefined, App | null | undefined]> => { if (token == null) { return [null, null]; } diff --git a/src/server/api/call.ts b/src/server/api/call.ts index d72f2cc9e6..c79f8eef5d 100644 --- a/src/server/api/call.ts +++ b/src/server/api/call.ts @@ -12,7 +12,7 @@ const accessDenied = { id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e' }; -export default async (endpoint: string, user: User, app: App, data: any, file?: any) => { +export default async (endpoint: string, user: User | null | undefined, app: App | null | undefined, data: any, file?: any) => { const isSecure = user != null && app == null; const ep = endpoints.find(e => e.name === endpoint); @@ -39,15 +39,15 @@ export default async (endpoint: string, user: User, app: App, data: any, file?: }); } - if (ep.meta.requireCredential && user.isSuspended) { + if (ep.meta.requireCredential && user!.isSuspended) { throw new ApiError(accessDenied, { reason: 'Your account has been suspended.' }); } - if (ep.meta.requireAdmin && !user.isAdmin) { + if (ep.meta.requireAdmin && !user!.isAdmin) { throw new ApiError(accessDenied, { reason: 'You are not the admin.' }); } - if (ep.meta.requireModerator && !user.isAdmin && !user.isModerator) { + if (ep.meta.requireModerator && !user!.isAdmin && !user!.isModerator) { throw new ApiError(accessDenied, { reason: 'You are not a moderator.' }); } @@ -61,7 +61,7 @@ export default async (endpoint: string, user: User, app: App, data: any, file?: if (ep.meta.requireCredential && ep.meta.limit) { // Rate limit - await limiter(ep, user).catch(e => { + await limiter(ep, user!).catch(e => { throw new ApiError({ message: 'Rate limit exceeded. Please try again later.', code: 'RATE_LIMIT_EXCEEDED', diff --git a/src/server/api/common/make-pagination-query.ts b/src/server/api/common/make-pagination-query.ts index 0c859a4f8d..51c11e5dff 100644 --- a/src/server/api/common/make-pagination-query.ts +++ b/src/server/api/common/make-pagination-query.ts @@ -1,6 +1,6 @@ import { SelectQueryBuilder } from 'typeorm'; -export function makePaginationQuery(q: SelectQueryBuilder, sinceId: string, untilId: string, sinceDate?: number, untilDate?: number) { +export function makePaginationQuery(q: SelectQueryBuilder, sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number) { if (sinceId && untilId) { q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); diff --git a/src/server/api/define.ts b/src/server/api/define.ts index a18419bcf6..990cbf2a86 100644 --- a/src/server/api/define.ts +++ b/src/server/api/define.ts @@ -5,9 +5,9 @@ import { ApiError } from './error'; import { App } from '../../models/entities/app'; type Params = { - [P in keyof T['params']]: T['params'][P]['transform'] extends Function - ? ReturnType - : ReturnType[0]; + [P in keyof T['params']]: NonNullable[P]['transform'] extends Function + ? ReturnType[P]['transform']> + : ReturnType[P]['validator']['get']>[0]; }; export type Response = Record | void; @@ -34,11 +34,11 @@ export default function (meta: T, cb: (params: Params(defs: T, params: any): [Params, ApiError] { +function getParams(defs: T, params: any): [Params, ApiError | null] { if (defs.params == null) return [params, null]; const x: any = {}; - let err: ApiError = null; + let err: ApiError | null = null; Object.entries(defs.params).some(([k, def]) => { const [v, e] = def.validator.get(params[k]); if (e) { diff --git a/src/server/api/endpoints/admin/abuse-user-reports.ts b/src/server/api/endpoints/admin/abuse-user-reports.ts index 5c5a734c1d..63d1dd795c 100644 --- a/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -29,7 +29,7 @@ export const meta = { export default define(meta, async (ps) => { const query = makePaginationQuery(AbuseUserReports.createQueryBuilder('report'), ps.sinceId, ps.untilId); - const reports = await query.take(ps.limit).getMany(); + const reports = await query.take(ps.limit!).getMany(); return await AbuseUserReports.packMany(reports); }); diff --git a/src/server/api/endpoints/admin/drive/files.ts b/src/server/api/endpoints/admin/drive/files.ts index 1ccabc92d9..7c6672e6de 100644 --- a/src/server/api/endpoints/admin/drive/files.ts +++ b/src/server/api/endpoints/admin/drive/files.ts @@ -56,8 +56,8 @@ export default define(meta, async (ps, me) => { const files = await DriveFiles.find({ where: q, - take: ps.limit, - order: sort[ps.sort] || sort[fallback], + take: ps.limit!, + order: sort[ps.sort!] || sort[fallback], skip: ps.offset }); diff --git a/src/server/api/endpoints/admin/emoji/list.ts b/src/server/api/endpoints/admin/emoji/list.ts index 26385d4e23..cf73e4cc75 100644 --- a/src/server/api/endpoints/admin/emoji/list.ts +++ b/src/server/api/endpoints/admin/emoji/list.ts @@ -23,7 +23,7 @@ export const meta = { export default define(meta, async (ps) => { const emojis = await Emojis.find({ - host: toPuny(ps.host) + host: ps.host ? toPuny(ps.host) : null }); return emojis.map(e => ({ diff --git a/src/server/api/endpoints/admin/federation/remove-all-following.ts b/src/server/api/endpoints/admin/federation/remove-all-following.ts index fca76e7086..25aae6db88 100644 --- a/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import define from '../../../define'; import deleteFollowing from '../../../../../services/following/delete'; import { Followings, Users } from '../../../../../models'; +import { ensure } from '../../../../../prelude/ensure'; export const meta = { tags: ['admin'], @@ -22,13 +23,11 @@ export default define(meta, async (ps, me) => { }); const pairs = await Promise.all(followings.map(f => Promise.all([ - Users.findOne(f.followerId), - Users.findOne(f.followeeId) + Users.findOne(f.followerId).then(ensure), + Users.findOne(f.followeeId).then(ensure) ]))); for (const pair of pairs) { deleteFollowing(pair[0], pair[1]); } - - return; }); diff --git a/src/server/api/endpoints/admin/logs.ts b/src/server/api/endpoints/admin/logs.ts index 907b1fdc14..e1419bdfe1 100644 --- a/src/server/api/endpoints/admin/logs.ts +++ b/src/server/api/endpoints/admin/logs.ts @@ -65,7 +65,7 @@ export default define(meta, async (ps) => { } } - const logs = await query.orderBy('log.createdAt', 'DESC').take(ps.limit).getMany(); + const logs = await query.orderBy('log.createdAt', 'DESC').take(ps.limit!).getMany(); return logs; }); diff --git a/src/server/api/endpoints/admin/queue/jobs.ts b/src/server/api/endpoints/admin/queue/jobs.ts index c2496d7ef7..4e47775692 100644 --- a/src/server/api/endpoints/admin/queue/jobs.ts +++ b/src/server/api/endpoints/admin/queue/jobs.ts @@ -28,9 +28,9 @@ export default define(meta, async (ps) => { const queue = ps.domain === 'deliver' ? deliverQueue : ps.domain === 'inbox' ? inboxQueue : - null; + null as never; - const jobs = await queue.getJobs([ps.state], 0, ps.limit); + const jobs = await queue.getJobs([ps.state], 0, ps.limit!); return jobs.map(job => ({ id: job.id, diff --git a/src/server/api/endpoints/admin/show-users.ts b/src/server/api/endpoints/admin/show-users.ts index 73976b9872..97760ae797 100644 --- a/src/server/api/endpoints/admin/show-users.ts +++ b/src/server/api/endpoints/admin/show-users.ts @@ -82,7 +82,7 @@ export default define(meta, async (ps, me) => { default: query.orderBy('user.id', 'ASC'); break; } - query.take(ps.limit); + query.take(ps.limit!); query.skip(ps.offset); const users = await query.getMany(); diff --git a/src/server/api/endpoints/admin/update-remote-user.ts b/src/server/api/endpoints/admin/update-remote-user.ts index 0be9047d5a..f9716328d5 100644 --- a/src/server/api/endpoints/admin/update-remote-user.ts +++ b/src/server/api/endpoints/admin/update-remote-user.ts @@ -28,5 +28,5 @@ export const meta = { export default define(meta, async (ps) => { const user = await getRemoteUser(ps.userId); - await updatePerson(user.uri); + await updatePerson(user.uri!); }); diff --git a/src/server/api/endpoints/ap/show.ts b/src/server/api/endpoints/ap/show.ts index 35c5dd318d..1b992eeaa7 100644 --- a/src/server/api/endpoints/ap/show.ts +++ b/src/server/api/endpoints/ap/show.ts @@ -123,14 +123,14 @@ async function fetchAny(uri: string) { const note = await createNote(object.id); return { type: 'Note', - object: await Notes.pack(note, null, { detail: true }) + object: await Notes.pack(note!, null, { detail: true }) }; } return null; } -async function mergePack(user: User, note: Note) { +async function mergePack(user: User | null | undefined, note: Note | null | undefined) { if (user != null) { return { type: 'User', diff --git a/src/server/api/endpoints/auth/accept.ts b/src/server/api/endpoints/auth/accept.ts index 0baa6b111c..55898b59db 100644 --- a/src/server/api/endpoints/auth/accept.ts +++ b/src/server/api/endpoints/auth/accept.ts @@ -5,6 +5,7 @@ import define from '../../define'; import { ApiError } from '../../error'; import { AuthSessions, AccessTokens, Apps } from '../../../../models'; import { genId } from '../../../../misc/gen-id'; +import { ensure } from '../../../../prelude/ensure'; export const meta = { tags: ['auth'], @@ -48,7 +49,7 @@ export default define(meta, async (ps, user) => { if (exist == null) { // Lookup app - const app = await Apps.findOne(session.appId); + const app = await Apps.findOne(session.appId).then(ensure); // Generate Hash const sha256 = crypto.createHash('sha256'); diff --git a/src/server/api/endpoints/auth/session/userkey.ts b/src/server/api/endpoints/auth/session/userkey.ts index 8524b96f94..7126ac52c1 100644 --- a/src/server/api/endpoints/auth/session/userkey.ts +++ b/src/server/api/endpoints/auth/session/userkey.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import define from '../../../define'; import { ApiError } from '../../../error'; import { Apps, AuthSessions, AccessTokens, Users } from '../../../../../models'; +import { ensure } from '../../../../../prelude/ensure'; export const meta = { tags: ['auth'], @@ -90,7 +91,7 @@ export default define(meta, async (ps) => { const accessToken = await AccessTokens.findOne({ appId: app.id, userId: session.userId - }); + }).then(ensure); // Delete session AuthSessions.delete(session.id); diff --git a/src/server/api/endpoints/blocking/list.ts b/src/server/api/endpoints/blocking/list.ts index a078891ab0..97f353579d 100644 --- a/src/server/api/endpoints/blocking/list.ts +++ b/src/server/api/endpoints/blocking/list.ts @@ -44,7 +44,7 @@ export default define(meta, async (ps, me) => { .andWhere(`blocking.blockerId = :meId`, { meId: me.id }); const blockings = await query - .take(ps.limit) + .take(ps.limit!) .getMany(); return await Blockings.packMany(blockings, me); diff --git a/src/server/api/endpoints/charts/active-users.ts b/src/server/api/endpoints/charts/active-users.ts index 60fa72c5c7..f0349b17f3 100644 --- a/src/server/api/endpoints/charts/active-users.ts +++ b/src/server/api/endpoints/charts/active-users.ts @@ -33,5 +33,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await activeUsersChart.getChart(ps.span as any, ps.limit); + return await activeUsersChart.getChart(ps.span as any, ps.limit!); }); diff --git a/src/server/api/endpoints/charts/drive.ts b/src/server/api/endpoints/charts/drive.ts index a9676e1586..ae6d894407 100644 --- a/src/server/api/endpoints/charts/drive.ts +++ b/src/server/api/endpoints/charts/drive.ts @@ -33,5 +33,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await driveChart.getChart(ps.span as any, ps.limit); + return await driveChart.getChart(ps.span as any, ps.limit!); }); diff --git a/src/server/api/endpoints/charts/federation.ts b/src/server/api/endpoints/charts/federation.ts index 95f16c76a7..34e9bfee5f 100644 --- a/src/server/api/endpoints/charts/federation.ts +++ b/src/server/api/endpoints/charts/federation.ts @@ -33,5 +33,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await federationChart.getChart(ps.span as any, ps.limit); + return await federationChart.getChart(ps.span as any, ps.limit!); }); diff --git a/src/server/api/endpoints/charts/hashtag.ts b/src/server/api/endpoints/charts/hashtag.ts index a7ec12707e..eceb0b275c 100644 --- a/src/server/api/endpoints/charts/hashtag.ts +++ b/src/server/api/endpoints/charts/hashtag.ts @@ -40,5 +40,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await hashtagChart.getChart(ps.span as any, ps.limit, ps.tag); + return await hashtagChart.getChart(ps.span as any, ps.limit!, ps.tag); }); diff --git a/src/server/api/endpoints/charts/instance.ts b/src/server/api/endpoints/charts/instance.ts index cf3094f7e1..e99c17ae65 100644 --- a/src/server/api/endpoints/charts/instance.ts +++ b/src/server/api/endpoints/charts/instance.ts @@ -41,5 +41,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await instanceChart.getChart(ps.span as any, ps.limit, ps.host); + return await instanceChart.getChart(ps.span as any, ps.limit!, ps.host); }); diff --git a/src/server/api/endpoints/charts/network.ts b/src/server/api/endpoints/charts/network.ts index c0fcd95fe9..648588fbe5 100644 --- a/src/server/api/endpoints/charts/network.ts +++ b/src/server/api/endpoints/charts/network.ts @@ -33,5 +33,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await networkChart.getChart(ps.span as any, ps.limit); + return await networkChart.getChart(ps.span as any, ps.limit!); }); diff --git a/src/server/api/endpoints/charts/notes.ts b/src/server/api/endpoints/charts/notes.ts index 86f30e4b89..074c4978cd 100644 --- a/src/server/api/endpoints/charts/notes.ts +++ b/src/server/api/endpoints/charts/notes.ts @@ -33,5 +33,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await notesChart.getChart(ps.span as any, ps.limit); + return await notesChart.getChart(ps.span as any, ps.limit!); }); diff --git a/src/server/api/endpoints/charts/user/drive.ts b/src/server/api/endpoints/charts/user/drive.ts index e3696dfda1..918fb62c6a 100644 --- a/src/server/api/endpoints/charts/user/drive.ts +++ b/src/server/api/endpoints/charts/user/drive.ts @@ -42,5 +42,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await perUserDriveChart.getChart(ps.span as any, ps.limit, ps.userId); + return await perUserDriveChart.getChart(ps.span as any, ps.limit!, ps.userId); }); diff --git a/src/server/api/endpoints/charts/user/following.ts b/src/server/api/endpoints/charts/user/following.ts index 8feba0bd16..5d86e85d31 100644 --- a/src/server/api/endpoints/charts/user/following.ts +++ b/src/server/api/endpoints/charts/user/following.ts @@ -42,5 +42,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await perUserFollowingChart.getChart(ps.span as any, ps.limit, ps.userId); + return await perUserFollowingChart.getChart(ps.span as any, ps.limit!, ps.userId); }); diff --git a/src/server/api/endpoints/charts/user/notes.ts b/src/server/api/endpoints/charts/user/notes.ts index 8c1db54f76..d39a20df16 100644 --- a/src/server/api/endpoints/charts/user/notes.ts +++ b/src/server/api/endpoints/charts/user/notes.ts @@ -42,5 +42,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await perUserNotesChart.getChart(ps.span as any, ps.limit, ps.userId); + return await perUserNotesChart.getChart(ps.span as any, ps.limit!, ps.userId); }); diff --git a/src/server/api/endpoints/charts/user/reactions.ts b/src/server/api/endpoints/charts/user/reactions.ts index 7c9b2508ae..5b88a1d715 100644 --- a/src/server/api/endpoints/charts/user/reactions.ts +++ b/src/server/api/endpoints/charts/user/reactions.ts @@ -42,5 +42,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await perUserReactionsChart.getChart(ps.span as any, ps.limit, ps.userId); + return await perUserReactionsChart.getChart(ps.span as any, ps.limit!, ps.userId); }); diff --git a/src/server/api/endpoints/charts/users.ts b/src/server/api/endpoints/charts/users.ts index 3ed5e09349..17de5756da 100644 --- a/src/server/api/endpoints/charts/users.ts +++ b/src/server/api/endpoints/charts/users.ts @@ -33,5 +33,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await usersChart.getChart(ps.span as any, ps.limit); + return await usersChart.getChart(ps.span as any, ps.limit!); }); diff --git a/src/server/api/endpoints/drive/files.ts b/src/server/api/endpoints/drive/files.ts index 400b73d3b7..4e4db6c780 100644 --- a/src/server/api/endpoints/drive/files.ts +++ b/src/server/api/endpoints/drive/files.ts @@ -66,7 +66,7 @@ export default define(meta, async (ps, user) => { } } - const files = await query.take(ps.limit).getMany(); + const files = await query.take(ps.limit!).getMany(); return await DriveFiles.packMany(files, { detail: false, self: true }); }); diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts index 5702c70fc0..340a39a41c 100644 --- a/src/server/api/endpoints/drive/files/create.ts +++ b/src/server/api/endpoints/drive/files/create.ts @@ -92,6 +92,6 @@ export default define(meta, async (ps, user, app, file, cleanup) => { apiLogger.error(e); throw new ApiError(); } finally { - cleanup(); + cleanup!(); } }); diff --git a/src/server/api/endpoints/drive/files/show.ts b/src/server/api/endpoints/drive/files/show.ts index b516ec2df6..e8c0e683c9 100644 --- a/src/server/api/endpoints/drive/files/show.ts +++ b/src/server/api/endpoints/drive/files/show.ts @@ -63,7 +63,7 @@ export const meta = { }; export default define(meta, async (ps, user) => { - let file: DriveFile; + let file: DriveFile | undefined; if (ps.fileId) { file = await DriveFiles.findOne(ps.fileId); @@ -81,14 +81,14 @@ export default define(meta, async (ps, user) => { throw new ApiError(meta.errors.fileIdOrUrlRequired); } - if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { - throw new ApiError(meta.errors.accessDenied); - } - if (file == null) { throw new ApiError(meta.errors.noSuchFile); } + if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { + throw new ApiError(meta.errors.accessDenied); + } + return await DriveFiles.pack(file, { detail: true, self: true diff --git a/src/server/api/endpoints/drive/folders.ts b/src/server/api/endpoints/drive/folders.ts index f5c3816407..08ae2ff709 100644 --- a/src/server/api/endpoints/drive/folders.ts +++ b/src/server/api/endpoints/drive/folders.ts @@ -54,7 +54,7 @@ export default define(meta, async (ps, user) => { query.andWhere('folder.parentId IS NULL'); } - const folders = await query.take(ps.limit).getMany(); + const folders = await query.take(ps.limit!).getMany(); return await Promise.all(folders.map(folder => DriveFolders.pack(folder))); }); diff --git a/src/server/api/endpoints/drive/folders/update.ts b/src/server/api/endpoints/drive/folders/update.ts index 90129bed63..7d3ece00a3 100644 --- a/src/server/api/endpoints/drive/folders/update.ts +++ b/src/server/api/endpoints/drive/folders/update.ts @@ -102,10 +102,10 @@ export default define(meta, async (ps, user) => { id: folderId }); - if (folder2.id === folder.id) { + if (folder2!.id === folder!.id) { return true; - } else if (folder2.parentId) { - return await checkCircle(folder2.parentId); + } else if (folder2!.parentId) { + return await checkCircle(folder2!.parentId); } else { return false; } diff --git a/src/server/api/endpoints/drive/stream.ts b/src/server/api/endpoints/drive/stream.ts index 9a84627767..96d9f82421 100644 --- a/src/server/api/endpoints/drive/stream.ts +++ b/src/server/api/endpoints/drive/stream.ts @@ -50,7 +50,7 @@ export default define(meta, async (ps, user) => { } } - const files = await query.take(ps.limit).getMany(); + const files = await query.take(ps.limit!).getMany(); return await DriveFiles.packMany(files, { detail: false, self: true }); }); diff --git a/src/server/api/endpoints/federation/instances.ts b/src/server/api/endpoints/federation/instances.ts index 1946d26dec..301338ed96 100644 --- a/src/server/api/endpoints/federation/instances.ts +++ b/src/server/api/endpoints/federation/instances.ts @@ -86,7 +86,7 @@ export default define(meta, async (ps, me) => { } } - const instances = await query.take(ps.limit).skip(ps.offset).getMany(); + const instances = await query.take(ps.limit!).skip(ps.offset).getMany(); return instances; }); diff --git a/src/server/api/endpoints/games/reversi/games.ts b/src/server/api/endpoints/games/reversi/games.ts index 07736e0424..7267157e0e 100644 --- a/src/server/api/endpoints/games/reversi/games.ts +++ b/src/server/api/endpoints/games/reversi/games.ts @@ -41,7 +41,7 @@ export default define(meta, async (ps, user) => { } // Fetch games - const games = await query.take(ps.limit).getMany(); + const games = await query.take(ps.limit!).getMany(); return await Promise.all(games.map((g) => ReversiGames.pack(g, user, { detail: false diff --git a/src/server/api/endpoints/games/reversi/match.ts b/src/server/api/endpoints/games/reversi/match.ts index e34d3c67f4..da367b5978 100644 --- a/src/server/api/endpoints/games/reversi/match.ts +++ b/src/server/api/endpoints/games/reversi/match.ts @@ -70,7 +70,7 @@ export default define(meta, async (ps, user) => { map: eighteight.data, bw: 'random', isLlotheo: false - } as ReversiGame); + } as Partial); publishReversiStream(exist.parentId, 'matched', await ReversiGames.pack(game, exist.parentId)); @@ -107,6 +107,6 @@ export default define(meta, async (ps, user) => { publishReversiStream(child.id, 'invited', packed); publishMainStream(child.id, 'reversiInvited', packed); - return null; + return; } }); diff --git a/src/server/api/endpoints/hashtags/list.ts b/src/server/api/endpoints/hashtags/list.ts index 7996c81669..2998bc1a13 100644 --- a/src/server/api/endpoints/hashtags/list.ts +++ b/src/server/api/endpoints/hashtags/list.ts @@ -86,7 +86,7 @@ export default define(meta, async (ps, me) => { 'tag.attachedRemoteUsersCount', ]); - const tags = await query.take(ps.limit).getMany(); + const tags = await query.take(ps.limit!).getMany(); return tags; }); diff --git a/src/server/api/endpoints/hashtags/search.ts b/src/server/api/endpoints/hashtags/search.ts index fd91a2ebcc..6a9a2df6ef 100644 --- a/src/server/api/endpoints/hashtags/search.ts +++ b/src/server/api/endpoints/hashtags/search.ts @@ -48,7 +48,7 @@ export default define(meta, async (ps) => { const hashtags = await Hashtags.createQueryBuilder('tag') .where('tag.name like :q', { q: ps.query.toLowerCase() + '%' }) .orderBy('tag.count', 'DESC') - .take(ps.limit) + .take(ps.limit!) .skip(ps.offset) .getMany(); diff --git a/src/server/api/endpoints/hashtags/users.ts b/src/server/api/endpoints/hashtags/users.ts index 20cba96d0e..fa58f2f2c0 100644 --- a/src/server/api/endpoints/hashtags/users.ts +++ b/src/server/api/endpoints/hashtags/users.ts @@ -79,7 +79,7 @@ export default define(meta, async (ps, me) => { case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break; } - const users = await query.take(ps.limit).getMany(); + const users = await query.take(ps.limit!).getMany(); return await Users.packMany(users, me, { detail: true }); }); diff --git a/src/server/api/endpoints/i/2fa/done.ts b/src/server/api/endpoints/i/2fa/done.ts index edc7cefd26..e23678dcbb 100644 --- a/src/server/api/endpoints/i/2fa/done.ts +++ b/src/server/api/endpoints/i/2fa/done.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import * as speakeasy from 'speakeasy'; import define from '../../../define'; import { UserProfiles } from '../../../../../models'; +import { ensure } from '../../../../../prelude/ensure'; export const meta = { requireCredential: true, @@ -18,7 +19,7 @@ export const meta = { export default define(meta, async (ps, user) => { const token = ps.token.replace(/\s/g, ''); - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); if (profile.twoFactorTempSecret == null) { throw new Error('二段階認証の設定が開始されていません'); diff --git a/src/server/api/endpoints/i/2fa/register.ts b/src/server/api/endpoints/i/2fa/register.ts index db9a2fe944..76d79b3a49 100644 --- a/src/server/api/endpoints/i/2fa/register.ts +++ b/src/server/api/endpoints/i/2fa/register.ts @@ -5,6 +5,7 @@ import * as QRCode from 'qrcode'; import config from '../../../../../config'; import define from '../../../define'; import { UserProfiles } from '../../../../../models'; +import { ensure } from '../../../../../prelude/ensure'; export const meta = { requireCredential: true, @@ -19,10 +20,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // Compare password - const same = await bcrypt.compare(ps.password, profile.password); + const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/2fa/unregister.ts b/src/server/api/endpoints/i/2fa/unregister.ts index fa25b74391..9c7857e7ef 100644 --- a/src/server/api/endpoints/i/2fa/unregister.ts +++ b/src/server/api/endpoints/i/2fa/unregister.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import define from '../../../define'; import { UserProfiles } from '../../../../../models'; +import { ensure } from '../../../../../prelude/ensure'; export const meta = { requireCredential: true, @@ -16,10 +17,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // Compare password - const same = await bcrypt.compare(ps.password, profile.password); + const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/authorized-apps.ts b/src/server/api/endpoints/i/authorized-apps.ts index ebf04fcb58..3e9fea19e2 100644 --- a/src/server/api/endpoints/i/authorized-apps.ts +++ b/src/server/api/endpoints/i/authorized-apps.ts @@ -31,7 +31,7 @@ export default define(meta, async (ps, user) => { where: { userId: user.id }, - take: ps.limit, + take: ps.limit!, skip: ps.offset, order: { id: ps.sort == 'asc' ? 1 : -1 diff --git a/src/server/api/endpoints/i/change-password.ts b/src/server/api/endpoints/i/change-password.ts index d0e0695e18..0dda125b9c 100644 --- a/src/server/api/endpoints/i/change-password.ts +++ b/src/server/api/endpoints/i/change-password.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import define from '../../define'; import { UserProfiles } from '../../../../models'; +import { ensure } from '../../../../prelude/ensure'; export const meta = { requireCredential: true, @@ -20,10 +21,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // Compare password - const same = await bcrypt.compare(ps.currentPassword, profile.password); + const same = await bcrypt.compare(ps.currentPassword, profile.password!); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/delete-account.ts b/src/server/api/endpoints/i/delete-account.ts index 7ef7aa5fac..389d0b3212 100644 --- a/src/server/api/endpoints/i/delete-account.ts +++ b/src/server/api/endpoints/i/delete-account.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import define from '../../define'; import { Users, UserProfiles } from '../../../../models'; +import { ensure } from '../../../../prelude/ensure'; export const meta = { requireCredential: true, @@ -16,10 +17,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // Compare password - const same = await bcrypt.compare(ps.password, profile.password); + const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/favorites.ts b/src/server/api/endpoints/i/favorites.ts index d2d149b2d1..2c25250bea 100644 --- a/src/server/api/endpoints/i/favorites.ts +++ b/src/server/api/endpoints/i/favorites.ts @@ -38,7 +38,7 @@ export default define(meta, async (ps, user) => { .leftJoinAndSelect('favorite.note', 'note'); const favorites = await query - .take(ps.limit) + .take(ps.limit!) .getMany(); return await NoteFavorites.packMany(favorites, user); diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts index 9b016e0a2d..56074c9d00 100644 --- a/src/server/api/endpoints/i/notifications.ts +++ b/src/server/api/endpoints/i/notifications.ts @@ -81,13 +81,13 @@ export default define(meta, async (ps, user) => { query.setParameters(followingQuery.getParameters()); } - if (ps.includeTypes.length > 0) { + if (ps.includeTypes!.length > 0) { query.andWhere(`notification.type IN (:...includeTypes)`, { includeTypes: ps.includeTypes }); - } else if (ps.excludeTypes.length > 0) { + } else if (ps.excludeTypes!.length > 0) { query.andWhere(`notification.type NOT IN (:...excludeTypes)`, { excludeTypes: ps.excludeTypes }); } - const notifications = await query.take(ps.limit).getMany(); + const notifications = await query.take(ps.limit!).getMany(); // Mark all as read if (notifications.length > 0 && ps.markAsRead) { diff --git a/src/server/api/endpoints/i/regenerate-token.ts b/src/server/api/endpoints/i/regenerate-token.ts index ec53bca979..56c0362c88 100644 --- a/src/server/api/endpoints/i/regenerate-token.ts +++ b/src/server/api/endpoints/i/regenerate-token.ts @@ -4,6 +4,7 @@ import { publishMainStream } from '../../../../services/stream'; import generateUserToken from '../../common/generate-native-user-token'; import define from '../../define'; import { Users, UserProfiles } from '../../../../models'; +import { ensure } from '../../../../prelude/ensure'; export const meta = { requireCredential: true, @@ -18,10 +19,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // Compare password - const same = await bcrypt.compare(ps.password, profile.password); + const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/signin-history.ts b/src/server/api/endpoints/i/signin-history.ts index e9ae19d734..74648951fd 100644 --- a/src/server/api/endpoints/i/signin-history.ts +++ b/src/server/api/endpoints/i/signin-history.ts @@ -29,7 +29,7 @@ export default define(meta, async (ps, user) => { const query = makePaginationQuery(Signins.createQueryBuilder('signin'), ps.sinceId, ps.untilId) .andWhere(`signin.userId = :meId`, { meId: user.id }); - const history = await query.take(ps.limit).getMany(); + const history = await query.take(ps.limit!).getMany(); return await Promise.all(history.map(record => Signins.pack(record))); }); diff --git a/src/server/api/endpoints/i/update-email.ts b/src/server/api/endpoints/i/update-email.ts index d98f0d753e..8b05f3c8cb 100644 --- a/src/server/api/endpoints/i/update-email.ts +++ b/src/server/api/endpoints/i/update-email.ts @@ -9,6 +9,7 @@ import * as ms from 'ms'; import * as bcrypt from 'bcryptjs'; import { apiLogger } from '../../logger'; import { Users, UserProfiles } from '../../../../models'; +import { ensure } from '../../../../prelude/ensure'; export const meta = { requireCredential: true, @@ -32,10 +33,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // Compare password - const same = await bcrypt.compare(ps.password, profile.password); + const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 3cb514d939..d06ab621c6 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -206,13 +206,13 @@ export default define(meta, async (ps, user, app) => { if (updates.name != null) { const tokens = parsePlain(updates.name); - emojis = emojis.concat(extractEmojis(tokens)); + emojis = emojis.concat(extractEmojis(tokens!)); } if (profile.description != null) { const tokens = parse(profile.description); - emojis = emojis.concat(extractEmojis(tokens)); - tags = extractHashtags(tokens).map(tag => tag.toLowerCase()); + emojis = emojis.concat(extractEmojis(tokens!)); + tags = extractHashtags(tokens!).map(tag => tag.toLowerCase()); } updates.emojis = emojis; diff --git a/src/server/api/endpoints/messaging/history.ts b/src/server/api/endpoints/messaging/history.ts index c0aec61212..c12378eb7e 100644 --- a/src/server/api/endpoints/messaging/history.ts +++ b/src/server/api/endpoints/messaging/history.ts @@ -38,7 +38,7 @@ export default define(meta, async (ps, user) => { const history: MessagingMessage[] = []; - for (let i = 0; i < ps.limit; i++) { + for (let i = 0; i < ps.limit!; i++) { const found = history.map(m => (m.userId === user.id) ? m.recipientId : m.userId); const query = MessagingMessages.createQueryBuilder('message') diff --git a/src/server/api/endpoints/mute/list.ts b/src/server/api/endpoints/mute/list.ts index 5f2d184472..0fd8a4860d 100644 --- a/src/server/api/endpoints/mute/list.ts +++ b/src/server/api/endpoints/mute/list.ts @@ -44,7 +44,7 @@ export default define(meta, async (ps, me) => { .andWhere(`muting.muterId = :meId`, { meId: me.id }); const mutings = await query - .take(ps.limit) + .take(ps.limit!) .getMany(); return await Mutings.packMany(mutings, me); diff --git a/src/server/api/endpoints/my/apps.ts b/src/server/api/endpoints/my/apps.ts index d205d1674c..e8b26362a4 100644 --- a/src/server/api/endpoints/my/apps.ts +++ b/src/server/api/endpoints/my/apps.ts @@ -32,7 +32,7 @@ export default define(meta, async (ps, user) => { const apps = await Apps.find({ where: query, - take: ps.limit, + take: ps.limit!, skip: ps.offset, }); diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts index 10f6e39845..17ba969350 100644 --- a/src/server/api/endpoints/notes.ts +++ b/src/server/api/endpoints/notes.ts @@ -100,7 +100,7 @@ export default define(meta, async (ps) => { // query.isBot = bot; //} - const notes = await query.take(ps.limit).getMany(); + const notes = await query.take(ps.limit!).getMany(); return await Notes.packMany(notes); }); diff --git a/src/server/api/endpoints/notes/children.ts b/src/server/api/endpoints/notes/children.ts index 72f2c39d6a..2b4ae2a312 100644 --- a/src/server/api/endpoints/notes/children.ts +++ b/src/server/api/endpoints/notes/children.ts @@ -66,7 +66,7 @@ export default define(meta, async (ps, user) => { if (user) generateVisibilityQuery(query, user); if (user) generateMuteQuery(query, user); - const notes = await query.take(ps.limit).getMany(); + const notes = await query.take(ps.limit!).getMany(); 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 6defd79042..6b26e31c07 100644 --- a/src/server/api/endpoints/notes/conversation.ts +++ b/src/server/api/endpoints/notes/conversation.ts @@ -64,12 +64,13 @@ export default define(meta, async (ps, user) => { async function get(id: any) { i++; const p = await Notes.findOne(id); + if (p == null) return; - if (i > ps.offset) { + if (i > ps.offset!) { conversation.push(p); } - if (conversation.length == ps.limit) { + if (conversation.length == ps.limit!) { return; } diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 138f05fb3b..83649015d8 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -225,23 +225,24 @@ export const meta = { export default define(meta, async (ps, user, app) => { let visibleUsers: User[] = []; if (ps.visibleUserIds) { - visibleUsers = await Promise.all(ps.visibleUserIds.map(id => Users.findOne(id))); + visibleUsers = (await Promise.all(ps.visibleUserIds.map(id => Users.findOne(id)))) + .filter(x => x != null) as User[]; } 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 DriveFiles.findOne({ + files = (await Promise.all(fileIds.map(fileId => + DriveFiles.findOne({ id: fileId, userId: user.id - }); - })); + }) + ))).filter(file => file != null) as DriveFile[]; - files = files.filter(file => file != null); + files = files; } - let renote: Note = null; + let renote: Note | undefined; if (ps.renoteId != null) { // Fetch renote to note renote = await Notes.findOne(ps.renoteId); @@ -253,7 +254,7 @@ export default define(meta, async (ps, user, app) => { } } - let reply: Note = null; + let reply: Note | undefined; if (ps.replyId != null) { // Fetch reply reply = await Notes.findOne(ps.replyId); @@ -290,8 +291,8 @@ 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 - } : null, - text: ps.text, + } : undefined, + text: ps.text || undefined, reply, renote, cw: ps.cw, @@ -300,9 +301,9 @@ export default define(meta, async (ps, user, app) => { localOnly: ps.localOnly, visibility: ps.visibility, visibleUsers, - apMentions: ps.noExtractMentions ? [] : null, - apHashtags: ps.noExtractHashtags ? [] : null, - apEmojis: ps.noExtractEmojis ? [] : null, + apMentions: ps.noExtractMentions ? [] : undefined, + apHashtags: ps.noExtractHashtags ? [] : undefined, + apEmojis: ps.noExtractEmojis ? [] : undefined, geo: ps.geo }); diff --git a/src/server/api/endpoints/notes/delete.ts b/src/server/api/endpoints/notes/delete.ts index dbaf91bca3..17d44c424d 100644 --- a/src/server/api/endpoints/notes/delete.ts +++ b/src/server/api/endpoints/notes/delete.ts @@ -6,6 +6,7 @@ import * as ms from 'ms'; import { getNote } from '../../common/getters'; import { ApiError } from '../../error'; import { Users } from '../../../../models'; +import { ensure } from '../../../../prelude/ensure'; export const meta = { stability: 'stable', @@ -63,5 +64,5 @@ export default define(meta, async (ps, user) => { } // この操作を行うのが投稿者とは限らない(例えばモデレーター)ため - await deleteNote(await Users.findOne(note.userId), note); + await deleteNote(await Users.findOne(note.userId).then(ensure), note); }); diff --git a/src/server/api/endpoints/notes/featured.ts b/src/server/api/endpoints/notes/featured.ts index c44a5275bb..5d2e5b7d44 100644 --- a/src/server/api/endpoints/notes/featured.ts +++ b/src/server/api/endpoints/notes/featured.ts @@ -41,7 +41,7 @@ export default define(meta, async (ps, user) => { if (user) generateMuteQuery(query, user); - const notes = await query.orderBy('note.score', 'DESC').take(ps.limit).getMany(); + const notes = await query.orderBy('note.score', 'DESC').take(ps.limit!).getMany(); 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 7bf62f366b..ceffb1cf4a 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -84,7 +84,7 @@ export default define(meta, async (ps, user) => { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit!).getMany(); if (user) { activeUsersChart.update(user); diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 76f1682a95..6dfb143003 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -185,7 +185,7 @@ export default define(meta, async (ps, user) => { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit!).getMany(); if (user) { activeUsersChart.update(user); diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index cd07341342..c10c0d7482 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -100,11 +100,11 @@ export default define(meta, async (ps, user) => { query.andWhere('note.fileIds != \'{}\''); } - if (ps.fileType) { + if (ps.fileType != null) { query.andWhere('note.fileIds != \'{}\''); query.andWhere(new Brackets(qb => { - for (const type of ps.fileType) { - const i = ps.fileType.indexOf(type); + for (const type of ps.fileType!) { + const i = ps.fileType!.indexOf(type); qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); } })); @@ -120,7 +120,7 @@ export default define(meta, async (ps, user) => { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit!).getMany(); if (user) { activeUsersChart.update(user); diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index 0bbe7d3327..b7f614915b 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -74,7 +74,7 @@ export default define(meta, async (ps, user) => { query.setParameters(followingQuery.getParameters()); } - const mentions = await query.take(ps.limit).getMany(); + const mentions = await query.take(ps.limit!).getMany(); for (const note of mentions) { read(user.id, note.id); diff --git a/src/server/api/endpoints/notes/polls/recommendation.ts b/src/server/api/endpoints/notes/polls/recommendation.ts index ff838d4f4f..cbd4d35cd4 100644 --- a/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/src/server/api/endpoints/notes/polls/recommendation.ts @@ -58,7 +58,7 @@ export default define(meta, async (ps, user) => { query.setParameters(mutingQuery.getParameters()); //#endregion - const polls = await query.take(ps.limit).skip(ps.offset).getMany(); + const polls = await query.take(ps.limit!).skip(ps.offset).getMany(); if (polls.length === 0) return []; diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts index dd4d93c7ac..68dee66828 100644 --- a/src/server/api/endpoints/notes/polls/vote.ts +++ b/src/server/api/endpoints/notes/polls/vote.ts @@ -14,6 +14,7 @@ import { PollVotes, NoteWatchings, Users, Polls, UserProfiles } from '../../../. import { Not } from 'typeorm'; import { IRemoteUser } from '../../../../../models/entities/user'; import { genId } from '../../../../../misc/gen-id'; +import { ensure } from '../../../../../prelude/ensure'; export const meta = { desc: { @@ -87,7 +88,7 @@ export default define(meta, async (ps, user) => { throw new ApiError(meta.errors.noPoll); } - const poll = await Polls.findOne({ noteId: note.id }); + const poll = await Polls.findOne({ noteId: note.id }).then(ensure); if (poll.expiresAt && poll.expiresAt < createdAt) { throw new ApiError(meta.errors.alreadyExpired); @@ -149,7 +150,7 @@ export default define(meta, async (ps, user) => { } }); - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // この投稿をWatchする if (profile.autoWatch !== false) { @@ -158,7 +159,7 @@ export default define(meta, async (ps, user) => { // リモート投票の場合リプライ送信 if (note.userHost != null) { - const pollOwner: IRemoteUser = await Users.findOne(note.userId); + const pollOwner = await Users.findOne(note.userId).then(ensure) as IRemoteUser; deliver(user, renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox); } diff --git a/src/server/api/endpoints/notes/reactions.ts b/src/server/api/endpoints/notes/reactions.ts index b1b5ca9d33..bcb0b6d1ec 100644 --- a/src/server/api/endpoints/notes/reactions.ts +++ b/src/server/api/endpoints/notes/reactions.ts @@ -71,7 +71,7 @@ export default define(meta, async (ps, user) => { const reactions = await NoteReactions.find({ where: query, - take: ps.limit, + take: ps.limit!, skip: ps.offset, order: { id: -1 diff --git a/src/server/api/endpoints/notes/renotes.ts b/src/server/api/endpoints/notes/renotes.ts index 81b899836d..74a8ea918e 100644 --- a/src/server/api/endpoints/notes/renotes.ts +++ b/src/server/api/endpoints/notes/renotes.ts @@ -70,7 +70,7 @@ export default define(meta, async (ps, user) => { if (user) generateVisibilityQuery(query, user); if (user) generateMuteQuery(query, user); - const renotes = await query.take(ps.limit).getMany(); + const renotes = await query.take(ps.limit!).getMany(); 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 09b0f17164..980ff2446e 100644 --- a/src/server/api/endpoints/notes/replies.ts +++ b/src/server/api/endpoints/notes/replies.ts @@ -61,7 +61,7 @@ export default define(meta, async (ps, user) => { if (user) generateVisibilityQuery(query, user); if (user) generateMuteQuery(query, user); - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit!).getMany(); 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 48de88d36e..cba3724b6f 100644 --- a/src/server/api/endpoints/notes/search-by-tag.ts +++ b/src/server/api/endpoints/notes/search-by-tag.ts @@ -100,7 +100,7 @@ export default define(meta, async (ps, me) => { } else { let i = 0; query.andWhere(new Brackets(qb => { - for (const tags of ps.query) { + 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 }); @@ -140,7 +140,7 @@ export default define(meta, async (ps, me) => { } // Search notes - const notes = await query.take(ps.limit).getMany(); + const notes = await query.take(ps.limit!).getMany(); 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 cc88fb9380..4d5ac6fbe0 100644 --- a/src/server/api/endpoints/notes/search.ts +++ b/src/server/api/endpoints/notes/search.ts @@ -54,7 +54,7 @@ export default define(meta, async (ps, me) => { index: 'misskey', type: 'note', body: { - size: ps.limit, + size: ps.limit!, from: ps.offset, query: { simple_query_string: { diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index e22db4d1b0..c27f3df1b7 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -171,7 +171,7 @@ export default define(meta, async (ps, user) => { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit!).getMany(); activeUsersChart.update(user); diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index deda04acb4..05f171af8b 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -189,7 +189,7 @@ export default define(meta, async (ps, user) => { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit!).getMany(); activeUsersChart.update(user); diff --git a/src/server/api/endpoints/users.ts b/src/server/api/endpoints/users.ts index f99165f3d5..18af0a2685 100644 --- a/src/server/api/endpoints/users.ts +++ b/src/server/api/endpoints/users.ts @@ -88,7 +88,7 @@ export default define(meta, async (ps, me) => { if (me) generateMuteQueryForUsers(query, me); - query.take(ps.limit); + query.take(ps.limit!); query.skip(ps.offset); const users = await query.getMany(); diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts index 64d63e2d03..88a474be7c 100644 --- a/src/server/api/endpoints/users/followers.ts +++ b/src/server/api/endpoints/users/followers.ts @@ -66,7 +66,7 @@ export const meta = { export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId != null ? { id: ps.userId } - : { usernameLower: ps.username.toLowerCase(), host: toPuny(ps.host) }); + : { usernameLower: ps.username!.toLowerCase(), host: toPuny(ps.host!) }); if (user == null) { throw new ApiError(meta.errors.noSuchUser); @@ -76,7 +76,7 @@ export default define(meta, async (ps, me) => { .andWhere(`following.followeeId = :userId`, { userId: user.id }); const followings = await query - .take(ps.limit) + .take(ps.limit!) .getMany(); return await Followings.packMany(followings, me, { populateFollower: true }); diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts index 0e28001680..5e017150e8 100644 --- a/src/server/api/endpoints/users/following.ts +++ b/src/server/api/endpoints/users/following.ts @@ -66,7 +66,7 @@ export const meta = { export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId != null ? { id: ps.userId } - : { usernameLower: ps.username.toLowerCase(), host: toPuny(ps.host) }); + : { usernameLower: ps.username!.toLowerCase(), host: toPuny(ps.host!) }); if (user == null) { throw new ApiError(meta.errors.noSuchUser); @@ -76,7 +76,7 @@ export default define(meta, async (ps, me) => { .andWhere(`following.followerId = :userId`, { userId: user.id }); const followings = await query - .take(ps.limit) + .take(ps.limit!) .getMany(); return await Followings.packMany(followings, me, { populateFollowee: true }); diff --git a/src/server/api/endpoints/users/get-frequently-replied-users.ts b/src/server/api/endpoints/users/get-frequently-replied-users.ts index f82f437629..a1d140c6c9 100644 --- a/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -94,7 +94,7 @@ export default define(meta, async (ps, me) => { const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]); // Extract top replied users - const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit); + const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit!); // Make replies object (includes weights) const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index 6df394cbb1..da23be3c55 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -153,11 +153,11 @@ export default define(meta, async (ps, me) => { query.andWhere('note.fileIds != \'{}\''); } - if (ps.fileType) { + if (ps.fileType != null) { query.andWhere('note.fileIds != \'{}\''); query.andWhere(new Brackets(qb => { - for (const type of ps.fileType) { - const i = ps.fileType.indexOf(type); + for (const type of ps.fileType!) { + const i = ps.fileType!.indexOf(type); qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); } })); @@ -194,7 +194,7 @@ export default define(meta, async (ps, me) => { //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit!).getMany(); return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index 2c82d6613e..9e16e34e39 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -53,7 +53,7 @@ export default define(meta, async (ps, me) => { query.setParameters(followingQuery.getParameters()); - const users = await query.take(ps.limit).skip(ps.offset).getMany(); + const users = await query.take(ps.limit!).skip(ps.offset).getMany(); return await Users.packMany(users, me, { detail: true }); }); diff --git a/src/server/api/endpoints/users/search.ts b/src/server/api/endpoints/users/search.ts index 443bd507f3..96da221d97 100644 --- a/src/server/api/endpoints/users/search.ts +++ b/src/server/api/endpoints/users/search.ts @@ -71,16 +71,16 @@ export default define(meta, async (ps, me) => { .where('user.host IS NULL') .andWhere('user.isSuspended = FALSE') .andWhere('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) - .take(ps.limit) + .take(ps.limit!) .skip(ps.offset) .getMany(); - if (users.length < ps.limit && !ps.localOnly) { + if (users.length < ps.limit! && !ps.localOnly) { const otherUsers = await Users.createQueryBuilder('user') .where('user.host IS NOT NULL') .andWhere('user.isSuspended = FALSE') .andWhere('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) - .take(ps.limit - users.length) + .take(ps.limit! - users.length) .getMany(); users = users.concat(otherUsers); diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts index ae93e40eaa..2be193f89b 100644 --- a/src/server/api/endpoints/users/show.ts +++ b/src/server/api/endpoints/users/show.ts @@ -74,7 +74,7 @@ export default define(meta, async (ps, me) => { }))); } else { // Lookup user - if (typeof ps.host === 'string') { + if (typeof ps.host === 'string' && typeof ps.username === 'string') { user = await resolveUser(ps.username, ps.host).catch(e => { apiLogger.warn(`failed to resolve remote user: ${e}`); throw new ApiError(meta.errors.failedToResolveRemoteUser); @@ -82,7 +82,7 @@ export default define(meta, async (ps, me) => { } else { const q: any = ps.userId != null ? { id: ps.userId } - : { usernameLower: ps.username.toLowerCase(), host: null }; + : { usernameLower: ps.username!.toLowerCase(), host: null }; user = await Users.findOne(q); } @@ -94,7 +94,7 @@ export default define(meta, async (ps, me) => { // ユーザー情報更新 if (Users.isRemoteUser(user)) { if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { - resolveUser(ps.username, ps.host, { }, true); + resolveUser(user.username, user.host, { }, true); } } diff --git a/src/server/api/error.ts b/src/server/api/error.ts index ca441d5811..cb0bdc9f47 100644 --- a/src/server/api/error.ts +++ b/src/server/api/error.ts @@ -1,3 +1,5 @@ +type E = { message: string, code: string, id: string, kind?: 'client' | 'server', httpStatusCode?: number }; + export class ApiError extends Error { public message: string; public code: string; @@ -6,7 +8,7 @@ export class ApiError extends Error { public httpStatusCode?: number; public info?: any; - constructor(e?: { message: string, code: string, id: string, kind?: 'client' | 'server', httpStatusCode?: number }, info?: any) { + constructor(e?: E | null | undefined, info?: any | null | undefined) { if (e == null) e = { message: 'Internal error occurred. Please contact us if the error persists.', code: 'INTERNAL_ERROR', diff --git a/src/server/api/limiter.ts b/src/server/api/limiter.ts index e29c061337..8a6c94458e 100644 --- a/src/server/api/limiter.ts +++ b/src/server/api/limiter.ts @@ -14,7 +14,7 @@ export default (endpoint: IEndpoint, user: User) => new Promise((ok, reject) => return; } - const limitation = endpoint.meta.limit; + const limitation = endpoint.meta.limit!; const key = limitation.hasOwnProperty('key') ? limitation.key @@ -41,7 +41,7 @@ export default (endpoint: IEndpoint, user: User) => new Promise((ok, reject) => id: `${user.id}:${key}:min`, duration: limitation.minInterval, max: 1, - db: limiterDB + db: limiterDB! }); minIntervalLimiter.get((err, info) => { @@ -69,7 +69,7 @@ export default (endpoint: IEndpoint, user: User) => new Promise((ok, reject) => id: `${user.id}:${key}`, duration: limitation.duration, max: limitation.max, - db: limiterDB + db: limiterDB! }); limiter.get((err, info) => { diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts index fe2e5577c2..676546f2aa 100644 --- a/src/server/api/private/signin.ts +++ b/src/server/api/private/signin.ts @@ -7,6 +7,7 @@ import config from '../../../config'; import { Users, Signins, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; import { genId } from '../../../misc/gen-id'; +import { ensure } from '../../../prelude/ensure'; export default async (ctx: Koa.BaseContext) => { ctx.set('Access-Control-Allow-Origin', config.url); @@ -45,10 +46,10 @@ export default async (ctx: Koa.BaseContext) => { return; } - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // Compare password - const same = await bcrypt.compare(password, profile.password); + const same = await bcrypt.compare(password, profile.password!); if (same) { if (profile.twoFactorEnabled) { diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 03d83efd94..ea4df060f8 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -21,7 +21,7 @@ export default async (ctx: Koa.BaseContext) => { // Verify recaptcha // ただしテスト時はこの機構は障害となるため無効にする - if (process.env.NODE_ENV !== 'test' && instance.enableRecaptcha) { + if (process.env.NODE_ENV !== 'test' && instance.enableRecaptcha && instance.recaptchaSecretKey) { recaptcha.init({ secret_key: instance.recaptchaSecretKey }); @@ -100,7 +100,7 @@ export default async (ctx: Koa.BaseContext) => { e ? j(e) : s([publicKey, privateKey]) )); - let account: User; + let account!: User; // Start transaction await getConnection().transaction(async transactionalEntityManager => { diff --git a/src/server/api/service/discord.ts b/src/server/api/service/discord.ts index 879b8b4849..b2561dee61 100644 --- a/src/server/api/service/discord.ts +++ b/src/server/api/service/discord.ts @@ -10,6 +10,7 @@ import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; import { Users, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; +import { ensure } from '../../../prelude/ensure'; function getUserToken(ctx: Koa.BaseContext) { return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1]; @@ -43,7 +44,7 @@ router.get('/disconnect/discord', async ctx => { const user = await Users.findOne({ host: null, token: userToken - }); + }).then(ensure); await UserProfiles.update({ userId: user.id @@ -71,8 +72,8 @@ async function getOAuth2() { if (meta.enableDiscordIntegration) { return new OAuth2( - meta.discordClientId, - meta.discordClientSecret, + meta.discordClientId!, + meta.discordClientSecret!, 'https://discordapp.com/', 'api/oauth2/authorize', 'api/oauth2/token'); @@ -82,6 +83,8 @@ async function getOAuth2() { } router.get('/connect/discord', async ctx => { + if (redis == null) return; + if (!compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; @@ -103,10 +106,12 @@ router.get('/connect/discord', async ctx => { redis.set(userToken, JSON.stringify(params)); const oauth2 = await getOAuth2(); - ctx.redirect(oauth2.getAuthorizeUrl(params)); + ctx.redirect(oauth2!.getAuthorizeUrl(params)); }); router.get('/signin/discord', async ctx => { + if (redis == null) return; + const sessid = uuid(); const params = { @@ -129,10 +134,12 @@ router.get('/signin/discord', async ctx => { redis.set(sessid, JSON.stringify(params)); const oauth2 = await getOAuth2(); - ctx.redirect(oauth2.getAuthorizeUrl(params)); + ctx.redirect(oauth2!.getAuthorizeUrl(params)); }); router.get('/dc/cb', async ctx => { + if (redis == null) return; + const userToken = getUserToken(ctx); const oauth2 = await getOAuth2(); @@ -153,7 +160,7 @@ router.get('/dc/cb', async ctx => { } const { redirect_uri, state } = await new Promise((res, rej) => { - redis.get(sessid, async (_, state) => { + redis!.get(sessid, async (_, state) => { res(JSON.parse(state)); }); }); @@ -164,24 +171,22 @@ router.get('/dc/cb', async ctx => { } const { accessToken, refreshToken, expiresDate } = await new Promise((res, rej) => - oauth2.getOAuthAccessToken( - code, - { - grant_type: 'authorization_code', - redirect_uri - }, - (err, accessToken, refreshToken, result) => { - if (err) - rej(err); - else if (result.error) - rej(result.error); - else + oauth2!.getOAuthAccessToken(code, { + grant_type: 'authorization_code', + redirect_uri + }, (err, accessToken, refreshToken, result) => { + if (err) { + rej(err); + } else if (result.error) { + rej(result.error); + } else { res({ accessToken, refreshToken, expiresDate: Date.now() + Number(result.expires_in) * 1000 }); - })); + } + })); const { id, username, discriminator } = await new Promise((res, rej) => request({ @@ -191,10 +196,11 @@ router.get('/dc/cb', async ctx => { 'User-Agent': config.userAgent } }, (err, response, body) => { - if (err) + if (err) { rej(err); - else + } else { res(JSON.parse(body)); + } })); if (!id || !username || !discriminator) { @@ -235,7 +241,7 @@ router.get('/dc/cb', async ctx => { } const { redirect_uri, state } = await new Promise((res, rej) => { - redis.get(userToken, async (_, state) => { + redis!.get(userToken, async (_, state) => { res(JSON.parse(state)); }); }); @@ -246,24 +252,22 @@ router.get('/dc/cb', async ctx => { } const { accessToken, refreshToken, expiresDate } = await new Promise((res, rej) => - oauth2.getOAuthAccessToken( - code, - { - grant_type: 'authorization_code', - redirect_uri - }, - (err, accessToken, refreshToken, result) => { - if (err) - rej(err); - else if (result.error) - rej(result.error); - else - res({ - accessToken, - refreshToken, - expiresDate: Date.now() + Number(result.expires_in) * 1000 - }); - })); + oauth2!.getOAuthAccessToken(code, { + grant_type: 'authorization_code', + redirect_uri + }, (err, accessToken, refreshToken, result) => { + if (err) { + rej(err); + } else if (result.error) { + rej(result.error); + } else { + res({ + accessToken, + refreshToken, + expiresDate: Date.now() + Number(result.expires_in) * 1000 + }); + } + })); const { id, username, discriminator } = await new Promise((res, rej) => request({ @@ -273,10 +277,11 @@ router.get('/dc/cb', async ctx => { 'User-Agent': config.userAgent } }, (err, response, body) => { - if (err) + if (err) { rej(err); - else + } else { res(JSON.parse(body)); + } })); if (!id || !username || !discriminator) { @@ -287,7 +292,7 @@ router.get('/dc/cb', async ctx => { const user = await Users.findOne({ host: null, token: userToken - }); + }).then(ensure); await UserProfiles.update({ userId: user.id }, { discord: true, diff --git a/src/server/api/service/github.ts b/src/server/api/service/github.ts index 580947811b..4878d8c529 100644 --- a/src/server/api/service/github.ts +++ b/src/server/api/service/github.ts @@ -10,6 +10,7 @@ import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; import { Users, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; +import { ensure } from '../../../prelude/ensure'; function getUserToken(ctx: Koa.BaseContext) { return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1]; @@ -43,7 +44,7 @@ router.get('/disconnect/github', async ctx => { const user = await Users.findOne({ host: null, token: userToken - }); + }).then(ensure); await UserProfiles.update({ userId: user.id @@ -66,7 +67,7 @@ router.get('/disconnect/github', async ctx => { async function getOath2() { const meta = await fetchMeta(); - if (meta.enableGithubIntegration) { + if (meta.enableGithubIntegration && meta.githubClientId && meta.githubClientSecret) { return new OAuth2( meta.githubClientId, meta.githubClientSecret, @@ -79,6 +80,8 @@ async function getOath2() { } router.get('/connect/github', async ctx => { + if (redis == null) return; + if (!compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; @@ -99,10 +102,12 @@ router.get('/connect/github', async ctx => { redis.set(userToken, JSON.stringify(params)); const oauth2 = await getOath2(); - ctx.redirect(oauth2.getAuthorizeUrl(params)); + ctx.redirect(oauth2!.getAuthorizeUrl(params)); }); router.get('/signin/github', async ctx => { + if (redis == null) return; + const sessid = uuid(); const params = { @@ -124,10 +129,12 @@ router.get('/signin/github', async ctx => { redis.set(sessid, JSON.stringify(params)); const oauth2 = await getOath2(); - ctx.redirect(oauth2.getAuthorizeUrl(params)); + ctx.redirect(oauth2!.getAuthorizeUrl(params)); }); router.get('/gh/cb', async ctx => { + if (redis == null) return; + const userToken = getUserToken(ctx); const oauth2 = await getOath2(); @@ -148,7 +155,7 @@ router.get('/gh/cb', async ctx => { } const { redirect_uri, state } = await new Promise((res, rej) => { - redis.get(sessid, async (_, state) => { + redis!.get(sessid, async (_, state) => { res(JSON.parse(state)); }); }); @@ -159,17 +166,17 @@ router.get('/gh/cb', async ctx => { } const { accessToken } = await new Promise((res, rej) => - oauth2.getOAuthAccessToken( - code, - { redirect_uri }, - (err, accessToken, refresh, result) => { - if (err) - rej(err); - else if (result.error) - rej(result.error); - else - res({ accessToken }); - })); + oauth2!.getOAuthAccessToken(code, { + redirect_uri + }, (err, accessToken, refresh, result) => { + if (err) { + rej(err); + } else if (result.error) { + rej(result.error); + } else { + res({ accessToken }); + } + })); const { login, id } = await new Promise((res, rej) => request({ @@ -215,7 +222,7 @@ router.get('/gh/cb', async ctx => { } const { redirect_uri, state } = await new Promise((res, rej) => { - redis.get(userToken, async (_, state) => { + redis!.get(userToken, async (_, state) => { res(JSON.parse(state)); }); }); @@ -226,7 +233,7 @@ router.get('/gh/cb', async ctx => { } const { accessToken } = await new Promise((res, rej) => - oauth2.getOAuthAccessToken( + oauth2!.getOAuthAccessToken( code, { redirect_uri }, (err, accessToken, refresh, result) => { @@ -261,7 +268,7 @@ router.get('/gh/cb', async ctx => { const user = await Users.findOne({ host: null, token: userToken - }); + }).then(ensure); await UserProfiles.update({ userId: user.id }, { github: true, diff --git a/src/server/api/service/twitter.ts b/src/server/api/service/twitter.ts index c0c762c6c3..b35ee8c479 100644 --- a/src/server/api/service/twitter.ts +++ b/src/server/api/service/twitter.ts @@ -9,6 +9,7 @@ import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; import { Users, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; +import { ensure } from '../../../prelude/ensure'; function getUserToken(ctx: Koa.BaseContext) { return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1]; @@ -42,7 +43,7 @@ router.get('/disconnect/twitter', async ctx => { const user = await Users.findOne({ host: null, token: userToken - }); + }).then(ensure); await UserProfiles.update({ userId: user.id @@ -66,7 +67,7 @@ router.get('/disconnect/twitter', async ctx => { async function getTwAuth() { const meta = await fetchMeta(); - if (meta.enableTwitterIntegration) { + if (meta.enableTwitterIntegration && meta.twitterConsumerKey && meta.twitterConsumerSecret) { return autwh({ consumerKey: meta.twitterConsumerKey, consumerSecret: meta.twitterConsumerSecret, @@ -78,6 +79,8 @@ async function getTwAuth() { } router.get('/connect/twitter', async ctx => { + if (redis == null) return; + if (!compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; @@ -90,14 +93,16 @@ router.get('/connect/twitter', async ctx => { } const twAuth = await getTwAuth(); - const twCtx = await twAuth.begin(); + const twCtx = await twAuth!.begin(); redis.set(userToken, JSON.stringify(twCtx)); ctx.redirect(twCtx.url); }); router.get('/signin/twitter', async ctx => { + if (redis == null) return; + const twAuth = await getTwAuth(); - const twCtx = await twAuth.begin(); + const twCtx = await twAuth!.begin(); const sessid = uuid(); @@ -117,6 +122,8 @@ router.get('/signin/twitter', async ctx => { }); router.get('/tw/cb', async ctx => { + if (redis == null) return; + const userToken = getUserToken(ctx); const twAuth = await getTwAuth(); @@ -130,14 +137,14 @@ router.get('/tw/cb', async ctx => { } const get = new Promise((res, rej) => { - redis.get(sessid, async (_, twCtx) => { + redis!.get(sessid, async (_, twCtx) => { res(twCtx); }); }); const twCtx = await get; - const result = await twAuth.done(JSON.parse(twCtx), ctx.query.oauth_verifier); + const result = await twAuth!.done(JSON.parse(twCtx), ctx.query.oauth_verifier); const link = await UserProfiles.createQueryBuilder() .where('twitter @> :twitter', { @@ -163,19 +170,19 @@ router.get('/tw/cb', async ctx => { } const get = new Promise((res, rej) => { - redis.get(userToken, async (_, twCtx) => { + redis!.get(userToken, async (_, twCtx) => { res(twCtx); }); }); const twCtx = await get; - const result = await twAuth.done(JSON.parse(twCtx), verifier); + const result = await twAuth!.done(JSON.parse(twCtx), verifier); const user = await Users.findOne({ host: null, token: userToken - }); + }).then(ensure); await UserProfiles.update({ userId: user.id }, { twitter: true, diff --git a/src/server/api/stream/channels/admin.ts b/src/server/api/stream/channels/admin.ts index e2eba10f78..1ff932d1dd 100644 --- a/src/server/api/stream/channels/admin.ts +++ b/src/server/api/stream/channels/admin.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe admin stream - this.subscriber.on(`adminStream:${this.user.id}`, data => { + this.subscriber.on(`adminStream:${this.user!.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/drive.ts b/src/server/api/stream/channels/drive.ts index 671aad4366..4112dd9b04 100644 --- a/src/server/api/stream/channels/drive.ts +++ b/src/server/api/stream/channels/drive.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe drive stream - this.subscriber.on(`driveStream:${this.user.id}`, data => { + this.subscriber.on(`driveStream:${this.user!.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/games/reversi-game.ts b/src/server/api/stream/channels/games/reversi-game.ts index 158f108c4e..d708eae9f7 100644 --- a/src/server/api/stream/channels/games/reversi-game.ts +++ b/src/server/api/stream/channels/games/reversi-game.ts @@ -12,7 +12,7 @@ export default class extends Channel { public static shouldShare = false; public static requireCredential = false; - private gameId: ReversiGame['id']; + private gameId: ReversiGame['id'] | null = null; @autobind public async init(params: any) { @@ -40,7 +40,10 @@ export default class extends Channel { @autobind private async updateSettings(key: string, value: any) { - const game = await ReversiGames.findOne(this.gameId); + if (this.user == null) return; + + const game = await ReversiGames.findOne(this.gameId!); + if (game == null) throw 'game not found'; if (game.isStarted) return; if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; @@ -49,11 +52,11 @@ export default class extends Channel { if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard'].includes(key)) return; - await ReversiGames.update({ id: this.gameId }, { + await ReversiGames.update(this.gameId!, { [key]: value }); - publishReversiGameStream(this.gameId, 'updateSettings', { + publishReversiGameStream(this.gameId!, 'updateSettings', { key: key, value: value }); @@ -61,7 +64,10 @@ export default class extends Channel { @autobind private async initForm(form: any) { - const game = await ReversiGames.findOne(this.gameId); + if (this.user == null) return; + + const game = await ReversiGames.findOne(this.gameId!); + if (game == null) throw 'game not found'; if (game.isStarted) return; if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; @@ -72,9 +78,9 @@ export default class extends Channel { form2: form }; - await ReversiGames.update({ id: this.gameId }, set); + await ReversiGames.update(this.gameId!, set); - publishReversiGameStream(this.gameId, 'initForm', { + publishReversiGameStream(this.gameId!, 'initForm', { userId: this.user.id, form }); @@ -82,7 +88,10 @@ export default class extends Channel { @autobind private async updateForm(id: string, value: any) { - const game = await ReversiGames.findOne({ id: this.gameId }); + if (this.user == null) return; + + const game = await ReversiGames.findOne(this.gameId!); + if (game == null) throw 'game not found'; if (game.isStarted) return; if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; @@ -101,9 +110,9 @@ export default class extends Channel { form1: form }; - await ReversiGames.update({ id: this.gameId }, set); + await ReversiGames.update(this.gameId!, set); - publishReversiGameStream(this.gameId, 'updateForm', { + publishReversiGameStream(this.gameId!, 'updateForm', { userId: this.user.id, id, value @@ -112,8 +121,10 @@ export default class extends Channel { @autobind private async message(message: any) { + if (this.user == null) return; + message.id = Math.random(); - publishReversiGameStream(this.gameId, 'message', { + publishReversiGameStream(this.gameId!, 'message', { userId: this.user.id, message }); @@ -121,29 +132,32 @@ export default class extends Channel { @autobind private async accept(accept: boolean) { - const game = await ReversiGames.findOne(this.gameId); + if (this.user == null) return; + + const game = await ReversiGames.findOne(this.gameId!); + if (game == null) throw 'game not found'; if (game.isStarted) return; let bothAccepted = false; if (game.user1Id === this.user.id) { - await ReversiGames.update({ id: this.gameId }, { + await ReversiGames.update(this.gameId!, { user1Accepted: accept }); - publishReversiGameStream(this.gameId, 'changeAccepts', { + publishReversiGameStream(this.gameId!, 'changeAccepts', { user1: accept, user2: game.user2Accepted }); if (accept && game.user2Accepted) bothAccepted = true; } else if (game.user2Id === this.user.id) { - await ReversiGames.update({ id: this.gameId }, { + await ReversiGames.update(this.gameId!, { user2Accepted: accept }); - publishReversiGameStream(this.gameId, 'changeAccepts', { + publishReversiGameStream(this.gameId!, 'changeAccepts', { user1: game.user1Accepted, user2: accept }); @@ -156,7 +170,7 @@ export default class extends Channel { if (bothAccepted) { // 3秒後、まだacceptされていたらゲーム開始 setTimeout(async () => { - const freshGame = await ReversiGames.findOne(this.gameId); + const freshGame = await ReversiGames.findOne(this.gameId!); if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return; if (!freshGame.user1Accepted || !freshGame.user2Accepted) return; @@ -175,7 +189,7 @@ export default class extends Channel { const map = freshGame.map != null ? freshGame.map : getRandomMap(); - await ReversiGames.update({ id: this.gameId }, { + await ReversiGames.update(this.gameId!, { startedAt: new Date(), isStarted: true, black: bw, @@ -199,22 +213,20 @@ export default class extends Channel { winner = null; } - await ReversiGames.update({ - id: this.gameId - }, { + await ReversiGames.update(this.gameId!, { isEnded: true, winnerId: winner }); - publishReversiGameStream(this.gameId, 'ended', { + publishReversiGameStream(this.gameId!, 'ended', { winnerId: winner, - game: await ReversiGames.pack(this.gameId, this.user) + game: await ReversiGames.pack(this.gameId!, this.user) }); } //#endregion - publishReversiGameStream(this.gameId, 'started', - await ReversiGames.pack(this.gameId, this.user)); + publishReversiGameStream(this.gameId!, 'started', + await ReversiGames.pack(this.gameId!, this.user)); }, 3000); } } @@ -222,7 +234,10 @@ export default class extends Channel { // 石を打つ @autobind private async set(pos: number) { - const game = await ReversiGames.findOne(this.gameId); + if (this.user == null) return; + + const game = await ReversiGames.findOne(this.gameId!); + if (game == null) throw 'game not found'; if (!game.isStarted) return; if (game.isEnded) return; @@ -267,30 +282,29 @@ export default class extends Channel { game.logs.push(log); - await ReversiGames.update({ - id: this.gameId - }, { + await ReversiGames.update(this.gameId!, { crc32, isEnded: o.isEnded, winnerId: winner, logs: game.logs }); - publishReversiGameStream(this.gameId, 'set', Object.assign(log, { + publishReversiGameStream(this.gameId!, 'set', Object.assign(log, { next: o.turn })); if (o.isEnded) { - publishReversiGameStream(this.gameId, 'ended', { + publishReversiGameStream(this.gameId!, 'ended', { winnerId: winner, - game: await ReversiGames.pack(this.gameId, this.user) + game: await ReversiGames.pack(this.gameId!, this.user) }); } } @autobind private async check(crc32: string) { - const game = await ReversiGames.findOne(this.gameId); + const game = await ReversiGames.findOne(this.gameId!); + if (game == null) throw 'game not found'; if (!game.isStarted) return; diff --git a/src/server/api/stream/channels/games/reversi.ts b/src/server/api/stream/channels/games/reversi.ts index 0498e5e017..3db338386a 100644 --- a/src/server/api/stream/channels/games/reversi.ts +++ b/src/server/api/stream/channels/games/reversi.ts @@ -11,7 +11,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe reversi stream - this.subscriber.on(`reversiStream:${this.user.id}`, data => { + this.subscriber.on(`reversiStream:${this.user!.id}`, data => { this.send(data); }); } @@ -22,7 +22,7 @@ export default class extends Channel { case 'ping': if (body.id == null) return; const matching = await ReversiMatchings.findOne({ - parentId: this.user.id, + parentId: this.user!.id, childId: body.id }); if (matching == null) return; diff --git a/src/server/api/stream/channels/home-timeline.ts b/src/server/api/stream/channels/home-timeline.ts index 2cece0947f..61960657b4 100644 --- a/src/server/api/stream/channels/home-timeline.ts +++ b/src/server/api/stream/channels/home-timeline.ts @@ -17,10 +17,10 @@ export default class extends Channel { @autobind private async onNote(note: any) { // その投稿のユーザーをフォローしていなかったら弾く - if (this.user.id !== note.userId && !this.following.includes(note.userId)) return; + if (this.user!.id !== note.userId && !this.following.includes(note.userId)) return; if (['followers', 'specified'].includes(note.visibility)) { - note = await Notes.pack(note.id, this.user, { + note = await Notes.pack(note.id, this.user!, { detail: true }); @@ -30,13 +30,13 @@ export default class extends Channel { } else { // リプライなら再pack if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user, { + note.reply = await Notes.pack(note.replyId, this.user!, { detail: true }); } // Renoteなら再pack if (note.renoteId != null) { - note.renote = await Notes.pack(note.renoteId, this.user, { + note.renote = await Notes.pack(note.renoteId, this.user!, { detail: true }); } diff --git a/src/server/api/stream/channels/hybrid-timeline.ts b/src/server/api/stream/channels/hybrid-timeline.ts index 30643aeda8..18e6aa8350 100644 --- a/src/server/api/stream/channels/hybrid-timeline.ts +++ b/src/server/api/stream/channels/hybrid-timeline.ts @@ -12,7 +12,7 @@ export default class extends Channel { @autobind public async init(params: any) { const meta = await fetchMeta(); - if (meta.disableLocalTimeline && !this.user.isAdmin && !this.user.isModerator) return; + if (meta.disableLocalTimeline && !this.user!.isAdmin && !this.user!.isModerator) return; // Subscribe events this.subscriber.on('notesStream', this.onNote); @@ -22,13 +22,13 @@ export default class extends Channel { private async onNote(note: any) { // 自分自身の投稿 または その投稿のユーザーをフォローしている または ローカルの投稿 の場合だけ if (!( - this.user.id === note.userId || + this.user!.id === note.userId || this.following.includes(note.userId) || note.user.host == null )) return; if (['followers', 'specified'].includes(note.visibility)) { - note = await Notes.pack(note.id, this.user, { + note = await Notes.pack(note.id, this.user!, { detail: true }); @@ -38,13 +38,13 @@ export default class extends Channel { } else { // リプライなら再pack if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user, { + note.reply = await Notes.pack(note.replyId, this.user!, { detail: true }); } // Renoteなら再pack if (note.renoteId != null) { - note.renote = await Notes.pack(note.renoteId, this.user, { + note.renote = await Notes.pack(note.renoteId, this.user!, { detail: true }); } diff --git a/src/server/api/stream/channels/main.ts b/src/server/api/stream/channels/main.ts index 0d9bf3149d..1100f87acb 100644 --- a/src/server/api/stream/channels/main.ts +++ b/src/server/api/stream/channels/main.ts @@ -9,10 +9,10 @@ export default class extends Channel { @autobind public async init(params: any) { - const mute = await Mutings.find({ muterId: this.user.id }); + const mute = await Mutings.find({ muterId: this.user!.id }); // Subscribe main stream channel - this.subscriber.on(`mainStream:${this.user.id}`, async data => { + this.subscriber.on(`mainStream:${this.user!.id}`, async data => { const { type, body } = data; switch (type) { diff --git a/src/server/api/stream/channels/messaging-index.ts b/src/server/api/stream/channels/messaging-index.ts index 648badc1dc..0c495398ab 100644 --- a/src/server/api/stream/channels/messaging-index.ts +++ b/src/server/api/stream/channels/messaging-index.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe messaging index stream - this.subscriber.on(`messagingIndexStream:${this.user.id}`, data => { + this.subscriber.on(`messagingIndexStream:${this.user!.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/messaging.ts b/src/server/api/stream/channels/messaging.ts index b81fbb9d4c..8397f849ff 100644 --- a/src/server/api/stream/channels/messaging.ts +++ b/src/server/api/stream/channels/messaging.ts @@ -14,7 +14,7 @@ export default class extends Channel { this.otherpartyId = params.otherparty as string; // Subscribe messaging stream - this.subscriber.on(`messagingStream:${this.user.id}-${this.otherpartyId}`, data => { + this.subscriber.on(`messagingStream:${this.user!.id}-${this.otherpartyId}`, data => { this.send(data); }); } @@ -23,7 +23,7 @@ export default class extends Channel { public onMessage(type: string, body: any) { switch (type) { case 'read': - read(this.user.id, this.otherpartyId, body.id); + read(this.user!.id, this.otherpartyId, body.id); break; } } diff --git a/src/server/api/stream/index.ts b/src/server/api/stream/index.ts index abbd91ec81..f73f3229d5 100644 --- a/src/server/api/stream/index.ts +++ b/src/server/api/stream/index.ts @@ -28,13 +28,13 @@ export default class Connection { constructor( wsConnection: websocket.connection, subscriber: EventEmitter, - user: User, - app: App + user: User | null | undefined, + app: App | null | undefined ) { this.wsConnection = wsConnection; - this.user = user; - this.app = app; this.subscriber = subscriber; + if (user) this.user = user; + if (app) this.app = app; this.wsConnection.on('message', this.onWsConnectionMessage); @@ -52,6 +52,8 @@ export default class Connection { */ @autobind private async onWsConnectionMessage(data: websocket.IMessage) { + if (data.utf8Data == null) return; + const { type, body } = JSON.parse(data.utf8Data); switch (type) { @@ -89,7 +91,7 @@ export default class Connection { @autobind private onReadNotification(payload: any) { if (!payload.id) return; - readNotification(this.user.id, [payload.id]); + readNotification(this.user!.id, [payload.id]); } /** @@ -109,7 +111,7 @@ export default class Connection { this.subscriber.on(`noteStream:${payload.id}`, this.onNoteStreamMessage); } - if (payload.read) { + if (payload.read && this.user) { readNote(this.user.id, payload.id); } } @@ -221,7 +223,7 @@ export default class Connection { private async updateFollowing() { const followings = await Followings.find({ where: { - followerId: this.user.id + followerId: this.user!.id }, select: ['followeeId'] }); @@ -233,7 +235,7 @@ export default class Connection { private async updateMuting() { const mutings = await Mutings.find({ where: { - muterId: this.user.id + muterId: this.user!.id }, select: ['muteeId'] }); @@ -247,7 +249,7 @@ export default class Connection { @autobind public dispose() { for (const c of this.channels.filter(c => c.dispose)) { - c.dispose(); + if (c.dispose) c.dispose(); } if (this.followingClock) clearInterval(this.followingClock); diff --git a/src/server/file/send-drive-file.ts b/src/server/file/send-drive-file.ts index f9b067b79c..5da3d79eb5 100644 --- a/src/server/file/send-drive-file.ts +++ b/src/server/file/send-drive-file.ts @@ -50,7 +50,7 @@ export default async function(ctx: Koa.BaseContext) { ctx.set('Content-Disposition', contentDisposition('attachment', `${file.name}`)); } - const readable = InternalStorage.read(file.accessKey); + const readable = InternalStorage.read(file.accessKey!); readable.on('error', commonReadableHandlerGenerator(ctx)); ctx.set('Content-Type', file.type); ctx.body = readable; diff --git a/src/server/index.ts b/src/server/index.ts index 9c153f0167..601e288f3b 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -32,7 +32,7 @@ export const serverLogger = new Logger('server', 'gray', false); const app = new Koa(); app.proxy = true; -if (!['production', 'test'].includes(process.env.NODE_ENV)) { +if (!['production', 'test'].includes(process.env.NODE_ENV || '')) { // Logger app.use(koaLogger(str => { serverLogger.info(str); diff --git a/src/server/web/docs.ts b/src/server/web/docs.ts index d9ba14a8ed..374dbf3bd2 100644 --- a/src/server/web/docs.ts +++ b/src/server/web/docs.ts @@ -34,14 +34,14 @@ async function genVars(lang: string): Promise<{ [key: string]: any }> { const docs = glob.sync(`src/docs/**/*.${lang}.md`, { cwd }); vars['docs'] = {}; for (const x of docs) { - const [, name] = x.match(/docs\/(.+?)\.(.+?)\.md$/); + const [, name] = x.match(/docs\/(.+?)\.(.+?)\.md$/)!; if (vars['docs'][name] == null) { vars['docs'][name] = { name, title: {} }; } - vars['docs'][name]['title'][lang] = fs.readFileSync(cwd + x, 'utf-8').match(/^# (.+?)\r?\n/)[1]; + vars['docs'][name]['title'][lang] = fs.readFileSync(cwd + x, 'utf-8').match(/^# (.+?)\r?\n/)![1]; } vars['kebab'] = (string: string) => string.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase(); @@ -97,7 +97,7 @@ router.get('/*/*', async ctx => { await ctx.render('../../../../src/docs/article', Object.assign({ id: doc, html: conv.makeHtml(md), - title: md.match(/^# (.+?)\r?\n/)[1], + title: md.match(/^# (.+?)\r?\n/)![1], src: `https://github.com/syuilo/misskey/tree/master/src/docs/${doc}.${lang}.md` }, await genVars(lang))); diff --git a/src/server/web/feed.ts b/src/server/web/feed.ts index 94f0643f75..285c20fcdb 100644 --- a/src/server/web/feed.ts +++ b/src/server/web/feed.ts @@ -3,6 +3,7 @@ import config from '../../config'; import { User } from '../../models/entities/user'; import { Notes, DriveFiles, UserProfiles } from '../../models'; import { In } from 'typeorm'; +import { ensure } from '../../prelude/ensure'; export default async function(user: User) { const author: Author = { @@ -10,7 +11,7 @@ export default async function(user: User) { name: user.name || user.username }; - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); const notes = await Notes.find({ where: { @@ -47,9 +48,9 @@ export default async function(user: User) { title: `New note by ${author.name}`, link: `${config.url}/notes/${note.id}`, date: note.createdAt, - description: note.cw, - content: note.text, - image: file ? DriveFiles.getPublicUrl(file) : null + description: note.cw || undefined, + content: note.text || undefined, + image: file ? DriveFiles.getPublicUrl(file) : undefined }); } diff --git a/src/server/web/index.ts b/src/server/web/index.ts index d1a15e3929..5cadf1b124 100644 --- a/src/server/web/index.ts +++ b/src/server/web/index.ts @@ -19,6 +19,7 @@ import config from '../../config'; import { Users, Notes, Emojis, UserProfiles } from '../../models'; import parseAcct from '../../misc/acct/parse'; import getNoteSummary from '../../misc/get-note-summary'; +import { ensure } from '../../prelude/ensure'; const client = `${__dirname}/../../client/`; @@ -149,11 +150,9 @@ router.get('/@:user', async (ctx, next) => { usernameLower: username.toLowerCase(), host }); - const profile = await UserProfiles.findOne({ - userId: user.id - }); if (user != null) { + const profile = await UserProfiles.findOne(user.id).then(ensure); const meta = await fetchMeta(); await ctx.render('user', { user, profile, diff --git a/src/server/web/url-preview.ts b/src/server/web/url-preview.ts index aed475e6ff..7d0080b4d2 100644 --- a/src/server/web/url-preview.ts +++ b/src/server/web/url-preview.ts @@ -43,7 +43,7 @@ module.exports = async (ctx: Koa.BaseContext) => { } }; -function wrap(url: string): string { +function wrap(url?: string): string | null { return url != null ? url.match(/^https?:\/\//) ? `${config.url}/proxy/preview.jpg?${query({ diff --git a/src/server/well-known.ts b/src/server/well-known.ts index 7c5684d2ce..d29b7a8888 100644 --- a/src/server/well-known.ts +++ b/src/server/well-known.ts @@ -54,9 +54,9 @@ router.get(webFingerPath, async ctx => { const generateQuery = (resource: string) => resource.startsWith(`${config.url.toLowerCase()}/users/`) ? - fromId(resource.split('/').pop()) : + fromId(resource.split('/').pop()!) : fromAcct(parseAcct( - resource.startsWith(`${config.url.toLowerCase()}/@`) ? resource.split('/').pop() : + resource.startsWith(`${config.url.toLowerCase()}/@`) ? resource.split('/').pop()! : resource.startsWith('acct:') ? resource.slice('acct:'.length) : resource)); diff --git a/src/services/blocking/create.ts b/src/services/blocking/create.ts index 79ca0d59f1..def4f33585 100644 --- a/src/services/blocking/create.ts +++ b/src/services/blocking/create.ts @@ -66,7 +66,7 @@ async function cancelRequest(follower: User, followee: User) { // リモートからフォローリクエストを受けていたらReject送信 if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) { - const content = renderActivity(renderReject(renderFollow(follower, followee, request.requestId), followee)); + const content = renderActivity(renderReject(renderFollow(follower, followee, request.requestId!), followee)); deliver(followee, content, follower.inbox); } } diff --git a/src/services/chart/core.ts b/src/services/chart/core.ts index 2a60b1a0a3..0a9ec8dae0 100644 --- a/src/services/chart/core.ts +++ b/src/services/chart/core.ts @@ -84,7 +84,7 @@ export default abstract class Chart> { } } }; - flatColumns(schema.properties); + flatColumns(schema.properties!); return columns; } @@ -182,7 +182,7 @@ export default abstract class Chart> { } @autobind - private getNewLog(latest?: T): T { + private getNewLog(latest: T | null): T { const log = latest ? this.genNewLog(latest) : {}; const flatColumns = (x: Obj, path?: string) => { for (const [k, v] of Object.entries(x)) { @@ -196,7 +196,7 @@ export default abstract class Chart> { } } }; - flatColumns(this.schema.properties); + flatColumns(this.schema.properties!); return log as T; } @@ -213,7 +213,7 @@ export default abstract class Chart> { } @autobind - private getLatestLog(span: Span, group: string = null): Promise { + private getLatestLog(span: Span, group: string | null = null): Promise { return this.repository.findOne({ group: group, span: span @@ -221,17 +221,17 @@ export default abstract class Chart> { order: { date: -1 } - }); + }).then(x => x || null); } @autobind - private async getCurrentLog(span: Span, group: string = null): Promise { + private async getCurrentLog(span: Span, group: string | null = null): Promise { const [y, m, d, h] = this.getCurrentDate(); const current = span == 'day' ? utc([y, m, d]) : span == 'hour' ? utc([y, m, d, h]) : - null; + null as never; // 現在(今日または今のHour)のログ const currentLog = await this.repository.findOne({ @@ -285,7 +285,7 @@ export default abstract class Chart> { // 並列動作している他のチャートエンジンプロセスと処理が重なる場合がある // その場合は再度最も新しいログを持ってくる if (isDuplicateKeyValueError(e)) { - log = await this.getLatestLog(span, group); + log = await this.getLatestLog(span, group) as Log; } else { logger.error(e); throw e; @@ -296,17 +296,17 @@ export default abstract class Chart> { } @autobind - protected commit(query: Record, group: string = null, uniqueKey?: string, uniqueValue?: string): Promise { + protected commit(query: Record, group: string | null = null, uniqueKey?: string, uniqueValue?: string): Promise { const update = async (log: Log) => { // ユニークインクリメントの場合、指定のキーに指定の値が既に存在していたら弾く if ( - uniqueKey && + uniqueKey && log.unique && log.unique[uniqueKey] && log.unique[uniqueKey].includes(uniqueValue) ) return; // ユニークインクリメントの指定のキーに値を追加 - if (uniqueKey) { + if (uniqueKey && log.unique) { if (log.unique[uniqueKey]) { const sql = `jsonb_set("unique", '{${uniqueKey}}', ("unique"->>'${uniqueKey}')::jsonb || '["${uniqueValue}"]'::jsonb)`; query['unique'] = () => sql; @@ -331,23 +331,23 @@ export default abstract class Chart> { } @autobind - protected async inc(inc: DeepPartial, group: string = null): Promise { + protected async inc(inc: DeepPartial, group: string | null = null): Promise { await this.commit(Chart.convertQuery(inc as any), group); } @autobind - protected async incIfUnique(inc: DeepPartial, key: string, value: string, group: string = null): Promise { + protected async incIfUnique(inc: DeepPartial, key: string, value: string, group: string | null = null): Promise { await this.commit(Chart.convertQuery(inc as any), group, key, value); } @autobind - public async getChart(span: Span, range: number, group: string = null): Promise> { + public async getChart(span: Span, range: number, group: string | null = null): Promise> { const [y, m, d, h] = this.getCurrentDate(); const gt = span == 'day' ? utc([y, m, d]).subtract(range, 'days') : span == 'hour' ? utc([y, m, d, h]).subtract(range, 'hours') : - null; + null as never; // ログ取得 let logs = await this.repository.find({ @@ -404,7 +404,7 @@ export default abstract class Chart> { const current = span == 'day' ? utc([y, m, d]).subtract(i, 'days') : span == 'hour' ? utc([y, m, d, h]).subtract(i, 'hours') : - null; + null as never; const log = logs.find(l => utc(l.date * 1000).isSame(current)); @@ -452,8 +452,8 @@ export function convertLog(logSchema: Schema): Schema { type: 'number' }; } else if (v.type === 'object') { - for (const k of Object.keys(v.properties)) { - v.properties[k] = convertLog(v.properties[k]); + for (const k of Object.keys(v.properties!)) { + v.properties![k] = convertLog(v.properties![k]); } } return v; diff --git a/src/services/create-notification.ts b/src/services/create-notification.ts index bcb8214c56..5bff8adfd4 100644 --- a/src/services/create-notification.ts +++ b/src/services/create-notification.ts @@ -46,6 +46,7 @@ export async function createNotification( // 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する setTimeout(async () => { const fresh = await Notifications.findOne(notification.id); + if (fresh == null) return; // 既に削除されているかもしれない if (!fresh.isRead) { //#region ただしミュートしているユーザーからの通知なら無視 const mutings = await Mutings.find({ diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index b83c3558d3..94b97fed61 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -55,10 +55,10 @@ async function save(file: DriveFile, path: string, name: string, type: string, h const url = `${ baseUrl }/${ key }`; // for alts - let webpublicKey: string = null; - let webpublicUrl: string = null; - let thumbnailKey: string = null; - let thumbnailUrl: string = null; + let webpublicKey: string | null = null; + let webpublicUrl: string | null = null; + let thumbnailKey: string | null = null; + let thumbnailUrl: string | null = null; //#endregion //#region Uploads @@ -106,8 +106,8 @@ async function save(file: DriveFile, path: string, name: string, type: string, h const url = InternalStorage.saveFromPath(accessKey, path); - let thumbnailUrl: string; - let webpublicUrl: string; + let thumbnailUrl: string | null = null; + let webpublicUrl: string | null = null; if (alts.thumbnail) { thumbnailUrl = InternalStorage.saveFromBuffer(thumbnailAccessKey, alts.thumbnail.data); @@ -143,7 +143,7 @@ async function save(file: DriveFile, path: string, name: string, type: string, h */ export async function generateAlts(path: string, type: string, generateWeb: boolean) { // #region webpublic - let webpublic: IImage; + let webpublic: IImage | null = null; if (generateWeb) { logger.info(`creating web image`); @@ -163,7 +163,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool // #endregion webpublic // #region thumbnail - let thumbnail: IImage; + let thumbnail: IImage | null = null; if (['image/jpeg', 'image/webp'].includes(type)) { thumbnail = await ConvertToJpeg(path, 498, 280); @@ -188,7 +188,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool * Upload to ObjectStorage */ async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { - const minio = new Minio.Client(config.drive.config); + const minio = new Minio.Client(config.drive!.config); const metadata = { 'Content-Type': type, @@ -197,7 +197,7 @@ async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, if (filename) metadata['Content-Disposition'] = contentDisposition('inline', filename); - await minio.putObject(config.drive.bucket, key, stream, null, metadata); + await minio.putObject(config.drive!.bucket!, key, stream, undefined, metadata); } async function deleteOldFile(user: IRemoteUser) { @@ -232,14 +232,14 @@ async function deleteOldFile(user: IRemoteUser) { export default async function( user: User, path: string, - name: string = null, - comment: string = null, + name: string | null = null, + comment: string | null = null, folderId: any = null, force: boolean = false, isLink: boolean = false, - url: string = null, - uri: string = null, - sensitive: boolean = null + url: string | null = null, + uri: string | null = null, + sensitive: boolean | null = null ): Promise { // Calc md5 hash const calcHash = new Promise((res, rej) => { @@ -300,7 +300,7 @@ export default async function( throw 'no-free-space'; } else { // (アバターまたはバナーを含まず)最も古いファイルを削除する - deleteOldFile(user); + deleteOldFile(user as IRemoteUser); } } } @@ -378,7 +378,7 @@ export default async function( file.comment = comment; file.properties = properties; file.isLink = isLink; - file.isSensitive = Users.isLocalUser(user) && profile.alwaysMarkNsfw ? true : + file.isSensitive = Users.isLocalUser(user) && profile!.alwaysMarkNsfw ? true : (sensitive !== null && sensitive !== undefined) ? sensitive : false; @@ -411,7 +411,7 @@ export default async function( file = await DriveFiles.findOne({ uri: file.uri, userId: user.id - }); + }) as DriveFile; } else { logger.error(e); throw e; diff --git a/src/services/drive/delete-file.ts b/src/services/drive/delete-file.ts index b7fa99391e..bba453b982 100644 --- a/src/services/drive/delete-file.ts +++ b/src/services/drive/delete-file.ts @@ -7,31 +7,31 @@ import { driveChart, perUserDriveChart, instanceChart } from '../chart'; export default async function(file: DriveFile, isExpired = false) { if (file.storedInternal) { - InternalStorage.del(file.accessKey); + InternalStorage.del(file.accessKey!); if (file.thumbnailUrl) { - InternalStorage.del(file.thumbnailAccessKey); + InternalStorage.del(file.thumbnailAccessKey!); } if (file.webpublicUrl) { - InternalStorage.del(file.webpublicAccessKey); + InternalStorage.del(file.webpublicAccessKey!); } } else if (!file.isLink) { - const minio = new Minio.Client(config.drive.config); + const minio = new Minio.Client(config.drive!.config); - await minio.removeObject(config.drive.bucket, file.accessKey); + await minio.removeObject(config.drive!.bucket!, file.accessKey!); if (file.thumbnailUrl) { - await minio.removeObject(config.drive.bucket, file.thumbnailAccessKey); + await minio.removeObject(config.drive!.bucket!, file.thumbnailAccessKey!); } if (file.webpublicUrl) { - await minio.removeObject(config.drive.bucket, file.webpublicAccessKey); + await minio.removeObject(config.drive!.bucket!, file.webpublicAccessKey!); } } // リモートファイル期限切れ削除後は直リンクにする - if (isExpired && file.userHost !== null) { + if (isExpired && file.userHost !== null && file.uri != null) { DriveFiles.update(file.id, { isLink: true, url: file.uri, diff --git a/src/services/drive/image-processor.ts b/src/services/drive/image-processor.ts index 3c538390b0..89ac331ca1 100644 --- a/src/services/drive/image-processor.ts +++ b/src/services/drive/image-processor.ts @@ -2,7 +2,7 @@ import * as sharp from 'sharp'; export type IImage = { data: Buffer; - ext: string; + ext: string | null; type: string; }; diff --git a/src/services/drive/upload-from-url.ts b/src/services/drive/upload-from-url.ts index a7fe1fbd26..bfe8c5c3b2 100644 --- a/src/services/drive/upload-from-url.ts +++ b/src/services/drive/upload-from-url.ts @@ -1,4 +1,3 @@ -import * as URL from 'url'; import create from './add-file'; import { User } from '../../models/entities/user'; import { driveLogger } from './logger'; @@ -13,14 +12,14 @@ const logger = driveLogger.createSubLogger('downloader'); export default async ( url: string, user: User, - folderId: DriveFolder['id'] = null, - uri: string = null, + folderId: DriveFolder['id'] | null = null, + uri: string | null = null, sensitive = false, force = false, link = false ): Promise => { - let name = URL.parse(url).pathname.split('/').pop(); - if (!DriveFiles.validateFileName(name)) { + let name = new URL(url).pathname.split('/').pop() || null; + if (name == null || !DriveFiles.validateFileName(name)) { name = null; } @@ -50,6 +49,6 @@ export default async ( if (error) { throw error; } else { - return driveFile; + return driveFile!; } }; diff --git a/src/services/following/create.ts b/src/services/following/create.ts index 57bb61fd92..b69dfe42b9 100644 --- a/src/services/following/create.ts +++ b/src/services/following/create.ts @@ -14,6 +14,7 @@ import { instanceChart, perUserFollowingChart } from '../chart'; import { genId } from '../../misc/gen-id'; import { createNotification } from '../create-notification'; import { isDuplicateKeyValueError } from '../../misc/is-duplicate-key-value-error'; +import { ensure } from '../../prelude/ensure'; const logger = new Logger('following/create'); @@ -115,7 +116,7 @@ export default async function(follower: User, followee: User, requestId?: string if (blocked != null) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked'); } - const followeeProfile = await UserProfiles.findOne({ userId: followee.id }); + const followeeProfile = await UserProfiles.findOne(followee.id).then(ensure); // フォロー対象が鍵アカウントである or // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or diff --git a/src/services/following/requests/accept-all.ts b/src/services/following/requests/accept-all.ts index b61c31a513..70e7448aad 100644 --- a/src/services/following/requests/accept-all.ts +++ b/src/services/following/requests/accept-all.ts @@ -1,6 +1,7 @@ import accept from './accept'; import { User } from '../../../models/entities/user'; import { FollowRequests, Users } from '../../../models'; +import { ensure } from '../../../prelude/ensure'; /** * 指定したユーザー宛てのフォローリクエストをすべて承認 @@ -12,7 +13,7 @@ export default async function(user: User) { }); for (const request of requests) { - const follower = await Users.findOne(request.followerId); + const follower = await Users.findOne(request.followerId).then(ensure); accept(user, follower); } } diff --git a/src/services/following/requests/accept.ts b/src/services/following/requests/accept.ts index 0be8e24e1a..1397514ad1 100644 --- a/src/services/following/requests/accept.ts +++ b/src/services/following/requests/accept.ts @@ -16,7 +16,7 @@ export default async function(followee: User, follower: User) { await insertFollowingDoc(followee, follower); if (Users.isRemoteUser(follower) && request) { - const content = renderActivity(renderAccept(renderFollow(follower, followee, request.requestId), followee as ILocalUser)); + const content = renderActivity(renderAccept(renderFollow(follower, followee, request.requestId!), followee as ILocalUser)); deliver(followee as ILocalUser, content, follower.inbox); } diff --git a/src/services/following/requests/reject.ts b/src/services/following/requests/reject.ts index c590edcfd8..d5b5e48c41 100644 --- a/src/services/following/requests/reject.ts +++ b/src/services/following/requests/reject.ts @@ -13,7 +13,7 @@ export default async function(followee: User, follower: User) { followerId: follower.id }); - const content = renderActivity(renderReject(renderFollow(follower, followee, request.requestId), followee as ILocalUser)); + const content = renderActivity(renderReject(renderFollow(follower, followee, request!.requestId!), followee as ILocalUser)); deliver(followee as ILocalUser, content, follower.inbox); } diff --git a/src/services/i/pin.ts b/src/services/i/pin.ts index 4e43421bdc..f5c957c742 100644 --- a/src/services/i/pin.ts +++ b/src/services/i/pin.ts @@ -77,9 +77,8 @@ export async function removePinned(user: User, noteId: Note['id']) { } export async function deliverPinnedChange(userId: User['id'], noteId: Note['id'], isAddition: boolean) { - const user = await Users.findOne({ - id: userId - }); + const user = await Users.findOne(userId); + if (user == null) throw 'user not found'; if (!Users.isLocalUser(user)) return; @@ -108,14 +107,8 @@ async function CreateRemoteInboxes(user: ILocalUser): Promise { const queue: string[] = []; for (const following of followers) { - const follower = { - host: following.followerHost, - inbox: following.followerInbox, - sharedInbox: following.followerSharedInbox, - }; - - if (follower.host !== null) { - const inbox = follower.sharedInbox || follower.inbox; + if (Followings.isRemoteFollower(following)) { + const inbox = following.followerSharedInbox || following.followerInbox; if (!queue.includes(inbox)) queue.push(inbox); } } diff --git a/src/services/i/update.ts b/src/services/i/update.ts index 7dba472e78..2bb5c7a100 100644 --- a/src/services/i/update.ts +++ b/src/services/i/update.ts @@ -6,9 +6,8 @@ import { User } from '../../models/entities/user'; import { renderPerson } from '../../remote/activitypub/renderer/person'; export async function publishToFollowers(userId: User['id']) { - const user = await Users.findOne({ - id: userId - }); + const user = await Users.findOne(userId); + if (user == null) throw 'user not found'; const followers = await Followings.find({ followeeId: user.id @@ -19,7 +18,7 @@ export async function publishToFollowers(userId: User['id']) { // フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信 if (Users.isLocalUser(user)) { for (const following of followers) { - if (following.followerHost !== null) { + if (Followings.isRemoteFollower(following)) { const inbox = following.followerSharedInbox || following.followerInbox; if (!queue.includes(inbox)) queue.push(inbox); } diff --git a/src/services/logger.ts b/src/services/logger.ts index e6a54e626d..190bbdd253 100644 --- a/src/services/logger.ts +++ b/src/services/logger.ts @@ -9,14 +9,14 @@ import { genId } from '../misc/gen-id'; type Domain = { name: string; - color: string; + color?: string; }; type Level = 'error' | 'success' | 'warning' | 'debug' | 'info'; export default class Logger { private domain: Domain; - private parentLogger: Logger; + private parentLogger: Logger | null = null; private store: boolean; constructor(domain: string, color?: string, store = true) { @@ -33,7 +33,7 @@ export default class Logger { return logger; } - private log(level: Level, message: string, data: Record, important = false, subDomains: Domain[] = [], store = true): void { + private log(level: Level, message: string, data?: Record | null, important = false, subDomains: Domain[] = [], store = true): void { if (program.quiet) return; if (!this.store) store = false; @@ -80,7 +80,7 @@ export default class Logger { } } - public error(x: string | Error, data?: Record, important = false): void { // 実行を継続できない状況で使う + public error(x: string | Error, data?: Record | null, important = false): void { // 実行を継続できない状況で使う if (x instanceof Error) { data = data || {}; data.e = x; @@ -90,21 +90,21 @@ export default class Logger { } } - public warn(message: string, data?: Record, important = false): void { // 実行を継続できるが改善すべき状況で使う + public warn(message: string, data?: Record | null, important = false): void { // 実行を継続できるが改善すべき状況で使う this.log('warning', message, data, important); } - public succ(message: string, data?: Record, important = false): void { // 何かに成功した状況で使う + public succ(message: string, data?: Record | null, important = false): void { // 何かに成功した状況で使う this.log('success', message, data, important); } - public debug(message: string, data?: Record, important = false): void { // デバッグ用に使う(開発者に必要だが利用者に不要な情報) + public debug(message: string, data?: Record | null, important = false): void { // デバッグ用に使う(開発者に必要だが利用者に不要な情報) if (process.env.NODE_ENV != 'production' || program.verbose) { this.log('debug', message, data, important); } } - public info(message: string, data?: Record, important = false): void { // それ以外 + public info(message: string, data?: Record | null, important = false): void { // それ以外 this.log('info', message, data, important); } } diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 6906bc71ce..9c5989696a 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -10,7 +10,7 @@ import { parse } from '../../mfm/parse'; import { resolveUser } from '../../remote/resolve-user'; import config from '../../config'; import { updateHashtag } from '../update-hashtag'; -import { erase, concat } from '../../prelude/array'; +import { concat } from '../../prelude/array'; import insertNoteUnread from './unread'; import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc'; import extractMentions from '../../misc/extract-mentions'; @@ -27,6 +27,7 @@ import { notesChart, perUserNotesChart, activeUsersChart, instanceChart } from ' import { Poll, IPoll } from '../../models/entities/poll'; import { createNotification } from '../create-notification'; import { isDuplicateKeyValueError } from '../../misc/is-duplicate-key-value-error'; +import { ensure } from '../../prelude/ensure'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -83,25 +84,25 @@ class NotificationManager { } type Option = { - createdAt?: Date; - name?: string; - text?: string; - reply?: Note; - renote?: Note; - files?: DriveFile[]; - geo?: any; - poll?: IPoll; - viaMobile?: boolean; - localOnly?: boolean; - cw?: string; + createdAt?: Date | null; + name?: string | null; + text?: string | null; + reply?: Note | null; + renote?: Note | null; + files?: DriveFile[] | null; + geo?: any | null; + poll?: IPoll | null; + viaMobile?: boolean | null; + localOnly?: boolean | null; + cw?: string | null; visibility?: string; - visibleUsers?: User[]; - apMentions?: User[]; - apHashtags?: string[]; - apEmojis?: string[]; - questionUri?: string; - uri?: string; - app?: App; + visibleUsers?: User[] | null; + apMentions?: User[] | null; + apHashtags?: string[] | null; + apEmojis?: string[] | null; + questionUri?: string | null; + uri?: string | null; + app?: App | null; }; export default async (user: User, data: Option, silent = false) => new Promise(async (res, rej) => { @@ -117,10 +118,6 @@ export default async (user: User, data: Option, silent = false) => new Promise new Promise parse(choice))) + ? concat(data.poll.choices.map(choice => parse(choice)!)) : []; const combinedTokens = tokens.concat(cwTokens).concat(choiceTokens); @@ -173,19 +170,21 @@ export default async (user: User, data: Option, silent = false) => new Promise tag.length <= 100); - if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply.userId)) { - mentionedUsers.push(await Users.findOne(data.reply.userId)); + if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) { + mentionedUsers.push(await Users.findOne(data.reply.userId).then(ensure)); } if (data.visibility == 'specified') { + if (data.visibleUsers == null) throw 'invalid param'; + for (const u of data.visibleUsers) { if (!mentionedUsers.some(x => x.id === u.id)) { mentionedUsers.push(u); } } - if (data.reply && !data.visibleUsers.some(x => x.id === data.reply.userId)) { - data.visibleUsers.push(await Users.findOne(data.reply.userId)); + if (data.reply && !data.visibleUsers.some(x => x.id === data.reply!.userId)) { + data.visibleUsers.push(await Users.findOne(data.reply.userId).then(ensure)); } } @@ -215,6 +214,8 @@ export default async (user: User, data: Option, silent = false) => new Promise new Promise { + Users.findOne(reply.userId).then(ensure).then(u => { deliver(user, noteActivity, u.inbox); }); } // 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送 if (renote && renote.userHost !== null) { - Users.findOne(renote.userId).then(u => { + Users.findOne(renote.userId).then(ensure).then(u => { deliver(user, noteActivity, u.inbox); }); } @@ -340,14 +341,14 @@ async function publish(user: User, note: Note, reply: Note, renote: Note, noteAc if (['public', 'home', 'followers'].includes(note.visibility)) { // フォロワーに配信 - publishToFollowers(note, user, noteActivity, reply); + publishToFollowers(note, user, noteActivity); } } async function insertNote(user: User, data: Option, tags: string[], emojis: string[], mentionedUsers: User[]) { const insert = new Note({ - id: genId(data.createdAt), - createdAt: data.createdAt, + id: genId(data.createdAt!), + createdAt: data.createdAt!, fileIds: data.files ? data.files.map(file => file.id) : [], replyId: data.reply ? data.reply.id : null, renoteId: data.renote ? data.renote.id : null, @@ -358,8 +359,8 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri tags: tags.map(tag => tag.toLowerCase()), emojis, userId: user.id, - viaMobile: data.viaMobile, - localOnly: data.localOnly, + viaMobile: data.viaMobile!, + localOnly: data.localOnly!, geo: data.geo || null, appId: data.app ? data.app.id : null, visibility: data.visibility as any, @@ -401,10 +402,10 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri const poll = new Poll({ noteId: note.id, - choices: data.poll.choices, - expiresAt: data.poll.expiresAt, - multiple: data.poll.multiple, - votes: new Array(data.poll.choices.length).fill(0), + choices: data.poll!.choices, + expiresAt: data.poll!.expiresAt, + multiple: data.poll!.multiple, + votes: new Array(data.poll!.choices.length).fill(0), noteVisibility: note.visibility, userId: user.id, userHost: user.host @@ -416,7 +417,7 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri note = await Notes.save(insert); } - return note; + return note!; } catch (e) { // duplicate key error if (isDuplicateKeyValueError(e)) { @@ -434,7 +435,7 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri function index(note: Note) { if (note.text == null || config.elasticsearch == null) return; - es.index({ + es!.index({ index: 'misskey', type: 'note', id: note.id.toString(), @@ -466,7 +467,7 @@ async function notifyToWatchersOfReplyee(reply: Note, user: User, nm: Notificati } } -async function publishToFollowers(note: Note, user: User, noteActivity: any, reply: Note) { +async function publishToFollowers(note: Note, user: User, noteActivity: any) { const followers = await Followings.find({ followeeId: note.userId }); @@ -474,7 +475,7 @@ async function publishToFollowers(note: Note, user: User, noteActivity: any, rep const queue: string[] = []; for (const following of followers) { - if (following.followerHost !== null) { + if (Followings.isRemoteFollower(following)) { // フォロワーがリモートユーザーかつ投稿者がローカルユーザーなら投稿を配信 if (Users.isLocalUser(user)) { const inbox = following.followerSharedInbox || following.followerInbox; @@ -523,11 +524,9 @@ async function extractMentionedUsers(user: User, tokens: ReturnType + let mentionedUsers = (await Promise.all(mentions.map(m => resolveUser(m.username, m.host || user.host).catch(() => null) - )); - - mentionedUsers = mentionedUsers.filter(x => x != null); + ))).filter(x => x != null) as User[]; // Drop duplicate users mentionedUsers = mentionedUsers.filter((u, i, self) => diff --git a/src/services/note/polls/update.ts b/src/services/note/polls/update.ts index beb35cc278..277ace204d 100644 --- a/src/services/note/polls/update.ts +++ b/src/services/note/polls/update.ts @@ -7,8 +7,10 @@ import { Note } from '../../../models/entities/note'; export async function deliverQuestionUpdate(noteId: Note['id']) { const note = await Notes.findOne(noteId); + if (note == null) throw 'note not found'; const user = await Users.findOne(note.userId); + if (user == null) throw 'note not found'; const followers = await Followings.find({ followeeId: user.id @@ -19,13 +21,8 @@ export async function deliverQuestionUpdate(noteId: Note['id']) { // フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信 if (Users.isLocalUser(user)) { for (const following of followers) { - const follower = { - inbox: following.followerInbox, - sharedInbox: following.followerSharedInbox - }; - - if (following.followerHost !== null) { - const inbox = follower.sharedInbox || follower.inbox; + if (Followings.isRemoteFollower(following)) { + const inbox = following.followerSharedInbox || following.followerInbox; if (!queue.includes(inbox)) queue.push(inbox); } } diff --git a/src/services/note/polls/vote.ts b/src/services/note/polls/vote.ts index 3051ff42d9..3bae617b4f 100644 --- a/src/services/note/polls/vote.ts +++ b/src/services/note/polls/vote.ts @@ -7,11 +7,13 @@ import { Not } from 'typeorm'; import { genId } from '../../../misc/gen-id'; import { createNotification } from '../../create-notification'; -export default (user: User, note: Note, choice: number) => new Promise(async (res, rej) => { +export default async function(user: User, note: Note, choice: number) { const poll = await Polls.findOne({ noteId: note.id }); + if (poll == null) throw 'poll not found'; + // Check whether is valid choice - if (poll.choices[choice] == null) return rej('invalid choice param'); + if (poll.choices[choice] == null) throw new Error('invalid choice param'); // if already voted const exist = await PollVotes.find({ @@ -21,10 +23,10 @@ export default (user: User, note: Note, choice: number) => new Promise(async (re if (poll.multiple) { if (exist.some(x => x.choice === choice)) { - return rej('already voted'); + throw new Error('already voted'); } } else if (exist.length !== 0) { - return rej('already voted'); + throw new Error('already voted'); } // Create vote @@ -36,8 +38,6 @@ export default (user: User, note: Note, choice: number) => new Promise(async (re choice: choice }); - res(); - // Increment votes count const index = choice + 1; // In SQL, array index is 1 based await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE noteId = '${poll.noteId}'`); @@ -70,7 +70,7 @@ export default (user: User, note: Note, choice: number) => new Promise(async (re const profile = await UserProfiles.findOne({ userId: user.id }); // ローカルユーザーが投票した場合この投稿をWatchする - if (Users.isLocalUser(user) && profile.autoWatch) { + if (Users.isLocalUser(user) && profile!.autoWatch) { watch(user.id, note); } -}); +} diff --git a/src/services/note/reaction/create.ts b/src/services/note/reaction/create.ts index 55f9bcd084..ea2108430a 100644 --- a/src/services/note/reaction/create.ts +++ b/src/services/note/reaction/create.ts @@ -82,7 +82,7 @@ export default async (user: User, note: Note, reaction: string) => { const profile = await UserProfiles.findOne({ userId: user.id }); // ユーザーがローカルユーザーかつ自動ウォッチ設定がオンならばこの投稿をWatchする - if (Users.isLocalUser(user) && profile.autoWatch) { + if (Users.isLocalUser(user) && profile!.autoWatch) { watch(user.id, note); } @@ -91,7 +91,7 @@ export default async (user: User, note: Note, reaction: string) => { if (Users.isLocalUser(user) && note.userHost !== null) { const content = renderActivity(renderLike(user, note, reaction)); Users.findOne(note.userId).then(u => { - deliver(user, content, u.inbox); + deliver(user, content, u!.inbox); }); } //#endregion diff --git a/src/services/note/reaction/delete.ts b/src/services/note/reaction/delete.ts index 76f5ed631e..355e1c5466 100644 --- a/src/services/note/reaction/delete.ts +++ b/src/services/note/reaction/delete.ts @@ -42,7 +42,7 @@ export default async (user: User, note: Note) => { if (Users.isLocalUser(user) && (note.userHost !== null)) { const content = renderActivity(renderUndo(renderLike(user, note, exist.reaction), user)); Users.findOne(note.userId).then(u => { - deliver(user, content, u.inbox); + deliver(user, content, u!.inbox); }); } //#endregion diff --git a/src/services/push-notification.ts b/src/services/push-notification.ts index defd4d6e2d..1830cad623 100644 --- a/src/services/push-notification.ts +++ b/src/services/push-notification.ts @@ -1,26 +1,17 @@ import * as push from 'web-push'; import config from '../config'; import { SwSubscriptions } from '../models'; -import { Meta } from '../models/entities/meta'; import fetchMeta from '../misc/fetch-meta'; -let meta: Meta = null; - -setInterval(() => { - fetchMeta().then(m => { - meta = m; +export default async function(userId: string, type: string, body?: any) { + const meta = await fetchMeta(); - if (meta.enableServiceWorker) { - // アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録 - push.setVapidDetails(config.url, - meta.swPublicKey, - meta.swPrivateKey); - } - }); -}, 3000); + if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return; -export default async function(userId: string, type: string, body?: any) { - if (!meta.enableServiceWorker) return; + // アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録 + push.setVapidDetails(config.url, + meta.swPublicKey, + meta.swPrivateKey); // Fetch const subscriptions = await SwSubscriptions.find({ diff --git a/src/services/register-or-fetch-instance-doc.ts b/src/services/register-or-fetch-instance-doc.ts index 459f538e96..9957edd3db 100644 --- a/src/services/register-or-fetch-instance-doc.ts +++ b/src/services/register-or-fetch-instance-doc.ts @@ -5,8 +5,6 @@ import { genId } from '../misc/gen-id'; import { toPuny } from '../misc/convert-host'; export async function registerOrFetchInstanceDoc(host: string): Promise { - if (host == null) return null; - host = toPuny(host); const index = await Instances.findOne({ host }); diff --git a/src/services/stream.ts b/src/services/stream.ts index c1d14b2779..c52af48b59 100644 --- a/src/services/stream.ts +++ b/src/services/stream.ts @@ -6,7 +6,7 @@ import { UserList } from '../models/entities/user-list'; import { ReversiGame } from '../models/entities/games/reversi/game'; class Publisher { - private ev: Xev; + private ev: Xev | null = null; constructor() { // Redisがインストールされてないときはプロセス間通信を使う @@ -15,7 +15,7 @@ class Publisher { } } - private publish = (channel: string, type: string, value?: any): void => { + private publish = (channel: string, type: string | null, value?: any): void => { const message = type == null ? value : value == null ? { type: type, body: null } : { type: type, body: value }; @@ -23,7 +23,7 @@ class Publisher { if (this.ev) { this.ev.emit(channel, message); } else { - redis.publish('misskey', JSON.stringify({ + redis!.publish('misskey', JSON.stringify({ channel: channel, message: message })); diff --git a/src/tools/clean-remote-files.ts b/src/tools/clean-remote-files.ts index f64affea97..e722552e14 100644 --- a/src/tools/clean-remote-files.ts +++ b/src/tools/clean-remote-files.ts @@ -3,6 +3,7 @@ import del from '../services/drive/delete-file'; import { DriveFiles } from '../models'; import { Not } from 'typeorm'; import { DriveFile } from '../models/entities/drive-file'; +import { ensure } from '../prelude/ensure'; const limit = promiseLimit(16); @@ -17,7 +18,7 @@ DriveFiles.find({ }); async function job(file: DriveFile): Promise { - file = await DriveFiles.findOne(file.id); + file = await DriveFiles.findOne(file.id).then(ensure); await del(file, true); diff --git a/src/tools/show-signin-history.ts b/src/tools/show-signin-history.ts index 584bece6bb..9e6e849f5d 100644 --- a/src/tools/show-signin-history.ts +++ b/src/tools/show-signin-history.ts @@ -9,7 +9,7 @@ import { Users, Signins } from '../models'; // node built/tools/show-signin-history username all // with full request headers -async function main(username: string, headers: string[]) { +async function main(username: string, headers?: string[]) { const user = await Users.findOne({ host: null, usernameLower: username.toLowerCase(), @@ -39,7 +39,7 @@ async function main(username: string, headers: string[]) { const args = process.argv.slice(2); let username = args[0]; -let headers: string[]; +let headers: string[] | undefined; if (args[1] != null) { headers = args[1].split(/,/).map(header => header.toLowerCase()); diff --git a/tsconfig.json b/tsconfig.json index 6bd8571207..4f1d1b9cd5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,8 @@ "removeComments": false, "noLib": false, "strict": true, - "strictNullChecks": false, + "strictNullChecks": true, + "strictPropertyInitialization": false, "experimentalDecorators": true, "emitDecoratorMetadata": true, "resolveJsonModule": true, -- cgit v1.2.3-freya From a51fbd73165caa1d79c0c030aa011e6e01d12fc6 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 04:00:02 +0900 Subject: Suppress errors --- src/server/api/endpoints/i/update-email.ts | 4 ++-- src/server/api/private/signup.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/i/update-email.ts b/src/server/api/endpoints/i/update-email.ts index 8b05f3c8cb..15c62a9d08 100644 --- a/src/server/api/endpoints/i/update-email.ts +++ b/src/server/api/endpoints/i/update-email.ts @@ -76,12 +76,12 @@ export default define(meta, async (ps, user) => { user: meta.smtpUser, pass: meta.smtpPass } : undefined - }); + } as any); const link = `${config.url}/verify-email/${code}`; transporter.sendMail({ - from: meta.email, + from: meta.email!, to: ps.email, subject: meta.name || 'Misskey', text: `To verify email, please click this link: ${link}` diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index ea4df060f8..4842a534bf 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -96,7 +96,7 @@ export default async (ctx: Koa.BaseContext) => { cipher: undefined, passphrase: undefined } - }, (e, publicKey, privateKey) => + } as any, (e, publicKey, privateKey) => e ? j(e) : s([publicKey, privateKey]) )); -- cgit v1.2.3-freya From 8c331da315b057752e4d469dc64e6d13cf0bfcf9 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 14:11:15 +0900 Subject: Fix bug --- src/server/api/endpoints/admin/logs.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/admin/logs.ts b/src/server/api/endpoints/admin/logs.ts index e1419bdfe1..0ea3fbdbf7 100644 --- a/src/server/api/endpoints/admin/logs.ts +++ b/src/server/api/endpoints/admin/logs.ts @@ -34,7 +34,7 @@ export default define(meta, async (ps) => { if (ps.domain) { const whiteDomains = ps.domain.split(' ').filter(x => !x.startsWith('-')); - const blackDomains = ps.domain.split(' ').filter(x => x.startsWith('-')); + const blackDomains = ps.domain.split(' ').filter(x => x.startsWith('-')).map(x => x.substr(1)); if (whiteDomains.length > 0) { query.andWhere(new Brackets(qb => { @@ -53,11 +53,17 @@ export default define(meta, async (ps) => { if (blackDomains.length > 0) { query.andWhere(new Brackets(qb => { for (const blackDomain of blackDomains) { + const subDomains = blackDomain.split('.'); let i = 0; - for (const subDomain of blackDomain.split('.')) { + for (const subDomain of subDomains) { const p = `blackSubDomain_${subDomain}_${i}`; - // SQL is 1 based, so we need '+ 1' - qb.andWhere(`log.domain[${i + 1}] != :${p}`, { [p]: subDomain }); + if (i === subDomains.length - 1) { + // SQL is 1 based, so we need '+ 1' + qb.andWhere(`log.domain[${i + 1}] != :${p}`, { [p]: subDomain }); + } else { + // SQL is 1 based, so we need '+ 1' + qb.andWhere(`log.domain[${i + 1}] == :${p}`, { [p]: subDomain }); + } i++; } } -- cgit v1.2.3-freya From 960268fd3354ff756e1269d3d81cce9807978be0 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 14:17:50 +0900 Subject: typo --- src/server/api/endpoints/admin/logs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/admin/logs.ts b/src/server/api/endpoints/admin/logs.ts index 0ea3fbdbf7..cb5d3940c5 100644 --- a/src/server/api/endpoints/admin/logs.ts +++ b/src/server/api/endpoints/admin/logs.ts @@ -62,7 +62,7 @@ export default define(meta, async (ps) => { qb.andWhere(`log.domain[${i + 1}] != :${p}`, { [p]: subDomain }); } else { // SQL is 1 based, so we need '+ 1' - qb.andWhere(`log.domain[${i + 1}] == :${p}`, { [p]: subDomain }); + qb.andWhere(`log.domain[${i + 1}] = :${p}`, { [p]: subDomain }); } i++; } -- cgit v1.2.3-freya From 5fcf5bc635aca41e647b005aabae29e5981a1e41 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 15:18:12 +0900 Subject: Fix bug --- src/server/api/private/signup.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 4842a534bf..80f2d87b12 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -36,7 +36,7 @@ export default async (ctx: Koa.BaseContext) => { const username = body['username']; const password = body['password']; - const host = process.env.NODE_ENV === 'test' ? (body['host'] || null) : null; + const host: string | null = process.env.NODE_ENV === 'test' ? (body['host'] || null) : null; const invitationCode = body['invitationCode']; if (instance && instance.disableRegistration) { @@ -109,7 +109,7 @@ export default async (ctx: Koa.BaseContext) => { createdAt: new Date(), username: username, usernameLower: username.toLowerCase(), - host: toPuny(host), + host: host ? toPuny(host) : null, token: secret, isAdmin: config.autoAdmin && usersCount === 0, })); -- cgit v1.2.3-freya From ae16b45c1170cd4361b8fac9fadc225101803f9a Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 16:54:21 +0900 Subject: Fix bug --- src/misc/convert-host.ts | 5 +++++ src/queue/processors/inbox.ts | 4 ++-- src/server/api/endpoints/admin/emoji/list.ts | 4 ++-- src/server/api/endpoints/users/followers.ts | 4 ++-- src/server/api/endpoints/users/following.ts | 4 ++-- src/server/api/private/signup.ts | 4 ++-- 6 files changed, 15 insertions(+), 10 deletions(-) (limited to 'src/server/api') diff --git a/src/misc/convert-host.ts b/src/misc/convert-host.ts index dbf7864550..a5fb15c66f 100644 --- a/src/misc/convert-host.ts +++ b/src/misc/convert-host.ts @@ -19,3 +19,8 @@ export function extractDbHost(uri: string) { export function toPuny(host: string) { return toASCII(host.toLowerCase()); } + +export function toPunyNullable(host: string | null | undefined): string | null { + if (host == null) return null; + return toASCII(host.toLowerCase()); +} diff --git a/src/queue/processors/inbox.ts b/src/queue/processors/inbox.ts index 4deaef2ae3..05fed0566d 100644 --- a/src/queue/processors/inbox.ts +++ b/src/queue/processors/inbox.ts @@ -12,7 +12,7 @@ import { Instances, Users, UserPublickeys } from '../../models'; import { instanceChart } from '../../services/chart'; import { UserPublickey } from '../../models/entities/user-publickey'; import fetchMeta from '../../misc/fetch-meta'; -import { toPuny } from '../../misc/convert-host'; +import { toPuny, toPunyNullable } from '../../misc/convert-host'; import { validActor } from '../../remote/activitypub/type'; import { ensure } from '../../prelude/ensure'; @@ -36,7 +36,7 @@ export default async (job: Bull.Job): Promise => { if (keyIdLower.startsWith('acct:')) { const acct = parseAcct(keyIdLower.slice('acct:'.length)); - const host = acct.host ? toPuny(acct.host) : null; + const host = toPunyNullable(acct.host); const username = toPuny(acct.username); if (host === null) { diff --git a/src/server/api/endpoints/admin/emoji/list.ts b/src/server/api/endpoints/admin/emoji/list.ts index cf73e4cc75..54686a5c5a 100644 --- a/src/server/api/endpoints/admin/emoji/list.ts +++ b/src/server/api/endpoints/admin/emoji/list.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import define from '../../../define'; import { Emojis } from '../../../../../models'; -import { toPuny } from '../../../../../misc/convert-host'; +import { toPunyNullable } from '../../../../../misc/convert-host'; export const meta = { desc: { @@ -23,7 +23,7 @@ export const meta = { export default define(meta, async (ps) => { const emojis = await Emojis.find({ - host: ps.host ? toPuny(ps.host) : null + host: toPunyNullable(ps.host) }); return emojis.map(e => ({ diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts index 88a474be7c..0cb68353ca 100644 --- a/src/server/api/endpoints/users/followers.ts +++ b/src/server/api/endpoints/users/followers.ts @@ -4,7 +4,7 @@ import define from '../../define'; import { ApiError } from '../../error'; import { Users, Followings } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; -import { toPuny } from '../../../../misc/convert-host'; +import { toPunyNullable } from '../../../../misc/convert-host'; export const meta = { desc: { @@ -66,7 +66,7 @@ export const meta = { export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId != null ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: toPuny(ps.host!) }); + : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) }); if (user == null) { throw new ApiError(meta.errors.noSuchUser); diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts index 5e017150e8..2e273dc0c2 100644 --- a/src/server/api/endpoints/users/following.ts +++ b/src/server/api/endpoints/users/following.ts @@ -4,7 +4,7 @@ import define from '../../define'; import { ApiError } from '../../error'; import { Users, Followings } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; -import { toPuny } from '../../../../misc/convert-host'; +import { toPunyNullable } from '../../../../misc/convert-host'; export const meta = { desc: { @@ -66,7 +66,7 @@ export const meta = { export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId != null ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: toPuny(ps.host!) }); + : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) }); if (user == null) { throw new ApiError(meta.errors.noSuchUser); diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 80f2d87b12..f8dba2eb29 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -10,7 +10,7 @@ import { genId } from '../../../misc/gen-id'; import { usersChart } from '../../../services/chart'; import { User } from '../../../models/entities/user'; import { UserKeypair } from '../../../models/entities/user-keypair'; -import { toPuny } from '../../../misc/convert-host'; +import { toPunyNullable } from '../../../misc/convert-host'; import { UserProfile } from '../../../models/entities/user-profile'; import { getConnection } from 'typeorm'; @@ -109,7 +109,7 @@ export default async (ctx: Koa.BaseContext) => { createdAt: new Date(), username: username, usernameLower: username.toLowerCase(), - host: host ? toPuny(host) : null, + host: toPunyNullable(host), token: secret, isAdmin: config.autoAdmin && usersCount === 0, })); -- cgit v1.2.3-freya From 69662e24c3ab135e3618d1a28c7914efdff66f24 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 17:24:56 +0900 Subject: Fix bug --- src/server/api/endpoints/notes/polls/vote.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts index 68dee66828..e8b8b66da5 100644 --- a/src/server/api/endpoints/notes/polls/vote.ts +++ b/src/server/api/endpoints/notes/polls/vote.ts @@ -124,7 +124,7 @@ export default define(meta, async (ps, user) => { // Increment votes count const index = ps.choice + 1; // In SQL, array index is 1 based - await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE noteId = '${poll.noteId}'`); + await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`); publishNoteStream(note.id, 'pollVoted', { choice: ps.choice, -- cgit v1.2.3-freya From e369031a28a7aae390d17d4d9f075c6f0df7fe55 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 19:19:32 +0900 Subject: Redis必須に MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .config/example.yml | 8 ++++---- CHANGELOG.md | 3 ++- docs/setup.en.md | 17 ++++++----------- docs/setup.fr.md | 15 +++++---------- docs/setup.ja.md | 23 ++++++----------------- src/db/redis.ts | 4 ++-- src/server/api/limiter.ts | 6 ------ src/server/api/service/discord.ts | 10 ++-------- src/server/api/service/github.ts | 10 ++-------- src/server/api/service/twitter.ts | 10 ++-------- src/server/api/streaming.ts | 31 +++++++++++++------------------ src/services/stream.ts | 22 ++++------------------ 12 files changed, 48 insertions(+), 111 deletions(-) (limited to 'src/server/api') diff --git a/.config/example.yml b/.config/example.yml index 10239f1a76..24985fd280 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -65,10 +65,10 @@ db: # ┌─────────────────────┐ #───┘ Redis configuration └───────────────────────────────────── -#redis: -# host: localhost -# port: 6379 -# pass: example-pass +redis: + host: localhost + port: 6379 + #pass: example-pass # ┌─────────────────────────────┐ #───┘ Elasticsearch configuration └───────────────────────────── diff --git a/CHANGELOG.md b/CHANGELOG.md index 3149777034..a17e89ff7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ If you encounter any problems with updating, please try the following: 11.0.0 ---------- -* データベースがMongoDBからPostgreSQLに変更されました +* **データベースがMongoDBからPostgreSQLに変更されました** +* **Redisが必須に** * アカウントを完全に削除できるように * ミュート/ブロック時にそのユーザーの投稿のウォッチをすべて解除するように * フォロー申請数が実際より1すくなくなる問題を修正 diff --git a/docs/setup.en.md b/docs/setup.en.md index 2e1706de35..45e3e2c68b 100644 --- a/docs/setup.en.md +++ b/docs/setup.en.md @@ -24,18 +24,13 @@ Please install and setup these softwares: #### Dependencies :package: * **[Node.js](https://nodejs.org/en/)** >= 11.7.0 * **[PostgreSQL](https://www.postgresql.org/)** >= 10 +* **[Redis](https://redis.io/)** ##### Optional -* [Redis](https://redis.io/) - * Redis is optional, but we strongly recommended to install it * [Elasticsearch](https://www.elastic.co/) - required to enable the search feature * [FFmpeg](https://www.ffmpeg.org/) -*3.* Setup PostgreSQL ----------------------------------------------------------------- -:) - -*4.* Install Misskey +*3.* Install Misskey ---------------------------------------------------------------- 1. `su - misskey` Connect to misskey user. 2. `git clone -b master git://github.com/syuilo/misskey.git` Clone the misskey repo from master branch. @@ -43,12 +38,12 @@ Please install and setup these softwares: 4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest) 5. `npm install` Install misskey dependencies. -*5.* Configure Misskey +*4.* Configure Misskey ---------------------------------------------------------------- 1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`. 2. Edit `default.yml` -*6.* Build Misskey +*5.* Build Misskey ---------------------------------------------------------------- Build misskey with the following: @@ -64,13 +59,13 @@ If you're still encountering errors about some modules, use node-gyp: 3. `node-gyp build` 4. `NODE_ENV=production npm run build` -*7.* Init DB +*6.* Init DB ---------------------------------------------------------------- ``` shell npm run init ``` -*8.* That is it. +*7.* That is it. ---------------------------------------------------------------- Well done! Now, you have an environment that run to Misskey. diff --git a/docs/setup.fr.md b/docs/setup.fr.md index 2f8694b9da..e6d36aeff8 100644 --- a/docs/setup.fr.md +++ b/docs/setup.fr.md @@ -24,18 +24,13 @@ Installez les paquets suivants : #### Dépendences :package: * **[Node.js](https://nodejs.org/en/)** >= 11.7.0 * **[PostgreSQL](https://www.postgresql.org/)** >= 10 +* **[Redis](https://redis.io/)** ##### Optionnels -* [Redis](https://redis.io/) - * Redis est optionnel mais nous vous recommandons vivement de l'installer * [Elasticsearch](https://www.elastic.co/) - requis pour pouvoir activer la fonctionnalité de recherche * [FFmpeg](https://www.ffmpeg.org/) -*3.* Paramètrage de PostgreSQL ----------------------------------------------------------------- -:) - -*4.* Installation de Misskey +*3.* Installation de Misskey ---------------------------------------------------------------- 1. `su - misskey` Basculez vers l'utilisateur misskey. 2. `git clone -b master git://github.com/syuilo/misskey.git` Clonez la branche master du dépôt misskey. @@ -43,12 +38,12 @@ Installez les paquets suivants : 4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` Checkout sur le tag de la [version la plus récente](https://github.com/syuilo/misskey/releases/latest) 5. `npm install` Installez les dépendances de misskey. -*5.* Création du fichier de configuration +*4.* Création du fichier de configuration ---------------------------------------------------------------- 1. `cp .config/example.yml .config/default.yml` Copiez le fichier `.config/example.yml` et renommez-le`default.yml`. 2. Editez le fichier `default.yml` -*6.* Construction de Misskey +*5.* Construction de Misskey ---------------------------------------------------------------- Construisez Misskey comme ceci : @@ -64,7 +59,7 @@ Si vous rencontrez des erreurs concernant certains modules, utilisez node-gyp: 3. `node-gyp build` 4. `NODE_ENV=production npm run build` -*7.* C'est tout. +*6.* C'est tout. ---------------------------------------------------------------- Excellent ! Maintenant, vous avez un environnement prêt pour lancer Misskey diff --git a/docs/setup.ja.md b/docs/setup.ja.md index f636a74181..1b1730b69e 100644 --- a/docs/setup.ja.md +++ b/docs/setup.ja.md @@ -24,25 +24,14 @@ adduser --disabled-password --disabled-login misskey #### 依存関係 :package: * **[Node.js](https://nodejs.org/en/)** (11.7.0以上) * **[PostgreSQL](https://www.postgresql.org/)** (10以上) +* **[Redis](https://redis.io/)** ##### オプション -* [Redis](https://redis.io/) - * Redisはオプションですが、インストールすることを強く推奨します。 - * インストールしなくていいのは、あなたのインスタンスが自分専用のときだけとお考えください。 - * 具体的には、Redisをインストールしないと、次の事が出来なくなります: - * Misskeyプロセスを複数起動しての負荷分散 - * レートリミット - * ジョブキュー - * Twitter連携 * [Elasticsearch](https://www.elastic.co/) * 検索機能を有効にするためにはインストールが必要です。 * [FFmpeg](https://www.ffmpeg.org/) -*3.* PostgreSQLの設定 ----------------------------------------------------------------- -:) - -*4.* Misskeyのインストール +*3.* Misskeyのインストール ---------------------------------------------------------------- 1. `su - misskey` misskeyユーザーを使用 2. `git clone -b master git://github.com/syuilo/misskey.git` masterブランチからMisskeyレポジトリをクローン @@ -50,12 +39,12 @@ adduser --disabled-password --disabled-login misskey 4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認 5. `npm install` Misskeyの依存パッケージをインストール -*5.* 設定ファイルを作成する +*4.* 設定ファイルを作成する ---------------------------------------------------------------- 1. `cp .config/example.yml .config/default.yml` `.config/example.yml`をコピーし名前を`default.yml`にする。 2. `default.yml` を編集する。 -*6.* Misskeyのビルド +*5.* Misskeyのビルド ---------------------------------------------------------------- 次のコマンドでMisskeyをビルドしてください: @@ -70,13 +59,13 @@ Debianをお使いであれば、`build-essential`パッケージをインスト 3. `node-gyp build` 4. `NODE_ENV=production npm run build` -*7.* データベースを初期化 +*6.* データベースを初期化 ---------------------------------------------------------------- ``` shell npm run init ``` -*8.* 以上です! +*7.* 以上です! ---------------------------------------------------------------- お疲れ様でした。これでMisskeyを動かす準備は整いました。 diff --git a/src/db/redis.ts b/src/db/redis.ts index cebf2a10af..6518cb0059 100644 --- a/src/db/redis.ts +++ b/src/db/redis.ts @@ -1,7 +1,7 @@ import * as redis from 'redis'; import config from '../config'; -export default config.redis ? redis.createClient( +export default redis.createClient( config.redis.port, config.redis.host, { @@ -9,4 +9,4 @@ export default config.redis ? redis.createClient( prefix: config.redis.prefix, db: config.redis.db || 0 } -) : null; +); diff --git a/src/server/api/limiter.ts b/src/server/api/limiter.ts index 8a6c94458e..48d12d3cc6 100644 --- a/src/server/api/limiter.ts +++ b/src/server/api/limiter.ts @@ -8,12 +8,6 @@ import Logger from '../../services/logger'; const logger = new Logger('limiter'); export default (endpoint: IEndpoint, user: User) => new Promise((ok, reject) => { - // Redisがインストールされてない場合は常に許可 - if (limiterDB == null) { - ok(); - return; - } - const limitation = endpoint.meta.limit!; const key = limitation.hasOwnProperty('key') diff --git a/src/server/api/service/discord.ts b/src/server/api/service/discord.ts index b2561dee61..d8a979b5b2 100644 --- a/src/server/api/service/discord.ts +++ b/src/server/api/service/discord.ts @@ -83,8 +83,6 @@ async function getOAuth2() { } router.get('/connect/discord', async ctx => { - if (redis == null) return; - if (!compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; @@ -110,8 +108,6 @@ router.get('/connect/discord', async ctx => { }); router.get('/signin/discord', async ctx => { - if (redis == null) return; - const sessid = uuid(); const params = { @@ -138,8 +134,6 @@ router.get('/signin/discord', async ctx => { }); router.get('/dc/cb', async ctx => { - if (redis == null) return; - const userToken = getUserToken(ctx); const oauth2 = await getOAuth2(); @@ -160,7 +154,7 @@ router.get('/dc/cb', async ctx => { } const { redirect_uri, state } = await new Promise((res, rej) => { - redis!.get(sessid, async (_, state) => { + redis.get(sessid, async (_, state) => { res(JSON.parse(state)); }); }); @@ -241,7 +235,7 @@ router.get('/dc/cb', async ctx => { } const { redirect_uri, state } = await new Promise((res, rej) => { - redis!.get(userToken, async (_, state) => { + redis.get(userToken, async (_, state) => { res(JSON.parse(state)); }); }); diff --git a/src/server/api/service/github.ts b/src/server/api/service/github.ts index 4878d8c529..a4d274cc62 100644 --- a/src/server/api/service/github.ts +++ b/src/server/api/service/github.ts @@ -80,8 +80,6 @@ async function getOath2() { } router.get('/connect/github', async ctx => { - if (redis == null) return; - if (!compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; @@ -106,8 +104,6 @@ router.get('/connect/github', async ctx => { }); router.get('/signin/github', async ctx => { - if (redis == null) return; - const sessid = uuid(); const params = { @@ -133,8 +129,6 @@ router.get('/signin/github', async ctx => { }); router.get('/gh/cb', async ctx => { - if (redis == null) return; - const userToken = getUserToken(ctx); const oauth2 = await getOath2(); @@ -155,7 +149,7 @@ router.get('/gh/cb', async ctx => { } const { redirect_uri, state } = await new Promise((res, rej) => { - redis!.get(sessid, async (_, state) => { + redis.get(sessid, async (_, state) => { res(JSON.parse(state)); }); }); @@ -222,7 +216,7 @@ router.get('/gh/cb', async ctx => { } const { redirect_uri, state } = await new Promise((res, rej) => { - redis!.get(userToken, async (_, state) => { + redis.get(userToken, async (_, state) => { res(JSON.parse(state)); }); }); diff --git a/src/server/api/service/twitter.ts b/src/server/api/service/twitter.ts index b35ee8c479..39fdfd8654 100644 --- a/src/server/api/service/twitter.ts +++ b/src/server/api/service/twitter.ts @@ -79,8 +79,6 @@ async function getTwAuth() { } router.get('/connect/twitter', async ctx => { - if (redis == null) return; - if (!compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; @@ -99,8 +97,6 @@ router.get('/connect/twitter', async ctx => { }); router.get('/signin/twitter', async ctx => { - if (redis == null) return; - const twAuth = await getTwAuth(); const twCtx = await twAuth!.begin(); @@ -122,8 +118,6 @@ router.get('/signin/twitter', async ctx => { }); router.get('/tw/cb', async ctx => { - if (redis == null) return; - const userToken = getUserToken(ctx); const twAuth = await getTwAuth(); @@ -137,7 +131,7 @@ router.get('/tw/cb', async ctx => { } const get = new Promise((res, rej) => { - redis!.get(sessid, async (_, twCtx) => { + redis.get(sessid, async (_, twCtx) => { res(twCtx); }); }); @@ -170,7 +164,7 @@ router.get('/tw/cb', async ctx => { } const get = new Promise((res, rej) => { - redis!.get(userToken, async (_, twCtx) => { + redis.get(userToken, async (_, twCtx) => { res(twCtx); }); }); diff --git a/src/server/api/streaming.ts b/src/server/api/streaming.ts index ab66f2b6d9..902c62ef98 100644 --- a/src/server/api/streaming.ts +++ b/src/server/api/streaming.ts @@ -1,7 +1,6 @@ import * as http from 'http'; import * as websocket from 'websocket'; import * as redis from 'redis'; -import Xev from 'xev'; import MainStreamConnection from './stream'; import { ParsedUrlQuery } from 'querystring'; @@ -23,28 +22,24 @@ module.exports = (server: http.Server) => { let ev: EventEmitter; - if (config.redis) { - // Connect to Redis - const subscriber = redis.createClient( - config.redis.port, config.redis.host); + // Connect to Redis + const subscriber = redis.createClient( + config.redis.port, config.redis.host); - subscriber.subscribe('misskey'); + subscriber.subscribe('misskey'); - ev = new EventEmitter(); + ev = new EventEmitter(); - subscriber.on('message', async (_, data) => { - const obj = JSON.parse(data); + subscriber.on('message', async (_, data) => { + const obj = JSON.parse(data); - ev.emit(obj.channel, obj.message); - }); + ev.emit(obj.channel, obj.message); + }); - connection.once('close', () => { - subscriber.unsubscribe(); - subscriber.quit(); - }); - } else { - ev = new Xev(); - } + connection.once('close', () => { + subscriber.unsubscribe(); + subscriber.quit(); + }); const main = new MainStreamConnection(connection, ev, user, app); diff --git a/src/services/stream.ts b/src/services/stream.ts index c52af48b59..28cb2057e2 100644 --- a/src/services/stream.ts +++ b/src/services/stream.ts @@ -1,33 +1,19 @@ import redis from '../db/redis'; -import Xev from 'xev'; import { User } from '../models/entities/user'; import { Note } from '../models/entities/note'; import { UserList } from '../models/entities/user-list'; import { ReversiGame } from '../models/entities/games/reversi/game'; class Publisher { - private ev: Xev | null = null; - - constructor() { - // Redisがインストールされてないときはプロセス間通信を使う - if (redis == null) { - this.ev = new Xev(); - } - } - private publish = (channel: string, type: string | null, value?: any): void => { const message = type == null ? value : value == null ? { type: type, body: null } : { type: type, body: value }; - if (this.ev) { - this.ev.emit(channel, message); - } else { - redis!.publish('misskey', JSON.stringify({ - channel: channel, - message: message - })); - } + redis.publish('misskey', JSON.stringify({ + channel: channel, + message: message + })); } public publishMainStream = (userId: User['id'], type: string, value?: any): void => { -- cgit v1.2.3-freya From bf472b0c5e761fd612b3ea085e3167df2343f316 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 19:36:57 +0900 Subject: refactor --- src/server/api/endpoints/app/create.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/app/create.ts b/src/server/api/endpoints/app/create.ts index c7e7e516ad..71f21fdf47 100644 --- a/src/server/api/endpoints/app/create.ts +++ b/src/server/api/endpoints/app/create.ts @@ -38,7 +38,7 @@ export default define(meta, async (ps, user) => { const app = await Apps.save({ id: genId(), createdAt: new Date(), - userId: user && user.id, + userId: user ? user.id : null, name: ps.name, description: ps.description, permission: ps.permission, -- cgit v1.2.3-freya From 2d590df9004e96a05ec85e0542fe587de0495f40 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 20:01:32 +0900 Subject: Clean up --- src/server/api/endpoints/auth/accept.ts | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/auth/accept.ts b/src/server/api/endpoints/auth/accept.ts index 55898b59db..a584e7267b 100644 --- a/src/server/api/endpoints/auth/accept.ts +++ b/src/server/api/endpoints/auth/accept.ts @@ -71,6 +71,4 @@ export default define(meta, async (ps, user) => { await AuthSessions.update(session.id, { userId: user.id }); - - return; }); -- cgit v1.2.3-freya From e3b3f8fac14fb4c4d150fb31c11ead1a193a36e0 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 14 Apr 2019 04:17:24 +0900 Subject: Better error handling --- src/misc/gen-id.ts | 2 +- src/prelude/ensure.ts | 2 +- src/remote/activitypub/kernel/accept/follow.ts | 2 +- src/remote/activitypub/kernel/block/index.ts | 2 +- src/remote/activitypub/kernel/follow.ts | 2 +- src/remote/activitypub/kernel/like.ts | 2 +- src/remote/activitypub/kernel/reject/follow.ts | 2 +- src/remote/activitypub/kernel/undo/block.ts | 2 +- src/remote/activitypub/kernel/undo/follow.ts | 2 +- src/remote/activitypub/kernel/undo/like.ts | 4 ++-- src/remote/activitypub/models/note.ts | 12 ++++++------ src/remote/activitypub/models/person.ts | 8 ++++---- src/remote/activitypub/models/question.ts | 10 +++++----- src/remote/resolve-user.ts | 6 +++--- src/server/api/authenticate.ts | 4 ++-- src/server/api/common/getters.ts | 4 ++-- src/server/api/stream/channels/games/reversi-game.ts | 12 ++++++------ src/services/drive/add-file.ts | 4 ++-- src/services/i/pin.ts | 2 +- src/services/i/update.ts | 2 +- src/services/note/create.ts | 6 +++--- src/services/note/polls/update.ts | 4 ++-- src/services/note/polls/vote.ts | 2 +- src/tools/add-emoji.ts | 4 ++-- src/tools/show-signin-history.ts | 2 +- 25 files changed, 52 insertions(+), 52 deletions(-) (limited to 'src/server/api') diff --git a/src/misc/gen-id.ts b/src/misc/gen-id.ts index 5ee65e8177..99cb70b3fb 100644 --- a/src/misc/gen-id.ts +++ b/src/misc/gen-id.ts @@ -14,6 +14,6 @@ export function genId(date?: Date): string { case 'meid': return genMeid(date); case 'ulid': return ulid(date.getTime()); case 'objectid': return genObjectId(date); - default: throw 'unknown id generation method'; + default: throw new Error('unknown id generation method'); } } diff --git a/src/prelude/ensure.ts b/src/prelude/ensure.ts index c9264adc13..1af281c056 100644 --- a/src/prelude/ensure.ts +++ b/src/prelude/ensure.ts @@ -3,7 +3,7 @@ */ export function ensure(x: T): NonNullable { if (x == null) { - throw 'ぬるぽ'; + throw new Error('ぬるぽ'); } else { return x!; } diff --git a/src/remote/activitypub/kernel/accept/follow.ts b/src/remote/activitypub/kernel/accept/follow.ts index f3e517ad9f..377b8dac42 100644 --- a/src/remote/activitypub/kernel/accept/follow.ts +++ b/src/remote/activitypub/kernel/accept/follow.ts @@ -6,7 +6,7 @@ import { Users } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id; - if (id == null) throw 'missing id'; + if (id == null) throw new Error('missing id'); if (!id.startsWith(config.url + '/')) { return; diff --git a/src/remote/activitypub/kernel/block/index.ts b/src/remote/activitypub/kernel/block/index.ts index 19e33eb7dd..5c247326cb 100644 --- a/src/remote/activitypub/kernel/block/index.ts +++ b/src/remote/activitypub/kernel/block/index.ts @@ -9,7 +9,7 @@ const logger = apLogger; export default async (actor: IRemoteUser, activity: IBlock): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; - if (id == null) throw 'missing id'; + if (id == null) throw new Error('missing id'); const uri = activity.id || activity; diff --git a/src/remote/activitypub/kernel/follow.ts b/src/remote/activitypub/kernel/follow.ts index d37404502f..c255067bfd 100644 --- a/src/remote/activitypub/kernel/follow.ts +++ b/src/remote/activitypub/kernel/follow.ts @@ -6,7 +6,7 @@ import { Users } from '../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; - if (id == null) throw 'missing id'; + if (id == null) throw new Error('missing id'); if (!id.startsWith(config.url + '/')) { return; diff --git a/src/remote/activitypub/kernel/like.ts b/src/remote/activitypub/kernel/like.ts index d4fa7bf387..a08b453a89 100644 --- a/src/remote/activitypub/kernel/like.ts +++ b/src/remote/activitypub/kernel/like.ts @@ -5,7 +5,7 @@ import { Notes } from '../../../models'; export default async (actor: IRemoteUser, activity: ILike) => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; - if (id == null) throw 'missing id'; + if (id == null) throw new Error('missing id'); // Transform: // https://misskey.ex/notes/xxxx to diff --git a/src/remote/activitypub/kernel/reject/follow.ts b/src/remote/activitypub/kernel/reject/follow.ts index 91689339ab..d8b5a4b9b9 100644 --- a/src/remote/activitypub/kernel/reject/follow.ts +++ b/src/remote/activitypub/kernel/reject/follow.ts @@ -6,7 +6,7 @@ import { Users } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id; - if (id == null) throw 'missing id'; + if (id == null) throw new Error('missing id'); if (!id.startsWith(config.url + '/')) { return; diff --git a/src/remote/activitypub/kernel/undo/block.ts b/src/remote/activitypub/kernel/undo/block.ts index 9c277ed7d2..8ef70a9bef 100644 --- a/src/remote/activitypub/kernel/undo/block.ts +++ b/src/remote/activitypub/kernel/undo/block.ts @@ -9,7 +9,7 @@ const logger = apLogger; export default async (actor: IRemoteUser, activity: IBlock): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; - if (id == null) throw 'missing id'; + if (id == null) throw new Error('missing id'); const uri = activity.id || activity; diff --git a/src/remote/activitypub/kernel/undo/follow.ts b/src/remote/activitypub/kernel/undo/follow.ts index ce84d0c791..d75f055640 100644 --- a/src/remote/activitypub/kernel/undo/follow.ts +++ b/src/remote/activitypub/kernel/undo/follow.ts @@ -7,7 +7,7 @@ import { Users, FollowRequests, Followings } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; - if (id == null) throw 'missing id'; + if (id == null) throw new Error('missing id'); if (!id.startsWith(config.url + '/')) { return; diff --git a/src/remote/activitypub/kernel/undo/like.ts b/src/remote/activitypub/kernel/undo/like.ts index 75879d697a..2678828a9a 100644 --- a/src/remote/activitypub/kernel/undo/like.ts +++ b/src/remote/activitypub/kernel/undo/like.ts @@ -8,13 +8,13 @@ import { Notes } from '../../../../models'; */ export default async (actor: IRemoteUser, activity: ILike): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; - if (id == null) throw 'missing id'; + if (id == null) throw new Error('missing id'); const noteId = id.split('/').pop(); const note = await Notes.findOne(noteId); if (note == null) { - throw 'note not found'; + throw new Error('note not found'); } await deleteReaction(actor, note); diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts index c11a77b0fe..8842342342 100644 --- a/src/remote/activitypub/models/note.ts +++ b/src/remote/activitypub/models/note.ts @@ -32,7 +32,7 @@ const logger = apLogger; */ export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise { const uri = typeof value == 'string' ? value : value.id; - if (uri == null) throw 'missing uri'; + if (uri == null) throw new Error('missing uri'); // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { @@ -67,7 +67,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false value: value, object: object }); - throw 'invalid note'; + throw new Error('invalid note'); } const note: INote = object; @@ -81,7 +81,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { - throw 'actor has been suspended'; + throw new Error('actor has been suspended'); } //#region Visibility @@ -124,7 +124,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false ? await resolveNote(note.inReplyTo, resolver).then(x => { if (x == null) { logger.warn(`Specified inReplyTo, but nout found`); - throw 'inReplyTo not found'; + throw new Error('inReplyTo not found'); } else { return x; } @@ -230,7 +230,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false */ export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise { const uri = typeof value == 'string' ? value : value.id; - if (uri == null) throw 'missing uri'; + if (uri == null) throw new Error('missing uri'); // ブロックしてたら中断 // TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく @@ -252,7 +252,7 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver): if (e.name === 'duplicated') { return fetchNote(uri).then(note => { if (note == null) { - throw 'something happened'; + throw new Error('something happened'); } else { return note; } diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index 9465cf0cd0..c1c07c7bbf 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -88,7 +88,7 @@ function validatePerson(x: any, uri: string) { * Misskeyに対象のPersonが登録されていればそれを返します。 */ export async function fetchPerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw 'uri is not string'; + if (typeof uri !== 'string') throw new Error('uri is not string'); // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { @@ -111,7 +111,7 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw 'uri is not string'; + if (typeof uri !== 'string') throw new Error('uri is not string'); if (resolver == null) resolver = new Resolver(); @@ -256,7 +256,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw 'uri is not string'; + if (typeof uri !== 'string') throw new Error('uri is not string'); // URIがこのサーバーを指しているならスキップ if (uri.startsWith(config.url + '/')) { @@ -380,7 +380,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ export async function resolvePerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw 'uri is not string'; + if (typeof uri !== 'string') throw new Error('uri is not string'); //#region このサーバーに既に登録されていたらそれを返す const exist = await fetchPerson(uri); diff --git a/src/remote/activitypub/models/question.ts b/src/remote/activitypub/models/question.ts index 708cdc2a66..5c126c3a56 100644 --- a/src/remote/activitypub/models/question.ts +++ b/src/remote/activitypub/models/question.ts @@ -11,7 +11,7 @@ export async function extractPollFromQuestion(source: string | IQuestion): Promi const expiresAt = question.endTime ? new Date(question.endTime) : null; if (multiple && !question.anyOf) { - throw 'invalid question'; + throw new Error('invalid question'); } const choices = question[multiple ? 'anyOf' : 'oneOf']! @@ -37,14 +37,14 @@ export async function updateQuestion(value: any) { const uri = typeof value == 'string' ? value : value.id; // URIがこのサーバーを指しているならスキップ - if (uri.startsWith(config.url + '/')) throw 'uri points local'; + if (uri.startsWith(config.url + '/')) throw new Error('uri points local'); //#region このサーバーに既に登録されているか const note = await Notes.findOne({ uri }); - if (note == null) throw 'Question is not registed'; + if (note == null) throw new Error('Question is not registed'); const poll = await Polls.findOne({ noteId: note.id }); - if (poll == null) throw 'Question is not registed'; + if (poll == null) throw new Error('Question is not registed'); //#endregion // resolve new Question object @@ -52,7 +52,7 @@ export async function updateQuestion(value: any) { const question = await resolver.resolve(value) as IQuestion; apLogger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); - if (question.type !== 'Question') throw 'object is not a Question'; + if (question.type !== 'Question') throw new Error('object is not a Question'); const apChoices = question.oneOf || question.anyOf; diff --git a/src/remote/resolve-user.ts b/src/remote/resolve-user.ts index 9b518f5e81..a4bfca8422 100644 --- a/src/remote/resolve-user.ts +++ b/src/remote/resolve-user.ts @@ -17,7 +17,7 @@ export async function resolveUser(username: string, host: string | null, option? logger.info(`return local user: ${usernameLower}`); return await Users.findOne({ usernameLower, host: null }).then(u => { if (u == null) { - throw 'user not found'; + throw new Error('user not found'); } else { return u; } @@ -30,7 +30,7 @@ export async function resolveUser(username: string, host: string | null, option? logger.info(`return local user: ${usernameLower}`); return await Users.findOne({ usernameLower, host: null }).then(u => { if (u == null) { - throw 'user not found'; + throw new Error('user not found'); } else { return u; } @@ -78,7 +78,7 @@ export async function resolveUser(username: string, host: string | null, option? logger.info(`return resynced remote user: ${acctLower}`); return await Users.findOne({ uri: self.href }).then(u => { if (u == null) { - throw 'user not found'; + throw new Error('user not found'); } else { return u; } diff --git a/src/server/api/authenticate.ts b/src/server/api/authenticate.ts index 5c2b7e45cf..ecf4a82c45 100644 --- a/src/server/api/authenticate.ts +++ b/src/server/api/authenticate.ts @@ -14,7 +14,7 @@ export default async (token: string): Promise<[User | null | undefined, App | nu .findOne({ token }); if (user == null) { - throw 'user not found'; + throw new Error('user not found'); } return [user, null]; @@ -24,7 +24,7 @@ export default async (token: string): Promise<[User | null | undefined, App | nu }); if (accessToken == null) { - throw 'invalid signature'; + throw new Error('invalid signature'); } const app = await Apps diff --git a/src/server/api/common/getters.ts b/src/server/api/common/getters.ts index b720840ebb..04716d19c6 100644 --- a/src/server/api/common/getters.ts +++ b/src/server/api/common/getters.ts @@ -36,7 +36,7 @@ export async function getRemoteUser(userId: User['id']) { const user = await getUser(userId); if (!Users.isRemoteUser(user)) { - throw 'user is not a remote user'; + throw new Error('user is not a remote user'); } return user; @@ -49,7 +49,7 @@ export async function getLocalUser(userId: User['id']) { const user = await getUser(userId); if (!Users.isLocalUser(user)) { - throw 'user is not a local user'; + throw new Error('user is not a local user'); } return user; diff --git a/src/server/api/stream/channels/games/reversi-game.ts b/src/server/api/stream/channels/games/reversi-game.ts index d708eae9f7..7c13666c51 100644 --- a/src/server/api/stream/channels/games/reversi-game.ts +++ b/src/server/api/stream/channels/games/reversi-game.ts @@ -43,7 +43,7 @@ export default class extends Channel { if (this.user == null) return; const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw 'game not found'; + if (game == null) throw new Error('game not found'); if (game.isStarted) return; if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; @@ -67,7 +67,7 @@ export default class extends Channel { if (this.user == null) return; const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw 'game not found'; + if (game == null) throw new Error('game not found'); if (game.isStarted) return; if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; @@ -91,7 +91,7 @@ export default class extends Channel { if (this.user == null) return; const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw 'game not found'; + if (game == null) throw new Error('game not found'); if (game.isStarted) return; if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; @@ -135,7 +135,7 @@ export default class extends Channel { if (this.user == null) return; const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw 'game not found'; + if (game == null) throw new Error('game not found'); if (game.isStarted) return; @@ -237,7 +237,7 @@ export default class extends Channel { if (this.user == null) return; const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw 'game not found'; + if (game == null) throw new Error('game not found'); if (!game.isStarted) return; if (game.isEnded) return; @@ -304,7 +304,7 @@ export default class extends Channel { @autobind private async check(crc32: string) { const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw 'game not found'; + if (game == null) throw new Error('game not found'); if (!game.isStarted) return; diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index 94b97fed61..9eccfb125c 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -297,7 +297,7 @@ export default async function( // If usage limit exceeded if (usage + size > driveCapacity) { if (Users.isLocalUser(user)) { - throw 'no-free-space'; + throw new Error('no-free-space'); } else { // (アバターまたはバナーを含まず)最も古いファイルを削除する deleteOldFile(user as IRemoteUser); @@ -316,7 +316,7 @@ export default async function( userId: user.id }); - if (driveFolder == null) throw 'folder-not-found'; + if (driveFolder == null) throw new Error('folder-not-found'); return driveFolder; }; diff --git a/src/services/i/pin.ts b/src/services/i/pin.ts index f5c957c742..a6d2dfcdbf 100644 --- a/src/services/i/pin.ts +++ b/src/services/i/pin.ts @@ -78,7 +78,7 @@ export async function removePinned(user: User, noteId: Note['id']) { export async function deliverPinnedChange(userId: User['id'], noteId: Note['id'], isAddition: boolean) { const user = await Users.findOne(userId); - if (user == null) throw 'user not found'; + if (user == null) throw new Error('user not found'); if (!Users.isLocalUser(user)) return; diff --git a/src/services/i/update.ts b/src/services/i/update.ts index 2bb5c7a100..ddb6704a03 100644 --- a/src/services/i/update.ts +++ b/src/services/i/update.ts @@ -7,7 +7,7 @@ import { renderPerson } from '../../remote/activitypub/renderer/person'; export async function publishToFollowers(userId: User['id']) { const user = await Users.findOne(userId); - if (user == null) throw 'user not found'; + if (user == null) throw new Error('user not found'); const followers = await Followings.find({ followeeId: user.id diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 9c5989696a..8c85a5c275 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -175,7 +175,7 @@ export default async (user: User, data: Option, silent = false) => new Promise x.id === u.id)) { @@ -214,7 +214,7 @@ export default async (user: User, data: Option, silent = false) => new Promise { console.log('success'); diff --git a/src/tools/show-signin-history.ts b/src/tools/show-signin-history.ts index 9e6e849f5d..fd7cd39e38 100644 --- a/src/tools/show-signin-history.ts +++ b/src/tools/show-signin-history.ts @@ -15,7 +15,7 @@ async function main(username: string, headers?: string[]) { usernameLower: username.toLowerCase(), }); - if (user == null) throw 'User not found'; + if (user == null) throw new Error('User not found'); const history = await Signins.find({ userId: user.id -- cgit v1.2.3-freya From c267baafdc6533a33985e00289caf4099edca79d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 14 Apr 2019 11:56:37 +0900 Subject: Fix bug --- src/server/api/endpoints/notes/featured.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/notes/featured.ts b/src/server/api/endpoints/notes/featured.ts index 5d2e5b7d44..6a76fb8bc6 100644 --- a/src/server/api/endpoints/notes/featured.ts +++ b/src/server/api/endpoints/notes/featured.ts @@ -35,6 +35,7 @@ export default define(meta, async (ps, user) => { const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで const query = Notes.createQueryBuilder('note') + .where('note.userHost IS NULL') .andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) }) .andWhere(`note.visibility = 'public'`) .leftJoinAndSelect('note.user', 'user'); -- cgit v1.2.3-freya From 2d5158c680a5e1506c63b8d4ba559920db4a70db Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 14 Apr 2019 16:06:04 +0900 Subject: Fix bug --- src/server/api/endpoints/admin/logs.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/admin/logs.ts b/src/server/api/endpoints/admin/logs.ts index cb5d3940c5..86e99730c5 100644 --- a/src/server/api/endpoints/admin/logs.ts +++ b/src/server/api/endpoints/admin/logs.ts @@ -57,13 +57,10 @@ export default define(meta, async (ps) => { let i = 0; for (const subDomain of subDomains) { const p = `blackSubDomain_${subDomain}_${i}`; - if (i === subDomains.length - 1) { - // SQL is 1 based, so we need '+ 1' - qb.andWhere(`log.domain[${i + 1}] != :${p}`, { [p]: subDomain }); - } else { - // SQL is 1 based, so we need '+ 1' - qb.andWhere(`log.domain[${i + 1}] = :${p}`, { [p]: subDomain }); - } + // 全体で否定できないのでド・モルガンの法則で + // !(P && Q) を !P || !Q で表す + // SQL is 1 based, so we need '+ 1' + qb.orWhere(`log.domain[${i + 1}] != :${p}`, { [p]: subDomain }); i++; } } -- cgit v1.2.3-freya