From b75184ec8e3436200bacdcd832e3324702553d20 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 18 Sep 2022 03:27:08 +0900 Subject: なんかもうめっちゃ変えた MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/src/server/ActivityPubServerService.ts | 584 +++++++++ packages/backend/src/server/FileServerService.ts | 178 +++ .../backend/src/server/MediaProxyServerService.ts | 137 +++ .../backend/src/server/NodeinfoServerService.ts | 129 ++ packages/backend/src/server/ServerModule.ts | 92 ++ packages/backend/src/server/ServerService.ts | 177 +++ .../backend/src/server/WellKnownServerService.ts | 168 +++ packages/backend/src/server/activitypub.ts | 254 ---- .../backend/src/server/activitypub/featured.ts | 41 - .../backend/src/server/activitypub/followers.ts | 95 -- .../backend/src/server/activitypub/following.ts | 95 -- packages/backend/src/server/activitypub/outbox.ts | 108 -- packages/backend/src/server/api/2fa.ts | 422 ------- packages/backend/src/server/api/ApiCallService.ts | 258 ++++ .../backend/src/server/api/ApiLoggerService.ts | 12 + .../backend/src/server/api/ApiServerService.ts | 160 +++ .../backend/src/server/api/AuthenticateService.ts | 86 ++ packages/backend/src/server/api/EndpointsModule.ts | 1268 ++++++++++++++++++++ .../backend/src/server/api/RateLimiterService.ts | 89 ++ .../backend/src/server/api/SigninApiService.ts | 282 +++++ packages/backend/src/server/api/SigninService.ts | 64 + .../backend/src/server/api/SignupApiService.ts | 175 +++ .../src/server/api/StreamingApiServerService.ts | 120 ++ packages/backend/src/server/api/api-handler.ts | 92 -- packages/backend/src/server/api/authenticate.ts | 66 - packages/backend/src/server/api/call.ts | 147 --- .../backend/src/server/api/common/GetterService.ts | 71 ++ .../src/server/api/common/generate-block-query.ts | 42 - .../server/api/common/generate-channel-query.ts | 24 - .../server/api/common/generate-muted-note-query.ts | 13 - .../api/common/generate-muted-note-thread-query.ts | 17 - .../server/api/common/generate-muted-user-query.ts | 57 - .../api/common/generate-native-user-token.ts | 3 - .../server/api/common/generate-replies-query.ts | 27 - .../server/api/common/generate-visibility-query.ts | 42 - packages/backend/src/server/api/common/getters.ts | 56 - .../src/server/api/common/inject-featured.ts | 4 +- .../backend/src/server/api/common/inject-promo.ts | 4 +- .../src/server/api/common/is-native-token.ts | 1 - .../src/server/api/common/make-pagination-query.ts | 28 - .../server/api/common/read-messaging-message.ts | 151 --- .../src/server/api/common/read-notification.ts | 50 - packages/backend/src/server/api/common/signin.ts | 44 - packages/backend/src/server/api/common/signup.ts | 114 -- packages/backend/src/server/api/define.ts | 59 - packages/backend/src/server/api/endpoint-base.ts | 62 + packages/backend/src/server/api/endpoints.ts | 10 +- .../api/endpoints/admin/abuse-user-reports.ts | 56 +- .../server/api/endpoints/admin/accounts/create.ts | 67 +- .../server/api/endpoints/admin/accounts/delete.ts | 82 +- .../src/server/api/endpoints/admin/ad/create.ts | 44 +- .../src/server/api/endpoints/admin/ad/delete.ts | 24 +- .../src/server/api/endpoints/admin/ad/list.ts | 30 +- .../src/server/api/endpoints/admin/ad/update.ts | 40 +- .../api/endpoints/admin/announcements/create.ts | 40 +- .../api/endpoints/admin/announcements/delete.ts | 24 +- .../api/endpoints/admin/announcements/list.ts | 61 +- .../api/endpoints/admin/announcements/update.ts | 34 +- .../server/api/endpoints/admin/delete-account.ts | 32 +- .../endpoints/admin/delete-all-files-of-a-user.ts | 32 +- .../api/endpoints/admin/drive-capacity-override.ts | 58 +- .../endpoints/admin/drive/clean-remote-files.ts | 18 +- .../server/api/endpoints/admin/drive/cleanup.ts | 32 +- .../src/server/api/endpoints/admin/drive/files.ts | 66 +- .../server/api/endpoints/admin/drive/show-file.ts | 52 +- .../api/endpoints/admin/emoji/add-aliases-bulk.ts | 47 +- .../src/server/api/endpoints/admin/emoji/add.ts | 104 +- .../src/server/api/endpoints/admin/emoji/copy.ts | 101 +- .../api/endpoints/admin/emoji/delete-bulk.ts | 47 +- .../src/server/api/endpoints/admin/emoji/delete.ts | 43 +- .../server/api/endpoints/admin/emoji/import-zip.ts | 19 +- .../api/endpoints/admin/emoji/list-remote.ts | 55 +- .../src/server/api/endpoints/admin/emoji/list.ts | 56 +- .../endpoints/admin/emoji/remove-aliases-bulk.ts | 47 +- .../api/endpoints/admin/emoji/set-aliases-bulk.ts | 41 +- .../api/endpoints/admin/emoji/set-category-bulk.ts | 41 +- .../src/server/api/endpoints/admin/emoji/update.ts | 43 +- .../endpoints/admin/federation/delete-all-files.ts | 32 +- .../federation/refresh-remote-instance-metadata.ts | 35 +- .../admin/federation/remove-all-following.ts | 47 +- .../endpoints/admin/federation/update-instance.ts | 36 +- .../server/api/endpoints/admin/get-index-stats.ts | 34 +- .../server/api/endpoints/admin/get-table-stats.ts | 43 +- .../src/server/api/endpoints/admin/get-user-ips.ts | 36 +- .../src/server/api/endpoints/admin/invite.ts | 50 +- .../backend/src/server/api/endpoints/admin/meta.ts | 192 +-- .../server/api/endpoints/admin/moderators/add.ts | 50 +- .../api/endpoints/admin/moderators/remove.ts | 38 +- .../src/server/api/endpoints/admin/promo/create.ts | 48 +- .../src/server/api/endpoints/admin/queue/clear.ts | 23 +- .../api/endpoints/admin/queue/deliver-delayed.ts | 44 +- .../api/endpoints/admin/queue/inbox-delayed.ts | 44 +- .../src/server/api/endpoints/admin/queue/stats.ts | 42 +- .../src/server/api/endpoints/admin/relays/add.ts | 28 +- .../src/server/api/endpoints/admin/relays/list.ts | 18 +- .../server/api/endpoints/admin/relays/remove.ts | 18 +- .../server/api/endpoints/admin/reset-password.ts | 57 +- .../endpoints/admin/resolve-abuse-user-report.ts | 68 +- .../src/server/api/endpoints/admin/send-email.ts | 18 +- .../src/server/api/endpoints/admin/server-info.ts | 78 +- .../api/endpoints/admin/show-moderation-logs.ts | 28 +- .../src/server/api/endpoints/admin/show-user.ts | 112 +- .../src/server/api/endpoints/admin/show-users.ts | 84 +- .../src/server/api/endpoints/admin/silence-user.ts | 61 +- .../src/server/api/endpoints/admin/suspend-user.ts | 145 ++- .../server/api/endpoints/admin/unsilence-user.ts | 53 +- .../server/api/endpoints/admin/unsuspend-user.ts | 53 +- .../src/server/api/endpoints/admin/update-meta.ts | 582 ++++----- .../server/api/endpoints/admin/update-user-note.ts | 35 +- .../src/server/api/endpoints/admin/vacuum.ts | 36 - .../src/server/api/endpoints/announcements.ts | 55 +- .../src/server/api/endpoints/antennas/create.ts | 113 +- .../src/server/api/endpoints/antennas/delete.ts | 44 +- .../src/server/api/endpoints/antennas/list.ts | 29 +- .../src/server/api/endpoints/antennas/notes.ts | 108 +- .../src/server/api/endpoints/antennas/show.ts | 41 +- .../src/server/api/endpoints/antennas/update.ts | 126 +- .../backend/src/server/api/endpoints/ap/get.ts | 24 +- .../backend/src/server/api/endpoints/ap/show.ts | 167 +-- .../backend/src/server/api/endpoints/app/create.ts | 70 +- .../backend/src/server/api/endpoints/app/show.ts | 45 +- .../src/server/api/endpoints/auth/accept.ts | 110 +- .../server/api/endpoints/auth/session/generate.ts | 70 +- .../src/server/api/endpoints/auth/session/show.ts | 39 +- .../server/api/endpoints/auth/session/userkey.ts | 104 +- .../src/server/api/endpoints/blocking/create.ts | 89 +- .../src/server/api/endpoints/blocking/delete.ts | 86 +- .../src/server/api/endpoints/blocking/list.ts | 40 +- .../src/server/api/endpoints/channels/create.ts | 69 +- .../src/server/api/endpoints/channels/featured.ts | 31 +- .../src/server/api/endpoints/channels/follow.ts | 54 +- .../src/server/api/endpoints/channels/followed.ts | 40 +- .../src/server/api/endpoints/channels/owned.ts | 40 +- .../src/server/api/endpoints/channels/show.ts | 37 +- .../src/server/api/endpoints/channels/timeline.ts | 80 +- .../src/server/api/endpoints/channels/unfollow.ts | 51 +- .../src/server/api/endpoints/channels/update.ts | 80 +- .../server/api/endpoints/charts/active-users.ts | 23 +- .../src/server/api/endpoints/charts/ap-request.ts | 23 +- .../src/server/api/endpoints/charts/drive.ts | 23 +- .../src/server/api/endpoints/charts/federation.ts | 23 +- .../src/server/api/endpoints/charts/hashtag.ts | 23 +- .../src/server/api/endpoints/charts/instance.ts | 23 +- .../src/server/api/endpoints/charts/notes.ts | 23 +- .../src/server/api/endpoints/charts/user/drive.ts | 23 +- .../server/api/endpoints/charts/user/following.ts | 23 +- .../src/server/api/endpoints/charts/user/notes.ts | 23 +- .../server/api/endpoints/charts/user/reactions.ts | 23 +- .../src/server/api/endpoints/charts/users.ts | 23 +- .../src/server/api/endpoints/clips/add-note.ts | 74 +- .../src/server/api/endpoints/clips/create.ts | 44 +- .../src/server/api/endpoints/clips/delete.ts | 36 +- .../backend/src/server/api/endpoints/clips/list.ts | 29 +- .../src/server/api/endpoints/clips/notes.ts | 99 +- .../src/server/api/endpoints/clips/remove-note.ts | 57 +- .../backend/src/server/api/endpoints/clips/show.ts | 45 +- .../src/server/api/endpoints/clips/update.ts | 51 +- packages/backend/src/server/api/endpoints/drive.ts | 37 +- .../src/server/api/endpoints/drive/files.ts | 58 +- .../api/endpoints/drive/files/attached-notes.ts | 54 +- .../api/endpoints/drive/files/check-existence.ts | 30 +- .../src/server/api/endpoints/drive/files/create.ts | 106 +- .../src/server/api/endpoints/drive/files/delete.ts | 47 +- .../api/endpoints/drive/files/find-by-hash.ts | 33 +- .../src/server/api/endpoints/drive/files/find.ts | 35 +- .../src/server/api/endpoints/drive/files/show.ts | 73 +- .../src/server/api/endpoints/drive/files/update.ts | 101 +- .../api/endpoints/drive/files/upload-from-url.ts | 39 +- .../src/server/api/endpoints/drive/folders.ts | 46 +- .../server/api/endpoints/drive/folders/create.ts | 77 +- .../server/api/endpoints/drive/folders/delete.ts | 63 +- .../src/server/api/endpoints/drive/folders/find.ts | 35 +- .../src/server/api/endpoints/drive/folders/show.ts | 45 +- .../server/api/endpoints/drive/folders/update.ts | 132 +- .../src/server/api/endpoints/drive/stream.ts | 48 +- .../api/endpoints/email-address/available.ts | 18 +- .../backend/src/server/api/endpoints/endpoint.ts | 29 +- .../backend/src/server/api/endpoints/endpoints.ts | 15 +- .../server/api/endpoints/export-custom-emojis.ts | 18 +- .../server/api/endpoints/federation/followers.ts | 36 +- .../server/api/endpoints/federation/following.ts | 36 +- .../server/api/endpoints/federation/instances.ts | 177 +-- .../api/endpoints/federation/show-instance.ts | 30 +- .../src/server/api/endpoints/federation/stats.ts | 102 +- .../api/endpoints/federation/update-remote-user.ts | 23 +- .../src/server/api/endpoints/federation/users.ts | 36 +- .../backend/src/server/api/endpoints/fetch-rss.ts | 48 +- .../src/server/api/endpoints/following/create.ts | 98 +- .../src/server/api/endpoints/following/delete.ts | 80 +- .../server/api/endpoints/following/invalidate.ts | 80 +- .../api/endpoints/following/requests/accept.ts | 43 +- .../api/endpoints/following/requests/cancel.ts | 60 +- .../api/endpoints/following/requests/list.ts | 29 +- .../api/endpoints/following/requests/reject.ts | 37 +- .../src/server/api/endpoints/gallery/featured.ts | 33 +- .../src/server/api/endpoints/gallery/popular.ts | 31 +- .../src/server/api/endpoints/gallery/posts.ts | 32 +- .../server/api/endpoints/gallery/posts/create.ts | 71 +- .../server/api/endpoints/gallery/posts/delete.ts | 36 +- .../src/server/api/endpoints/gallery/posts/like.ts | 71 +- .../src/server/api/endpoints/gallery/posts/show.ts | 37 +- .../server/api/endpoints/gallery/posts/unlike.ts | 49 +- .../server/api/endpoints/gallery/posts/update.ts | 70 +- .../server/api/endpoints/get-online-users-count.ts | 30 +- .../src/server/api/endpoints/hashtags/list.ts | 79 +- .../src/server/api/endpoints/hashtags/search.ts | 34 +- .../src/server/api/endpoints/hashtags/show.ts | 33 +- .../src/server/api/endpoints/hashtags/trend.ts | 184 +-- .../src/server/api/endpoints/hashtags/users.ts | 67 +- packages/backend/src/server/api/endpoints/i.ts | 33 +- .../backend/src/server/api/endpoints/i/2fa/done.ts | 60 +- .../src/server/api/endpoints/i/2fa/key-done.ts | 248 ++-- .../server/api/endpoints/i/2fa/password-less.ts | 24 +- .../src/server/api/endpoints/i/2fa/register-key.ts | 90 +- .../src/server/api/endpoints/i/2fa/register.ts | 85 +- .../src/server/api/endpoints/i/2fa/remove-key.ts | 70 +- .../src/server/api/endpoints/i/2fa/unregister.ts | 38 +- .../backend/src/server/api/endpoints/i/apps.ts | 52 +- .../src/server/api/endpoints/i/authorized-apps.ts | 51 +- .../src/server/api/endpoints/i/change-password.ts | 48 +- .../src/server/api/endpoints/i/delete-account.ts | 47 +- .../src/server/api/endpoints/i/export-blocking.ts | 18 +- .../src/server/api/endpoints/i/export-following.ts | 18 +- .../src/server/api/endpoints/i/export-mute.ts | 18 +- .../src/server/api/endpoints/i/export-notes.ts | 18 +- .../server/api/endpoints/i/export-user-lists.ts | 18 +- .../src/server/api/endpoints/i/favorites.ts | 42 +- .../src/server/api/endpoints/i/gallery/likes.ts | 40 +- .../src/server/api/endpoints/i/gallery/posts.ts | 40 +- .../api/endpoints/i/get-word-muted-notes-count.ts | 30 +- .../src/server/api/endpoints/i/import-blocking.ts | 34 +- .../src/server/api/endpoints/i/import-following.ts | 34 +- .../src/server/api/endpoints/i/import-muting.ts | 34 +- .../server/api/endpoints/i/import-user-lists.ts | 34 +- .../src/server/api/endpoints/i/notifications.ts | 222 ++-- .../src/server/api/endpoints/i/page-likes.ts | 40 +- .../backend/src/server/api/endpoints/i/pages.ts | 40 +- packages/backend/src/server/api/endpoints/i/pin.ts | 40 +- .../api/endpoints/i/read-all-messaging-messages.ts | 65 +- .../api/endpoints/i/read-all-unread-notes.ts | 36 +- .../server/api/endpoints/i/read-announcement.ts | 75 +- .../src/server/api/endpoints/i/regenerate-token.ts | 65 +- .../src/server/api/endpoints/i/registry/get-all.ts | 44 +- .../server/api/endpoints/i/registry/get-detail.ts | 48 +- .../src/server/api/endpoints/i/registry/get.ts | 42 +- .../api/endpoints/i/registry/keys-with-type.ts | 46 +- .../src/server/api/endpoints/i/registry/keys.ts | 32 +- .../src/server/api/endpoints/i/registry/remove.ts | 42 +- .../src/server/api/endpoints/i/registry/scopes.ts | 46 +- .../src/server/api/endpoints/i/registry/set.ts | 85 +- .../src/server/api/endpoints/i/revoke-token.ts | 38 +- .../src/server/api/endpoints/i/signin-history.ts | 32 +- .../backend/src/server/api/endpoints/i/unpin.ts | 36 +- .../src/server/api/endpoints/i/update-email.ts | 122 +- .../backend/src/server/api/endpoints/i/update.ts | 273 +++-- .../server/api/endpoints/i/user-group-invites.ts | 38 +- .../src/server/api/endpoints/i/webhooks/create.ts | 55 +- .../src/server/api/endpoints/i/webhooks/delete.ts | 46 +- .../src/server/api/endpoints/i/webhooks/list.ts | 26 +- .../src/server/api/endpoints/i/webhooks/show.ts | 36 +- .../src/server/api/endpoints/i/webhooks/update.ts | 58 +- .../src/server/api/endpoints/messaging/history.ts | 131 +- .../src/server/api/endpoints/messaging/messages.ts | 171 +-- .../api/endpoints/messaging/messages/create.ts | 154 ++- .../api/endpoints/messaging/messages/delete.ts | 40 +- .../api/endpoints/messaging/messages/read.ts | 52 +- packages/backend/src/server/api/endpoints/meta.ts | 226 ++-- .../src/server/api/endpoints/miauth/gen-token.ts | 62 +- .../src/server/api/endpoints/mute/create.ts | 107 +- .../src/server/api/endpoints/mute/delete.ts | 71 +- .../backend/src/server/api/endpoints/mute/list.ts | 40 +- .../backend/src/server/api/endpoints/my/apps.ts | 47 +- packages/backend/src/server/api/endpoints/notes.ts | 106 +- .../src/server/api/endpoints/notes/children.ts | 85 +- .../src/server/api/endpoints/notes/clips.ts | 57 +- .../src/server/api/endpoints/notes/conversation.ts | 74 +- .../src/server/api/endpoints/notes/create.ts | 234 ++-- .../src/server/api/endpoints/notes/delete.ts | 45 +- .../server/api/endpoints/notes/favorites/create.ts | 69 +- .../server/api/endpoints/notes/favorites/delete.ts | 56 +- .../src/server/api/endpoints/notes/featured.ts | 81 +- .../server/api/endpoints/notes/global-timeline.ts | 112 +- .../server/api/endpoints/notes/hybrid-timeline.ts | 193 +-- .../server/api/endpoints/notes/local-timeline.ts | 137 ++- .../src/server/api/endpoints/notes/mentions.ts | 101 +- .../api/endpoints/notes/polls/recommendation.ts | 130 +- .../src/server/api/endpoints/notes/polls/vote.ts | 204 ++-- .../src/server/api/endpoints/notes/reactions.ts | 66 +- .../server/api/endpoints/notes/reactions/create.ts | 39 +- .../server/api/endpoints/notes/reactions/delete.ts | 35 +- .../src/server/api/endpoints/notes/renotes.ts | 74 +- .../src/server/api/endpoints/notes/replies.ts | 69 +- .../server/api/endpoints/notes/search-by-tag.ts | 145 +-- .../src/server/api/endpoints/notes/search.ts | 151 +-- .../backend/src/server/api/endpoints/notes/show.ts | 40 +- .../src/server/api/endpoints/notes/state.ts | 74 +- .../api/endpoints/notes/thread-muting/create.ts | 73 +- .../api/endpoints/notes/thread-muting/delete.ts | 40 +- .../src/server/api/endpoints/notes/timeline.ts | 185 +-- .../src/server/api/endpoints/notes/translate.ts | 129 +- .../src/server/api/endpoints/notes/unrenote.ts | 52 +- .../api/endpoints/notes/user-list-timeline.ts | 166 +-- .../server/api/endpoints/notes/watching/create.ts | 38 - .../server/api/endpoints/notes/watching/delete.ts | 38 - .../server/api/endpoints/notifications/create.ts | 28 +- .../endpoints/notifications/mark-all-as-read.ts | 45 +- .../src/server/api/endpoints/notifications/read.ts | 20 +- .../backend/src/server/api/endpoints/page-push.ts | 51 +- .../src/server/api/endpoints/pages/create.ts | 101 +- .../src/server/api/endpoints/pages/delete.ts | 34 +- .../src/server/api/endpoints/pages/featured.ts | 33 +- .../backend/src/server/api/endpoints/pages/like.ts | 71 +- .../backend/src/server/api/endpoints/pages/show.ts | 64 +- .../src/server/api/endpoints/pages/unlike.ts | 49 +- .../src/server/api/endpoints/pages/update.ts | 107 +- packages/backend/src/server/api/endpoints/ping.ts | 19 +- .../src/server/api/endpoints/pinned-users.ts | 38 +- .../backend/src/server/api/endpoints/promo/read.ts | 63 +- .../server/api/endpoints/request-reset-password.ts | 107 +- .../backend/src/server/api/endpoints/reset-db.ts | 34 +- .../src/server/api/endpoints/reset-password.ts | 57 +- .../src/server/api/endpoints/server-info.ts | 45 +- packages/backend/src/server/api/endpoints/stats.ts | 84 +- .../src/server/api/endpoints/sw/register.ts | 77 +- .../src/server/api/endpoints/sw/unregister.ts | 26 +- packages/backend/src/server/api/endpoints/test.ts | 15 +- .../src/server/api/endpoints/username/available.ts | 46 +- packages/backend/src/server/api/endpoints/users.ts | 85 +- .../src/server/api/endpoints/users/clips.ts | 38 +- .../src/server/api/endpoints/users/followers.ts | 95 +- .../src/server/api/endpoints/users/following.ts | 95 +- .../server/api/endpoints/users/gallery/posts.ts | 36 +- .../users/get-frequently-replied-users.ts | 133 +- .../server/api/endpoints/users/groups/create.ts | 63 +- .../server/api/endpoints/users/groups/delete.ts | 36 +- .../endpoints/users/groups/invitations/accept.ts | 65 +- .../endpoints/users/groups/invitations/reject.ts | 44 +- .../server/api/endpoints/users/groups/invite.ts | 126 +- .../server/api/endpoints/users/groups/joined.ts | 48 +- .../src/server/api/endpoints/users/groups/leave.ts | 45 +- .../src/server/api/endpoints/users/groups/owned.ts | 31 +- .../src/server/api/endpoints/users/groups/pull.ts | 61 +- .../src/server/api/endpoints/users/groups/show.ts | 56 +- .../server/api/endpoints/users/groups/transfer.ts | 85 +- .../server/api/endpoints/users/groups/update.ts | 47 +- .../src/server/api/endpoints/users/lists/create.ts | 42 +- .../src/server/api/endpoints/users/lists/delete.ts | 36 +- .../src/server/api/endpoints/users/lists/list.ts | 31 +- .../src/server/api/endpoints/users/lists/pull.ts | 64 +- .../src/server/api/endpoints/users/lists/push.ts | 103 +- .../src/server/api/endpoints/users/lists/show.ts | 41 +- .../src/server/api/endpoints/users/lists/update.ts | 47 +- .../src/server/api/endpoints/users/notes.ts | 144 ++- .../src/server/api/endpoints/users/pages.ts | 38 +- .../src/server/api/endpoints/users/reactions.ts | 54 +- .../server/api/endpoints/users/recommendation.ts | 64 +- .../src/server/api/endpoints/users/relation.ts | 27 +- .../src/server/api/endpoints/users/report-abuse.ts | 129 +- .../endpoints/users/search-by-username-and-host.ts | 168 +-- .../src/server/api/endpoints/users/search.ts | 186 +-- .../backend/src/server/api/endpoints/users/show.ts | 124 +- .../src/server/api/endpoints/users/stats.ts | 187 +-- packages/backend/src/server/api/error.ts | 16 +- packages/backend/src/server/api/index.ts | 126 -- .../server/api/integration/DiscordServerService.ts | 315 +++++ .../server/api/integration/GithubServerService.ts | 287 +++++ .../server/api/integration/TwitterServerService.ts | 230 ++++ packages/backend/src/server/api/limiter.ts | 77 -- packages/backend/src/server/api/logger.ts | 3 - .../backend/src/server/api/openapi/gen-spec.ts | 190 --- packages/backend/src/server/api/private/signin.ts | 250 ---- .../src/server/api/private/signup-pending.ts | 35 - packages/backend/src/server/api/private/signup.ts | 112 -- packages/backend/src/server/api/service/discord.ts | 287 ----- packages/backend/src/server/api/service/github.ts | 259 ---- packages/backend/src/server/api/service/twitter.ts | 201 ---- .../src/server/api/stream/ChannelsService.ts | 62 + packages/backend/src/server/api/stream/channel.ts | 2 +- .../src/server/api/stream/channels/admin.ts | 20 +- .../src/server/api/stream/channels/antenna.ts | 38 +- .../src/server/api/stream/channels/channel.ts | 50 +- .../src/server/api/stream/channels/drive.ts | 20 +- .../server/api/stream/channels/global-timeline.ts | 47 +- .../src/server/api/stream/channels/hashtag.ts | 38 +- .../server/api/stream/channels/home-timeline.ts | 42 +- .../server/api/stream/channels/hybrid-timeline.ts | 54 +- .../src/server/api/stream/channels/index.ts | 33 - .../server/api/stream/channels/local-timeline.ts | 47 +- .../backend/src/server/api/stream/channels/main.ts | 40 +- .../server/api/stream/channels/messaging-index.ts | 20 +- .../src/server/api/stream/channels/messaging.ts | 75 +- .../src/server/api/stream/channels/queue-stats.ts | 20 +- .../src/server/api/stream/channels/server-stats.ts | 20 +- .../src/server/api/stream/channels/user-list.ts | 60 +- packages/backend/src/server/api/stream/index.ts | 68 +- packages/backend/src/server/api/stream/types.ts | 35 +- packages/backend/src/server/api/streaming.ts | 67 -- packages/backend/src/server/assets/bad-egg.png | Bin 0 -> 1676 bytes .../backend/src/server/assets/cache-expired.png | Bin 0 -> 6048 bytes packages/backend/src/server/assets/dummy.png | Bin 0 -> 6285 bytes .../backend/src/server/assets/not-an-image.png | Bin 0 -> 2780 bytes .../src/server/assets/thumbnail-not-available.png | Bin 0 -> 5705 bytes packages/backend/src/server/assets/tombstone.png | Bin 0 -> 5028 bytes .../backend/src/server/file/assets/bad-egg.png | Bin 1676 -> 0 bytes .../src/server/file/assets/cache-expired.png | Bin 6048 -> 0 bytes packages/backend/src/server/file/assets/dummy.png | Bin 6285 -> 0 bytes .../src/server/file/assets/not-an-image.png | Bin 2780 -> 0 bytes .../server/file/assets/thumbnail-not-available.png | Bin 5705 -> 0 bytes .../backend/src/server/file/assets/tombstone.png | Bin 5028 -> 0 bytes packages/backend/src/server/file/index.ts | 40 - .../backend/src/server/file/send-drive-file.ts | 126 -- packages/backend/src/server/index.ts | 168 --- packages/backend/src/server/nodeinfo.ts | 104 -- packages/backend/src/server/proxy/index.ts | 26 - packages/backend/src/server/proxy/proxy-media.ts | 98 -- .../backend/src/server/web/ClientServerService.ts | 594 +++++++++ packages/backend/src/server/web/FeedService.ts | 86 ++ .../backend/src/server/web/UrlPreviewService.ts | 84 ++ packages/backend/src/server/web/feed.ts | 58 - packages/backend/src/server/web/index.ts | 521 -------- packages/backend/src/server/web/manifest.ts | 18 - packages/backend/src/server/web/url-preview.ts | 65 - packages/backend/src/server/well-known.ts | 151 --- 423 files changed, 18447 insertions(+), 13323 deletions(-) create mode 100644 packages/backend/src/server/ActivityPubServerService.ts create mode 100644 packages/backend/src/server/FileServerService.ts create mode 100644 packages/backend/src/server/MediaProxyServerService.ts create mode 100644 packages/backend/src/server/NodeinfoServerService.ts create mode 100644 packages/backend/src/server/ServerModule.ts create mode 100644 packages/backend/src/server/ServerService.ts create mode 100644 packages/backend/src/server/WellKnownServerService.ts delete mode 100644 packages/backend/src/server/activitypub.ts delete mode 100644 packages/backend/src/server/activitypub/featured.ts delete mode 100644 packages/backend/src/server/activitypub/followers.ts delete mode 100644 packages/backend/src/server/activitypub/following.ts delete mode 100644 packages/backend/src/server/activitypub/outbox.ts delete mode 100644 packages/backend/src/server/api/2fa.ts create mode 100644 packages/backend/src/server/api/ApiCallService.ts create mode 100644 packages/backend/src/server/api/ApiLoggerService.ts create mode 100644 packages/backend/src/server/api/ApiServerService.ts create mode 100644 packages/backend/src/server/api/AuthenticateService.ts create mode 100644 packages/backend/src/server/api/EndpointsModule.ts create mode 100644 packages/backend/src/server/api/RateLimiterService.ts create mode 100644 packages/backend/src/server/api/SigninApiService.ts create mode 100644 packages/backend/src/server/api/SigninService.ts create mode 100644 packages/backend/src/server/api/SignupApiService.ts create mode 100644 packages/backend/src/server/api/StreamingApiServerService.ts delete mode 100644 packages/backend/src/server/api/api-handler.ts delete mode 100644 packages/backend/src/server/api/authenticate.ts delete mode 100644 packages/backend/src/server/api/call.ts create mode 100644 packages/backend/src/server/api/common/GetterService.ts delete mode 100644 packages/backend/src/server/api/common/generate-block-query.ts delete mode 100644 packages/backend/src/server/api/common/generate-channel-query.ts delete mode 100644 packages/backend/src/server/api/common/generate-muted-note-query.ts delete mode 100644 packages/backend/src/server/api/common/generate-muted-note-thread-query.ts delete mode 100644 packages/backend/src/server/api/common/generate-muted-user-query.ts delete mode 100644 packages/backend/src/server/api/common/generate-native-user-token.ts delete mode 100644 packages/backend/src/server/api/common/generate-replies-query.ts delete mode 100644 packages/backend/src/server/api/common/generate-visibility-query.ts delete mode 100644 packages/backend/src/server/api/common/getters.ts delete mode 100644 packages/backend/src/server/api/common/is-native-token.ts delete mode 100644 packages/backend/src/server/api/common/make-pagination-query.ts delete mode 100644 packages/backend/src/server/api/common/read-messaging-message.ts delete mode 100644 packages/backend/src/server/api/common/read-notification.ts delete mode 100644 packages/backend/src/server/api/common/signin.ts delete mode 100644 packages/backend/src/server/api/common/signup.ts delete mode 100644 packages/backend/src/server/api/define.ts create mode 100644 packages/backend/src/server/api/endpoint-base.ts delete mode 100644 packages/backend/src/server/api/endpoints/admin/vacuum.ts delete mode 100644 packages/backend/src/server/api/endpoints/notes/watching/create.ts delete mode 100644 packages/backend/src/server/api/endpoints/notes/watching/delete.ts delete mode 100644 packages/backend/src/server/api/index.ts create mode 100644 packages/backend/src/server/api/integration/DiscordServerService.ts create mode 100644 packages/backend/src/server/api/integration/GithubServerService.ts create mode 100644 packages/backend/src/server/api/integration/TwitterServerService.ts delete mode 100644 packages/backend/src/server/api/limiter.ts delete mode 100644 packages/backend/src/server/api/logger.ts delete mode 100644 packages/backend/src/server/api/openapi/gen-spec.ts delete mode 100644 packages/backend/src/server/api/private/signin.ts delete mode 100644 packages/backend/src/server/api/private/signup-pending.ts delete mode 100644 packages/backend/src/server/api/private/signup.ts delete mode 100644 packages/backend/src/server/api/service/discord.ts delete mode 100644 packages/backend/src/server/api/service/github.ts delete mode 100644 packages/backend/src/server/api/service/twitter.ts create mode 100644 packages/backend/src/server/api/stream/ChannelsService.ts delete mode 100644 packages/backend/src/server/api/stream/channels/index.ts delete mode 100644 packages/backend/src/server/api/streaming.ts create mode 100644 packages/backend/src/server/assets/bad-egg.png create mode 100644 packages/backend/src/server/assets/cache-expired.png create mode 100644 packages/backend/src/server/assets/dummy.png create mode 100644 packages/backend/src/server/assets/not-an-image.png create mode 100644 packages/backend/src/server/assets/thumbnail-not-available.png create mode 100644 packages/backend/src/server/assets/tombstone.png delete mode 100644 packages/backend/src/server/file/assets/bad-egg.png delete mode 100644 packages/backend/src/server/file/assets/cache-expired.png delete mode 100644 packages/backend/src/server/file/assets/dummy.png delete mode 100644 packages/backend/src/server/file/assets/not-an-image.png delete mode 100644 packages/backend/src/server/file/assets/thumbnail-not-available.png delete mode 100644 packages/backend/src/server/file/assets/tombstone.png delete mode 100644 packages/backend/src/server/file/index.ts delete mode 100644 packages/backend/src/server/file/send-drive-file.ts delete mode 100644 packages/backend/src/server/index.ts delete mode 100644 packages/backend/src/server/nodeinfo.ts delete mode 100644 packages/backend/src/server/proxy/index.ts delete mode 100644 packages/backend/src/server/proxy/proxy-media.ts create mode 100644 packages/backend/src/server/web/ClientServerService.ts create mode 100644 packages/backend/src/server/web/FeedService.ts create mode 100644 packages/backend/src/server/web/UrlPreviewService.ts delete mode 100644 packages/backend/src/server/web/feed.ts delete mode 100644 packages/backend/src/server/web/index.ts delete mode 100644 packages/backend/src/server/web/manifest.ts delete mode 100644 packages/backend/src/server/web/url-preview.ts delete mode 100644 packages/backend/src/server/well-known.ts (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts new file mode 100644 index 0000000000..281da5175d --- /dev/null +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -0,0 +1,584 @@ +import { Inject, Injectable } from '@nestjs/common'; +import Router from '@koa/router'; +import json from 'koa-json-body'; +import httpSignature from '@peertube/http-signature'; +import { Brackets, In, IsNull, LessThan, Not } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import { EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; +import * as url from '@/misc/prelude/url.js'; +import { Config } from '@/config.js'; +import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { QueueService } from '@/core/QueueService.js'; +import type { ILocalUser, User } from '@/models/entities/User.js'; +import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; +import type { Following } from '@/models/entities/Following.js'; +import { countIf } from '@/misc/prelude/array.js'; +import type { Note } from '@/models/entities/Note.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import type { FindOptionsWhere } from 'typeorm'; + +const ACTIVITY_JSON = 'application/activity+json; charset=utf-8'; +const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8'; + +@Injectable() +export class ActivityPubServerService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.noteReactionsRepository) + private noteReactionsRepository: NoteReactionsRepository, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + + @Inject(DI.userNotePiningsRepository) + private userNotePiningsRepository: UserNotePiningsRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private utilityService: UtilityService, + private userEntityService: UserEntityService, + private apRendererService: ApRendererService, + private queueService: QueueService, + private userKeypairStoreService: UserKeypairStoreService, + private queryService: QueryService, + ) { + } + + #setResponseType(ctx: Router.RouterContext) { + const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON); + if (accept === LD_JSON) { + ctx.response.type = LD_JSON; + } else { + ctx.response.type = ACTIVITY_JSON; + } + } + + /** + * Pack Create or Announce Activity + * @param note Note + */ + async #packActivity(note: Note): Promise { + if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { + const renote = await Notes.findOneByOrFail({ id: note.renoteId }); + return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note); + } + + return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note); + } + + #inbox(ctx: Router.RouterContext) { + let signature; + + try { + signature = httpSignature.parseRequest(ctx.req, { 'headers': [] }); + } catch (e) { + ctx.status = 401; + return; + } + + this.queueService.inbox(ctx.request.body, signature); + + ctx.status = 202; + } + + async #followers(ctx: Router.RouterContext) { + const userId = ctx.params.user; + + const cursor = ctx.request.query.cursor; + if (cursor != null && typeof cursor !== 'string') { + ctx.status = 400; + return; + } + + const page = ctx.request.query.page === 'true'; + + const user = await this.usersRepository.findOneBy({ + id: userId, + host: IsNull(), + }); + + if (user == null) { + ctx.status = 404; + return; + } + + //#region Check ff visibility + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + if (profile.ffVisibility === 'private') { + ctx.status = 403; + ctx.set('Cache-Control', 'public, max-age=30'); + return; + } else if (profile.ffVisibility === 'followers') { + ctx.status = 403; + ctx.set('Cache-Control', 'public, max-age=30'); + return; + } + //#endregion + + const limit = 10; + const partOf = `${this.config.url}/users/${userId}/followers`; + + if (page) { + const query = { + followeeId: user.id, + } as FindOptionsWhere; + + // カーソルが指定されている場合 + if (cursor) { + query.id = LessThan(cursor); + } + + // Get followers + const followings = await this.followingsRepository.find({ + where: query, + take: limit + 1, + order: { id: -1 }, + }); + + // 「次のページ」があるかどうか + const inStock = followings.length === limit + 1; + if (inStock) followings.pop(); + + const renderedFollowers = await Promise.all(followings.map(following => this.apRendererService.renderFollowUser(following.followerId))); + const rendered = this.apRendererService.renderOrderedCollectionPage( + `${partOf}?${url.query({ + page: 'true', + cursor, + })}`, + user.followersCount, renderedFollowers, partOf, + undefined, + inStock ? `${partOf}?${url.query({ + page: 'true', + cursor: followings[followings.length - 1].id, + })}` : undefined, + ); + + ctx.body = this.apRendererService.renderActivity(rendered); + this.#setResponseType(ctx); + } else { + // index page + const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`); + ctx.body = this.apRendererService.renderActivity(rendered); + ctx.set('Cache-Control', 'public, max-age=180'); + this.#setResponseType(ctx); + } + } + + async #following(ctx: Router.RouterContext) { + const userId = ctx.params.user; + + const cursor = ctx.request.query.cursor; + if (cursor != null && typeof cursor !== 'string') { + ctx.status = 400; + return; + } + + const page = ctx.request.query.page === 'true'; + + const user = await this.usersRepository.findOneBy({ + id: userId, + host: IsNull(), + }); + + if (user == null) { + ctx.status = 404; + return; + } + + //#region Check ff visibility + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + if (profile.ffVisibility === 'private') { + ctx.status = 403; + ctx.set('Cache-Control', 'public, max-age=30'); + return; + } else if (profile.ffVisibility === 'followers') { + ctx.status = 403; + ctx.set('Cache-Control', 'public, max-age=30'); + return; + } + //#endregion + + const limit = 10; + const partOf = `${this.config.url}/users/${userId}/following`; + + if (page) { + const query = { + followerId: user.id, + } as FindOptionsWhere; + + // カーソルが指定されている場合 + if (cursor) { + query.id = LessThan(cursor); + } + + // Get followings + const followings = await this.followingsRepository.find({ + where: query, + take: limit + 1, + order: { id: -1 }, + }); + + // 「次のページ」があるかどうか + const inStock = followings.length === limit + 1; + if (inStock) followings.pop(); + + const renderedFollowees = await Promise.all(followings.map(following => this.apRendererService.renderFollowUser(following.followeeId))); + const rendered = this.apRendererService.renderOrderedCollectionPage( + `${partOf}?${url.query({ + page: 'true', + cursor, + })}`, + user.followingCount, renderedFollowees, partOf, + undefined, + inStock ? `${partOf}?${url.query({ + page: 'true', + cursor: followings[followings.length - 1].id, + })}` : undefined, + ); + + ctx.body = this.apRendererService.renderActivity(rendered); + this.#setResponseType(ctx); + } else { + // index page + const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`); + ctx.body = this.apRendererService.renderActivity(rendered); + ctx.set('Cache-Control', 'public, max-age=180'); + this.#setResponseType(ctx); + } + } + + async #featured(ctx: Router.RouterContext) { + const userId = ctx.params.user; + + const user = await this.usersRepository.findOneBy({ + id: userId, + host: IsNull(), + }); + + if (user == null) { + ctx.status = 404; + return; + } + + const pinings = await this.userNotePiningsRepository.find({ + where: { userId: user.id }, + order: { id: 'DESC' }, + }); + + const pinnedNotes = await Promise.all(pinings.map(pining => + this.notesRepository.findOneByOrFail({ id: pining.noteId }))); + + const renderedNotes = await Promise.all(pinnedNotes.map(note => this.apRendererService.renderNote(note))); + + const rendered = this.apRendererService.renderOrderedCollection( + `${this.config.url}/users/${userId}/collections/featured`, + renderedNotes.length, undefined, undefined, renderedNotes, + ); + + ctx.body = this.apRendererService.renderActivity(rendered); + ctx.set('Cache-Control', 'public, max-age=180'); + this.#setResponseType(ctx); + } + + async #outbox(ctx: Router.RouterContext) { + const userId = ctx.params.user; + + const sinceId = ctx.request.query.since_id; + if (sinceId != null && typeof sinceId !== 'string') { + ctx.status = 400; + return; + } + + const untilId = ctx.request.query.until_id; + if (untilId != null && typeof untilId !== 'string') { + ctx.status = 400; + return; + } + + const page = ctx.request.query.page === 'true'; + + if (countIf(x => x != null, [sinceId, untilId]) > 1) { + ctx.status = 400; + return; + } + + const user = await this.usersRepository.findOneBy({ + id: userId, + host: IsNull(), + }); + + if (user == null) { + ctx.status = 404; + return; + } + + const limit = 20; + const partOf = `${this.config.url}/users/${userId}/outbox`; + + if (page) { + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), sinceId, untilId) + .andWhere('note.userId = :userId', { userId: user.id }) + .andWhere(new Brackets(qb => { qb + .where('note.visibility = \'public\'') + .orWhere('note.visibility = \'home\''); + })) + .andWhere('note.localOnly = FALSE'); + + const notes = await query.take(limit).getMany(); + + if (sinceId) notes.reverse(); + + const activities = await Promise.all(notes.map(note => this.#packActivity(note))); + const rendered = this.apRendererService.renderOrderedCollectionPage( + `${partOf}?${url.query({ + page: 'true', + since_id: sinceId, + until_id: untilId, + })}`, + user.notesCount, activities, partOf, + notes.length ? `${partOf}?${url.query({ + page: 'true', + since_id: notes[0].id, + })}` : undefined, + notes.length ? `${partOf}?${url.query({ + page: 'true', + until_id: notes[notes.length - 1].id, + })}` : undefined, + ); + + ctx.body = this.apRendererService.renderActivity(rendered); + this.#setResponseType(ctx); + } else { + // index page + const rendered = this.apRendererService.renderOrderedCollection(partOf, user.notesCount, + `${partOf}?page=true`, + `${partOf}?page=true&since_id=000000000000000000000000`, + ); + ctx.body = this.apRendererService.renderActivity(rendered); + ctx.set('Cache-Control', 'public, max-age=180'); + this.#setResponseType(ctx); + } + } + + async #userInfo(ctx: Router.RouterContext, user: User | null) { + if (user == null) { + ctx.status = 404; + return; + } + + ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderPerson(user as ILocalUser)); + ctx.set('Cache-Control', 'public, max-age=180'); + this.#setResponseType(ctx); + } + + public createRouter() { + // Init router + const router = new Router(); + + //#region Routing + function isActivityPubReq(ctx: Router.RouterContext) { + ctx.response.vary('Accept'); + const accepted = ctx.accepts('html', ACTIVITY_JSON, LD_JSON); + return typeof accepted === 'string' && !accepted.match(/html/); + } + + // inbox + router.post('/inbox', json(), ctx => this.#inbox(ctx)); + router.post('/users/:user/inbox', json(), ctx => this.#inbox(ctx)); + + // note + router.get('/notes/:note', async (ctx, next) => { + if (!isActivityPubReq(ctx)) return await next(); + + const note = await this.notesRepository.findOneBy({ + id: ctx.params.note, + visibility: In(['public' as const, 'home' as const]), + localOnly: false, + }); + + if (note == null) { + ctx.status = 404; + return; + } + + // リモートだったらリダイレクト + if (note.userHost != null) { + if (note.uri == null || this.utilityService.isSelfHost(note.userHost)) { + ctx.status = 500; + return; + } + ctx.redirect(note.uri); + return; + } + + ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderNote(note, false)); + ctx.set('Cache-Control', 'public, max-age=180'); + this.#setResponseType(ctx); + }); + + // note activity + router.get('/notes/:note/activity', async ctx => { + const note = await this.notesRepository.findOneBy({ + id: ctx.params.note, + userHost: IsNull(), + visibility: In(['public' as const, 'home' as const]), + localOnly: false, + }); + + if (note == null) { + ctx.status = 404; + return; + } + + ctx.body = this.apRendererService.renderActivity(await this.#packActivity(note)); + ctx.set('Cache-Control', 'public, max-age=180'); + this.#setResponseType(ctx); + }); + + // outbox + router.get('/users/:user/outbox', (ctx) => this.#outbox(ctx)); + + // followers + router.get('/users/:user/followers', (ctx) => this.#followers(ctx)); + + // following + router.get('/users/:user/following', (ctx) => this.#following(ctx)); + + // featured + router.get('/users/:user/collections/featured', (ctx) => this.#featured(ctx)); + + // publickey + router.get('/users/:user/publickey', async ctx => { + const userId = ctx.params.user; + + const user = await this.usersRepository.findOneBy({ + id: userId, + host: IsNull(), + }); + + if (user == null) { + ctx.status = 404; + return; + } + + const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); + + if (this.userEntityService.isLocalUser(user)) { + ctx.body = this.apRendererService.renderActivity(this.apRendererService.renderKey(user, keypair)); + ctx.set('Cache-Control', 'public, max-age=180'); + this.#setResponseType(ctx); + } else { + ctx.status = 400; + } + }); + + router.get('/users/:user', async (ctx, next) => { + if (!isActivityPubReq(ctx)) return await next(); + + const userId = ctx.params.user; + + const user = await this.usersRepository.findOneBy({ + id: userId, + host: IsNull(), + isSuspended: false, + }); + + await this.#userInfo(ctx, user); + }); + + router.get('/@:user', async (ctx, next) => { + if (!isActivityPubReq(ctx)) return await next(); + + const user = await this.usersRepository.findOneBy({ + usernameLower: ctx.params.user.toLowerCase(), + host: IsNull(), + isSuspended: false, + }); + + await this.#userInfo(ctx, user); + }); + //#endregion + + // emoji + router.get('/emojis/:emoji', async ctx => { + const emoji = await this.emojisRepository.findOneBy({ + host: IsNull(), + name: ctx.params.emoji, + }); + + if (emoji == null) { + ctx.status = 404; + return; + } + + ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderEmoji(emoji)); + ctx.set('Cache-Control', 'public, max-age=180'); + this.#setResponseType(ctx); + }); + + // like + router.get('/likes/:like', async ctx => { + const reaction = await this.noteReactionsRepository.findOneBy({ id: ctx.params.like }); + + if (reaction == null) { + ctx.status = 404; + return; + } + + const note = await this.notesRepository.findOneBy({ id: reaction.noteId }); + + if (note == null) { + ctx.status = 404; + return; + } + + ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderLike(reaction, note)); + ctx.set('Cache-Control', 'public, max-age=180'); + this.#setResponseType(ctx); + }); + + // follow + router.get('/follows/:follower/:followee', async ctx => { + // This may be used before the follow is completed, so we do not + // check if the following exists. + + const [follower, followee] = await Promise.all([ + this.usersRepository.findOneBy({ + id: ctx.params.follower, + host: IsNull(), + }), + this.usersRepository.findOneBy({ + id: ctx.params.followee, + host: Not(IsNull()), + }), + ]); + + if (follower == null || followee == null) { + ctx.status = 404; + return; + } + + ctx.body = this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee)); + ctx.set('Cache-Control', 'public, max-age=180'); + this.#setResponseType(ctx); + }); + + return router; + } +} diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts new file mode 100644 index 0000000000..0f4246bfd1 --- /dev/null +++ b/packages/backend/src/server/FileServerService.ts @@ -0,0 +1,178 @@ +import * as fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; +import { Inject, Injectable } from '@nestjs/common'; +import Koa from 'koa'; +import cors from '@koa/cors'; +import Router from '@koa/router'; +import send from 'koa-send'; +import rename from 'rename'; +import { Config } from '@/config.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; +import { createTemp } from '@/misc/create-temp.js'; +import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; +import { StatusError } from '@/misc/status-error.js'; +import Logger from '@/logger.js'; +import { DownloadService } from '@/core/DownloadService.js'; +import { ImageProcessingService } from '@/core/ImageProcessingService.js'; +import { VideoProcessingService } from '@/core/VideoProcessingService.js'; +import { InternalStorageService } from '@/core/InternalStorageService.js'; +import { contentDisposition } from '@/misc/content-disposition.js'; +import { FileInfoService } from '@/core/FileInfoService.js'; + +const serverLogger = new Logger('server', 'gray', false); + +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); + +const assets = `${_dirname}/../../server/file/assets/`; + +const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void => { + serverLogger.error(e); + ctx.status = 500; + ctx.set('Cache-Control', 'max-age=300'); +}; + +@Injectable() +export class FileServerService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private fileInfoService: FileInfoService, + private downloadService: DownloadService, + private imageProcessingService: ImageProcessingService, + private videoProcessingService: VideoProcessingService, + private internalStorageService: InternalStorageService, + ) { + } + + public createServer() { + const app = new Koa(); + app.use(cors()); + app.use(async (ctx, next) => { + ctx.set('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); + await next(); + }); + + // Init router + const router = new Router(); + + router.get('/app-default.jpg', ctx => { + const file = fs.createReadStream(`${_dirname}/assets/dummy.png`); + ctx.body = file; + ctx.set('Content-Type', 'image/jpeg'); + ctx.set('Cache-Control', 'max-age=31536000, immutable'); + }); + + router.get('/:key', ctx => this.#sendDriveFile(ctx)); + router.get('/:key/(.*)', ctx => this.#sendDriveFile(ctx)); + + // Register router + app.use(router.routes()); + + return app; + } + + async #sendDriveFile(ctx: Koa.Context) { + const key = ctx.params.key; + + // Fetch drive file + const file = await this.driveFilesRepository.createQueryBuilder('file') + .where('file.accessKey = :accessKey', { accessKey: key }) + .orWhere('file.thumbnailAccessKey = :thumbnailAccessKey', { thumbnailAccessKey: key }) + .orWhere('file.webpublicAccessKey = :webpublicAccessKey', { webpublicAccessKey: key }) + .getOne(); + + if (file == null) { + ctx.status = 404; + ctx.set('Cache-Control', 'max-age=86400'); + await send(ctx as any, '/dummy.png', { root: assets }); + return; + } + + const isThumbnail = file.thumbnailAccessKey === key; + const isWebpublic = file.webpublicAccessKey === key; + + if (!file.storedInternal) { + if (file.isLink && file.uri) { // 期限切れリモートファイル + const [path, cleanup] = await createTemp(); + + try { + await this.downloadService.downloadUrl(file.uri, path); + + const { mime, ext } = await this.fileInfoService.detectType(path); + + const convertFile = async () => { + if (isThumbnail) { + if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(mime)) { + return await this.imageProcessingService.convertToWebp(path, 498, 280); + } else if (mime.startsWith('video/')) { + return await this.videoProcessingService.generateVideoThumbnail(path); + } + } + + if (isWebpublic) { + if (['image/svg+xml'].includes(mime)) { + return await this.imageProcessingService.convertToPng(path, 2048, 2048); + } + } + + return { + data: fs.readFileSync(path), + ext, + type: mime, + }; + }; + + const image = await convertFile(); + ctx.body = image.data; + ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream'); + ctx.set('Cache-Control', 'max-age=31536000, immutable'); + } catch (err) { + serverLogger.error(`${err}`); + + if (err instanceof StatusError && err.isClientError) { + ctx.status = err.statusCode; + ctx.set('Cache-Control', 'max-age=86400'); + } else { + ctx.status = 500; + ctx.set('Cache-Control', 'max-age=300'); + } + } finally { + cleanup(); + } + return; + } + + ctx.status = 204; + ctx.set('Cache-Control', 'max-age=86400'); + return; + } + + if (isThumbnail || isWebpublic) { + const { mime, ext } = await this.fileInfoService.detectType(this.internalStorageService.resolvePath(key)); + const filename = rename(file.name, { + suffix: isThumbnail ? '-thumb' : '-web', + extname: ext ? `.${ext}` : undefined, + }).toString(); + + ctx.body = this.internalStorageService.read(key); + ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : 'application/octet-stream'); + ctx.set('Cache-Control', 'max-age=31536000, immutable'); + ctx.set('Content-Disposition', contentDisposition('inline', filename)); + } else { + const readable = this.internalStorageService.read(file.accessKey!); + readable.on('error', commonReadableHandlerGenerator(ctx)); + ctx.body = readable; + ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.type) ? file.type : 'application/octet-stream'); + ctx.set('Cache-Control', 'max-age=31536000, immutable'); + ctx.set('Content-Disposition', contentDisposition('inline', file.name)); + } + } +} + diff --git a/packages/backend/src/server/MediaProxyServerService.ts b/packages/backend/src/server/MediaProxyServerService.ts new file mode 100644 index 0000000000..b9cee38f0a --- /dev/null +++ b/packages/backend/src/server/MediaProxyServerService.ts @@ -0,0 +1,137 @@ +import * as fs from 'node:fs'; +import { Inject, Injectable } from '@nestjs/common'; +import Koa from 'koa'; +import cors from '@koa/cors'; +import Router from '@koa/router'; +import sharp from 'sharp'; +import { DI } from '@/di-symbols.js'; +import { Config } from '@/config.js'; +import { isMimeImage } from '@/misc/is-mime-image.js'; +import { createTemp } from '@/misc/create-temp.js'; +import { DownloadService } from '@/core/DownloadService.js'; +import { ImageProcessingService } from '@/core/ImageProcessingService.js'; +import type { IImage } from '@/core/ImageProcessingService.js'; +import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; +import { StatusError } from '@/misc/status-error.js'; +import Logger from '@/logger.js'; +import { FileInfoService } from '@/core/FileInfoService.js'; + +const serverLogger = new Logger('server', 'gray', false); + +@Injectable() +export class MediaProxyServerService { + constructor( + @Inject(DI.config) + private config: Config, + + private fileInfoService: FileInfoService, + private downloadService: DownloadService, + private imageProcessingService: ImageProcessingService, + ) { + } + + public createServer() { + const app = new Koa(); + app.use(cors()); + app.use(async (ctx, next) => { + ctx.set('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); + await next(); + }); + + // Init router + const router = new Router(); + + router.get('/:url*', ctx => this.#handler(ctx)); + + // Register router + app.use(router.routes()); + + return app; + } + + async #handler(ctx: Koa.Context) { + const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url; + + if (typeof url !== 'string') { + ctx.status = 400; + return; + } + + // Create temp file + const [path, cleanup] = await createTemp(); + + try { + await this.downloadService.downloadUrl(url, path); + + const { mime, ext } = await this.fileInfoService.detectType(path); + const isConvertibleImage = isMimeImage(mime, 'sharp-convertible-image'); + + let image: IImage; + + if ('static' in ctx.query && isConvertibleImage) { + image = await this.imageProcessingService.convertToWebp(path, 498, 280); + } else if ('preview' in ctx.query && isConvertibleImage) { + image = await this.imageProcessingService.convertToWebp(path, 200, 200); + } else if ('badge' in ctx.query) { + if (!isConvertibleImage) { + // 画像でないなら404でお茶を濁す + throw new StatusError('Unexpected mime', 404); + } + + const mask = sharp(path) + .resize(96, 96, { + fit: 'inside', + withoutEnlargement: false, + }) + .greyscale() + .normalise() + .linear(1.75, -(128 * 1.75) + 128) // 1.75x contrast + .flatten({ background: '#000' }) + .toColorspace('b-w'); + + const stats = await mask.clone().stats(); + + if (stats.entropy < 0.1) { + // エントロピーがあまりない場合は404にする + throw new StatusError('Skip to provide badge', 404); + } + + const data = sharp({ + create: { width: 96, height: 96, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } }, + }) + .pipelineColorspace('b-w') + .boolean(await mask.png().toBuffer(), 'eor'); + + image = { + data: await data.png().toBuffer(), + ext: 'png', + type: 'image/png', + }; + } else if (mime === 'image/svg+xml') { + image = await this.imageProcessingService.convertToWebp(path, 2048, 2048, 1); + } else if (!mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(mime)) { + throw new StatusError('Rejected type', 403, 'Rejected type'); + } else { + image = { + data: fs.readFileSync(path), + ext, + type: mime, + }; + } + + ctx.set('Content-Type', image.type); + ctx.set('Cache-Control', 'max-age=31536000, immutable'); + ctx.body = image.data; + } catch (err) { + serverLogger.error(`${err}`); + + if (err instanceof StatusError && (err.statusCode === 302 || err.isClientError)) { + ctx.status = err.statusCode; + } else { + ctx.status = 500; + } + } finally { + cleanup(); + } + } +} diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts new file mode 100644 index 0000000000..04a5f1484b --- /dev/null +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -0,0 +1,129 @@ +import { Inject, Injectable } from '@nestjs/common'; +import Router from '@koa/router'; +import { IsNull, MoreThan } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import { NotesRepository, UsersRepository } from '@/models/index.js'; +import { Config } from '@/config.js'; +import { MetaService } from '@/core/MetaService.js'; +import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; +import { Cache } from '@/misc/cache.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; + +const nodeinfo2_1path = '/nodeinfo/2.1'; +const nodeinfo2_0path = '/nodeinfo/2.0'; + +@Injectable() +export class NodeinfoServerService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private userEntityService: UserEntityService, + private metaService: MetaService, + ) { + } + + public getLinks() { + return [/* (awaiting release) { + rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1', + href: config.url + nodeinfo2_1path + }, */{ + rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', + href: this.config.url + nodeinfo2_0path, + }]; + } + + public createRouter() { + const router = new Router(); + + const nodeinfo2 = async () => { + const now = Date.now(); + const [ + meta, + total, + activeHalfyear, + activeMonth, + localPosts, + ] = await Promise.all([ + this.metaService.fetch(true), + this.usersRepository.count({ where: { host: IsNull() } }), + this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 15552000000)) } }), + this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 2592000000)) } }), + this.notesRepository.count({ where: { userHost: IsNull() } }), + ]); + + const proxyAccount = meta.proxyAccountId ? await this.userEntityService.pack(meta.proxyAccountId).catch(() => null) : null; + + return { + software: { + name: 'misskey', + version: this.config.version, + repository: meta.repositoryUrl, + }, + protocols: ['activitypub'], + services: { + inbound: [] as string[], + outbound: ['atom1.0', 'rss2.0'], + }, + openRegistrations: !meta.disableRegistration, + usage: { + users: { total, activeHalfyear, activeMonth }, + localPosts, + localComments: 0, + }, + metadata: { + nodeName: meta.name, + nodeDescription: meta.description, + maintainer: { + name: meta.maintainerName, + email: meta.maintainerEmail, + }, + langs: meta.langs, + tosUrl: meta.ToSUrl, + repositoryUrl: meta.repositoryUrl, + feedbackUrl: meta.feedbackUrl, + disableRegistration: meta.disableRegistration, + disableLocalTimeline: meta.disableLocalTimeline, + disableGlobalTimeline: meta.disableGlobalTimeline, + emailRequiredForSignup: meta.emailRequiredForSignup, + enableHcaptcha: meta.enableHcaptcha, + enableRecaptcha: meta.enableRecaptcha, + maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, + enableTwitterIntegration: meta.enableTwitterIntegration, + enableGithubIntegration: meta.enableGithubIntegration, + enableDiscordIntegration: meta.enableDiscordIntegration, + enableEmail: meta.enableEmail, + enableServiceWorker: meta.enableServiceWorker, + proxyAccountName: proxyAccount ? proxyAccount.username : null, + themeColor: meta.themeColor ?? '#86b300', + }, + }; + }; + + const cache = new Cache>>(1000 * 60 * 10); + + router.get(nodeinfo2_1path, async ctx => { + const base = await cache.fetch(null, () => nodeinfo2()); + + ctx.body = { version: '2.1', ...base }; + ctx.set('Cache-Control', 'public, max-age=600'); + }); + + router.get(nodeinfo2_0path, async ctx => { + const base = await cache.fetch(null, () => nodeinfo2()); + + delete base.software.repository; + + ctx.body = { version: '2.0', ...base }; + ctx.set('Cache-Control', 'public, max-age=600'); + }); + + return router; + } +} diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts new file mode 100644 index 0000000000..f05eda1cb8 --- /dev/null +++ b/packages/backend/src/server/ServerModule.ts @@ -0,0 +1,92 @@ +import { Module } from '@nestjs/common'; +import { EndpointsModule } from '@/server/api/EndpointsModule.js'; +import { CoreModule } from '@/core/CoreModule.js'; +import { ApiCallService } from './api/ApiCallService.js'; +import { FileServerService } from './FileServerService.js'; +import { MediaProxyServerService } from './MediaProxyServerService.js'; +import { NodeinfoServerService } from './NodeinfoServerService.js'; +import { ServerService } from './ServerService.js'; +import { WellKnownServerService } from './WellKnownServerService.js'; +import { GetterService } from './api/common/GetterService.js'; +import { DiscordServerService } from './api/integration/DiscordServerService.js'; +import { GithubServerService } from './api/integration/GithubServerService.js'; +import { TwitterServerService } from './api/integration/TwitterServerService.js'; +import { ChannelsService } from './api/stream/ChannelsService.js'; +import { ActivityPubServerService } from './ActivityPubServerService.js'; +import { ApiLoggerService } from './api/ApiLoggerService.js'; +import { ApiServerService } from './api/ApiServerService.js'; +import { AuthenticateService } from './api/AuthenticateService.js'; +import { RateLimiterService } from './api/RateLimiterService.js'; +import { SigninApiService } from './api/SigninApiService.js'; +import { SigninService } from './api/SigninService.js'; +import { SignupApiService } from './api/SignupApiService.js'; +import { StreamingApiServerService } from './api/StreamingApiServerService.js'; +import { ClientServerService } from './web/ClientServerService.js'; +import { FeedService } from './web/FeedService.js'; +import { UrlPreviewService } from './web/UrlPreviewService.js'; +import { MainChannelService } from './api/stream/channels/main.js'; +import { AdminChannelService } from './api/stream/channels/admin.js'; +import { AntennaChannelService } from './api/stream/channels/antenna.js'; +import { ChannelChannelService } from './api/stream/channels/channel.js'; +import { DriveChannelService } from './api/stream/channels/drive.js'; +import { GlobalTimelineChannelService } from './api/stream/channels/global-timeline.js'; +import { HashtagChannelService } from './api/stream/channels/hashtag.js'; +import { HomeTimelineChannelService } from './api/stream/channels/home-timeline.js'; +import { HybridTimelineChannelService } from './api/stream/channels/hybrid-timeline.js'; +import { LocalTimelineChannelService } from './api/stream/channels/local-timeline.js'; +import { MessagingIndexChannelService } from './api/stream/channels/messaging-index.js'; +import { MessagingChannelService } from './api/stream/channels/messaging.js'; +import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js'; +import { ServerStatsChannelService } from './api/stream/channels/server-stats.js'; +import { UserListChannelService } from './api/stream/channels/user-list.js'; + +@Module({ + imports: [ + EndpointsModule, + CoreModule, + ], + providers: [ + ClientServerService, + FeedService, + UrlPreviewService, + ActivityPubServerService, + FileServerService, + MediaProxyServerService, + NodeinfoServerService, + ServerService, + WellKnownServerService, + GetterService, + DiscordServerService, + GithubServerService, + TwitterServerService, + ChannelsService, + ApiCallService, + ApiLoggerService, + ApiServerService, + AuthenticateService, + RateLimiterService, + SigninApiService, + SigninService, + SignupApiService, + StreamingApiServerService, + MainChannelService, + AdminChannelService, + AntennaChannelService, + ChannelChannelService, + DriveChannelService, + GlobalTimelineChannelService, + HashtagChannelService, + HomeTimelineChannelService, + HybridTimelineChannelService, + LocalTimelineChannelService, + MessagingIndexChannelService, + MessagingChannelService, + QueueStatsChannelService, + ServerStatsChannelService, + UserListChannelService, + ], + exports: [ + ServerService, + ], +}) +export class ServerModule {} diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts new file mode 100644 index 0000000000..2ee7135c2a --- /dev/null +++ b/packages/backend/src/server/ServerService.ts @@ -0,0 +1,177 @@ +import cluster from 'node:cluster'; +import * as fs from 'node:fs'; +import * as http from 'node:http'; +import { Inject, Injectable } from '@nestjs/common'; +import Koa from 'koa'; +import Router from '@koa/router'; +import mount from 'koa-mount'; +import koaLogger from 'koa-logger'; +import * as slow from 'koa-slow'; +import { IsNull } from 'typeorm'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { Config } from '@/config.js'; +import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; +import Logger from '@/logger.js'; +import { envOption } from '@/env.js'; +import * as Acct from '@/misc/acct.js'; +import { genIdenticon } from '@/misc/gen-identicon.js'; +import { createTemp } from '@/misc/create-temp.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ActivityPubServerService } from './ActivityPubServerService.js'; +import { NodeinfoServerService } from './NodeinfoServerService.js'; +import { ApiServerService } from './api/ApiServerService.js'; +import { StreamingApiServerService } from './api/StreamingApiServerService.js'; +import { WellKnownServerService } from './WellKnownServerService.js'; +import { MediaProxyServerService } from './MediaProxyServerService.js'; +import { FileServerService } from './FileServerService.js'; +import { ClientServerService } from './web/ClientServerService.js'; + +const serverLogger = new Logger('server', 'gray', false); + +@Injectable() +export class ServerService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private userEntityService: UserEntityService, + private apiServerService: ApiServerService, + private streamingApiServerService: StreamingApiServerService, + private activityPubServerService: ActivityPubServerService, + private wellKnownServerService: WellKnownServerService, + private nodeinfoServerService: NodeinfoServerService, + private fileServerService: FileServerService, + private mediaProxyServerService: MediaProxyServerService, + private clientServerService: ClientServerService, + private globalEventService: GlobalEventService, + ) { + } + + public launch() { + // Init app + const koa = new Koa(); + koa.proxy = true; + + if (!['production', 'test'].includes(process.env.NODE_ENV ?? '')) { + // Logger + koa.use(koaLogger(str => { + serverLogger.info(str); + })); + + // Delay + if (envOption.slow) { + koa.use(slow({ + delay: 3000, + })); + } + } + + // HSTS + // 6months (15552000sec) + if (this.config.url.startsWith('https') && !this.config.disableHsts) { + koa.use(async (ctx, next) => { + ctx.set('strict-transport-security', 'max-age=15552000; preload'); + await next(); + }); + } + + koa.use(mount('/api', this.apiServerService.createApiServer(koa))); + koa.use(mount('/files', this.fileServerService.createServer())); + koa.use(mount('/proxy', this.mediaProxyServerService.createServer())); + + // Init router + const router = new Router(); + + // Routing + router.use(this.activityPubServerService.createRouter().routes()); + router.use(this.nodeinfoServerService.createRouter().routes()); + router.use(this.wellKnownServerService.createRouter().routes()); + + router.get('/avatar/@:acct', async ctx => { + const { username, host } = Acct.parse(ctx.params.acct); + const user = await this.usersRepository.findOne({ + where: { + usernameLower: username.toLowerCase(), + host: (host == null) || (host === this.config.host) ? IsNull() : host, + isSuspended: false, + }, + relations: ['avatar'], + }); + + if (user) { + ctx.redirect(this.userEntityService.getAvatarUrlSync(user)); + } else { + ctx.redirect('/static-assets/user-unknown.png'); + } + }); + + router.get('/identicon/:x', async ctx => { + const [temp, cleanup] = await createTemp(); + await genIdenticon(ctx.params.x, fs.createWriteStream(temp)); + ctx.set('Content-Type', 'image/png'); + ctx.body = fs.createReadStream(temp).on('close', () => cleanup()); + }); + + router.get('/verify-email/:code', async ctx => { + const profile = await this.userProfilesRepository.findOneBy({ + emailVerifyCode: ctx.params.code, + }); + + if (profile != null) { + ctx.body = 'Verify succeeded!'; + ctx.status = 200; + + await this.userProfilesRepository.update({ userId: profile.userId }, { + emailVerified: true, + emailVerifyCode: null, + }); + + this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, { + detail: true, + includeSecrets: true, + })); + } else { + ctx.status = 404; + } + }); + + // Register router + koa.use(router.routes()); + + koa.use(mount(this.clientServerService.createApp())); + + const server = http.createServer(koa.callback()); + + this.streamingApiServerService.attachStreamingApi(server); + + server.on('error', e => { + switch ((e as any).code) { + case 'EACCES': + serverLogger.error(`You do not have permission to listen on port ${this.config.port}.`); + break; + case 'EADDRINUSE': + serverLogger.error(`Port ${this.config.port} is already in use by another process.`); + break; + default: + serverLogger.error(e); + break; + } + + if (cluster.isWorker) { + process.send!('listenFailed'); + } else { + // disableClustering + process.exit(1); + } + }); + + server.listen(this.config.port); + } +} diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts new file mode 100644 index 0000000000..7f827d439b --- /dev/null +++ b/packages/backend/src/server/WellKnownServerService.ts @@ -0,0 +1,168 @@ +import { Inject, Injectable } from '@nestjs/common'; +import Router from '@koa/router'; +import { IsNull, MoreThan } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import { UsersRepository } from '@/models/index.js'; +import { Config } from '@/config.js'; +import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js'; +import type { User } from '@/models/entities/User.js'; +import * as Acct from '@/misc/acct.js'; +import { NodeinfoServerService } from './NodeinfoServerService.js'; +import type { FindOptionsWhere } from 'typeorm'; + +@Injectable() +export class WellKnownServerService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private nodeinfoServerService: NodeinfoServerService, + ) { + } + + public createRouter() { + const router = new Router(); + + const XRD = (...x: { element: string, value?: string, attributes?: Record }[]) => + `${x.map(({ element, value, attributes }) => + `<${ + Object.entries(typeof attributes === 'object' && attributes || {}).reduce((a, [k, v]) => `${a} ${k}="${escapeAttribute(v)}"`, element) + }${ + typeof value === 'string' ? `>${escapeValue(value)}`).reduce((a, c) => a + c, '')}`; + + const allPath = '/.well-known/(.*)'; + const webFingerPath = '/.well-known/webfinger'; + const jrd = 'application/jrd+json'; + const xrd = 'application/xrd+xml'; + + router.use(allPath, async (ctx, next) => { + ctx.set({ + 'Access-Control-Allow-Headers': 'Accept', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Expose-Headers': 'Vary', + }); + await next(); + }); + + router.options(allPath, async ctx => { + ctx.status = 204; + }); + + router.get('/.well-known/host-meta', async ctx => { + ctx.set('Content-Type', xrd); + ctx.body = XRD({ element: 'Link', attributes: { + rel: 'lrdd', + type: xrd, + template: `${this.config.url}${webFingerPath}?resource={uri}`, + } }); + }); + + router.get('/.well-known/host-meta.json', async ctx => { + ctx.set('Content-Type', jrd); + ctx.body = { + links: [{ + rel: 'lrdd', + type: jrd, + template: `${this.config.url}${webFingerPath}?resource={uri}`, + }], + }; + }); + + router.get('/.well-known/nodeinfo', async ctx => { + ctx.body = { links: this.nodeinfoServerService.getLinks() }; + }); + + /* TODO +router.get('/.well-known/change-password', async ctx => { +}); +*/ + + router.get(webFingerPath, async ctx => { + const fromId = (id: User['id']): FindOptionsWhere => ({ + id, + host: IsNull(), + isSuspended: false, + }); + + const generateQuery = (resource: string): FindOptionsWhere | number => + resource.startsWith(`${this.config.url.toLowerCase()}/users/`) ? + fromId(resource.split('/').pop()!) : + fromAcct(Acct.parse( + resource.startsWith(`${this.config.url.toLowerCase()}/@`) ? resource.split('/').pop()! : + resource.startsWith('acct:') ? resource.slice('acct:'.length) : + resource)); + + const fromAcct = (acct: Acct.Acct): FindOptionsWhere | number => + !acct.host || acct.host === this.config.host.toLowerCase() ? { + usernameLower: acct.username, + host: IsNull(), + isSuspended: false, + } : 422; + + if (typeof ctx.query.resource !== 'string') { + ctx.status = 400; + return; + } + + const query = generateQuery(ctx.query.resource.toLowerCase()); + + if (typeof query === 'number') { + ctx.status = query; + return; + } + + const user = await this.usersRepository.findOneBy(query); + + if (user == null) { + ctx.status = 404; + return; + } + + const subject = `acct:${user.username}@${this.config.host}`; + const self = { + rel: 'self', + type: 'application/activity+json', + href: `${this.config.url}/users/${user.id}`, + }; + const profilePage = { + rel: 'http://webfinger.net/rel/profile-page', + type: 'text/html', + href: `${this.config.url}/@${user.username}`, + }; + const subscribe = { + rel: 'http://ostatus.org/schema/1.0/subscribe', + template: `${this.config.url}/authorize-follow?acct={uri}`, + }; + + if (ctx.accepts(jrd, xrd) === xrd) { + ctx.body = XRD( + { element: 'Subject', value: subject }, + { element: 'Link', attributes: self }, + { element: 'Link', attributes: profilePage }, + { element: 'Link', attributes: subscribe }); + ctx.type = xrd; + } else { + ctx.body = { + subject, + links: [self, profilePage, subscribe], + }; + ctx.type = jrd; + } + + ctx.vary('Accept'); + ctx.set('Cache-Control', 'public, max-age=180'); + }); + + // Return 404 for other .well-known + router.all(allPath, async ctx => { + ctx.status = 404; + }); + + return router; + } +} diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts deleted file mode 100644 index cd5f917c40..0000000000 --- a/packages/backend/src/server/activitypub.ts +++ /dev/null @@ -1,254 +0,0 @@ -import Router from '@koa/router'; -import json from 'koa-json-body'; -import httpSignature from '@peertube/http-signature'; - -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderNote from '@/remote/activitypub/renderer/note.js'; -import renderKey from '@/remote/activitypub/renderer/key.js'; -import { renderPerson } from '@/remote/activitypub/renderer/person.js'; -import renderEmoji from '@/remote/activitypub/renderer/emoji.js'; -import Outbox, { packActivity } from './activitypub/outbox.js'; -import Followers from './activitypub/followers.js'; -import Following from './activitypub/following.js'; -import Featured from './activitypub/featured.js'; -import { inbox as processInbox } from '@/queue/index.js'; -import { isSelfHost } from '@/misc/convert-host.js'; -import { Notes, Users, Emojis, NoteReactions } from '@/models/index.js'; -import { ILocalUser, User } from '@/models/entities/user.js'; -import { In, IsNull, Not } from 'typeorm'; -import { renderLike } from '@/remote/activitypub/renderer/like.js'; -import { getUserKeypair } from '@/misc/keypair-store.js'; -import renderFollow from '@/remote/activitypub/renderer/follow.js'; - -// Init router -const router = new Router(); - -//#region Routing - -function inbox(ctx: Router.RouterContext) { - let signature; - - try { - signature = httpSignature.parseRequest(ctx.req, { 'headers': [] }); - } catch (e) { - ctx.status = 401; - return; - } - - processInbox(ctx.request.body, signature); - - ctx.status = 202; -} - -const ACTIVITY_JSON = 'application/activity+json; charset=utf-8'; -const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8'; - -function isActivityPubReq(ctx: Router.RouterContext) { - ctx.response.vary('Accept'); - const accepted = ctx.accepts('html', ACTIVITY_JSON, LD_JSON); - return typeof accepted === 'string' && !accepted.match(/html/); -} - -export function setResponseType(ctx: Router.RouterContext) { - const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON); - if (accept === LD_JSON) { - ctx.response.type = LD_JSON; - } else { - ctx.response.type = ACTIVITY_JSON; - } -} - -// inbox -router.post('/inbox', json(), inbox); -router.post('/users/:user/inbox', json(), inbox); - -// note -router.get('/notes/:note', async (ctx, next) => { - if (!isActivityPubReq(ctx)) return await next(); - - const note = await Notes.findOneBy({ - id: ctx.params.note, - visibility: In(['public' as const, 'home' as const]), - localOnly: false, - }); - - if (note == null) { - ctx.status = 404; - return; - } - - // リモートだったらリダイレクト - if (note.userHost != null) { - if (note.uri == null || isSelfHost(note.userHost)) { - ctx.status = 500; - return; - } - ctx.redirect(note.uri); - return; - } - - ctx.body = renderActivity(await renderNote(note, false)); - ctx.set('Cache-Control', 'public, max-age=180'); - setResponseType(ctx); -}); - -// note activity -router.get('/notes/:note/activity', async ctx => { - const note = await Notes.findOneBy({ - id: ctx.params.note, - userHost: IsNull(), - visibility: In(['public' as const, 'home' as const]), - localOnly: false, - }); - - if (note == null) { - ctx.status = 404; - return; - } - - ctx.body = renderActivity(await packActivity(note)); - ctx.set('Cache-Control', 'public, max-age=180'); - setResponseType(ctx); -}); - -// outbox -router.get('/users/:user/outbox', Outbox); - -// followers -router.get('/users/:user/followers', Followers); - -// following -router.get('/users/:user/following', Following); - -// featured -router.get('/users/:user/collections/featured', Featured); - -// publickey -router.get('/users/:user/publickey', async ctx => { - const userId = ctx.params.user; - - const user = await Users.findOneBy({ - id: userId, - host: IsNull(), - }); - - if (user == null) { - ctx.status = 404; - return; - } - - const keypair = await getUserKeypair(user.id); - - if (Users.isLocalUser(user)) { - ctx.body = renderActivity(renderKey(user, keypair)); - ctx.set('Cache-Control', 'public, max-age=180'); - setResponseType(ctx); - } else { - ctx.status = 400; - } -}); - -// user -async function userInfo(ctx: Router.RouterContext, user: User | null) { - if (user == null) { - ctx.status = 404; - return; - } - - ctx.body = renderActivity(await renderPerson(user as ILocalUser)); - ctx.set('Cache-Control', 'public, max-age=180'); - setResponseType(ctx); -} - -router.get('/users/:user', async (ctx, next) => { - if (!isActivityPubReq(ctx)) return await next(); - - const userId = ctx.params.user; - - const user = await Users.findOneBy({ - id: userId, - host: IsNull(), - isSuspended: false, - }); - - await userInfo(ctx, user); -}); - -router.get('/@:user', async (ctx, next) => { - if (!isActivityPubReq(ctx)) return await next(); - - const user = await Users.findOneBy({ - usernameLower: ctx.params.user.toLowerCase(), - host: IsNull(), - isSuspended: false, - }); - - await userInfo(ctx, user); -}); -//#endregion - -// emoji -router.get('/emojis/:emoji', async ctx => { - const emoji = await Emojis.findOneBy({ - host: IsNull(), - name: ctx.params.emoji, - }); - - if (emoji == null) { - ctx.status = 404; - return; - } - - ctx.body = renderActivity(await renderEmoji(emoji)); - ctx.set('Cache-Control', 'public, max-age=180'); - setResponseType(ctx); -}); - -// like -router.get('/likes/:like', async ctx => { - const reaction = await NoteReactions.findOneBy({ id: ctx.params.like }); - - if (reaction == null) { - ctx.status = 404; - return; - } - - const note = await Notes.findOneBy({ id: reaction.noteId }); - - if (note == null) { - ctx.status = 404; - return; - } - - ctx.body = renderActivity(await renderLike(reaction, note)); - ctx.set('Cache-Control', 'public, max-age=180'); - setResponseType(ctx); -}); - -// follow -router.get('/follows/:follower/:followee', async ctx => { - // This may be used before the follow is completed, so we do not - // check if the following exists. - - const [follower, followee] = await Promise.all([ - Users.findOneBy({ - id: ctx.params.follower, - host: IsNull(), - }), - Users.findOneBy({ - id: ctx.params.followee, - host: Not(IsNull()), - }), - ]); - - if (follower == null || followee == null) { - ctx.status = 404; - return; - } - - ctx.body = renderActivity(renderFollow(follower, followee)); - ctx.set('Cache-Control', 'public, max-age=180'); - setResponseType(ctx); -}); - -export default router; diff --git a/packages/backend/src/server/activitypub/featured.ts b/packages/backend/src/server/activitypub/featured.ts deleted file mode 100644 index c03fd1049f..0000000000 --- a/packages/backend/src/server/activitypub/featured.ts +++ /dev/null @@ -1,41 +0,0 @@ -import Router from '@koa/router'; -import config from '@/config/index.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; -import { setResponseType } from '../activitypub.js'; -import renderNote from '@/remote/activitypub/renderer/note.js'; -import { Users, Notes, UserNotePinings } from '@/models/index.js'; -import { IsNull } from 'typeorm'; - -export default async (ctx: Router.RouterContext) => { - const userId = ctx.params.user; - - const user = await Users.findOneBy({ - id: userId, - host: IsNull(), - }); - - if (user == null) { - ctx.status = 404; - return; - } - - const pinings = await UserNotePinings.find({ - where: { userId: user.id }, - order: { id: 'DESC' }, - }); - - const pinnedNotes = await Promise.all(pinings.map(pining => - Notes.findOneByOrFail({ id: pining.noteId }))); - - const renderedNotes = await Promise.all(pinnedNotes.map(note => renderNote(note))); - - const rendered = renderOrderedCollection( - `${config.url}/users/${userId}/collections/featured`, - renderedNotes.length, undefined, undefined, renderedNotes, - ); - - ctx.body = renderActivity(rendered); - ctx.set('Cache-Control', 'public, max-age=180'); - setResponseType(ctx); -}; diff --git a/packages/backend/src/server/activitypub/followers.ts b/packages/backend/src/server/activitypub/followers.ts deleted file mode 100644 index beb48713a6..0000000000 --- a/packages/backend/src/server/activitypub/followers.ts +++ /dev/null @@ -1,95 +0,0 @@ -import Router from '@koa/router'; -import { FindOptionsWhere, IsNull, LessThan } from 'typeorm'; -import config from '@/config/index.js'; -import * as url from '@/prelude/url.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; -import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js'; -import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; -import { Users, Followings, UserProfiles } from '@/models/index.js'; -import { Following } from '@/models/entities/following.js'; -import { setResponseType } from '../activitypub.js'; - -export default async (ctx: Router.RouterContext) => { - const userId = ctx.params.user; - - const cursor = ctx.request.query.cursor; - if (cursor != null && typeof cursor !== 'string') { - ctx.status = 400; - return; - } - - const page = ctx.request.query.page === 'true'; - - const user = await Users.findOneBy({ - id: userId, - host: IsNull(), - }); - - if (user == null) { - ctx.status = 404; - return; - } - - //#region Check ff visibility - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - if (profile.ffVisibility === 'private') { - ctx.status = 403; - ctx.set('Cache-Control', 'public, max-age=30'); - return; - } else if (profile.ffVisibility === 'followers') { - ctx.status = 403; - ctx.set('Cache-Control', 'public, max-age=30'); - return; - } - //#endregion - - const limit = 10; - const partOf = `${config.url}/users/${userId}/followers`; - - if (page) { - const query = { - followeeId: user.id, - } as FindOptionsWhere; - - // カーソルが指定されている場合 - if (cursor) { - query.id = LessThan(cursor); - } - - // Get followers - const followings = await Followings.find({ - where: query, - take: limit + 1, - order: { id: -1 }, - }); - - // 「次のページ」があるかどうか - const inStock = followings.length === limit + 1; - if (inStock) followings.pop(); - - const renderedFollowers = await Promise.all(followings.map(following => renderFollowUser(following.followerId))); - const rendered = renderOrderedCollectionPage( - `${partOf}?${url.query({ - page: 'true', - cursor, - })}`, - user.followersCount, renderedFollowers, partOf, - undefined, - inStock ? `${partOf}?${url.query({ - page: 'true', - cursor: followings[followings.length - 1].id, - })}` : undefined, - ); - - ctx.body = renderActivity(rendered); - setResponseType(ctx); - } else { - // index page - const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`); - ctx.body = renderActivity(rendered); - ctx.set('Cache-Control', 'public, max-age=180'); - setResponseType(ctx); - } -}; diff --git a/packages/backend/src/server/activitypub/following.ts b/packages/backend/src/server/activitypub/following.ts deleted file mode 100644 index 3a25a6316c..0000000000 --- a/packages/backend/src/server/activitypub/following.ts +++ /dev/null @@ -1,95 +0,0 @@ -import Router from '@koa/router'; -import { LessThan, IsNull, FindOptionsWhere } from 'typeorm'; -import config from '@/config/index.js'; -import * as url from '@/prelude/url.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; -import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js'; -import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; -import { Users, Followings, UserProfiles } from '@/models/index.js'; -import { Following } from '@/models/entities/following.js'; -import { setResponseType } from '../activitypub.js'; - -export default async (ctx: Router.RouterContext) => { - const userId = ctx.params.user; - - const cursor = ctx.request.query.cursor; - if (cursor != null && typeof cursor !== 'string') { - ctx.status = 400; - return; - } - - const page = ctx.request.query.page === 'true'; - - const user = await Users.findOneBy({ - id: userId, - host: IsNull(), - }); - - if (user == null) { - ctx.status = 404; - return; - } - - //#region Check ff visibility - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - if (profile.ffVisibility === 'private') { - ctx.status = 403; - ctx.set('Cache-Control', 'public, max-age=30'); - return; - } else if (profile.ffVisibility === 'followers') { - ctx.status = 403; - ctx.set('Cache-Control', 'public, max-age=30'); - return; - } - //#endregion - - const limit = 10; - const partOf = `${config.url}/users/${userId}/following`; - - if (page) { - const query = { - followerId: user.id, - } as FindOptionsWhere; - - // カーソルが指定されている場合 - if (cursor) { - query.id = LessThan(cursor); - } - - // Get followings - const followings = await Followings.find({ - where: query, - take: limit + 1, - order: { id: -1 }, - }); - - // 「次のページ」があるかどうか - const inStock = followings.length === limit + 1; - if (inStock) followings.pop(); - - const renderedFollowees = await Promise.all(followings.map(following => renderFollowUser(following.followeeId))); - const rendered = renderOrderedCollectionPage( - `${partOf}?${url.query({ - page: 'true', - cursor, - })}`, - user.followingCount, renderedFollowees, partOf, - undefined, - inStock ? `${partOf}?${url.query({ - page: 'true', - cursor: followings[followings.length - 1].id, - })}` : undefined, - ); - - ctx.body = renderActivity(rendered); - setResponseType(ctx); - } else { - // index page - const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`); - ctx.body = renderActivity(rendered); - ctx.set('Cache-Control', 'public, max-age=180'); - setResponseType(ctx); - } -}; diff --git a/packages/backend/src/server/activitypub/outbox.ts b/packages/backend/src/server/activitypub/outbox.ts deleted file mode 100644 index 7a2586998a..0000000000 --- a/packages/backend/src/server/activitypub/outbox.ts +++ /dev/null @@ -1,108 +0,0 @@ -import Router from '@koa/router'; -import { Brackets, IsNull } from 'typeorm'; -import config from '@/config/index.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; -import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js'; -import renderNote from '@/remote/activitypub/renderer/note.js'; -import renderCreate from '@/remote/activitypub/renderer/create.js'; -import renderAnnounce from '@/remote/activitypub/renderer/announce.js'; -import { countIf } from '@/prelude/array.js'; -import * as url from '@/prelude/url.js'; -import { Users, Notes } from '@/models/index.js'; -import { Note } from '@/models/entities/note.js'; -import { makePaginationQuery } from '../api/common/make-pagination-query.js'; -import { setResponseType } from '../activitypub.js'; - -export default async (ctx: Router.RouterContext) => { - const userId = ctx.params.user; - - const sinceId = ctx.request.query.since_id; - if (sinceId != null && typeof sinceId !== 'string') { - ctx.status = 400; - return; - } - - const untilId = ctx.request.query.until_id; - if (untilId != null && typeof untilId !== 'string') { - ctx.status = 400; - return; - } - - const page = ctx.request.query.page === 'true'; - - if (countIf(x => x != null, [sinceId, untilId]) > 1) { - ctx.status = 400; - return; - } - - const user = await Users.findOneBy({ - id: userId, - host: IsNull(), - }); - - if (user == null) { - ctx.status = 404; - return; - } - - const limit = 20; - const partOf = `${config.url}/users/${userId}/outbox`; - - if (page) { - const query = makePaginationQuery(Notes.createQueryBuilder('note'), sinceId, untilId) - .andWhere('note.userId = :userId', { userId: user.id }) - .andWhere(new Brackets(qb => { qb - .where('note.visibility = \'public\'') - .orWhere('note.visibility = \'home\''); - })) - .andWhere('note.localOnly = FALSE'); - - const notes = await query.take(limit).getMany(); - - if (sinceId) notes.reverse(); - - const activities = await Promise.all(notes.map(note => packActivity(note))); - const rendered = renderOrderedCollectionPage( - `${partOf}?${url.query({ - page: 'true', - since_id: sinceId, - until_id: untilId, - })}`, - user.notesCount, activities, partOf, - notes.length ? `${partOf}?${url.query({ - page: 'true', - since_id: notes[0].id, - })}` : undefined, - notes.length ? `${partOf}?${url.query({ - page: 'true', - until_id: notes[notes.length - 1].id, - })}` : undefined, - ); - - ctx.body = renderActivity(rendered); - setResponseType(ctx); - } else { - // index page - const rendered = renderOrderedCollection(partOf, user.notesCount, - `${partOf}?page=true`, - `${partOf}?page=true&since_id=000000000000000000000000`, - ); - ctx.body = renderActivity(rendered); - ctx.set('Cache-Control', 'public, max-age=180'); - setResponseType(ctx); - } -}; - -/** - * Pack Create or Announce Activity - * @param note Note - */ -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.findOneByOrFail({ id: note.renoteId }); - return renderAnnounce(renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`, note); - } - - return renderCreate(await renderNote(note, false), note); -} diff --git a/packages/backend/src/server/api/2fa.ts b/packages/backend/src/server/api/2fa.ts deleted file mode 100644 index 96b9316e47..0000000000 --- a/packages/backend/src/server/api/2fa.ts +++ /dev/null @@ -1,422 +0,0 @@ -import * as crypto from 'node:crypto'; -import * as jsrsasign from 'jsrsasign'; -import config from '@/config/index.js'; - -const ECC_PRELUDE = Buffer.from([0x04]); -const NULL_BYTE = Buffer.from([0]); -const PEM_PRELUDE = Buffer.from( - '3059301306072a8648ce3d020106082a8648ce3d030107034200', - 'hex', -); - -// Android Safetynet attestations are signed with this cert: -const GSR2 = `-----BEGIN CERTIFICATE----- -MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G -A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp -Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 -MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG -A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL -v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 -eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq -tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd -C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa -zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB -mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH -V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n -bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG -3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs -J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO -291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS -ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd -AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 -TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== ------END CERTIFICATE-----\n`; - -function base64URLDecode(source: string) { - return Buffer.from(source.replace(/\-/g, '+').replace(/_/g, '/'), 'base64'); -} - -function getCertSubject(certificate: string) { - const subjectCert = new jsrsasign.X509(); - subjectCert.readCertPEM(certificate); - - const subjectString = subjectCert.getSubjectString(); - const subjectFields = subjectString.slice(1).split('/'); - - const fields = {} as Record; - for (const field of subjectFields) { - const eqIndex = field.indexOf('='); - fields[field.substring(0, eqIndex)] = field.substring(eqIndex + 1); - } - - return fields; -} - -function verifyCertificateChain(certificates: string[]) { - let valid = true; - - for (let i = 0; i < certificates.length; i++) { - const Cert = certificates[i]; - const certificate = new jsrsasign.X509(); - certificate.readCertPEM(Cert); - - const CACert = i + 1 >= certificates.length ? Cert : certificates[i + 1]; - - const certStruct = jsrsasign.ASN1HEX.getTLVbyList(certificate.hex!, 0, [0]); - const algorithm = certificate.getSignatureAlgorithmField(); - const signatureHex = certificate.getSignatureValueHex(); - - // Verify against CA - const Signature = new jsrsasign.KJUR.crypto.Signature({ alg: algorithm }); - Signature.init(CACert); - Signature.updateHex(certStruct); - valid = valid && !!Signature.verify(signatureHex); // true if CA signed the certificate - } - - return valid; -} - -function PEMString(pemBuffer: Buffer, type = 'CERTIFICATE') { - if (pemBuffer.length === 65 && pemBuffer[0] === 0x04) { - pemBuffer = Buffer.concat([PEM_PRELUDE, pemBuffer], 91); - type = 'PUBLIC KEY'; - } - const cert = pemBuffer.toString('base64'); - - const keyParts = []; - const max = Math.ceil(cert.length / 64); - let start = 0; - for (let i = 0; i < max; i++) { - keyParts.push(cert.substring(start, start + 64)); - start += 64; - } - - return ( - `-----BEGIN ${type}-----\n` + - keyParts.join('\n') + - `\n-----END ${type}-----\n` - ); -} - -export function hash(data: Buffer) { - return crypto - .createHash('sha256') - .update(data) - .digest(); -} - -export function verifyLogin({ - publicKey, - authenticatorData, - clientDataJSON, - clientData, - signature, - challenge, -}: { - publicKey: Buffer, - authenticatorData: Buffer, - clientDataJSON: Buffer, - clientData: any, - signature: Buffer, - challenge: string -}) { - if (clientData.type !== 'webauthn.get') { - throw new Error('type is not webauthn.get'); - } - - if (hash(clientData.challenge).toString('hex') !== challenge) { - throw new Error('challenge mismatch'); - } - if (clientData.origin !== config.scheme + '://' + config.host) { - throw new Error('origin mismatch'); - } - - const verificationData = Buffer.concat( - [authenticatorData, hash(clientDataJSON)], - 32 + authenticatorData.length, - ); - - return crypto - .createVerify('SHA256') - .update(verificationData) - .verify(PEMString(publicKey), signature); -} - -export const procedures = { - none: { - verify({ publicKey }: { publicKey: Map }) { - const negTwo = publicKey.get(-2); - - if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); - } - const negThree = publicKey.get(-3); - if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); - } - - const publicKeyU2F = Buffer.concat( - [ECC_PRELUDE, negTwo, negThree], - 1 + 32 + 32, - ); - - return { - publicKey: publicKeyU2F, - valid: true, - }; - }, - }, - 'android-key': { - verify({ - attStmt, - authenticatorData, - clientDataHash, - publicKey, - rpIdHash, - credentialId, - }: { - attStmt: any, - authenticatorData: Buffer, - clientDataHash: Buffer, - publicKey: Map; - rpIdHash: Buffer, - credentialId: Buffer, - }) { - if (attStmt.alg !== -7) { - throw new Error('alg mismatch'); - } - - const verificationData = Buffer.concat([ - authenticatorData, - clientDataHash, - ]); - - const attCert: Buffer = attStmt.x5c[0]; - - const negTwo = publicKey.get(-2); - - if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); - } - const negThree = publicKey.get(-3); - if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); - } - - const publicKeyData = Buffer.concat( - [ECC_PRELUDE, negTwo, negThree], - 1 + 32 + 32, - ); - - if (!attCert.equals(publicKeyData)) { - throw new Error('public key mismatch'); - } - - const isValid = crypto - .createVerify('SHA256') - .update(verificationData) - .verify(PEMString(attCert), attStmt.sig); - - // TODO: Check 'attestationChallenge' field in extension of cert matches hash(clientDataJSON) - - return { - valid: isValid, - publicKey: publicKeyData, - }; - }, - }, - // what a stupid attestation - 'android-safetynet': { - verify({ - attStmt, - authenticatorData, - clientDataHash, - publicKey, - rpIdHash, - credentialId, - }: { - attStmt: any, - authenticatorData: Buffer, - clientDataHash: Buffer, - publicKey: Map; - rpIdHash: Buffer, - credentialId: Buffer, - }) { - const verificationData = hash( - Buffer.concat([authenticatorData, clientDataHash]), - ); - - const jwsParts = attStmt.response.toString('utf-8').split('.'); - - const header = JSON.parse(base64URLDecode(jwsParts[0]).toString('utf-8')); - const response = JSON.parse( - base64URLDecode(jwsParts[1]).toString('utf-8'), - ); - const signature = jwsParts[2]; - - if (!verificationData.equals(Buffer.from(response.nonce, 'base64'))) { - throw new Error('invalid nonce'); - } - - const certificateChain = header.x5c - .map((key: any) => PEMString(key)) - .concat([GSR2]); - - if (getCertSubject(certificateChain[0]).CN !== 'attest.android.com') { - throw new Error('invalid common name'); - } - - if (!verifyCertificateChain(certificateChain)) { - throw new Error('Invalid certificate chain!'); - } - - const signatureBase = Buffer.from( - jwsParts[0] + '.' + jwsParts[1], - 'utf-8', - ); - - const valid = crypto - .createVerify('sha256') - .update(signatureBase) - .verify(certificateChain[0], base64URLDecode(signature)); - - const negTwo = publicKey.get(-2); - - if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); - } - const negThree = publicKey.get(-3); - if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); - } - - const publicKeyData = Buffer.concat( - [ECC_PRELUDE, negTwo, negThree], - 1 + 32 + 32, - ); - return { - valid, - publicKey: publicKeyData, - }; - }, - }, - packed: { - verify({ - attStmt, - authenticatorData, - clientDataHash, - publicKey, - rpIdHash, - credentialId, - }: { - attStmt: any, - authenticatorData: Buffer, - clientDataHash: Buffer, - publicKey: Map; - rpIdHash: Buffer, - credentialId: Buffer, - }) { - const verificationData = Buffer.concat([ - authenticatorData, - clientDataHash, - ]); - - if (attStmt.x5c) { - const attCert = attStmt.x5c[0]; - - const validSignature = crypto - .createVerify('SHA256') - .update(verificationData) - .verify(PEMString(attCert), attStmt.sig); - - const negTwo = publicKey.get(-2); - - if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); - } - const negThree = publicKey.get(-3); - if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); - } - - const publicKeyData = Buffer.concat( - [ECC_PRELUDE, negTwo, negThree], - 1 + 32 + 32, - ); - - return { - valid: validSignature, - publicKey: publicKeyData, - }; - } else if (attStmt.ecdaaKeyId) { - // https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-ecdaa-algorithm-v2.0-id-20180227.html#ecdaa-verify-operation - throw new Error('ECDAA-Verify is not supported'); - } else { - if (attStmt.alg !== -7) throw new Error('alg mismatch'); - - throw new Error('self attestation is not supported'); - } - }, - }, - - 'fido-u2f': { - verify({ - attStmt, - authenticatorData, - clientDataHash, - publicKey, - rpIdHash, - credentialId, - }: { - attStmt: any, - authenticatorData: Buffer, - clientDataHash: Buffer, - publicKey: Map, - rpIdHash: Buffer, - credentialId: Buffer - }) { - const x5c: Buffer[] = attStmt.x5c; - if (x5c.length !== 1) { - throw new Error('x5c length does not match expectation'); - } - - const attCert = x5c[0]; - - // TODO: make sure attCert is an Elliptic Curve (EC) public key over the P-256 curve - - const negTwo: Buffer = publicKey.get(-2); - - if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); - } - const negThree: Buffer = publicKey.get(-3); - if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); - } - - const publicKeyU2F = Buffer.concat( - [ECC_PRELUDE, negTwo, negThree], - 1 + 32 + 32, - ); - - const verificationData = Buffer.concat([ - NULL_BYTE, - rpIdHash, - clientDataHash, - credentialId, - publicKeyU2F, - ]); - - const validSignature = crypto - .createVerify('SHA256') - .update(verificationData) - .verify(PEMString(attCert), attStmt.sig); - - return { - valid: validSignature, - publicKey: publicKeyU2F, - }; - }, - }, -}; diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts new file mode 100644 index 0000000000..f2ead3d4a1 --- /dev/null +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -0,0 +1,258 @@ +import { performance } from 'perf_hooks'; +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import { getIpHash } from '@/misc/get-ip-hash.js'; +import type { CacheableLocalUser, User } from '@/models/entities/User.js'; +import type { AccessToken } from '@/models/entities/AccessToken.js'; +import type Logger from '@/logger.js'; +import { UserIpsRepository } from '@/models/index.js'; +import { MetaService } from '@/core/MetaService.js'; +import { ApiError } from './error.js'; +import { RateLimiterService } from './RateLimiterService.js'; +import { ApiLoggerService } from './ApiLoggerService.js'; +import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; +import type { OnApplicationShutdown } from '@nestjs/common'; +import type { IEndpointMeta, IEndpoint } from './endpoints.js'; +import type Koa from 'koa'; + +const accessDenied = { + message: 'Access denied.', + code: 'ACCESS_DENIED', + id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e', +}; + +@Injectable() +export class ApiCallService implements OnApplicationShutdown { + #logger: Logger; + #userIpHistories: Map>; + #userIpHistoriesClearIntervalId: NodeJS.Timer; + + constructor( + @Inject(DI.userIpsRepository) + private userIpsRepository: UserIpsRepository, + + private metaService: MetaService, + private authenticateService: AuthenticateService, + private rateLimiterService: RateLimiterService, + private apiLoggerService: ApiLoggerService, + ) { + this.#logger = this.apiLoggerService.logger; + this.#userIpHistories = new Map>(); + + this.#userIpHistoriesClearIntervalId = setInterval(() => { + this.#userIpHistories.clear(); + }, 1000 * 60 * 60); + } + + public handleRequest(endpoint: IEndpoint, exec: any, ctx: Koa.Context) { + return new Promise((res) => { + const body = ctx.is('multipart/form-data') + ? (ctx.request as any).body + : ctx.method === 'GET' + ? ctx.query + : ctx.request.body; + + const reply = (x?: any, y?: ApiError) => { + if (x == null) { + ctx.status = 204; + } else if (typeof x === 'number' && y) { + ctx.status = x; + ctx.body = { + error: { + message: y!.message, + code: y!.code, + id: y!.id, + kind: y!.kind, + ...(y!.info ? { info: y!.info } : {}), + }, + }; + } else { + // 文字列を返す場合は、JSON.stringify通さないとJSONと認識されない + ctx.body = typeof x === 'string' ? JSON.stringify(x) : x; + } + res(); + }; + + // Authentication + this.authenticateService.authenticate(body['i']).then(([user, app]) => { + // API invoking + this.#call(endpoint, exec, user, app, body, ctx).then((res: any) => { + if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) { + ctx.set('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`); + } + reply(res); + }).catch((e: ApiError) => { + reply(e.httpStatusCode ? e.httpStatusCode : e.kind === 'client' ? 400 : 500, e); + }); + + // Log IP + if (user) { + this.metaService.fetch().then(meta => { + if (!meta.enableIpLogging) return; + const ip = ctx.ip; + const ips = this.#userIpHistories.get(user.id); + if (ips == null || !ips.has(ip)) { + if (ips == null) { + this.#userIpHistories.set(user.id, new Set([ip])); + } else { + ips.add(ip); + } + + try { + this.userIpsRepository.createQueryBuilder().insert().values({ + createdAt: new Date(), + userId: user.id, + ip: ip, + }).orIgnore(true).execute(); + } catch { + } + } + }); + } + }).catch(e => { + if (e instanceof AuthenticationError) { + reply(403, new ApiError({ + message: 'Authentication failed. Please ensure your token is correct.', + code: 'AUTHENTICATION_FAILED', + id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', + })); + } else { + reply(500, new ApiError()); + } + }); + }); + } + + async #call( + ep: IEndpoint, + exec: any, + user: CacheableLocalUser | null | undefined, + token: AccessToken | null | undefined, + data: any, + ctx?: Koa.Context, + ) { + const isSecure = user != null && token == null; + const isModerator = user != null && (user.isModerator || user.isAdmin); + + if (ep.meta.secure && !isSecure) { + throw new ApiError(accessDenied); + } + + if (ep.meta.limit) { + // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app. + let limitActor: string; + if (user) { + limitActor = user.id; + } else { + limitActor = getIpHash(ctx!.ip); + } + + const limit = Object.assign({}, ep.meta.limit); + + if (!limit.key) { + limit.key = ep.name; + } + + // Rate limit + await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable }, limitActor).catch(e => { + throw new ApiError({ + message: 'Rate limit exceeded. Please try again later.', + code: 'RATE_LIMIT_EXCEEDED', + id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef', + httpStatusCode: 429, + }); + }); + } + + if (ep.meta.requireCredential && user == null) { + throw new ApiError({ + message: 'Credential required.', + code: 'CREDENTIAL_REQUIRED', + id: '1384574d-a912-4b81-8601-c7b1c4085df1', + httpStatusCode: 401, + }); + } + + if (ep.meta.requireCredential && user!.isSuspended) { + throw new ApiError({ + message: 'Your account has been suspended.', + code: 'YOUR_ACCOUNT_SUSPENDED', + id: 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370', + httpStatusCode: 403, + }); + } + + if (ep.meta.requireAdmin && !user!.isAdmin) { + throw new ApiError(accessDenied, { reason: 'You are not the admin.' }); + } + + if (ep.meta.requireModerator && !isModerator) { + throw new ApiError(accessDenied, { reason: 'You are not a moderator.' }); + } + + if (token && ep.meta.kind && !token.permission.some(p => p === ep.meta.kind)) { + throw new ApiError({ + message: 'Your app does not have the necessary permissions to use this endpoint.', + code: 'PERMISSION_DENIED', + id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838', + }); + } + + // Cast non JSON input + if ((ep.meta.requireFile || ctx?.method === 'GET') && ep.params.properties) { + for (const k of Object.keys(ep.params.properties)) { + const param = ep.params.properties![k]; + if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') { + try { + data[k] = JSON.parse(data[k]); + } catch (e) { + throw new ApiError({ + message: 'Invalid param.', + code: 'INVALID_PARAM', + id: '0b5f1631-7c1a-41a6-b399-cce335f34d85', + }, { + param: k, + reason: `cannot cast to ${param.type}`, + }); + } + } + } + } + + // API invoking + const before = performance.now(); + return await exec(data, user, token, ctx?.file, ctx?.ip, ctx?.headers).catch((err: Error) => { + if (err instanceof ApiError) { + throw err; + } else { + this.#logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, { + ep: ep.name, + ps: data, + e: { + message: err.message, + code: err.name, + stack: err.stack, + }, + }); + console.error(err); + throw new ApiError(null, { + e: { + message: err.message, + code: err.name, + stack: err.stack, + }, + }); + } + }).finally(() => { + const after = performance.now(); + const time = after - before; + if (time > 1000) { + this.#logger.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`); + } + }); + } + + public onApplicationShutdown(signal?: string | undefined) { + clearInterval(this.#userIpHistoriesClearIntervalId); + } +} diff --git a/packages/backend/src/server/api/ApiLoggerService.ts b/packages/backend/src/server/api/ApiLoggerService.ts new file mode 100644 index 0000000000..c8c5fec85c --- /dev/null +++ b/packages/backend/src/server/api/ApiLoggerService.ts @@ -0,0 +1,12 @@ +import { Inject, Injectable } from '@nestjs/common'; +import Logger from '@/logger.js'; + +@Injectable() +export class ApiLoggerService { + public logger: Logger; + + constructor( + ) { + this.logger = new Logger('api'); + } +} diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts new file mode 100644 index 0000000000..cfe39238dd --- /dev/null +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -0,0 +1,160 @@ +import { Inject, Injectable } from '@nestjs/common'; +import Koa from 'koa'; +import Router from '@koa/router'; +import multer from '@koa/multer'; +import bodyParser from 'koa-bodyparser'; +import cors from '@koa/cors'; +import { ModuleRef } from '@nestjs/core'; +import { Config } from '@/config.js'; +import { UsersRepository, InstancesRepository, AccessTokensRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import endpoints from './endpoints.js'; +import { ApiCallService } from './ApiCallService.js'; +import { SignupApiService } from './SignupApiService.js'; +import { SigninApiService } from './SigninApiService.js'; +import { GithubServerService } from './integration/GithubServerService.js'; +import { DiscordServerService } from './integration/DiscordServerService.js'; +import { TwitterServerService } from './integration/TwitterServerService.js'; + +@Injectable() +export class ApiServerService { + constructor( + private moduleRef: ModuleRef, + + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, + + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, + + private userEntityService: UserEntityService, + private apiCallService: ApiCallService, + private signupApiServiceService: SignupApiService, + private signinApiServiceService: SigninApiService, + private githubServerService: GithubServerService, + private discordServerService: DiscordServerService, + private twitterServerService: TwitterServerService, + ) { + } + + public createApiServer() { + const handlers: Record = {}; + + for (const endpoint of endpoints) { + handlers[endpoint.name] = this.moduleRef.get('ep:' + endpoint.name, { strict: false }).exec; + } + + // Init app + const apiServer = new Koa(); + + apiServer.use(cors({ + origin: '*', + })); + + // No caching + apiServer.use(async (ctx, next) => { + ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + await next(); + }); + + apiServer.use(bodyParser({ + // リクエストが multipart/form-data でない限りはJSONだと見なす + detectJSON: ctx => !ctx.is('multipart/form-data'), + })); + + // Init multer instance + const upload = multer({ + storage: multer.diskStorage({}), + limits: { + fileSize: this.config.maxFileSize ?? 262144000, + files: 1, + }, + }); + + // Init router + const router = new Router(); + + /** + * Register endpoint handlers + */ + for (const endpoint of endpoints) { + if (endpoint.meta.requireFile) { + router.post(`/${endpoint.name}`, upload.single('file'), this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name])); + } else { + // 後方互換性のため + if (endpoint.name.includes('-')) { + router.post(`/${endpoint.name.replace(/-/g, '_')}`, this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name])); + + if (endpoint.meta.allowGet) { + router.get(`/${endpoint.name.replace(/-/g, '_')}`, this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name])); + } else { + router.get(`/${endpoint.name.replace(/-/g, '_')}`, async ctx => { ctx.status = 405; }); + } + } + + router.post(`/${endpoint.name}`, this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name])); + + if (endpoint.meta.allowGet) { + router.get(`/${endpoint.name}`, this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name])); + } else { + router.get(`/${endpoint.name}`, async ctx => { ctx.status = 405; }); + } + } + } + + router.post('/signup', ctx => this.signupApiServiceService.signup(ctx)); + router.post('/signin', ctx => this.signinApiServiceService.signin(ctx)); + router.post('/signup-pending', ctx => this.signupApiServiceService.signupPending(ctx)); + + router.use(this.discordServerService.create().routes()); + router.use(this.githubServerService.create().routes()); + router.use(this.twitterServerService.create().routes()); + + router.get('/v1/instance/peers', async ctx => { + const instances = await this.instancesRepository.find({ + select: ['host'], + }); + + ctx.body = instances.map(instance => instance.host); + }); + + router.post('/miauth/:session/check', async ctx => { + const token = await this.accessTokensRepository.findOneBy({ + session: ctx.params.session, + }); + + if (token && token.session != null && !token.fetched) { + this.accessTokensRepository.update(token.id, { + fetched: true, + }); + + ctx.body = { + ok: true, + token: token.token, + user: await this.userEntityService.pack(token.userId, null, { detail: true }), + }; + } else { + ctx.body = { + ok: false, + }; + } + }); + + // Return 404 for unknown API + router.all('(.*)', async ctx => { + ctx.status = 404; + }); + + // Register router + apiServer.use(router.routes()); + + return apiServer; + } +} diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts new file mode 100644 index 0000000000..b8bd09509a --- /dev/null +++ b/packages/backend/src/server/api/AuthenticateService.ts @@ -0,0 +1,86 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/index.js'; +import type { CacheableLocalUser, ILocalUser } from '@/models/entities/User.js'; +import type { AccessToken } from '@/models/entities/AccessToken.js'; +import { Cache } from '@/misc/cache.js'; +import type { App } from '@/models/entities/App.js'; +import { UserCacheService } from '@/core/UserCacheService.js'; +import isNativeToken from '@/misc/is-native-token.js'; + +export class AuthenticationError extends Error { + constructor(message: string) { + super(message); + this.name = 'AuthenticationError'; + } +} + +@Injectable() +export class AuthenticateService { + #appCache: Cache; + + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, + + @Inject(DI.appsRepository) + private appsRepository: AppsRepository, + + private userCacheService: UserCacheService, + ) { + this.#appCache = new Cache(Infinity); + } + + public async authenticate(token: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> { + if (token == null) { + return [null, null]; + } + + if (isNativeToken(token)) { + const user = await this.userCacheService.localUserByNativeTokenCache.fetch(token, + () => this.usersRepository.findOneBy({ token }) as Promise); + + if (user == null) { + throw new AuthenticationError('user not found'); + } + + return [user, null]; + } else { + const accessToken = await this.accessTokensRepository.findOne({ + where: [{ + hash: token.toLowerCase(), // app + }, { + token: token, // miauth + }], + }); + + if (accessToken == null) { + throw new AuthenticationError('invalid signature'); + } + + this.accessTokensRepository.update(accessToken.id, { + lastUsedAt: new Date(), + }); + + const user = await this.userCacheService.localUserByIdCache.fetch(accessToken.userId, + () => this.usersRepository.findOneBy({ + id: accessToken.userId, + }) as Promise); + + if (accessToken.appId) { + const app = await this.#appCache.fetch(accessToken.appId, + () => this.appsRepository.findOneByOrFail({ id: accessToken.appId! })); + + return [user, { + id: accessToken.id, + permission: app.permission, + } as AccessToken]; + } else { + return [user, accessToken]; + } + } + } +} diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts new file mode 100644 index 0000000000..d2dfd78fd4 --- /dev/null +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -0,0 +1,1268 @@ +import { Module } from '@nestjs/common'; + +import { CoreModule } from '@/core/CoreModule.js'; +import * as ep___admin_meta from './endpoints/admin/meta.js'; +import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js'; +import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js'; +import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js'; +import * as ep___admin_ad_create from './endpoints/admin/ad/create.js'; +import * as ep___admin_ad_delete from './endpoints/admin/ad/delete.js'; +import * as ep___admin_ad_list from './endpoints/admin/ad/list.js'; +import * as ep___admin_ad_update from './endpoints/admin/ad/update.js'; +import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js'; +import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js'; +import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js'; +import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js'; +import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js'; +import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js'; +import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js'; +import * as ep___admin_drive_files from './endpoints/admin/drive/files.js'; +import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js'; +import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js'; +import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js'; +import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js'; +import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js'; +import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js'; +import * as ep___admin_emoji_importZip from './endpoints/admin/emoji/import-zip.js'; +import * as ep___admin_emoji_listRemote from './endpoints/admin/emoji/list-remote.js'; +import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js'; +import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js'; +import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js'; +import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js'; +import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js'; +import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js'; +import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js'; +import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js'; +import * as ep___admin_federation_updateInstance from './endpoints/admin/federation/update-instance.js'; +import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js'; +import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js'; +import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js'; +import * as ep___admin_invite from './endpoints/admin/invite.js'; +import * as ep___admin_moderators_add from './endpoints/admin/moderators/add.js'; +import * as ep___admin_moderators_remove from './endpoints/admin/moderators/remove.js'; +import * as ep___admin_promo_create from './endpoints/admin/promo/create.js'; +import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js'; +import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js'; +import * as ep___admin_queue_inboxDelayed from './endpoints/admin/queue/inbox-delayed.js'; +import * as ep___admin_queue_stats from './endpoints/admin/queue/stats.js'; +import * as ep___admin_relays_add from './endpoints/admin/relays/add.js'; +import * as ep___admin_relays_list from './endpoints/admin/relays/list.js'; +import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js'; +import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js'; +import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js'; +import * as ep___admin_sendEmail from './endpoints/admin/send-email.js'; +import * as ep___admin_serverInfo from './endpoints/admin/server-info.js'; +import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js'; +import * as ep___admin_showUser from './endpoints/admin/show-user.js'; +import * as ep___admin_showUsers from './endpoints/admin/show-users.js'; +import * as ep___admin_silenceUser from './endpoints/admin/silence-user.js'; +import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js'; +import * as ep___admin_unsilenceUser from './endpoints/admin/unsilence-user.js'; +import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js'; +import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js'; +import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js'; +import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js'; +import * as ep___announcements from './endpoints/announcements.js'; +import * as ep___antennas_create from './endpoints/antennas/create.js'; +import * as ep___antennas_delete from './endpoints/antennas/delete.js'; +import * as ep___antennas_list from './endpoints/antennas/list.js'; +import * as ep___antennas_notes from './endpoints/antennas/notes.js'; +import * as ep___antennas_show from './endpoints/antennas/show.js'; +import * as ep___antennas_update from './endpoints/antennas/update.js'; +import * as ep___ap_get from './endpoints/ap/get.js'; +import * as ep___ap_show from './endpoints/ap/show.js'; +import * as ep___app_create from './endpoints/app/create.js'; +import * as ep___app_show from './endpoints/app/show.js'; +import * as ep___auth_accept from './endpoints/auth/accept.js'; +import * as ep___auth_session_generate from './endpoints/auth/session/generate.js'; +import * as ep___auth_session_show from './endpoints/auth/session/show.js'; +import * as ep___auth_session_userkey from './endpoints/auth/session/userkey.js'; +import * as ep___blocking_create from './endpoints/blocking/create.js'; +import * as ep___blocking_delete from './endpoints/blocking/delete.js'; +import * as ep___blocking_list from './endpoints/blocking/list.js'; +import * as ep___channels_create from './endpoints/channels/create.js'; +import * as ep___channels_featured from './endpoints/channels/featured.js'; +import * as ep___channels_follow from './endpoints/channels/follow.js'; +import * as ep___channels_followed from './endpoints/channels/followed.js'; +import * as ep___channels_owned from './endpoints/channels/owned.js'; +import * as ep___channels_show from './endpoints/channels/show.js'; +import * as ep___channels_timeline from './endpoints/channels/timeline.js'; +import * as ep___channels_unfollow from './endpoints/channels/unfollow.js'; +import * as ep___channels_update from './endpoints/channels/update.js'; +import * as ep___charts_activeUsers from './endpoints/charts/active-users.js'; +import * as ep___charts_apRequest from './endpoints/charts/ap-request.js'; +import * as ep___charts_drive from './endpoints/charts/drive.js'; +import * as ep___charts_federation from './endpoints/charts/federation.js'; +import * as ep___charts_hashtag from './endpoints/charts/hashtag.js'; +import * as ep___charts_instance from './endpoints/charts/instance.js'; +import * as ep___charts_notes from './endpoints/charts/notes.js'; +import * as ep___charts_user_drive from './endpoints/charts/user/drive.js'; +import * as ep___charts_user_following from './endpoints/charts/user/following.js'; +import * as ep___charts_user_notes from './endpoints/charts/user/notes.js'; +import * as ep___charts_user_reactions from './endpoints/charts/user/reactions.js'; +import * as ep___charts_users from './endpoints/charts/users.js'; +import * as ep___clips_addNote from './endpoints/clips/add-note.js'; +import * as ep___clips_removeNote from './endpoints/clips/remove-note.js'; +import * as ep___clips_create from './endpoints/clips/create.js'; +import * as ep___clips_delete from './endpoints/clips/delete.js'; +import * as ep___clips_list from './endpoints/clips/list.js'; +import * as ep___clips_notes from './endpoints/clips/notes.js'; +import * as ep___clips_show from './endpoints/clips/show.js'; +import * as ep___clips_update from './endpoints/clips/update.js'; +import * as ep___drive from './endpoints/drive.js'; +import * as ep___drive_files from './endpoints/drive/files.js'; +import * as ep___drive_files_attachedNotes from './endpoints/drive/files/attached-notes.js'; +import * as ep___drive_files_checkExistence from './endpoints/drive/files/check-existence.js'; +import * as ep___drive_files_create from './endpoints/drive/files/create.js'; +import * as ep___drive_files_delete from './endpoints/drive/files/delete.js'; +import * as ep___drive_files_findByHash from './endpoints/drive/files/find-by-hash.js'; +import * as ep___drive_files_find from './endpoints/drive/files/find.js'; +import * as ep___drive_files_show from './endpoints/drive/files/show.js'; +import * as ep___drive_files_update from './endpoints/drive/files/update.js'; +import * as ep___drive_files_uploadFromUrl from './endpoints/drive/files/upload-from-url.js'; +import * as ep___drive_folders from './endpoints/drive/folders.js'; +import * as ep___drive_folders_create from './endpoints/drive/folders/create.js'; +import * as ep___drive_folders_delete from './endpoints/drive/folders/delete.js'; +import * as ep___drive_folders_find from './endpoints/drive/folders/find.js'; +import * as ep___drive_folders_show from './endpoints/drive/folders/show.js'; +import * as ep___drive_folders_update from './endpoints/drive/folders/update.js'; +import * as ep___drive_stream from './endpoints/drive/stream.js'; +import * as ep___emailAddress_available from './endpoints/email-address/available.js'; +import * as ep___endpoint from './endpoints/endpoint.js'; +import * as ep___endpoints from './endpoints/endpoints.js'; +import * as ep___exportCustomEmojis from './endpoints/export-custom-emojis.js'; +import * as ep___federation_followers from './endpoints/federation/followers.js'; +import * as ep___federation_following from './endpoints/federation/following.js'; +import * as ep___federation_instances from './endpoints/federation/instances.js'; +import * as ep___federation_showInstance from './endpoints/federation/show-instance.js'; +import * as ep___federation_updateRemoteUser from './endpoints/federation/update-remote-user.js'; +import * as ep___federation_users from './endpoints/federation/users.js'; +import * as ep___federation_stats from './endpoints/federation/stats.js'; +import * as ep___following_create from './endpoints/following/create.js'; +import * as ep___following_delete from './endpoints/following/delete.js'; +import * as ep___following_invalidate from './endpoints/following/invalidate.js'; +import * as ep___following_requests_accept from './endpoints/following/requests/accept.js'; +import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js'; +import * as ep___following_requests_list from './endpoints/following/requests/list.js'; +import * as ep___following_requests_reject from './endpoints/following/requests/reject.js'; +import * as ep___gallery_featured from './endpoints/gallery/featured.js'; +import * as ep___gallery_popular from './endpoints/gallery/popular.js'; +import * as ep___gallery_posts from './endpoints/gallery/posts.js'; +import * as ep___gallery_posts_create from './endpoints/gallery/posts/create.js'; +import * as ep___gallery_posts_delete from './endpoints/gallery/posts/delete.js'; +import * as ep___gallery_posts_like from './endpoints/gallery/posts/like.js'; +import * as ep___gallery_posts_show from './endpoints/gallery/posts/show.js'; +import * as ep___gallery_posts_unlike from './endpoints/gallery/posts/unlike.js'; +import * as ep___gallery_posts_update from './endpoints/gallery/posts/update.js'; +import * as ep___getOnlineUsersCount from './endpoints/get-online-users-count.js'; +import * as ep___hashtags_list from './endpoints/hashtags/list.js'; +import * as ep___hashtags_search from './endpoints/hashtags/search.js'; +import * as ep___hashtags_show from './endpoints/hashtags/show.js'; +import * as ep___hashtags_trend from './endpoints/hashtags/trend.js'; +import * as ep___hashtags_users from './endpoints/hashtags/users.js'; +import * as ep___i from './endpoints/i.js'; +import * as ep___i_2fa_done from './endpoints/i/2fa/done.js'; +import * as ep___i_2fa_keyDone from './endpoints/i/2fa/key-done.js'; +import * as ep___i_2fa_passwordLess from './endpoints/i/2fa/password-less.js'; +import * as ep___i_2fa_registerKey from './endpoints/i/2fa/register-key.js'; +import * as ep___i_2fa_register from './endpoints/i/2fa/register.js'; +import * as ep___i_2fa_removeKey from './endpoints/i/2fa/remove-key.js'; +import * as ep___i_2fa_unregister from './endpoints/i/2fa/unregister.js'; +import * as ep___i_apps from './endpoints/i/apps.js'; +import * as ep___i_authorizedApps from './endpoints/i/authorized-apps.js'; +import * as ep___i_changePassword from './endpoints/i/change-password.js'; +import * as ep___i_deleteAccount from './endpoints/i/delete-account.js'; +import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js'; +import * as ep___i_exportFollowing from './endpoints/i/export-following.js'; +import * as ep___i_exportMute from './endpoints/i/export-mute.js'; +import * as ep___i_exportNotes from './endpoints/i/export-notes.js'; +import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js'; +import * as ep___i_favorites from './endpoints/i/favorites.js'; +import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js'; +import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js'; +import * as ep___i_getWordMutedNotesCount from './endpoints/i/get-word-muted-notes-count.js'; +import * as ep___i_importBlocking from './endpoints/i/import-blocking.js'; +import * as ep___i_importFollowing from './endpoints/i/import-following.js'; +import * as ep___i_importMuting from './endpoints/i/import-muting.js'; +import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js'; +import * as ep___i_notifications from './endpoints/i/notifications.js'; +import * as ep___i_pageLikes from './endpoints/i/page-likes.js'; +import * as ep___i_pages from './endpoints/i/pages.js'; +import * as ep___i_pin from './endpoints/i/pin.js'; +import * as ep___i_readAllMessagingMessages from './endpoints/i/read-all-messaging-messages.js'; +import * as ep___i_readAllUnreadNotes from './endpoints/i/read-all-unread-notes.js'; +import * as ep___i_readAnnouncement from './endpoints/i/read-announcement.js'; +import * as ep___i_regenerateToken from './endpoints/i/regenerate-token.js'; +import * as ep___i_registry_getAll from './endpoints/i/registry/get-all.js'; +import * as ep___i_registry_getDetail from './endpoints/i/registry/get-detail.js'; +import * as ep___i_registry_get from './endpoints/i/registry/get.js'; +import * as ep___i_registry_keysWithType from './endpoints/i/registry/keys-with-type.js'; +import * as ep___i_registry_keys from './endpoints/i/registry/keys.js'; +import * as ep___i_registry_remove from './endpoints/i/registry/remove.js'; +import * as ep___i_registry_scopes from './endpoints/i/registry/scopes.js'; +import * as ep___i_registry_set from './endpoints/i/registry/set.js'; +import * as ep___i_revokeToken from './endpoints/i/revoke-token.js'; +import * as ep___i_signinHistory from './endpoints/i/signin-history.js'; +import * as ep___i_unpin from './endpoints/i/unpin.js'; +import * as ep___i_updateEmail from './endpoints/i/update-email.js'; +import * as ep___i_update from './endpoints/i/update.js'; +import * as ep___i_userGroupInvites from './endpoints/i/user-group-invites.js'; +import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js'; +import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js'; +import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js'; +import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js'; +import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js'; +import * as ep___messaging_history from './endpoints/messaging/history.js'; +import * as ep___messaging_messages from './endpoints/messaging/messages.js'; +import * as ep___messaging_messages_create from './endpoints/messaging/messages/create.js'; +import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js'; +import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js'; +import * as ep___meta from './endpoints/meta.js'; +import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js'; +import * as ep___mute_create from './endpoints/mute/create.js'; +import * as ep___mute_delete from './endpoints/mute/delete.js'; +import * as ep___mute_list from './endpoints/mute/list.js'; +import * as ep___my_apps from './endpoints/my/apps.js'; +import * as ep___notes from './endpoints/notes.js'; +import * as ep___notes_children from './endpoints/notes/children.js'; +import * as ep___notes_clips from './endpoints/notes/clips.js'; +import * as ep___notes_conversation from './endpoints/notes/conversation.js'; +import * as ep___notes_create from './endpoints/notes/create.js'; +import * as ep___notes_delete from './endpoints/notes/delete.js'; +import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js'; +import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; +import * as ep___notes_featured from './endpoints/notes/featured.js'; +import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js'; +import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js'; +import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js'; +import * as ep___notes_mentions from './endpoints/notes/mentions.js'; +import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js'; +import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js'; +import * as ep___notes_reactions from './endpoints/notes/reactions.js'; +import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js'; +import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js'; +import * as ep___notes_renotes from './endpoints/notes/renotes.js'; +import * as ep___notes_replies from './endpoints/notes/replies.js'; +import * as ep___notes_searchByTag from './endpoints/notes/search-by-tag.js'; +import * as ep___notes_search from './endpoints/notes/search.js'; +import * as ep___notes_show from './endpoints/notes/show.js'; +import * as ep___notes_state from './endpoints/notes/state.js'; +import * as ep___notes_threadMuting_create from './endpoints/notes/thread-muting/create.js'; +import * as ep___notes_threadMuting_delete from './endpoints/notes/thread-muting/delete.js'; +import * as ep___notes_timeline from './endpoints/notes/timeline.js'; +import * as ep___notes_translate from './endpoints/notes/translate.js'; +import * as ep___notes_unrenote from './endpoints/notes/unrenote.js'; +import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js'; +import * as ep___notifications_create from './endpoints/notifications/create.js'; +import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js'; +import * as ep___notifications_read from './endpoints/notifications/read.js'; +import * as ep___pagePush from './endpoints/page-push.js'; +import * as ep___pages_create from './endpoints/pages/create.js'; +import * as ep___pages_delete from './endpoints/pages/delete.js'; +import * as ep___pages_featured from './endpoints/pages/featured.js'; +import * as ep___pages_like from './endpoints/pages/like.js'; +import * as ep___pages_show from './endpoints/pages/show.js'; +import * as ep___pages_unlike from './endpoints/pages/unlike.js'; +import * as ep___pages_update from './endpoints/pages/update.js'; +import * as ep___ping from './endpoints/ping.js'; +import * as ep___pinnedUsers from './endpoints/pinned-users.js'; +import * as ep___promo_read from './endpoints/promo/read.js'; +import * as ep___requestResetPassword from './endpoints/request-reset-password.js'; +import * as ep___resetDb from './endpoints/reset-db.js'; +import * as ep___resetPassword from './endpoints/reset-password.js'; +import * as ep___serverInfo from './endpoints/server-info.js'; +import * as ep___stats from './endpoints/stats.js'; +import * as ep___sw_register from './endpoints/sw/register.js'; +import * as ep___sw_unregister from './endpoints/sw/unregister.js'; +import * as ep___test from './endpoints/test.js'; +import * as ep___username_available from './endpoints/username/available.js'; +import * as ep___users from './endpoints/users.js'; +import * as ep___users_clips from './endpoints/users/clips.js'; +import * as ep___users_followers from './endpoints/users/followers.js'; +import * as ep___users_following from './endpoints/users/following.js'; +import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js'; +import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js'; +import * as ep___users_groups_create from './endpoints/users/groups/create.js'; +import * as ep___users_groups_delete from './endpoints/users/groups/delete.js'; +import * as ep___users_groups_invitations_accept from './endpoints/users/groups/invitations/accept.js'; +import * as ep___users_groups_invitations_reject from './endpoints/users/groups/invitations/reject.js'; +import * as ep___users_groups_invite from './endpoints/users/groups/invite.js'; +import * as ep___users_groups_joined from './endpoints/users/groups/joined.js'; +import * as ep___users_groups_leave from './endpoints/users/groups/leave.js'; +import * as ep___users_groups_owned from './endpoints/users/groups/owned.js'; +import * as ep___users_groups_pull from './endpoints/users/groups/pull.js'; +import * as ep___users_groups_show from './endpoints/users/groups/show.js'; +import * as ep___users_groups_transfer from './endpoints/users/groups/transfer.js'; +import * as ep___users_groups_update from './endpoints/users/groups/update.js'; +import * as ep___users_lists_create from './endpoints/users/lists/create.js'; +import * as ep___users_lists_delete from './endpoints/users/lists/delete.js'; +import * as ep___users_lists_list from './endpoints/users/lists/list.js'; +import * as ep___users_lists_pull from './endpoints/users/lists/pull.js'; +import * as ep___users_lists_push from './endpoints/users/lists/push.js'; +import * as ep___users_lists_show from './endpoints/users/lists/show.js'; +import * as ep___users_lists_update from './endpoints/users/lists/update.js'; +import * as ep___users_notes from './endpoints/users/notes.js'; +import * as ep___users_pages from './endpoints/users/pages.js'; +import * as ep___users_reactions from './endpoints/users/reactions.js'; +import * as ep___users_recommendation from './endpoints/users/recommendation.js'; +import * as ep___users_relation from './endpoints/users/relation.js'; +import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js'; +import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js'; +import * as ep___users_search from './endpoints/users/search.js'; +import * as ep___users_show from './endpoints/users/show.js'; +import * as ep___users_stats from './endpoints/users/stats.js'; +import * as ep___fetchRss from './endpoints/fetch-rss.js'; +import * as ep___admin_driveCapOverride from './endpoints/admin/drive-capacity-override.js'; +import { GetterService } from './common/GetterService.js'; +import { ApiLoggerService } from './ApiLoggerService.js'; +import type { Provider } from '@nestjs/common'; + +const $admin_meta: Provider = { provide: 'ep:admin/meta', useClass: ep___admin_meta.default }; +const $admin_abuseUserReports: Provider = { provide: 'ep:admin/abuse-user-reports', useClass: ep___admin_abuseUserReports.default }; +const $admin_accounts_create: Provider = { provide: 'ep:admin/accounts/create', useClass: ep___admin_accounts_create.default }; +const $admin_accounts_delete: Provider = { provide: 'ep:admin/accounts/delete', useClass: ep___admin_accounts_delete.default }; +const $admin_ad_create: Provider = { provide: 'ep:admin/ad/create', useClass: ep___admin_ad_create.default }; +const $admin_ad_delete: Provider = { provide: 'ep:admin/ad/delete', useClass: ep___admin_ad_delete.default }; +const $admin_ad_list: Provider = { provide: 'ep:admin/ad/list', useClass: ep___admin_ad_list.default }; +const $admin_ad_update: Provider = { provide: 'ep:admin/ad/update', useClass: ep___admin_ad_update.default }; +const $admin_announcements_create: Provider = { provide: 'ep:admin/announcements/create', useClass: ep___admin_announcements_create.default }; +const $admin_announcements_delete: Provider = { provide: 'ep:admin/announcements/delete', useClass: ep___admin_announcements_delete.default }; +const $admin_announcements_list: Provider = { provide: 'ep:admin/announcements/list', useClass: ep___admin_announcements_list.default }; +const $admin_announcements_update: Provider = { provide: 'ep:admin/announcements/update', useClass: ep___admin_announcements_update.default }; +const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default }; +const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default }; +const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default }; +const $admin_drive_files: Provider = { provide: 'ep:admin/drive/files', useClass: ep___admin_drive_files.default }; +const $admin_drive_showFile: Provider = { provide: 'ep:admin/drive/show-file', useClass: ep___admin_drive_showFile.default }; +const $admin_emoji_addAliasesBulk: Provider = { provide: 'ep:admin/emoji/add-aliases-bulk', useClass: ep___admin_emoji_addAliasesBulk.default }; +const $admin_emoji_add: Provider = { provide: 'ep:admin/emoji/add', useClass: ep___admin_emoji_add.default }; +const $admin_emoji_copy: Provider = { provide: 'ep:admin/emoji/copy', useClass: ep___admin_emoji_copy.default }; +const $admin_emoji_deleteBulk: Provider = { provide: 'ep:admin/emoji/delete-bulk', useClass: ep___admin_emoji_deleteBulk.default }; +const $admin_emoji_delete: Provider = { provide: 'ep:admin/emoji/delete', useClass: ep___admin_emoji_delete.default }; +const $admin_emoji_importZip: Provider = { provide: 'ep:admin/emoji/import-zip', useClass: ep___admin_emoji_importZip.default }; +const $admin_emoji_listRemote: Provider = { provide: 'ep:admin/emoji/list-remote', useClass: ep___admin_emoji_listRemote.default }; +const $admin_emoji_list: Provider = { provide: 'ep:admin/emoji/list', useClass: ep___admin_emoji_list.default }; +const $admin_emoji_removeAliasesBulk: Provider = { provide: 'ep:admin/emoji/remove-aliases-bulk', useClass: ep___admin_emoji_removeAliasesBulk.default }; +const $admin_emoji_setAliasesBulk: Provider = { provide: 'ep:admin/emoji/set-aliases-bulk', useClass: ep___admin_emoji_setAliasesBulk.default }; +const $admin_emoji_setCategoryBulk: Provider = { provide: 'ep:admin/emoji/set-category-bulk', useClass: ep___admin_emoji_setCategoryBulk.default }; +const $admin_emoji_update: Provider = { provide: 'ep:admin/emoji/update', useClass: ep___admin_emoji_update.default }; +const $admin_federation_deleteAllFiles: Provider = { provide: 'ep:admin/federation/delete-all-files', useClass: ep___admin_federation_deleteAllFiles.default }; +const $admin_federation_refreshRemoteInstanceMetadata: Provider = { provide: 'ep:admin/federation/refresh-remote-instance-metadata', useClass: ep___admin_federation_refreshRemoteInstanceMetadata.default }; +const $admin_federation_removeAllFollowing: Provider = { provide: 'ep:admin/federation/remove-all-following', useClass: ep___admin_federation_removeAllFollowing.default }; +const $admin_federation_updateInstance: Provider = { provide: 'ep:admin/federation/update-instance', useClass: ep___admin_federation_updateInstance.default }; +const $admin_getIndexStats: Provider = { provide: 'ep:admin/get-index-stats', useClass: ep___admin_getIndexStats.default }; +const $admin_getTableStats: Provider = { provide: 'ep:admin/get-table-stats', useClass: ep___admin_getTableStats.default }; +const $admin_getUserIps: Provider = { provide: 'ep:admin/get-user-ips', useClass: ep___admin_getUserIps.default }; +const $admin_invite: Provider = { provide: 'ep:admin/invite', useClass: ep___admin_invite.default }; +const $admin_moderators_add: Provider = { provide: 'ep:admin/moderators/add', useClass: ep___admin_moderators_add.default }; +const $admin_moderators_remove: Provider = { provide: 'ep:admin/moderators/remove', useClass: ep___admin_moderators_remove.default }; +const $admin_promo_create: Provider = { provide: 'ep:admin/promo/create', useClass: ep___admin_promo_create.default }; +const $admin_queue_clear: Provider = { provide: 'ep:admin/queue/clear', useClass: ep___admin_queue_clear.default }; +const $admin_queue_deliverDelayed: Provider = { provide: 'ep:admin/queue/deliver-delayed', useClass: ep___admin_queue_deliverDelayed.default }; +const $admin_queue_inboxDelayed: Provider = { provide: 'ep:admin/queue/inbox-delayed', useClass: ep___admin_queue_inboxDelayed.default }; +const $admin_queue_stats: Provider = { provide: 'ep:admin/queue/stats', useClass: ep___admin_queue_stats.default }; +const $admin_relays_add: Provider = { provide: 'ep:admin/relays/add', useClass: ep___admin_relays_add.default }; +const $admin_relays_list: Provider = { provide: 'ep:admin/relays/list', useClass: ep___admin_relays_list.default }; +const $admin_relays_remove: Provider = { provide: 'ep:admin/relays/remove', useClass: ep___admin_relays_remove.default }; +const $admin_resetPassword: Provider = { provide: 'ep:admin/reset-password', useClass: ep___admin_resetPassword.default }; +const $admin_resolveAbuseUserReport: Provider = { provide: 'ep:admin/resolve-abuse-user-report', useClass: ep___admin_resolveAbuseUserReport.default }; +const $admin_sendEmail: Provider = { provide: 'ep:admin/send-email', useClass: ep___admin_sendEmail.default }; +const $admin_serverInfo: Provider = { provide: 'ep:admin/server-info', useClass: ep___admin_serverInfo.default }; +const $admin_showModerationLogs: Provider = { provide: 'ep:admin/show-moderation-logs', useClass: ep___admin_showModerationLogs.default }; +const $admin_showUser: Provider = { provide: 'ep:admin/show-user', useClass: ep___admin_showUser.default }; +const $admin_showUsers: Provider = { provide: 'ep:admin/show-users', useClass: ep___admin_showUsers.default }; +const $admin_silenceUser: Provider = { provide: 'ep:admin/silence-user', useClass: ep___admin_silenceUser.default }; +const $admin_suspendUser: Provider = { provide: 'ep:admin/suspend-user', useClass: ep___admin_suspendUser.default }; +const $admin_unsilenceUser: Provider = { provide: 'ep:admin/unsilence-user', useClass: ep___admin_unsilenceUser.default }; +const $admin_unsuspendUser: Provider = { provide: 'ep:admin/unsuspend-user', useClass: ep___admin_unsuspendUser.default }; +const $admin_updateMeta: Provider = { provide: 'ep:admin/update-meta', useClass: ep___admin_updateMeta.default }; +const $admin_deleteAccount: Provider = { provide: 'ep:admin/delete-account', useClass: ep___admin_deleteAccount.default }; +const $admin_updateUserNote: Provider = { provide: 'ep:admin/update-user-note', useClass: ep___admin_updateUserNote.default }; +const $announcements: Provider = { provide: 'ep:announcements', useClass: ep___announcements.default }; +const $antennas_create: Provider = { provide: 'ep:antennas/create', useClass: ep___antennas_create.default }; +const $antennas_delete: Provider = { provide: 'ep:antennas/delete', useClass: ep___antennas_delete.default }; +const $antennas_list: Provider = { provide: 'ep:antennas/list', useClass: ep___antennas_list.default }; +const $antennas_notes: Provider = { provide: 'ep:antennas/notes', useClass: ep___antennas_notes.default }; +const $antennas_show: Provider = { provide: 'ep:antennas/show', useClass: ep___antennas_show.default }; +const $antennas_update: Provider = { provide: 'ep:antennas/update', useClass: ep___antennas_update.default }; +const $ap_get: Provider = { provide: 'ep:ap/get', useClass: ep___ap_get.default }; +const $ap_show: Provider = { provide: 'ep:ap/show', useClass: ep___ap_show.default }; +const $app_create: Provider = { provide: 'ep:app/create', useClass: ep___app_create.default }; +const $app_show: Provider = { provide: 'ep:app/show', useClass: ep___app_show.default }; +const $auth_accept: Provider = { provide: 'ep:auth/accept', useClass: ep___auth_accept.default }; +const $auth_session_generate: Provider = { provide: 'ep:auth/session/generate', useClass: ep___auth_session_generate.default }; +const $auth_session_show: Provider = { provide: 'ep:auth/session/show', useClass: ep___auth_session_show.default }; +const $auth_session_userkey: Provider = { provide: 'ep:auth/session/userkey', useClass: ep___auth_session_userkey.default }; +const $blocking_create: Provider = { provide: 'ep:blocking/create', useClass: ep___blocking_create.default }; +const $blocking_delete: Provider = { provide: 'ep:blocking/delete', useClass: ep___blocking_delete.default }; +const $blocking_list: Provider = { provide: 'ep:blocking/list', useClass: ep___blocking_list.default }; +const $channels_create: Provider = { provide: 'ep:channels/create', useClass: ep___channels_create.default }; +const $channels_featured: Provider = { provide: 'ep:channels/featured', useClass: ep___channels_featured.default }; +const $channels_follow: Provider = { provide: 'ep:channels/follow', useClass: ep___channels_follow.default }; +const $channels_followed: Provider = { provide: 'ep:channels/followed', useClass: ep___channels_followed.default }; +const $channels_owned: Provider = { provide: 'ep:channels/owned', useClass: ep___channels_owned.default }; +const $channels_show: Provider = { provide: 'ep:channels/show', useClass: ep___channels_show.default }; +const $channels_timeline: Provider = { provide: 'ep:channels/timeline', useClass: ep___channels_timeline.default }; +const $channels_unfollow: Provider = { provide: 'ep:channels/unfollow', useClass: ep___channels_unfollow.default }; +const $channels_update: Provider = { provide: 'ep:channels/update', useClass: ep___channels_update.default }; +const $charts_activeUsers: Provider = { provide: 'ep:charts/active-users', useClass: ep___charts_activeUsers.default }; +const $charts_apRequest: Provider = { provide: 'ep:charts/ap-request', useClass: ep___charts_apRequest.default }; +const $charts_drive: Provider = { provide: 'ep:charts/drive', useClass: ep___charts_drive.default }; +const $charts_federation: Provider = { provide: 'ep:charts/federation', useClass: ep___charts_federation.default }; +const $charts_hashtag: Provider = { provide: 'ep:charts/hashtag', useClass: ep___charts_hashtag.default }; +const $charts_instance: Provider = { provide: 'ep:charts/instance', useClass: ep___charts_instance.default }; +const $charts_notes: Provider = { provide: 'ep:charts/notes', useClass: ep___charts_notes.default }; +const $charts_user_drive: Provider = { provide: 'ep:charts/user/drive', useClass: ep___charts_user_drive.default }; +const $charts_user_following: Provider = { provide: 'ep:charts/user/following', useClass: ep___charts_user_following.default }; +const $charts_user_notes: Provider = { provide: 'ep:charts/user/notes', useClass: ep___charts_user_notes.default }; +const $charts_user_reactions: Provider = { provide: 'ep:charts/user/reactions', useClass: ep___charts_user_reactions.default }; +const $charts_users: Provider = { provide: 'ep:charts/users', useClass: ep___charts_users.default }; +const $clips_addNote: Provider = { provide: 'ep:clips/add-note', useClass: ep___clips_addNote.default }; +const $clips_removeNote: Provider = { provide: 'ep:clips/remove-note', useClass: ep___clips_removeNote.default }; +const $clips_create: Provider = { provide: 'ep:clips/create', useClass: ep___clips_create.default }; +const $clips_delete: Provider = { provide: 'ep:clips/delete', useClass: ep___clips_delete.default }; +const $clips_list: Provider = { provide: 'ep:clips/list', useClass: ep___clips_list.default }; +const $clips_notes: Provider = { provide: 'ep:clips/notes', useClass: ep___clips_notes.default }; +const $clips_show: Provider = { provide: 'ep:clips/show', useClass: ep___clips_show.default }; +const $clips_update: Provider = { provide: 'ep:clips/update', useClass: ep___clips_update.default }; +const $drive: Provider = { provide: 'ep:drive', useClass: ep___drive.default }; +const $drive_files: Provider = { provide: 'ep:drive/files', useClass: ep___drive_files.default }; +const $drive_files_attachedNotes: Provider = { provide: 'ep:drive/files/attached-notes', useClass: ep___drive_files_attachedNotes.default }; +const $drive_files_checkExistence: Provider = { provide: 'ep:drive/files/check-existence', useClass: ep___drive_files_checkExistence.default }; +const $drive_files_create: Provider = { provide: 'ep:drive/files/create', useClass: ep___drive_files_create.default }; +const $drive_files_delete: Provider = { provide: 'ep:drive/files/delete', useClass: ep___drive_files_delete.default }; +const $drive_files_findByHash: Provider = { provide: 'ep:drive/files/find-by-hash', useClass: ep___drive_files_findByHash.default }; +const $drive_files_find: Provider = { provide: 'ep:drive/files/find', useClass: ep___drive_files_find.default }; +const $drive_files_show: Provider = { provide: 'ep:drive/files/show', useClass: ep___drive_files_show.default }; +const $drive_files_update: Provider = { provide: 'ep:drive/files/update', useClass: ep___drive_files_update.default }; +const $drive_files_uploadFromUrl: Provider = { provide: 'ep:drive/files/upload-from-url', useClass: ep___drive_files_uploadFromUrl.default }; +const $drive_folders: Provider = { provide: 'ep:drive/folders', useClass: ep___drive_folders.default }; +const $drive_folders_create: Provider = { provide: 'ep:drive/folders/create', useClass: ep___drive_folders_create.default }; +const $drive_folders_delete: Provider = { provide: 'ep:drive/folders/delete', useClass: ep___drive_folders_delete.default }; +const $drive_folders_find: Provider = { provide: 'ep:drive/folders/find', useClass: ep___drive_folders_find.default }; +const $drive_folders_show: Provider = { provide: 'ep:drive/folders/show', useClass: ep___drive_folders_show.default }; +const $drive_folders_update: Provider = { provide: 'ep:drive/folders/update', useClass: ep___drive_folders_update.default }; +const $drive_stream: Provider = { provide: 'ep:drive/stream', useClass: ep___drive_stream.default }; +const $emailAddress_available: Provider = { provide: 'ep:email-address/available', useClass: ep___emailAddress_available.default }; +const $endpoint: Provider = { provide: 'ep:endpoint', useClass: ep___endpoint.default }; +const $endpoints: Provider = { provide: 'ep:endpoints', useClass: ep___endpoints.default }; +const $exportCustomEmojis: Provider = { provide: 'ep:export-custom-emojis', useClass: ep___exportCustomEmojis.default }; +const $federation_followers: Provider = { provide: 'ep:federation/followers', useClass: ep___federation_followers.default }; +const $federation_following: Provider = { provide: 'ep:federation/following', useClass: ep___federation_following.default }; +const $federation_instances: Provider = { provide: 'ep:federation/instances', useClass: ep___federation_instances.default }; +const $federation_showInstance: Provider = { provide: 'ep:federation/show-instance', useClass: ep___federation_showInstance.default }; +const $federation_updateRemoteUser: Provider = { provide: 'ep:federation/update-remote-user', useClass: ep___federation_updateRemoteUser.default }; +const $federation_users: Provider = { provide: 'ep:federation/users', useClass: ep___federation_users.default }; +const $federation_stats: Provider = { provide: 'ep:federation/stats', useClass: ep___federation_stats.default }; +const $following_create: Provider = { provide: 'ep:following/create', useClass: ep___following_create.default }; +const $following_delete: Provider = { provide: 'ep:following/delete', useClass: ep___following_delete.default }; +const $following_invalidate: Provider = { provide: 'ep:following/invalidate', useClass: ep___following_invalidate.default }; +const $following_requests_accept: Provider = { provide: 'ep:following/requests/accept', useClass: ep___following_requests_accept.default }; +const $following_requests_cancel: Provider = { provide: 'ep:following/requests/cancel', useClass: ep___following_requests_cancel.default }; +const $following_requests_list: Provider = { provide: 'ep:following/requests/list', useClass: ep___following_requests_list.default }; +const $following_requests_reject: Provider = { provide: 'ep:following/requests/reject', useClass: ep___following_requests_reject.default }; +const $gallery_featured: Provider = { provide: 'ep:gallery/featured', useClass: ep___gallery_featured.default }; +const $gallery_popular: Provider = { provide: 'ep:gallery/popular', useClass: ep___gallery_popular.default }; +const $gallery_posts: Provider = { provide: 'ep:gallery/posts', useClass: ep___gallery_posts.default }; +const $gallery_posts_create: Provider = { provide: 'ep:gallery/posts/create', useClass: ep___gallery_posts_create.default }; +const $gallery_posts_delete: Provider = { provide: 'ep:gallery/posts/delete', useClass: ep___gallery_posts_delete.default }; +const $gallery_posts_like: Provider = { provide: 'ep:gallery/posts/like', useClass: ep___gallery_posts_like.default }; +const $gallery_posts_show: Provider = { provide: 'ep:gallery/posts/show', useClass: ep___gallery_posts_show.default }; +const $gallery_posts_unlike: Provider = { provide: 'ep:gallery/posts/unlike', useClass: ep___gallery_posts_unlike.default }; +const $gallery_posts_update: Provider = { provide: 'ep:gallery/posts/update', useClass: ep___gallery_posts_update.default }; +const $getOnlineUsersCount: Provider = { provide: 'ep:get-online-users-count', useClass: ep___getOnlineUsersCount.default }; +const $hashtags_list: Provider = { provide: 'ep:hashtags/list', useClass: ep___hashtags_list.default }; +const $hashtags_search: Provider = { provide: 'ep:hashtags/search', useClass: ep___hashtags_search.default }; +const $hashtags_show: Provider = { provide: 'ep:hashtags/show', useClass: ep___hashtags_show.default }; +const $hashtags_trend: Provider = { provide: 'ep:hashtags/trend', useClass: ep___hashtags_trend.default }; +const $hashtags_users: Provider = { provide: 'ep:hashtags/users', useClass: ep___hashtags_users.default }; +const $i: Provider = { provide: 'ep:i', useClass: ep___i.default }; +const $i_2fa_done: Provider = { provide: 'ep:i/2fa/done', useClass: ep___i_2fa_done.default }; +const $i_2fa_keyDone: Provider = { provide: 'ep:i/2fa/key-done', useClass: ep___i_2fa_keyDone.default }; +const $i_2fa_passwordLess: Provider = { provide: 'ep:i/2fa/password-less', useClass: ep___i_2fa_passwordLess.default }; +const $i_2fa_registerKey: Provider = { provide: 'ep:i/2fa/register-key', useClass: ep___i_2fa_registerKey.default }; +const $i_2fa_register: Provider = { provide: 'ep:i/2fa/register', useClass: ep___i_2fa_register.default }; +const $i_2fa_removeKey: Provider = { provide: 'ep:i/2fa/remove-key', useClass: ep___i_2fa_removeKey.default }; +const $i_2fa_unregister: Provider = { provide: 'ep:i/2fa/unregister', useClass: ep___i_2fa_unregister.default }; +const $i_apps: Provider = { provide: 'ep:i/apps', useClass: ep___i_apps.default }; +const $i_authorizedApps: Provider = { provide: 'ep:i/authorized-apps', useClass: ep___i_authorizedApps.default }; +const $i_changePassword: Provider = { provide: 'ep:i/change-password', useClass: ep___i_changePassword.default }; +const $i_deleteAccount: Provider = { provide: 'ep:i/delete-account', useClass: ep___i_deleteAccount.default }; +const $i_exportBlocking: Provider = { provide: 'ep:i/export-blocking', useClass: ep___i_exportBlocking.default }; +const $i_exportFollowing: Provider = { provide: 'ep:i/export-following', useClass: ep___i_exportFollowing.default }; +const $i_exportMute: Provider = { provide: 'ep:i/export-mute', useClass: ep___i_exportMute.default }; +const $i_exportNotes: Provider = { provide: 'ep:i/export-notes', useClass: ep___i_exportNotes.default }; +const $i_exportUserLists: Provider = { provide: 'ep:i/export-user-lists', useClass: ep___i_exportUserLists.default }; +const $i_favorites: Provider = { provide: 'ep:i/favorites', useClass: ep___i_favorites.default }; +const $i_gallery_likes: Provider = { provide: 'ep:i/gallery/likes', useClass: ep___i_gallery_likes.default }; +const $i_gallery_posts: Provider = { provide: 'ep:i/gallery/posts', useClass: ep___i_gallery_posts.default }; +const $i_getWordMutedNotesCount: Provider = { provide: 'ep:i/get-word-muted-notes-count', useClass: ep___i_getWordMutedNotesCount.default }; +const $i_importBlocking: Provider = { provide: 'ep:i/import-blocking', useClass: ep___i_importBlocking.default }; +const $i_importFollowing: Provider = { provide: 'ep:i/import-following', useClass: ep___i_importFollowing.default }; +const $i_importMuting: Provider = { provide: 'ep:i/import-muting', useClass: ep___i_importMuting.default }; +const $i_importUserLists: Provider = { provide: 'ep:i/import-user-lists', useClass: ep___i_importUserLists.default }; +const $i_notifications: Provider = { provide: 'ep:i/notifications', useClass: ep___i_notifications.default }; +const $i_pageLikes: Provider = { provide: 'ep:i/page-likes', useClass: ep___i_pageLikes.default }; +const $i_pages: Provider = { provide: 'ep:i/pages', useClass: ep___i_pages.default }; +const $i_pin: Provider = { provide: 'ep:i/pin', useClass: ep___i_pin.default }; +const $i_readAllMessagingMessages: Provider = { provide: 'ep:i/read-all-messaging-messages', useClass: ep___i_readAllMessagingMessages.default }; +const $i_readAllUnreadNotes: Provider = { provide: 'ep:i/read-all-unread-notes', useClass: ep___i_readAllUnreadNotes.default }; +const $i_readAnnouncement: Provider = { provide: 'ep:i/read-announcement', useClass: ep___i_readAnnouncement.default }; +const $i_regenerateToken: Provider = { provide: 'ep:i/regenerate-token', useClass: ep___i_regenerateToken.default }; +const $i_registry_getAll: Provider = { provide: 'ep:i/registry/get-all', useClass: ep___i_registry_getAll.default }; +const $i_registry_getDetail: Provider = { provide: 'ep:i/registry/get-detail', useClass: ep___i_registry_getDetail.default }; +const $i_registry_get: Provider = { provide: 'ep:i/registry/get', useClass: ep___i_registry_get.default }; +const $i_registry_keysWithType: Provider = { provide: 'ep:i/registry/keys-with-type', useClass: ep___i_registry_keysWithType.default }; +const $i_registry_keys: Provider = { provide: 'ep:i/registry/keys', useClass: ep___i_registry_keys.default }; +const $i_registry_remove: Provider = { provide: 'ep:i/registry/remove', useClass: ep___i_registry_remove.default }; +const $i_registry_scopes: Provider = { provide: 'ep:i/registry/scopes', useClass: ep___i_registry_scopes.default }; +const $i_registry_set: Provider = { provide: 'ep:i/registry/set', useClass: ep___i_registry_set.default }; +const $i_revokeToken: Provider = { provide: 'ep:i/revoke-token', useClass: ep___i_revokeToken.default }; +const $i_signinHistory: Provider = { provide: 'ep:i/signin-history', useClass: ep___i_signinHistory.default }; +const $i_unpin: Provider = { provide: 'ep:i/unpin', useClass: ep___i_unpin.default }; +const $i_updateEmail: Provider = { provide: 'ep:i/update-email', useClass: ep___i_updateEmail.default }; +const $i_update: Provider = { provide: 'ep:i/update', useClass: ep___i_update.default }; +const $i_userGroupInvites: Provider = { provide: 'ep:i/user-group-invites', useClass: ep___i_userGroupInvites.default }; +const $i_webhooks_create: Provider = { provide: 'ep:i/webhooks/create', useClass: ep___i_webhooks_create.default }; +const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep___i_webhooks_list.default }; +const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default }; +const $i_webhooks_update: Provider = { provide: 'ep:i/webhooks/update', useClass: ep___i_webhooks_update.default }; +const $i_webhooks_delete: Provider = { provide: 'ep:i/webhooks/delete', useClass: ep___i_webhooks_delete.default }; +const $messaging_history: Provider = { provide: 'ep:messaging/history', useClass: ep___messaging_history.default }; +const $messaging_messages: Provider = { provide: 'ep:messaging/messages', useClass: ep___messaging_messages.default }; +const $messaging_messages_create: Provider = { provide: 'ep:messaging/messages/create', useClass: ep___messaging_messages_create.default }; +const $messaging_messages_delete: Provider = { provide: 'ep:messaging/messages/delete', useClass: ep___messaging_messages_delete.default }; +const $messaging_messages_read: Provider = { provide: 'ep:messaging/messages/read', useClass: ep___messaging_messages_read.default }; +const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default }; +const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default }; +const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default }; +const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default }; +const $mute_list: Provider = { provide: 'ep:mute/list', useClass: ep___mute_list.default }; +const $my_apps: Provider = { provide: 'ep:my/apps', useClass: ep___my_apps.default }; +const $notes: Provider = { provide: 'ep:notes', useClass: ep___notes.default }; +const $notes_children: Provider = { provide: 'ep:notes/children', useClass: ep___notes_children.default }; +const $notes_clips: Provider = { provide: 'ep:notes/clips', useClass: ep___notes_clips.default }; +const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default }; +const $notes_create: Provider = { provide: 'ep:notes/create', useClass: ep___notes_create.default }; +const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___notes_delete.default }; +const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default }; +const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default }; +const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default }; +const $notes_globalTimeline: Provider = { provide: 'ep:notes/global-timeline', useClass: ep___notes_globalTimeline.default }; +const $notes_hybridTimeline: Provider = { provide: 'ep:notes/hybrid-timeline', useClass: ep___notes_hybridTimeline.default }; +const $notes_localTimeline: Provider = { provide: 'ep:notes/local-timeline', useClass: ep___notes_localTimeline.default }; +const $notes_mentions: Provider = { provide: 'ep:notes/mentions', useClass: ep___notes_mentions.default }; +const $notes_polls_recommendation: Provider = { provide: 'ep:notes/polls/recommendation', useClass: ep___notes_polls_recommendation.default }; +const $notes_polls_vote: Provider = { provide: 'ep:notes/polls/vote', useClass: ep___notes_polls_vote.default }; +const $notes_reactions: Provider = { provide: 'ep:notes/reactions', useClass: ep___notes_reactions.default }; +const $notes_reactions_create: Provider = { provide: 'ep:notes/reactions/create', useClass: ep___notes_reactions_create.default }; +const $notes_reactions_delete: Provider = { provide: 'ep:notes/reactions/delete', useClass: ep___notes_reactions_delete.default }; +const $notes_renotes: Provider = { provide: 'ep:notes/renotes', useClass: ep___notes_renotes.default }; +const $notes_replies: Provider = { provide: 'ep:notes/replies', useClass: ep___notes_replies.default }; +const $notes_searchByTag: Provider = { provide: 'ep:notes/search-by-tag', useClass: ep___notes_searchByTag.default }; +const $notes_search: Provider = { provide: 'ep:notes/search', useClass: ep___notes_search.default }; +const $notes_show: Provider = { provide: 'ep:notes/show', useClass: ep___notes_show.default }; +const $notes_state: Provider = { provide: 'ep:notes/state', useClass: ep___notes_state.default }; +const $notes_threadMuting_create: Provider = { provide: 'ep:notes/thread-muting/create', useClass: ep___notes_threadMuting_create.default }; +const $notes_threadMuting_delete: Provider = { provide: 'ep:notes/thread-muting/delete', useClass: ep___notes_threadMuting_delete.default }; +const $notes_timeline: Provider = { provide: 'ep:notes/timeline', useClass: ep___notes_timeline.default }; +const $notes_translate: Provider = { provide: 'ep:notes/translate', useClass: ep___notes_translate.default }; +const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep___notes_unrenote.default }; +const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timeline', useClass: ep___notes_userListTimeline.default }; +const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default }; +const $notifications_markAllAsRead: Provider = { provide: 'ep:notifications/mark-all-as-read', useClass: ep___notifications_markAllAsRead.default }; +const $notifications_read: Provider = { provide: 'ep:notifications/read', useClass: ep___notifications_read.default }; +const $pagePush: Provider = { provide: 'ep:page-push', useClass: ep___pagePush.default }; +const $pages_create: Provider = { provide: 'ep:pages/create', useClass: ep___pages_create.default }; +const $pages_delete: Provider = { provide: 'ep:pages/delete', useClass: ep___pages_delete.default }; +const $pages_featured: Provider = { provide: 'ep:pages/featured', useClass: ep___pages_featured.default }; +const $pages_like: Provider = { provide: 'ep:pages/like', useClass: ep___pages_like.default }; +const $pages_show: Provider = { provide: 'ep:pages/show', useClass: ep___pages_show.default }; +const $pages_unlike: Provider = { provide: 'ep:pages/unlike', useClass: ep___pages_unlike.default }; +const $pages_update: Provider = { provide: 'ep:pages/update', useClass: ep___pages_update.default }; +const $ping: Provider = { provide: 'ep:ping', useClass: ep___ping.default }; +const $pinnedUsers: Provider = { provide: 'ep:pinned-users', useClass: ep___pinnedUsers.default }; +const $promo_read: Provider = { provide: 'ep:promo/read', useClass: ep___promo_read.default }; +const $requestResetPassword: Provider = { provide: 'ep:request-reset-password', useClass: ep___requestResetPassword.default }; +const $resetDb: Provider = { provide: 'ep:reset-db', useClass: ep___resetDb.default }; +const $resetPassword: Provider = { provide: 'ep:reset-password', useClass: ep___resetPassword.default }; +const $serverInfo: Provider = { provide: 'ep:server-info', useClass: ep___serverInfo.default }; +const $stats: Provider = { provide: 'ep:stats', useClass: ep___stats.default }; +const $sw_register: Provider = { provide: 'ep:sw/register', useClass: ep___sw_register.default }; +const $sw_unregister: Provider = { provide: 'ep:sw/unregister', useClass: ep___sw_unregister.default }; +const $test: Provider = { provide: 'ep:test', useClass: ep___test.default }; +const $username_available: Provider = { provide: 'ep:username/available', useClass: ep___username_available.default }; +const $users: Provider = { provide: 'ep:users', useClass: ep___users.default }; +const $users_clips: Provider = { provide: 'ep:users/clips', useClass: ep___users_clips.default }; +const $users_followers: Provider = { provide: 'ep:users/followers', useClass: ep___users_followers.default }; +const $users_following: Provider = { provide: 'ep:users/following', useClass: ep___users_following.default }; +const $users_gallery_posts: Provider = { provide: 'ep:users/gallery/posts', useClass: ep___users_gallery_posts.default }; +const $users_getFrequentlyRepliedUsers: Provider = { provide: 'ep:users/get-frequently-replied-users', useClass: ep___users_getFrequentlyRepliedUsers.default }; +const $users_groups_create: Provider = { provide: 'ep:users/groups/create', useClass: ep___users_groups_create.default }; +const $users_groups_delete: Provider = { provide: 'ep:users/groups/delete', useClass: ep___users_groups_delete.default }; +const $users_groups_invitations_accept: Provider = { provide: 'ep:users/groups/invitations/accept', useClass: ep___users_groups_invitations_accept.default }; +const $users_groups_invitations_reject: Provider = { provide: 'ep:users/groups/invitations/reject', useClass: ep___users_groups_invitations_reject.default }; +const $users_groups_invite: Provider = { provide: 'ep:users/groups/invite', useClass: ep___users_groups_invite.default }; +const $users_groups_joined: Provider = { provide: 'ep:users/groups/joined', useClass: ep___users_groups_joined.default }; +const $users_groups_leave: Provider = { provide: 'ep:users/groups/leave', useClass: ep___users_groups_leave.default }; +const $users_groups_owned: Provider = { provide: 'ep:users/groups/owned', useClass: ep___users_groups_owned.default }; +const $users_groups_pull: Provider = { provide: 'ep:users/groups/pull', useClass: ep___users_groups_pull.default }; +const $users_groups_show: Provider = { provide: 'ep:users/groups/show', useClass: ep___users_groups_show.default }; +const $users_groups_transfer: Provider = { provide: 'ep:users/groups/transfer', useClass: ep___users_groups_transfer.default }; +const $users_groups_update: Provider = { provide: 'ep:users/groups/update', useClass: ep___users_groups_update.default }; +const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default }; +const $users_lists_delete: Provider = { provide: 'ep:users/lists/delete', useClass: ep___users_lists_delete.default }; +const $users_lists_list: Provider = { provide: 'ep:users/lists/list', useClass: ep___users_lists_list.default }; +const $users_lists_pull: Provider = { provide: 'ep:users/lists/pull', useClass: ep___users_lists_pull.default }; +const $users_lists_push: Provider = { provide: 'ep:users/lists/push', useClass: ep___users_lists_push.default }; +const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass: ep___users_lists_show.default }; +const $users_lists_update: Provider = { provide: 'ep:users/lists/update', useClass: ep___users_lists_update.default }; +const $users_notes: Provider = { provide: 'ep:users/notes', useClass: ep___users_notes.default }; +const $users_pages: Provider = { provide: 'ep:users/pages', useClass: ep___users_pages.default }; +const $users_reactions: Provider = { provide: 'ep:users/reactions', useClass: ep___users_reactions.default }; +const $users_recommendation: Provider = { provide: 'ep:users/recommendation', useClass: ep___users_recommendation.default }; +const $users_relation: Provider = { provide: 'ep:users/relation', useClass: ep___users_relation.default }; +const $users_reportAbuse: Provider = { provide: 'ep:users/report-abuse', useClass: ep___users_reportAbuse.default }; +const $users_searchByUsernameAndHost: Provider = { provide: 'ep:users/search-by-username-and-host', useClass: ep___users_searchByUsernameAndHost.default }; +const $users_search: Provider = { provide: 'ep:users/search', useClass: ep___users_search.default }; +const $users_show: Provider = { provide: 'ep:users/show', useClass: ep___users_show.default }; +const $users_stats: Provider = { provide: 'ep:users/stats', useClass: ep___users_stats.default }; +const $admin_driveCapOverride: Provider = { provide: 'ep:admin/drive-capacity-override', useClass: ep___admin_driveCapOverride.default }; +const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.default }; + +@Module({ + imports: [ + CoreModule, + ], + providers: [ + GetterService, + ApiLoggerService, + $admin_meta, + $admin_abuseUserReports, + $admin_accounts_create, + $admin_accounts_delete, + $admin_ad_create, + $admin_ad_delete, + $admin_ad_list, + $admin_ad_update, + $admin_announcements_create, + $admin_announcements_delete, + $admin_announcements_list, + $admin_announcements_update, + $admin_deleteAllFilesOfAUser, + $admin_drive_cleanRemoteFiles, + $admin_drive_cleanup, + $admin_drive_files, + $admin_drive_showFile, + $admin_emoji_addAliasesBulk, + $admin_emoji_add, + $admin_emoji_copy, + $admin_emoji_deleteBulk, + $admin_emoji_delete, + $admin_emoji_importZip, + $admin_emoji_listRemote, + $admin_emoji_list, + $admin_emoji_removeAliasesBulk, + $admin_emoji_setAliasesBulk, + $admin_emoji_setCategoryBulk, + $admin_emoji_update, + $admin_federation_deleteAllFiles, + $admin_federation_refreshRemoteInstanceMetadata, + $admin_federation_removeAllFollowing, + $admin_federation_updateInstance, + $admin_getIndexStats, + $admin_getTableStats, + $admin_getUserIps, + $admin_invite, + $admin_moderators_add, + $admin_moderators_remove, + $admin_promo_create, + $admin_queue_clear, + $admin_queue_deliverDelayed, + $admin_queue_inboxDelayed, + $admin_queue_stats, + $admin_relays_add, + $admin_relays_list, + $admin_relays_remove, + $admin_resetPassword, + $admin_resolveAbuseUserReport, + $admin_sendEmail, + $admin_serverInfo, + $admin_showModerationLogs, + $admin_showUser, + $admin_showUsers, + $admin_silenceUser, + $admin_suspendUser, + $admin_unsilenceUser, + $admin_unsuspendUser, + $admin_updateMeta, + $admin_deleteAccount, + $admin_updateUserNote, + $announcements, + $antennas_create, + $antennas_delete, + $antennas_list, + $antennas_notes, + $antennas_show, + $antennas_update, + $ap_get, + $ap_show, + $app_create, + $app_show, + $auth_accept, + $auth_session_generate, + $auth_session_show, + $auth_session_userkey, + $blocking_create, + $blocking_delete, + $blocking_list, + $channels_create, + $channels_featured, + $channels_follow, + $channels_followed, + $channels_owned, + $channels_show, + $channels_timeline, + $channels_unfollow, + $channels_update, + $charts_activeUsers, + $charts_apRequest, + $charts_drive, + $charts_federation, + $charts_hashtag, + $charts_instance, + $charts_notes, + $charts_user_drive, + $charts_user_following, + $charts_user_notes, + $charts_user_reactions, + $charts_users, + $clips_addNote, + $clips_removeNote, + $clips_create, + $clips_delete, + $clips_list, + $clips_notes, + $clips_show, + $clips_update, + $drive, + $drive_files, + $drive_files_attachedNotes, + $drive_files_checkExistence, + $drive_files_create, + $drive_files_delete, + $drive_files_findByHash, + $drive_files_find, + $drive_files_show, + $drive_files_update, + $drive_files_uploadFromUrl, + $drive_folders, + $drive_folders_create, + $drive_folders_delete, + $drive_folders_find, + $drive_folders_show, + $drive_folders_update, + $drive_stream, + $emailAddress_available, + $endpoint, + $endpoints, + $exportCustomEmojis, + $federation_followers, + $federation_following, + $federation_instances, + $federation_showInstance, + $federation_updateRemoteUser, + $federation_users, + $federation_stats, + $following_create, + $following_delete, + $following_invalidate, + $following_requests_accept, + $following_requests_cancel, + $following_requests_list, + $following_requests_reject, + $gallery_featured, + $gallery_popular, + $gallery_posts, + $gallery_posts_create, + $gallery_posts_delete, + $gallery_posts_like, + $gallery_posts_show, + $gallery_posts_unlike, + $gallery_posts_update, + $getOnlineUsersCount, + $hashtags_list, + $hashtags_search, + $hashtags_show, + $hashtags_trend, + $hashtags_users, + $i, + $i_2fa_done, + $i_2fa_keyDone, + $i_2fa_passwordLess, + $i_2fa_registerKey, + $i_2fa_register, + $i_2fa_removeKey, + $i_2fa_unregister, + $i_apps, + $i_authorizedApps, + $i_changePassword, + $i_deleteAccount, + $i_exportBlocking, + $i_exportFollowing, + $i_exportMute, + $i_exportNotes, + $i_exportUserLists, + $i_favorites, + $i_gallery_likes, + $i_gallery_posts, + $i_getWordMutedNotesCount, + $i_importBlocking, + $i_importFollowing, + $i_importMuting, + $i_importUserLists, + $i_notifications, + $i_pageLikes, + $i_pages, + $i_pin, + $i_readAllMessagingMessages, + $i_readAllUnreadNotes, + $i_readAnnouncement, + $i_regenerateToken, + $i_registry_getAll, + $i_registry_getDetail, + $i_registry_get, + $i_registry_keysWithType, + $i_registry_keys, + $i_registry_remove, + $i_registry_scopes, + $i_registry_set, + $i_revokeToken, + $i_signinHistory, + $i_unpin, + $i_updateEmail, + $i_update, + $i_userGroupInvites, + $i_webhooks_create, + $i_webhooks_list, + $i_webhooks_show, + $i_webhooks_update, + $i_webhooks_delete, + $messaging_history, + $messaging_messages, + $messaging_messages_create, + $messaging_messages_delete, + $messaging_messages_read, + $meta, + $miauth_genToken, + $mute_create, + $mute_delete, + $mute_list, + $my_apps, + $notes, + $notes_children, + $notes_clips, + $notes_conversation, + $notes_create, + $notes_delete, + $notes_favorites_create, + $notes_favorites_delete, + $notes_featured, + $notes_globalTimeline, + $notes_hybridTimeline, + $notes_localTimeline, + $notes_mentions, + $notes_polls_recommendation, + $notes_polls_vote, + $notes_reactions, + $notes_reactions_create, + $notes_reactions_delete, + $notes_renotes, + $notes_replies, + $notes_searchByTag, + $notes_search, + $notes_show, + $notes_state, + $notes_threadMuting_create, + $notes_threadMuting_delete, + $notes_timeline, + $notes_translate, + $notes_unrenote, + $notes_userListTimeline, + $notifications_create, + $notifications_markAllAsRead, + $notifications_read, + $pagePush, + $pages_create, + $pages_delete, + $pages_featured, + $pages_like, + $pages_show, + $pages_unlike, + $pages_update, + $ping, + $pinnedUsers, + $promo_read, + $requestResetPassword, + $resetDb, + $resetPassword, + $serverInfo, + $stats, + $sw_register, + $sw_unregister, + $test, + $username_available, + $users, + $users_clips, + $users_followers, + $users_following, + $users_gallery_posts, + $users_getFrequentlyRepliedUsers, + $users_groups_create, + $users_groups_delete, + $users_groups_invitations_accept, + $users_groups_invitations_reject, + $users_groups_invite, + $users_groups_joined, + $users_groups_leave, + $users_groups_owned, + $users_groups_pull, + $users_groups_show, + $users_groups_transfer, + $users_groups_update, + $users_lists_create, + $users_lists_delete, + $users_lists_list, + $users_lists_pull, + $users_lists_push, + $users_lists_show, + $users_lists_update, + $users_notes, + $users_pages, + $users_reactions, + $users_recommendation, + $users_relation, + $users_reportAbuse, + $users_searchByUsernameAndHost, + $users_search, + $users_show, + $users_stats, + $admin_driveCapOverride, + $fetchRss, + ], + exports: [ + $admin_meta, + $admin_abuseUserReports, + $admin_accounts_create, + $admin_accounts_delete, + $admin_ad_create, + $admin_ad_delete, + $admin_ad_list, + $admin_ad_update, + $admin_announcements_create, + $admin_announcements_delete, + $admin_announcements_list, + $admin_announcements_update, + $admin_deleteAllFilesOfAUser, + $admin_drive_cleanRemoteFiles, + $admin_drive_cleanup, + $admin_drive_files, + $admin_drive_showFile, + $admin_emoji_addAliasesBulk, + $admin_emoji_add, + $admin_emoji_copy, + $admin_emoji_deleteBulk, + $admin_emoji_delete, + $admin_emoji_importZip, + $admin_emoji_listRemote, + $admin_emoji_list, + $admin_emoji_removeAliasesBulk, + $admin_emoji_setAliasesBulk, + $admin_emoji_setCategoryBulk, + $admin_emoji_update, + $admin_federation_deleteAllFiles, + $admin_federation_refreshRemoteInstanceMetadata, + $admin_federation_removeAllFollowing, + $admin_federation_updateInstance, + $admin_getIndexStats, + $admin_getTableStats, + $admin_getUserIps, + $admin_invite, + $admin_moderators_add, + $admin_moderators_remove, + $admin_promo_create, + $admin_queue_clear, + $admin_queue_deliverDelayed, + $admin_queue_inboxDelayed, + $admin_queue_stats, + $admin_relays_add, + $admin_relays_list, + $admin_relays_remove, + $admin_resetPassword, + $admin_resolveAbuseUserReport, + $admin_sendEmail, + $admin_serverInfo, + $admin_showModerationLogs, + $admin_showUser, + $admin_showUsers, + $admin_silenceUser, + $admin_suspendUser, + $admin_unsilenceUser, + $admin_unsuspendUser, + $admin_updateMeta, + $admin_deleteAccount, + $admin_updateUserNote, + $announcements, + $antennas_create, + $antennas_delete, + $antennas_list, + $antennas_notes, + $antennas_show, + $antennas_update, + $ap_get, + $ap_show, + $app_create, + $app_show, + $auth_accept, + $auth_session_generate, + $auth_session_show, + $auth_session_userkey, + $blocking_create, + $blocking_delete, + $blocking_list, + $channels_create, + $channels_featured, + $channels_follow, + $channels_followed, + $channels_owned, + $channels_show, + $channels_timeline, + $channels_unfollow, + $channels_update, + $charts_activeUsers, + $charts_apRequest, + $charts_drive, + $charts_federation, + $charts_hashtag, + $charts_instance, + $charts_notes, + $charts_user_drive, + $charts_user_following, + $charts_user_notes, + $charts_user_reactions, + $charts_users, + $clips_addNote, + $clips_removeNote, + $clips_create, + $clips_delete, + $clips_list, + $clips_notes, + $clips_show, + $clips_update, + $drive, + $drive_files, + $drive_files_attachedNotes, + $drive_files_checkExistence, + $drive_files_create, + $drive_files_delete, + $drive_files_findByHash, + $drive_files_find, + $drive_files_show, + $drive_files_update, + $drive_files_uploadFromUrl, + $drive_folders, + $drive_folders_create, + $drive_folders_delete, + $drive_folders_find, + $drive_folders_show, + $drive_folders_update, + $drive_stream, + $emailAddress_available, + $endpoint, + $endpoints, + $exportCustomEmojis, + $federation_followers, + $federation_following, + $federation_instances, + $federation_showInstance, + $federation_updateRemoteUser, + $federation_users, + $federation_stats, + $following_create, + $following_delete, + $following_invalidate, + $following_requests_accept, + $following_requests_cancel, + $following_requests_list, + $following_requests_reject, + $gallery_featured, + $gallery_popular, + $gallery_posts, + $gallery_posts_create, + $gallery_posts_delete, + $gallery_posts_like, + $gallery_posts_show, + $gallery_posts_unlike, + $gallery_posts_update, + $getOnlineUsersCount, + $hashtags_list, + $hashtags_search, + $hashtags_show, + $hashtags_trend, + $hashtags_users, + $i, + $i_2fa_done, + $i_2fa_keyDone, + $i_2fa_passwordLess, + $i_2fa_registerKey, + $i_2fa_register, + $i_2fa_removeKey, + $i_2fa_unregister, + $i_apps, + $i_authorizedApps, + $i_changePassword, + $i_deleteAccount, + $i_exportBlocking, + $i_exportFollowing, + $i_exportMute, + $i_exportNotes, + $i_exportUserLists, + $i_favorites, + $i_gallery_likes, + $i_gallery_posts, + $i_getWordMutedNotesCount, + $i_importBlocking, + $i_importFollowing, + $i_importMuting, + $i_importUserLists, + $i_notifications, + $i_pageLikes, + $i_pages, + $i_pin, + $i_readAllMessagingMessages, + $i_readAllUnreadNotes, + $i_readAnnouncement, + $i_regenerateToken, + $i_registry_getAll, + $i_registry_getDetail, + $i_registry_get, + $i_registry_keysWithType, + $i_registry_keys, + $i_registry_remove, + $i_registry_scopes, + $i_registry_set, + $i_revokeToken, + $i_signinHistory, + $i_unpin, + $i_updateEmail, + $i_update, + $i_userGroupInvites, + $i_webhooks_create, + $i_webhooks_list, + $i_webhooks_show, + $i_webhooks_update, + $i_webhooks_delete, + $messaging_history, + $messaging_messages, + $messaging_messages_create, + $messaging_messages_delete, + $messaging_messages_read, + $meta, + $miauth_genToken, + $mute_create, + $mute_delete, + $mute_list, + $my_apps, + $notes, + $notes_children, + $notes_clips, + $notes_conversation, + $notes_create, + $notes_delete, + $notes_favorites_create, + $notes_favorites_delete, + $notes_featured, + $notes_globalTimeline, + $notes_hybridTimeline, + $notes_localTimeline, + $notes_mentions, + $notes_polls_recommendation, + $notes_polls_vote, + $notes_reactions, + $notes_reactions_create, + $notes_reactions_delete, + $notes_renotes, + $notes_replies, + $notes_searchByTag, + $notes_search, + $notes_show, + $notes_state, + $notes_threadMuting_create, + $notes_threadMuting_delete, + $notes_timeline, + $notes_translate, + $notes_unrenote, + $notes_userListTimeline, + $notifications_create, + $notifications_markAllAsRead, + $notifications_read, + $pagePush, + $pages_create, + $pages_delete, + $pages_featured, + $pages_like, + $pages_show, + $pages_unlike, + $pages_update, + $ping, + $pinnedUsers, + $promo_read, + $requestResetPassword, + $resetDb, + $resetPassword, + $serverInfo, + $stats, + $sw_register, + $sw_unregister, + $test, + $username_available, + $users, + $users_clips, + $users_followers, + $users_following, + $users_gallery_posts, + $users_getFrequentlyRepliedUsers, + $users_groups_create, + $users_groups_delete, + $users_groups_invitations_accept, + $users_groups_invitations_reject, + $users_groups_invite, + $users_groups_joined, + $users_groups_leave, + $users_groups_owned, + $users_groups_pull, + $users_groups_show, + $users_groups_transfer, + $users_groups_update, + $users_lists_create, + $users_lists_delete, + $users_lists_list, + $users_lists_pull, + $users_lists_push, + $users_lists_show, + $users_lists_update, + $users_notes, + $users_pages, + $users_reactions, + $users_recommendation, + $users_relation, + $users_reportAbuse, + $users_searchByUsernameAndHost, + $users_search, + $users_show, + $users_stats, + $admin_driveCapOverride, + $fetchRss, + ], +}) +export class EndpointsModule {} diff --git a/packages/backend/src/server/api/RateLimiterService.ts b/packages/backend/src/server/api/RateLimiterService.ts new file mode 100644 index 0000000000..d390a47b8f --- /dev/null +++ b/packages/backend/src/server/api/RateLimiterService.ts @@ -0,0 +1,89 @@ +import { Inject, Injectable } from '@nestjs/common'; +import Limiter from 'ratelimiter'; +import Redis from 'ioredis'; +import { DI } from '@/di-symbols.js'; +import Logger from '@/logger.js'; +import type { IEndpointMeta } from './endpoints.js'; + +const logger = new Logger('limiter'); + +@Injectable() +export class RateLimiterService { + constructor( + @Inject(DI.redis) + private redisClient: Redis.Redis, + ) { + } + + public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable }, actor: string) { + return new Promise((ok, reject) => { + if (process.env.NODE_ENV === 'test') ok(); + + // Short-term limit + const min = (): void => { + const minIntervalLimiter = new Limiter({ + id: `${actor}:${limitation.key}:min`, + duration: limitation.minInterval, + max: 1, + db: this.redisClient, + }); + + minIntervalLimiter.get((err, info) => { + if (err) { + return reject('ERR'); + } + + logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); + + if (info.remaining === 0) { + reject('BRIEF_REQUEST_INTERVAL'); + } else { + if (hasLongTermLimit) { + max(); + } else { + ok(); + } + } + }); + }; + + // Long term limit + const max = (): void => { + const limiter = new Limiter({ + id: `${actor}:${limitation.key}`, + duration: limitation.duration, + max: limitation.max, + db: this.redisClient, + }); + + limiter.get((err, info) => { + if (err) { + return reject('ERR'); + } + + logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); + + if (info.remaining === 0) { + reject('RATE_LIMIT_EXCEEDED'); + } else { + ok(); + } + }); + }; + + const hasShortTermLimit = typeof limitation.minInterval === 'number'; + + const hasLongTermLimit = + typeof limitation.duration === 'number' && + typeof limitation.max === 'number'; + + if (hasShortTermLimit) { + min(); + } else if (hasLongTermLimit) { + max(); + } else { + ok(); + } + }); + } +} diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts new file mode 100644 index 0000000000..5cda3c6205 --- /dev/null +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -0,0 +1,282 @@ +import { randomBytes } from 'node:crypto'; +import { Inject, Injectable } from '@nestjs/common'; +import bcrypt from 'bcryptjs'; +import * as speakeasy from 'speakeasy'; +import { IsNull } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import { UserSecurityKeysRepository, SigninsRepository, UserProfilesRepository, AttestationChallengesRepository, UsersRepository } from '@/models/index.js'; +import { Config } from '@/config.js'; +import { getIpHash } from '@/misc/get-ip-hash.js'; +import type { ILocalUser } from '@/models/entities/User.js'; +import { IdService } from '@/core/IdService.js'; +import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; +import { RateLimiterService } from './RateLimiterService.js'; +import { SigninService } from './SigninService.js'; +import type Koa from 'koa'; + +@Injectable() +export class SigninApiService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userSecurityKeysRepository) + private userSecurityKeysRepository: UserSecurityKeysRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.attestationChallengesRepository) + private attestationChallengesRepository: AttestationChallengesRepository, + + @Inject(DI.signinsRepository) + private signinsRepository: SigninsRepository, + + private idService: IdService, + private rateLimiterService: RateLimiterService, + private signinService: SigninService, + private twoFactorAuthenticationService: TwoFactorAuthenticationService, + ) { + } + + public async signin(ctx: Koa.Context) { + ctx.set('Access-Control-Allow-Origin', this.config.url); + ctx.set('Access-Control-Allow-Credentials', 'true'); + + const body = ctx.request.body as any; + const username = body['username']; + const password = body['password']; + const token = body['token']; + + function error(status: number, error: { id: string }) { + ctx.status = status; + ctx.body = { error }; + } + + try { + // not more than 1 attempt per second and not more than 10 attempts per hour + await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(ctx.ip)); + } catch (err) { + ctx.status = 429; + ctx.body = { + error: { + message: 'Too many failed attempts to sign in. Try again later.', + code: 'TOO_MANY_AUTHENTICATION_FAILURES', + id: '22d05606-fbcf-421a-a2db-b32610dcfd1b', + }, + }; + return; + } + + if (typeof username !== 'string') { + ctx.status = 400; + return; + } + + if (typeof password !== 'string') { + ctx.status = 400; + return; + } + + if (token != null && typeof token !== 'string') { + ctx.status = 400; + return; + } + + // Fetch user + const user = await this.usersRepository.findOneBy({ + usernameLower: username.toLowerCase(), + host: IsNull(), + }) as ILocalUser; + + if (user == null) { + error(404, { + id: '6cc579cc-885d-43d8-95c2-b8c7fc963280', + }); + return; + } + + if (user.isSuspended) { + error(403, { + id: 'e03a5f46-d309-4865-9b69-56282d94e1eb', + }); + return; + } + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + // Compare password + const same = await bcrypt.compare(password, profile.password!); + + const fail = async (status?: number, failure?: { id: string }) => { + // Append signin history + await this.signinsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: user.id, + ip: ctx.ip, + headers: ctx.headers, + success: false, + }); + + error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' }); + }; + + if (!profile.twoFactorEnabled) { + if (same) { + this.signinService.signin(ctx, user); + return; + } else { + await fail(403, { + id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', + }); + return; + } + } + + if (token) { + if (!same) { + await fail(403, { + id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', + }); + return; + } + + const verified = (speakeasy as any).totp.verify({ + secret: profile.twoFactorSecret, + encoding: 'base32', + token: token, + window: 2, + }); + + if (verified) { + this.signinService.signin(ctx, user); + return; + } else { + await fail(403, { + id: 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f', + }); + return; + } + } else if (body.credentialId) { + if (!same && !profile.usePasswordLessLogin) { + await fail(403, { + id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', + }); + return; + } + + const clientDataJSON = Buffer.from(body.clientDataJSON, 'hex'); + const clientData = JSON.parse(clientDataJSON.toString('utf-8')); + const challenge = await this.attestationChallengesRepository.findOneBy({ + userId: user.id, + id: body.challengeId, + registrationChallenge: false, + challenge: this.twoFactorAuthenticationService.hash(clientData.challenge).toString('hex'), + }); + + if (!challenge) { + await fail(403, { + id: '2715a88a-2125-4013-932f-aa6fe72792da', + }); + return; + } + + await this.attestationChallengesRepository.delete({ + userId: user.id, + id: body.challengeId, + }); + + if (new Date().getTime() - challenge.createdAt.getTime() >= 5 * 60 * 1000) { + await fail(403, { + id: '2715a88a-2125-4013-932f-aa6fe72792da', + }); + return; + } + + const securityKey = await this.userSecurityKeysRepository.findOneBy({ + id: Buffer.from( + body.credentialId + .replace(/-/g, '+') + .replace(/_/g, '/'), + 'base64', + ).toString('hex'), + }); + + if (!securityKey) { + await fail(403, { + id: '66269679-aeaf-4474-862b-eb761197e046', + }); + return; + } + + const isValid = this.twoFactorAuthenticationService.verifySignin({ + publicKey: Buffer.from(securityKey.publicKey, 'hex'), + authenticatorData: Buffer.from(body.authenticatorData, 'hex'), + clientDataJSON, + clientData, + signature: Buffer.from(body.signature, 'hex'), + challenge: challenge.challenge, + }); + + if (isValid) { + this.signinService.signin(ctx, user); + return; + } else { + await fail(403, { + id: '93b86c4b-72f9-40eb-9815-798928603d1e', + }); + return; + } + } else { + if (!same && !profile.usePasswordLessLogin) { + await fail(403, { + id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', + }); + return; + } + + const keys = await this.userSecurityKeysRepository.findBy({ + userId: user.id, + }); + + if (keys.length === 0) { + await fail(403, { + id: 'f27fd449-9af4-4841-9249-1f989b9fa4a4', + }); + return; + } + + // 32 byte challenge + const challenge = randomBytes(32).toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + + const challengeId = this.idService.genId(); + + await this.attestationChallengesRepository.insert({ + userId: user.id, + id: challengeId, + challenge: this.twoFactorAuthenticationService.hash(Buffer.from(challenge, 'utf-8')).toString('hex'), + createdAt: new Date(), + registrationChallenge: false, + }); + + ctx.body = { + challenge, + challengeId, + securityKeys: keys.map(key => ({ + id: key.id, + })), + }; + ctx.status = 200; + return; + } + // never get here + } +} + diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts new file mode 100644 index 0000000000..19d14bad67 --- /dev/null +++ b/packages/backend/src/server/api/SigninService.ts @@ -0,0 +1,64 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import { SigninsRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; +import { Config } from '@/config.js'; +import { IdService } from '@/core/IdService.js'; +import type { ILocalUser } from '@/models/entities/User.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { SigninEntityService } from '@/core/entities/SigninEntityService.js'; +import type Koa from 'koa'; + +@Injectable() +export class SigninService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.signinsRepository) + private signinsRepository: SigninsRepository, + + private signinEntityService: SigninEntityService, + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + } + + public signin(ctx: Koa.Context, user: ILocalUser, redirect = false) { + if (redirect) { + //#region Cookie + ctx.cookies.set('igi', user.token!, { + path: '/', + // SEE: https://github.com/koajs/koa/issues/974 + // When using a SSL proxy it should be configured to add the "X-Forwarded-Proto: https" header + secure: this.config.url.startsWith('https'), + httpOnly: false, + }); + //#endregion + + ctx.redirect(this.config.url); + } else { + ctx.body = { + id: user.id, + i: user.token, + }; + ctx.status = 200; + } + + (async () => { + // Append signin history + const record = await this.signinsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: user.id, + ip: ctx.ip, + headers: ctx.headers, + success: true, + }).then(x => this.signinsRepository.findOneByOrFail(x.identifiers[0])); + + // Publish signin event + this.globalEventService.publishMainStream(user.id, 'signin', await this.signinEntityService.pack(record)); + })(); + } +} + diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts new file mode 100644 index 0000000000..df040ddcf8 --- /dev/null +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -0,0 +1,175 @@ +import { Inject, Injectable } from '@nestjs/common'; +import rndstr from 'rndstr'; +import bcrypt from 'bcryptjs'; +import { DI } from '@/di-symbols.js'; +import { RegistrationTicketsRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import { Config } from '@/config.js'; +import { MetaService } from '@/core/MetaService.js'; +import { CaptchaService } from '@/core/CaptchaService.js'; +import { IdService } from '@/core/IdService.js'; +import { SignupService } from '@/core/SignupService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { EmailService } from '@/core/EmailService.js'; +import { SigninService } from './SigninService.js'; +import type Koa from 'koa'; + +@Injectable() +export class SignupApiService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.userPendingsRepository) + private userPendingsRepository: UserPendingsRepository, + + @Inject(DI.registrationTicketsRepository) + private registrationTicketsRepository: RegistrationTicketsRepository, + + private userEntityService: UserEntityService, + private idService: IdService, + private metaService: MetaService, + private captchaService: CaptchaService, + private signupService: SignupService, + private signinService: SigninService, + private emailService: EmailService, + ) { + } + + public async signup(ctx: Koa.Context) { + const body = ctx.request.body; + + const instance = await this.metaService.fetch(true); + + // Verify *Captcha + // ただしテスト時はこの機構は障害となるため無効にする + if (process.env.NODE_ENV !== 'test') { + if (instance.enableHcaptcha && instance.hcaptchaSecretKey) { + await this.captchaService.verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(e => { + ctx.throw(400, e); + }); + } + + if (instance.enableRecaptcha && instance.recaptchaSecretKey) { + await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(e => { + ctx.throw(400, e); + }); + } + } + + const username = body['username']; + const password = body['password']; + const host: string | null = process.env.NODE_ENV === 'test' ? (body['host'] ?? null) : null; + const invitationCode = body['invitationCode']; + const emailAddress = body['emailAddress']; + + if (instance.emailRequiredForSignup) { + if (emailAddress == null || typeof emailAddress !== 'string') { + ctx.status = 400; + return; + } + + const available = await this.emailService.validateEmailForAccount(emailAddress); + if (!available) { + ctx.status = 400; + return; + } + } + + if (instance.disableRegistration) { + if (invitationCode == null || typeof invitationCode !== 'string') { + ctx.status = 400; + return; + } + + const ticket = await this.registrationTicketsRepository.findOneBy({ + code: invitationCode, + }); + + if (ticket == null) { + ctx.status = 400; + return; + } + + this.registrationTicketsRepository.delete(ticket.id); + } + + if (instance.emailRequiredForSignup) { + const code = rndstr('a-z0-9', 16); + + // Generate hash of password + const salt = await bcrypt.genSalt(8); + const hash = await bcrypt.hash(password, salt); + + await this.userPendingsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + code, + email: emailAddress, + username: username, + password: hash, + }); + + const link = `${this.config.url}/signup-complete/${code}`; + + sendEmail(emailAddress, 'Signup', + `To complete signup, please click this link:
${link}`, + `To complete signup, please click this link: ${link}`); + + ctx.status = 204; + } else { + try { + const { account, secret } = await this.signupService.signup({ + username, password, host, + }); + + const res = await this.userEntityService.pack(account, account, { + detail: true, + includeSecrets: true, + }); + + (res as any).token = secret; + + ctx.body = res; + } catch (e) { + ctx.throw(400, e); + } + } + } + + public async signupPending(ctx: Koa.Context) { + const body = ctx.request.body; + + const code = body['code']; + + try { + const pendingUser = await this.userPendingsRepository.findOneByOrFail({ code }); + + const { account, secret } = await this.signupService.signup({ + username: pendingUser.username, + passwordHash: pendingUser.password, + }); + + this.userPendingsRepository.delete({ + id: pendingUser.id, + }); + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: account.id }); + + await this.userProfilesRepository.update({ userId: profile.userId }, { + email: pendingUser.email, + emailVerified: true, + emailVerifyCode: null, + }); + + this.signinService.signin(ctx, account); + } catch (e) { + ctx.throw(400, e); + } + } +} diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts new file mode 100644 index 0000000000..b08b01aef9 --- /dev/null +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -0,0 +1,120 @@ +import { EventEmitter } from 'events'; +import { Inject, Injectable } from '@nestjs/common'; +import Redis from 'ioredis'; +import * as websocket from 'websocket'; +import { DI } from '@/di-symbols.js'; +import { UsersRepository, BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository } from '@/models/index.js'; +import { Config } from '@/config.js'; +import { NoteReadService } from '@/core/NoteReadService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { NotificationService } from '@/core/NotificationService.js'; +import { AuthenticateService } from './AuthenticateService.js'; +import MainStreamConnection from './stream/index.js'; +import { ChannelsService } from './stream/ChannelsService.js'; +import type { ParsedUrlQuery } from 'querystring'; +import type * as http from 'node:http'; + +@Injectable() +export class StreamingApiServerService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.redisSubscriber) + private redisSubscriber: Redis.Redis, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + @Inject(DI.channelFollowingsRepository) + private channelFollowingsRepository: ChannelFollowingsRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private globalEventService: GlobalEventService, + private noteReadService: NoteReadService, + private authenticateService: AuthenticateService, + private channelsService: ChannelsService, + private notificationService: NotificationService, + ) { + } + + public attachStreamingApi(server: http.Server) { + // Init websocket server + const ws = new websocket.server({ + httpServer: server, + }); + + ws.on('request', async (request) => { + const q = request.resourceURL.query as ParsedUrlQuery; + + // TODO: トークンが間違ってるなどしてauthenticateに失敗したら + // コネクション切断するなりエラーメッセージ返すなりする + // (現状はエラーがキャッチされておらずサーバーのログに流れて邪魔なので) + const [user, miapp] = await this.authenticateService.authenticate(q.i as string); + + if (user?.isSuspended) { + request.reject(400); + return; + } + + const connection = request.accept(); + + const ev = new EventEmitter(); + + async function onRedisMessage(_: string, data: string) { + const parsed = JSON.parse(data); + ev.emit(parsed.channel, parsed.message); + } + + this.redisSubscriber.on('message', onRedisMessage); + + const main = new MainStreamConnection( + this.followingsRepository, + this.mutingsRepository, + this.blockingsRepository, + this.channelFollowingsRepository, + this.userProfilesRepository, + this.channelsService, + this.globalEventService, + this.noteReadService, + this.notificationService, + connection, ev, user, miapp, + ); + + const intervalId = user ? setInterval(() => { + this.usersRepository.update(user.id, { + lastActiveDate: new Date(), + }); + }, 1000 * 60 * 5) : null; + if (user) { + this.usersRepository.update(user.id, { + lastActiveDate: new Date(), + }); + } + + connection.once('close', () => { + ev.removeAllListeners(); + main.dispose(); + this.redisSubscriber.off('message', onRedisMessage); + if (intervalId) clearInterval(intervalId); + }); + + connection.on('message', async (data) => { + if (data.type === 'utf8' && data.utf8Data === 'ping') { + connection.send('pong'); + } + }); + }); + } +} diff --git a/packages/backend/src/server/api/api-handler.ts b/packages/backend/src/server/api/api-handler.ts deleted file mode 100644 index ec71ddd2c0..0000000000 --- a/packages/backend/src/server/api/api-handler.ts +++ /dev/null @@ -1,92 +0,0 @@ -import Koa from 'koa'; - -import { User } from '@/models/entities/user.js'; -import { UserIps } from '@/models/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { IEndpoint } from './endpoints.js'; -import authenticate, { AuthenticationError } from './authenticate.js'; -import call from './call.js'; -import { ApiError } from './error.js'; - -const userIpHistories = new Map>(); - -setInterval(() => { - userIpHistories.clear(); -}, 1000 * 60 * 60); - -export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise((res) => { - const body = ctx.is('multipart/form-data') - ? (ctx.request as any).body - : ctx.method === 'GET' - ? ctx.query - : ctx.request.body; - - const reply = (x?: any, y?: ApiError) => { - if (x == null) { - ctx.status = 204; - } else if (typeof x === 'number' && y) { - ctx.status = x; - ctx.body = { - error: { - message: y!.message, - code: y!.code, - id: y!.id, - kind: y!.kind, - ...(y!.info ? { info: y!.info } : {}), - }, - }; - } else { - // 文字列を返す場合は、JSON.stringify通さないとJSONと認識されない - ctx.body = typeof x === 'string' ? JSON.stringify(x) : x; - } - res(); - }; - - // Authentication - authenticate(body['i']).then(([user, app]) => { - // API invoking - call(endpoint.name, user, app, body, ctx).then((res: any) => { - if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) { - ctx.set('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`); - } - reply(res); - }).catch((e: ApiError) => { - reply(e.httpStatusCode ? e.httpStatusCode : e.kind === 'client' ? 400 : 500, e); - }); - - // Log IP - if (user) { - fetchMeta().then(meta => { - if (!meta.enableIpLogging) return; - const ip = ctx.ip; - const ips = userIpHistories.get(user.id); - if (ips == null || !ips.has(ip)) { - if (ips == null) { - userIpHistories.set(user.id, new Set([ip])); - } else { - ips.add(ip); - } - - try { - UserIps.createQueryBuilder().insert().values({ - createdAt: new Date(), - userId: user.id, - ip: ip, - }).orIgnore(true).execute(); - } catch { - } - } - }); - } - }).catch(e => { - if (e instanceof AuthenticationError) { - reply(403, new ApiError({ - message: 'Authentication failed. Please ensure your token is correct.', - code: 'AUTHENTICATION_FAILED', - id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', - })); - } else { - reply(500, new ApiError()); - } - }); -}); diff --git a/packages/backend/src/server/api/authenticate.ts b/packages/backend/src/server/api/authenticate.ts deleted file mode 100644 index 65ccfcf551..0000000000 --- a/packages/backend/src/server/api/authenticate.ts +++ /dev/null @@ -1,66 +0,0 @@ -import isNativeToken from './common/is-native-token.js'; -import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js'; -import { Users, AccessTokens, Apps } from '@/models/index.js'; -import { AccessToken } from '@/models/entities/access-token.js'; -import { Cache } from '@/misc/cache.js'; -import { App } from '@/models/entities/app.js'; -import { localUserByIdCache, localUserByNativeTokenCache } from '@/services/user-cache.js'; - -const appCache = new Cache(Infinity); - -export class AuthenticationError extends Error { - constructor(message: string) { - super(message); - this.name = 'AuthenticationError'; - } -} - -export default async (token: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> => { - if (token == null) { - return [null, null]; - } - - if (isNativeToken(token)) { - const user = await localUserByNativeTokenCache.fetch(token, - () => Users.findOneBy({ token }) as Promise); - - if (user == null) { - throw new AuthenticationError('user not found'); - } - - return [user, null]; - } else { - const accessToken = await AccessTokens.findOne({ - where: [{ - hash: token.toLowerCase(), // app - }, { - token: token, // miauth - }], - }); - - if (accessToken == null) { - throw new AuthenticationError('invalid signature'); - } - - AccessTokens.update(accessToken.id, { - lastUsedAt: new Date(), - }); - - const user = await localUserByIdCache.fetch(accessToken.userId, - () => Users.findOneBy({ - id: accessToken.userId, - }) as Promise); - - if (accessToken.appId) { - const app = await appCache.fetch(accessToken.appId, - () => Apps.findOneByOrFail({ id: accessToken.appId! })); - - return [user, { - id: accessToken.id, - permission: app.permission, - } as AccessToken]; - } else { - return [user, accessToken]; - } - } -}; diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts deleted file mode 100644 index aa130459a3..0000000000 --- a/packages/backend/src/server/api/call.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { performance } from 'perf_hooks'; -import Koa from 'koa'; -import { CacheableLocalUser, User } from '@/models/entities/user.js'; -import { AccessToken } from '@/models/entities/access-token.js'; -import { getIpHash } from '@/misc/get-ip-hash.js'; -import { limiter } from './limiter.js'; -import endpoints, { IEndpointMeta } from './endpoints.js'; -import { ApiError } from './error.js'; -import { apiLogger } from './logger.js'; - -const accessDenied = { - message: 'Access denied.', - code: 'ACCESS_DENIED', - id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e', -}; - -export default async (endpoint: string, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => { - const isSecure = user != null && token == null; - const isModerator = user != null && (user.isModerator || user.isAdmin); - - const ep = endpoints.find(e => e.name === endpoint); - - if (ep == null) { - throw new ApiError({ - message: 'No such endpoint.', - code: 'NO_SUCH_ENDPOINT', - id: 'f8080b67-5f9c-4eb7-8c18-7f1eeae8f709', - httpStatusCode: 404, - }); - } - - if (ep.meta.secure && !isSecure) { - throw new ApiError(accessDenied); - } - - if (ep.meta.limit) { - // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app. - let limitActor: string; - if (user) { - limitActor = user.id; - } else { - limitActor = getIpHash(ctx!.ip); - } - - const limit = Object.assign({}, ep.meta.limit); - - if (!limit.key) { - limit.key = ep.name; - } - - // Rate limit - await limiter(limit as IEndpointMeta['limit'] & { key: NonNullable }, limitActor).catch(e => { - throw new ApiError({ - message: 'Rate limit exceeded. Please try again later.', - code: 'RATE_LIMIT_EXCEEDED', - id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef', - httpStatusCode: 429, - }); - }); - } - - if (ep.meta.requireCredential && user == null) { - throw new ApiError({ - message: 'Credential required.', - code: 'CREDENTIAL_REQUIRED', - id: '1384574d-a912-4b81-8601-c7b1c4085df1', - httpStatusCode: 401, - }); - } - - if (ep.meta.requireCredential && user!.isSuspended) { - throw new ApiError({ - message: 'Your account has been suspended.', - code: 'YOUR_ACCOUNT_SUSPENDED', - id: 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370', - httpStatusCode: 403, - }); - } - - if (ep.meta.requireAdmin && !user!.isAdmin) { - throw new ApiError(accessDenied, { reason: 'You are not the admin.' }); - } - - if (ep.meta.requireModerator && !isModerator) { - throw new ApiError(accessDenied, { reason: 'You are not a moderator.' }); - } - - if (token && ep.meta.kind && !token.permission.some(p => p === ep.meta.kind)) { - throw new ApiError({ - message: 'Your app does not have the necessary permissions to use this endpoint.', - code: 'PERMISSION_DENIED', - id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838', - }); - } - - // Cast non JSON input - if ((ep.meta.requireFile || ctx?.method === 'GET') && ep.params.properties) { - for (const k of Object.keys(ep.params.properties)) { - const param = ep.params.properties![k]; - if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') { - try { - data[k] = JSON.parse(data[k]); - } catch (e) { - throw new ApiError({ - message: 'Invalid param.', - code: 'INVALID_PARAM', - id: '0b5f1631-7c1a-41a6-b399-cce335f34d85', - }, { - param: k, - reason: `cannot cast to ${param.type}`, - }); - } - } - } - } - - // API invoking - const before = performance.now(); - return await ep.exec(data, user, token, ctx?.file, ctx?.ip, ctx?.headers).catch((e: Error) => { - if (e instanceof ApiError) { - throw e; - } else { - apiLogger.error(`Internal error occurred in ${ep.name}: ${e.message}`, { - ep: ep.name, - ps: data, - e: { - message: e.message, - code: e.name, - stack: e.stack, - }, - }); - throw new ApiError(null, { - e: { - message: e.message, - code: e.name, - stack: e.stack, - }, - }); - } - }).finally(() => { - const after = performance.now(); - const time = after - before; - if (time > 1000) { - apiLogger.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`); - } - }); -}; diff --git a/packages/backend/src/server/api/common/GetterService.ts b/packages/backend/src/server/api/common/GetterService.ts new file mode 100644 index 0000000000..a6b60d1f5a --- /dev/null +++ b/packages/backend/src/server/api/common/GetterService.ts @@ -0,0 +1,71 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { NotesRepository, UsersRepository } from '@/models/index.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import type { User } from '@/models/entities/User.js'; +import type { Note } from '@/models/entities/Note.js'; + +@Injectable() +export class GetterService { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + ) { + } + + /** + * Get note for API processing + */ + public async getNote(noteId: Note['id']) { + const note = await this.notesRepository.findOneBy({ id: noteId }); + + if (note == null) { + throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); + } + + return note; + } + + /** + * Get user for API processing + */ + public async getUser(userId: User['id']) { + const user = await this.usersRepository.findOneBy({ id: userId }); + + if (user == null) { + throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.'); + } + + return user; + } + + /** + * Get remote user for API processing + */ + public async getRemoteUser(userId: User['id']) { + const user = await this.getUser(userId); + + if (!this.userEntityService.isRemoteUser(user)) { + throw new Error('user is not a remote user'); + } + + return user; + } + + /** + * Get local user for API processing + */ + public async getLocalUser(userId: User['id']) { + const user = await this.getUser(userId); + + if (!this.userEntityService.isLocalUser(user)) { + throw new Error('user is not a local user'); + } + + return user; + } +} + diff --git a/packages/backend/src/server/api/common/generate-block-query.ts b/packages/backend/src/server/api/common/generate-block-query.ts deleted file mode 100644 index 60db1e731b..0000000000 --- a/packages/backend/src/server/api/common/generate-block-query.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { User } from '@/models/entities/user.js'; -import { Blockings } from '@/models/index.js'; -import { Brackets, SelectQueryBuilder } from 'typeorm'; - -// ここでいうBlockedは被Blockedの意 -export function generateBlockedUserQuery(q: SelectQueryBuilder, me: { id: User['id'] }) { - const blockingQuery = Blockings.createQueryBuilder('blocking') - .select('blocking.blockerId') - .where('blocking.blockeeId = :blockeeId', { blockeeId: me.id }); - - // 投稿の作者にブロックされていない かつ - // 投稿の返信先の作者にブロックされていない かつ - // 投稿の引用元の作者にブロックされていない - q - .andWhere(`note.userId NOT IN (${ blockingQuery.getQuery() })`) - .andWhere(new Brackets(qb => { qb - .where(`note.replyUserId IS NULL`) - .orWhere(`note.replyUserId NOT IN (${ blockingQuery.getQuery() })`); - })) - .andWhere(new Brackets(qb => { qb - .where(`note.renoteUserId IS NULL`) - .orWhere(`note.renoteUserId NOT IN (${ blockingQuery.getQuery() })`); - })); - - q.setParameters(blockingQuery.getParameters()); -} - -export function generateBlockQueryForUsers(q: SelectQueryBuilder, me: { id: User['id'] }) { - const blockingQuery = Blockings.createQueryBuilder('blocking') - .select('blocking.blockeeId') - .where('blocking.blockerId = :blockerId', { blockerId: me.id }); - - const blockedQuery = Blockings.createQueryBuilder('blocking') - .select('blocking.blockerId') - .where('blocking.blockeeId = :blockeeId', { blockeeId: me.id }); - - q.andWhere(`user.id NOT IN (${ blockingQuery.getQuery() })`); - q.setParameters(blockingQuery.getParameters()); - - q.andWhere(`user.id NOT IN (${ blockedQuery.getQuery() })`); - q.setParameters(blockedQuery.getParameters()); -} diff --git a/packages/backend/src/server/api/common/generate-channel-query.ts b/packages/backend/src/server/api/common/generate-channel-query.ts deleted file mode 100644 index 333bb73b86..0000000000 --- a/packages/backend/src/server/api/common/generate-channel-query.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { User } from '@/models/entities/user.js'; -import { ChannelFollowings } from '@/models/index.js'; -import { Brackets, SelectQueryBuilder } from 'typeorm'; - -export function generateChannelQuery(q: SelectQueryBuilder, me?: { id: User['id'] } | null) { - if (me == null) { - q.andWhere('note.channelId IS NULL'); - } else { - q.leftJoinAndSelect('note.channel', 'channel'); - - const channelFollowingQuery = ChannelFollowings.createQueryBuilder('channelFollowing') - .select('channelFollowing.followeeId') - .where('channelFollowing.followerId = :followerId', { followerId: me.id }); - - q.andWhere(new Brackets(qb => { qb - // チャンネルのノートではない - .where('note.channelId IS NULL') - // または自分がフォローしているチャンネルのノート - .orWhere(`note.channelId IN (${ channelFollowingQuery.getQuery() })`); - })); - - q.setParameters(channelFollowingQuery.getParameters()); - } -} diff --git a/packages/backend/src/server/api/common/generate-muted-note-query.ts b/packages/backend/src/server/api/common/generate-muted-note-query.ts deleted file mode 100644 index f544e334d3..0000000000 --- a/packages/backend/src/server/api/common/generate-muted-note-query.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { User } from '@/models/entities/user.js'; -import { MutedNotes } from '@/models/index.js'; -import { SelectQueryBuilder } from 'typeorm'; - -export function generateMutedNoteQuery(q: SelectQueryBuilder, me: { id: User['id'] }) { - const mutedQuery = MutedNotes.createQueryBuilder('muted') - .select('muted.noteId') - .where('muted.userId = :userId', { userId: me.id }); - - q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`); - - q.setParameters(mutedQuery.getParameters()); -} diff --git a/packages/backend/src/server/api/common/generate-muted-note-thread-query.ts b/packages/backend/src/server/api/common/generate-muted-note-thread-query.ts deleted file mode 100644 index 7263ea2e60..0000000000 --- a/packages/backend/src/server/api/common/generate-muted-note-thread-query.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { User } from '@/models/entities/user.js'; -import { NoteThreadMutings } from '@/models/index.js'; -import { Brackets, SelectQueryBuilder } from 'typeorm'; - -export function generateMutedNoteThreadQuery(q: SelectQueryBuilder, me: { id: User['id'] }) { - const mutedQuery = NoteThreadMutings.createQueryBuilder('threadMuted') - .select('threadMuted.threadId') - .where('threadMuted.userId = :userId', { userId: me.id }); - - q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`); - q.andWhere(new Brackets(qb => { qb - .where(`note.threadId IS NULL`) - .orWhere(`note.threadId NOT IN (${ mutedQuery.getQuery() })`); - })); - - q.setParameters(mutedQuery.getParameters()); -} diff --git a/packages/backend/src/server/api/common/generate-muted-user-query.ts b/packages/backend/src/server/api/common/generate-muted-user-query.ts deleted file mode 100644 index 470ece1a62..0000000000 --- a/packages/backend/src/server/api/common/generate-muted-user-query.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { SelectQueryBuilder, Brackets } from 'typeorm'; -import { User } from '@/models/entities/user.js'; -import { Mutings, UserProfiles } from '@/models/index.js'; - -export function generateMutedUserQuery(q: SelectQueryBuilder, me: { id: User['id'] }, exclude?: User) { - const mutingQuery = Mutings.createQueryBuilder('muting') - .select('muting.muteeId') - .where('muting.muterId = :muterId', { muterId: me.id }); - - if (exclude) { - mutingQuery.andWhere('muting.muteeId != :excludeId', { excludeId: exclude.id }); - } - - const mutingInstanceQuery = UserProfiles.createQueryBuilder('user_profile') - .select('user_profile.mutedInstances') - .where('user_profile.userId = :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() })`); - })) - // mute instances - .andWhere(new Brackets(qb => { qb - .andWhere('note.userHost IS NULL') - .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.userHost)`); - })) - .andWhere(new Brackets(qb => { qb - .where('note.replyUserHost IS NULL') - .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.replyUserHost)`); - })) - .andWhere(new Brackets(qb => { qb - .where('note.renoteUserHost IS NULL') - .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.renoteUserHost)`); - })); - - q.setParameters(mutingQuery.getParameters()); - q.setParameters(mutingInstanceQuery.getParameters()); -} - -export function generateMutedUserQueryForUsers(q: SelectQueryBuilder, me: { id: User['id'] }) { - 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/packages/backend/src/server/api/common/generate-native-user-token.ts b/packages/backend/src/server/api/common/generate-native-user-token.ts deleted file mode 100644 index 5d8a4c5378..0000000000 --- a/packages/backend/src/server/api/common/generate-native-user-token.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { secureRndstr } from '@/misc/secure-rndstr.js'; - -export default () => secureRndstr(16, true); diff --git a/packages/backend/src/server/api/common/generate-replies-query.ts b/packages/backend/src/server/api/common/generate-replies-query.ts deleted file mode 100644 index 301782eab9..0000000000 --- a/packages/backend/src/server/api/common/generate-replies-query.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { User } from '@/models/entities/user.js'; -import { Brackets, SelectQueryBuilder } from 'typeorm'; - -export function generateRepliesQuery(q: SelectQueryBuilder, me?: Pick | null) { - if (me == null) { - q.andWhere(new Brackets(qb => { qb - .where(`note.replyId IS NULL`) // 返信ではない - .orWhere(new Brackets(qb => { qb // 返信だけど投稿者自身への返信 - .where(`note.replyId IS NOT NULL`) - .andWhere('note.replyUserId = note.userId'); - })); - })); - } else if (!me.showTimelineReplies) { - q.andWhere(new Brackets(qb => { qb - .where(`note.replyId IS NULL`) // 返信ではない - .orWhere('note.replyUserId = :meId', { meId: me.id }) // 返信だけど自分のノートへの返信 - .orWhere(new Brackets(qb => { qb // 返信だけど自分の行った返信 - .where(`note.replyId IS NOT NULL`) - .andWhere('note.userId = :meId', { meId: me.id }); - })) - .orWhere(new Brackets(qb => { qb // 返信だけど投稿者自身への返信 - .where(`note.replyId IS NOT NULL`) - .andWhere('note.replyUserId = note.userId'); - })); - })); - } -} diff --git a/packages/backend/src/server/api/common/generate-visibility-query.ts b/packages/backend/src/server/api/common/generate-visibility-query.ts deleted file mode 100644 index b50b6812f4..0000000000 --- a/packages/backend/src/server/api/common/generate-visibility-query.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { User } from '@/models/entities/user.js'; -import { Followings } from '@/models/index.js'; -import { Brackets, SelectQueryBuilder } from 'typeorm'; - -export function generateVisibilityQuery(q: SelectQueryBuilder, me?: { id: User['id'] } | null) { - // This code must always be synchronized with the checks in Notes.isVisibleForMe. - 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 = :meId'); - - q.andWhere(new Brackets(qb => { qb - // 公開投稿である - .where(new Brackets(qb => { qb - .where(`note.visibility = 'public'`) - .orWhere(`note.visibility = 'home'`); - })) - // または 自分自身 - .orWhere('note.userId = :meId') - // または 自分宛て - .orWhere(':meId = ANY(note.visibleUserIds)') - .orWhere(':meId = ANY(note.mentions)') - .orWhere(new Brackets(qb => { qb - // または フォロワー宛ての投稿であり、 - .where(`note.visibility = 'followers'`) - .andWhere(new Brackets(qb => { qb - // 自分がフォロワーである - .where(`note.userId IN (${ followingQuery.getQuery() })`) - // または 自分の投稿へのリプライ - .orWhere('note.replyUserId = :meId'); - })); - })); - })); - - q.setParameters({ meId: me.id }); - } -} diff --git a/packages/backend/src/server/api/common/getters.ts b/packages/backend/src/server/api/common/getters.ts deleted file mode 100644 index 783ea9ef75..0000000000 --- a/packages/backend/src/server/api/common/getters.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { User } from '@/models/entities/user.js'; -import { Note } from '@/models/entities/note.js'; -import { Notes, Users } from '@/models/index.js'; - -/** - * Get note for API processing - */ -export async function getNote(noteId: Note['id']) { - const note = await Notes.findOneBy({ id: noteId }); - - if (note == null) { - throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); - } - - return note; -} - -/** - * Get user for API processing - */ -export async function getUser(userId: User['id']) { - const user = await Users.findOneBy({ id: userId }); - - if (user == null) { - throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.'); - } - - return user; -} - -/** - * Get remote user for API processing - */ -export async function getRemoteUser(userId: User['id']) { - const user = await getUser(userId); - - if (!Users.isRemoteUser(user)) { - throw new Error('user is not a remote user'); - } - - return user; -} - -/** - * Get local user for API processing - */ -export async function getLocalUser(userId: User['id']) { - const user = await getUser(userId); - - if (!Users.isLocalUser(user)) { - throw new Error('user is not a local user'); - } - - return user; -} diff --git a/packages/backend/src/server/api/common/inject-featured.ts b/packages/backend/src/server/api/common/inject-featured.ts index f7cdd365ed..75126fa304 100644 --- a/packages/backend/src/server/api/common/inject-featured.ts +++ b/packages/backend/src/server/api/common/inject-featured.ts @@ -1,6 +1,6 @@ import rndstr from 'rndstr'; -import { Note } from '@/models/entities/note.js'; -import { User } from '@/models/entities/user.js'; +import { Note } from '@/models/entities/Note.js'; +import { User } from '@/models/entities/User.js'; import { Notes, UserProfiles, NoteReactions } from '@/models/index.js'; import { generateMutedUserQuery } from './generate-muted-user-query.js'; import { generateBlockedUserQuery } from './generate-block-query.js'; diff --git a/packages/backend/src/server/api/common/inject-promo.ts b/packages/backend/src/server/api/common/inject-promo.ts index b0da8118b4..454f5dbb0e 100644 --- a/packages/backend/src/server/api/common/inject-promo.ts +++ b/packages/backend/src/server/api/common/inject-promo.ts @@ -1,6 +1,6 @@ import rndstr from 'rndstr'; -import { Note } from '@/models/entities/note.js'; -import { User } from '@/models/entities/user.js'; +import { Note } from '@/models/entities/Note.js'; +import { User } from '@/models/entities/User.js'; import { PromoReads, PromoNotes, Notes, Users } from '@/models/index.js'; export async function injectPromo(timeline: Note[], user?: User | null) { diff --git a/packages/backend/src/server/api/common/is-native-token.ts b/packages/backend/src/server/api/common/is-native-token.ts deleted file mode 100644 index 2833c570c8..0000000000 --- a/packages/backend/src/server/api/common/is-native-token.ts +++ /dev/null @@ -1 +0,0 @@ -export default (token: string) => token.length === 16; diff --git a/packages/backend/src/server/api/common/make-pagination-query.ts b/packages/backend/src/server/api/common/make-pagination-query.ts deleted file mode 100644 index 51c11e5dff..0000000000 --- a/packages/backend/src/server/api/common/make-pagination-query.ts +++ /dev/null @@ -1,28 +0,0 @@ -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/packages/backend/src/server/api/common/read-messaging-message.ts b/packages/backend/src/server/api/common/read-messaging-message.ts deleted file mode 100644 index c4c18ffa06..0000000000 --- a/packages/backend/src/server/api/common/read-messaging-message.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { publishMainStream, publishGroupMessagingStream } from '@/services/stream.js'; -import { publishMessagingStream } from '@/services/stream.js'; -import { publishMessagingIndexStream } from '@/services/stream.js'; -import { pushNotification } from '@/services/push-notification.js'; -import { User, IRemoteUser } from '@/models/entities/user.js'; -import { MessagingMessage } from '@/models/entities/messaging-message.js'; -import { MessagingMessages, UserGroupJoinings, Users } from '@/models/index.js'; -import { In } from 'typeorm'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { toArray } from '@/prelude/array.js'; -import { renderReadActivity } from '@/remote/activitypub/renderer/read.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import { deliver } from '@/queue/index.js'; -import orderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; - -/** - * Mark messages as read - */ -export async function readUserMessagingMessage( - userId: User['id'], - otherpartyId: User['id'], - messageIds: MessagingMessage['id'][] -) { - if (messageIds.length === 0) return; - - const messages = await MessagingMessages.findBy({ - id: In(messageIds), - }); - - for (const message of messages) { - if (message.recipientId !== userId) { - throw new IdentifiableError('e140a4bf-49ce-4fb6-b67c-b78dadf6b52f', 'Access denied (user).'); - } - } - - // Update documents - await MessagingMessages.update({ - id: In(messageIds), - userId: otherpartyId, - recipientId: userId, - isRead: false, - }, { - isRead: true, - }); - - // Publish event - publishMessagingStream(otherpartyId, userId, 'read', messageIds); - publishMessagingIndexStream(userId, 'read', messageIds); - - if (!await Users.getHasUnreadMessagingMessage(userId)) { - // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 - publishMainStream(userId, 'readAllMessagingMessages'); - pushNotification(userId, 'readAllMessagingMessages', undefined); - } else { - // そのユーザーとのメッセージで未読がなければイベント発行 - const count = await MessagingMessages.count({ - where: { - userId: otherpartyId, - recipientId: userId, - isRead: false, - }, - take: 1 - }); - - if (!count) { - pushNotification(userId, 'readAllMessagingMessagesOfARoom', { userId: otherpartyId }); - } - } -} - -/** - * Mark messages as read - */ -export async function readGroupMessagingMessage( - userId: User['id'], - groupId: UserGroup['id'], - messageIds: MessagingMessage['id'][] -) { - if (messageIds.length === 0) return; - - // check joined - const joining = await UserGroupJoinings.findOneBy({ - userId: userId, - userGroupId: groupId, - }); - - if (joining == null) { - throw new IdentifiableError('930a270c-714a-46b2-b776-ad27276dc569', 'Access denied (group).'); - } - - const messages = await MessagingMessages.findBy({ - id: In(messageIds), - }); - - const reads: MessagingMessage['id'][] = []; - - for (const message of messages) { - if (message.userId === userId) continue; - if (message.reads.includes(userId)) continue; - - // Update document - await MessagingMessages.createQueryBuilder().update() - .set({ - reads: (() => `array_append("reads", '${joining.userId}')`) as any, - }) - .where('id = :id', { id: message.id }) - .execute(); - - reads.push(message.id); - } - - // Publish event - publishGroupMessagingStream(groupId, 'read', { - ids: reads, - userId: userId, - }); - publishMessagingIndexStream(userId, 'read', reads); - - if (!await Users.getHasUnreadMessagingMessage(userId)) { - // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 - publishMainStream(userId, 'readAllMessagingMessages'); - pushNotification(userId, 'readAllMessagingMessages', undefined); - } else { - // そのグループにおいて未読がなければイベント発行 - const unreadExist = await MessagingMessages.createQueryBuilder('message') - .where(`message.groupId = :groupId`, { groupId: groupId }) - .andWhere('message.userId != :userId', { userId: userId }) - .andWhere('NOT (:userId = ANY(message.reads))', { userId: userId }) - .andWhere('message.createdAt > :joinedAt', { joinedAt: joining.createdAt }) // 自分が加入する前の会話については、未読扱いしない - .getOne().then(x => x != null); - - if (!unreadExist) { - pushNotification(userId, 'readAllMessagingMessagesOfARoom', { groupId }); - } - } -} - -export async function deliverReadActivity(user: { id: User['id']; host: null; }, recipient: IRemoteUser, messages: MessagingMessage | MessagingMessage[]) { - messages = toArray(messages).filter(x => x.uri); - const contents = messages.map(x => renderReadActivity(user, x)); - - if (contents.length > 1) { - const collection = orderedCollection(null, contents.length, undefined, undefined, contents); - deliver(user, renderActivity(collection), recipient.inbox); - } else { - for (const content of contents) { - deliver(user, renderActivity(content), recipient.inbox); - } - } -} diff --git a/packages/backend/src/server/api/common/read-notification.ts b/packages/backend/src/server/api/common/read-notification.ts deleted file mode 100644 index b0d38a9e39..0000000000 --- a/packages/backend/src/server/api/common/read-notification.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { In } from 'typeorm'; -import { publishMainStream } from '@/services/stream.js'; -import { pushNotification } from '@/services/push-notification.js'; -import { User } from '@/models/entities/user.js'; -import { Notification } from '@/models/entities/notification.js'; -import { Notifications, Users } from '@/models/index.js'; - -export async function readNotification( - userId: User['id'], - notificationIds: Notification['id'][], -) { - if (notificationIds.length === 0) return; - - // Update documents - const result = await Notifications.update({ - notifieeId: userId, - id: In(notificationIds), - isRead: false, - }, { - isRead: true, - }); - - if (result.affected === 0) return; - - if (!await Users.getHasUnreadNotification(userId)) return postReadAllNotifications(userId); - else return postReadNotifications(userId, notificationIds); -} - -export async function readNotificationByQuery( - userId: User['id'], - query: Record, -) { - const notificationIds = await Notifications.findBy({ - ...query, - notifieeId: userId, - isRead: false, - }).then(notifications => notifications.map(notification => notification.id)); - - return readNotification(userId, notificationIds); -} - -function postReadAllNotifications(userId: User['id']) { - publishMainStream(userId, 'readAllNotifications'); - return pushNotification(userId, 'readAllNotifications', undefined); -} - -function postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) { - publishMainStream(userId, 'readNotifications', notificationIds); - return pushNotification(userId, 'readNotifications', { notificationIds }); -} diff --git a/packages/backend/src/server/api/common/signin.ts b/packages/backend/src/server/api/common/signin.ts deleted file mode 100644 index 038fd8d96e..0000000000 --- a/packages/backend/src/server/api/common/signin.ts +++ /dev/null @@ -1,44 +0,0 @@ -import Koa from 'koa'; - -import config from '@/config/index.js'; -import { ILocalUser } from '@/models/entities/user.js'; -import { Signins } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { publishMainStream } from '@/services/stream.js'; - -export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) { - if (redirect) { - //#region Cookie - ctx.cookies.set('igi', user.token!, { - path: '/', - // SEE: https://github.com/koajs/koa/issues/974 - // When using a SSL proxy it should be configured to add the "X-Forwarded-Proto: https" header - secure: config.url.startsWith('https'), - httpOnly: false, - }); - //#endregion - - ctx.redirect(config.url); - } else { - ctx.body = { - id: user.id, - i: user.token, - }; - ctx.status = 200; - } - - (async () => { - // Append signin history - const record = await Signins.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - ip: ctx.ip, - headers: ctx.headers, - success: true, - }).then(x => Signins.findOneByOrFail(x.identifiers[0])); - - // Publish signin event - publishMainStream(user.id, 'signin', await Signins.pack(record)); - })(); -} diff --git a/packages/backend/src/server/api/common/signup.ts b/packages/backend/src/server/api/common/signup.ts deleted file mode 100644 index abc142472a..0000000000 --- a/packages/backend/src/server/api/common/signup.ts +++ /dev/null @@ -1,114 +0,0 @@ -import bcrypt from 'bcryptjs'; -import { generateKeyPair } from 'node:crypto'; -import generateUserToken from './generate-native-user-token.js'; -import { User } from '@/models/entities/user.js'; -import { Users, UsedUsernames } from '@/models/index.js'; -import { UserProfile } from '@/models/entities/user-profile.js'; -import { IsNull } from 'typeorm'; -import { genId } from '@/misc/gen-id.js'; -import { toPunyNullable } from '@/misc/convert-host.js'; -import { UserKeypair } from '@/models/entities/user-keypair.js'; -import { usersChart } from '@/services/chart/index.js'; -import { UsedUsername } from '@/models/entities/used-username.js'; -import { db } from '@/db/postgre.js'; - -export async function signup(opts: { - username: User['username']; - password?: string | null; - passwordHash?: UserProfile['password'] | null; - host?: string | null; -}) { - const { username, password, passwordHash, host } = opts; - let hash = passwordHash; - - // Validate username - if (!Users.validateLocalUsername(username)) { - throw new Error('INVALID_USERNAME'); - } - - if (password != null && passwordHash == null) { - // Validate password - if (!Users.validatePassword(password)) { - throw new Error('INVALID_PASSWORD'); - } - - // Generate hash of password - const salt = await bcrypt.genSalt(8); - hash = await bcrypt.hash(password, salt); - } - - // Generate secret - const secret = generateUserToken(); - - // Check username duplication - if (await Users.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() })) { - throw new Error('DUPLICATED_USERNAME'); - } - - // Check deleted username duplication - if (await UsedUsernames.findOneBy({ username: username.toLowerCase() })) { - throw new Error('USED_USERNAME'); - } - - const keyPair = await new Promise((res, rej) => - generateKeyPair('rsa', { - modulusLength: 4096, - publicKeyEncoding: { - type: 'spki', - format: 'pem', - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem', - cipher: undefined, - passphrase: undefined, - }, - } as any, (err, publicKey, privateKey) => - err ? rej(err) : res([publicKey, privateKey]) - )); - - let account!: User; - - // Start transaction - await db.transaction(async transactionalEntityManager => { - const exist = await transactionalEntityManager.findOneBy(User, { - usernameLower: username.toLowerCase(), - host: IsNull(), - }); - - if (exist) throw new Error(' the username is already used'); - - account = await transactionalEntityManager.save(new User({ - id: genId(), - createdAt: new Date(), - username: username, - usernameLower: username.toLowerCase(), - host: toPunyNullable(host), - token: secret, - isAdmin: (await Users.countBy({ - host: IsNull(), - })) === 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, - password: hash, - })); - - await transactionalEntityManager.save(new UsedUsername({ - createdAt: new Date(), - username: username.toLowerCase(), - })); - }); - - usersChart.update(account, true); - - return { account, secret }; -} diff --git a/packages/backend/src/server/api/define.ts b/packages/backend/src/server/api/define.ts deleted file mode 100644 index c1b56b8a83..0000000000 --- a/packages/backend/src/server/api/define.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as fs from 'node:fs'; -import Ajv from 'ajv'; -import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js'; -import { Schema, SchemaType } from '@/misc/schema.js'; -import { AccessToken } from '@/models/entities/access-token.js'; -import { IEndpointMeta } from './endpoints.js'; -import { ApiError } from './error.js'; - -export type Response = Record | void; - -// TODO: paramsの型をT['params']のスキーマ定義から推論する -type executor = - (params: SchemaType, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any, ip?: string | null, headers?: Record | null) => - Promise>>; - -const ajv = new Ajv({ - useDefaults: true, -}); - -ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/); - -export default function (meta: T, paramDef: Ps, cb: executor) - : (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, ip?: string | null, headers?: Record | null) => Promise { - const validate = ajv.compile(paramDef); - - return (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, ip?: string | null, headers?: Record | null) => { - let cleanup: undefined | (() => void) = undefined; - - if (meta.requireFile) { - cleanup = () => { - fs.unlink(file.path, () => {}); - }; - - if (file == null) return Promise.reject(new ApiError({ - message: 'File required.', - code: 'FILE_REQUIRED', - id: '4267801e-70d1-416a-b011-4ee502885d8b', - })); - } - - const valid = validate(params); - if (!valid) { - if (file) cleanup!(); - - const errors = validate.errors!; - const err = new ApiError({ - message: 'Invalid param.', - code: 'INVALID_PARAM', - id: '3d81ceae-475f-4600-b2a8-2bc116157532', - }, { - param: errors[0].schemaPath, - reason: errors[0].message, - }); - return Promise.reject(err); - } - - return cb(params as SchemaType, user, token, file, cleanup, ip, headers); - }; -} diff --git a/packages/backend/src/server/api/endpoint-base.ts b/packages/backend/src/server/api/endpoint-base.ts new file mode 100644 index 0000000000..0a7f9b3008 --- /dev/null +++ b/packages/backend/src/server/api/endpoint-base.ts @@ -0,0 +1,62 @@ +import * as fs from 'node:fs'; +import Ajv from 'ajv'; +import type { Schema, SchemaType } from '@/misc/schema.js'; +import type { CacheableLocalUser } from '@/models/entities/User.js'; +import type { AccessToken } from '@/models/entities/AccessToken.js'; +import { ApiError } from './error.js'; +import type { IEndpointMeta } from './endpoints.js'; + +const ajv = new Ajv({ + useDefaults: true, +}); + +ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/); + +export type Response = Record | void; + +// TODO: paramsの型をT['params']のスキーマ定義から推論する +type executor = + (params: SchemaType, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any, ip?: string | null, headers?: Record | null) => + Promise>>; + +export abstract class Endpoint { + public exec: (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, ip?: string | null, headers?: Record | null) => Promise; + + constructor(meta: T, paramDef: Ps, cb: executor) { + const validate = ajv.compile(paramDef); + + this.exec = (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, ip?: string | null, headers?: Record | null) => { + let cleanup: undefined | (() => void) = undefined; + + if (meta.requireFile) { + cleanup = () => { + fs.unlink(file.path, () => {}); + }; + + if (file == null) return Promise.reject(new ApiError({ + message: 'File required.', + code: 'FILE_REQUIRED', + id: '4267801e-70d1-416a-b011-4ee502885d8b', + })); + } + + const valid = validate(params); + if (!valid) { + if (file) cleanup!(); + + const errors = validate.errors!; + const err = new ApiError({ + message: 'Invalid param.', + code: 'INVALID_PARAM', + id: '3d81ceae-475f-4600-b2a8-2bc116157532', + }, { + param: errors[0].schemaPath, + reason: errors[0].message, + }); + return Promise.reject(err); + } + + return cb(params as SchemaType, user, token, file, cleanup, ip, headers); + }; + } +} diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 4644f34d94..a05bb5a7e0 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -1,4 +1,4 @@ -import { Schema } from '@/misc/schema.js'; +import type { Schema } from '@/misc/schema.js'; import * as ep___admin_meta from './endpoints/admin/meta.js'; import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js'; @@ -59,7 +59,6 @@ import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js'; import * as ep___admin_unsilenceUser from './endpoints/admin/unsilence-user.js'; import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js'; import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js'; -import * as ep___admin_vacuum from './endpoints/admin/vacuum.js'; import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js'; import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js'; import * as ep___announcements from './endpoints/announcements.js'; @@ -253,8 +252,6 @@ import * as ep___notes_timeline from './endpoints/notes/timeline.js'; import * as ep___notes_translate from './endpoints/notes/translate.js'; import * as ep___notes_unrenote from './endpoints/notes/unrenote.js'; import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js'; -import * as ep___notes_watching_create from './endpoints/notes/watching/create.js'; -import * as ep___notes_watching_delete from './endpoints/notes/watching/delete.js'; import * as ep___notifications_create from './endpoints/notifications/create.js'; import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js'; import * as ep___notifications_read from './endpoints/notifications/read.js'; @@ -376,7 +373,6 @@ const eps = [ ['admin/unsilence-user', ep___admin_unsilenceUser], ['admin/unsuspend-user', ep___admin_unsuspendUser], ['admin/update-meta', ep___admin_updateMeta], - ['admin/vacuum', ep___admin_vacuum], ['admin/delete-account', ep___admin_deleteAccount], ['admin/update-user-note', ep___admin_updateUserNote], ['announcements', ep___announcements], @@ -570,8 +566,6 @@ const eps = [ ['notes/translate', ep___notes_translate], ['notes/unrenote', ep___notes_unrenote], ['notes/user-list-timeline', ep___notes_userListTimeline], - ['notes/watching/create', ep___notes_watching_create], - ['notes/watching/delete', ep___notes_watching_delete], ['notifications/create', ep___notifications_create], ['notifications/mark-all-as-read', ep___notifications_markAllAsRead], ['notifications/read', ep___notifications_read], @@ -727,7 +721,6 @@ export interface IEndpointMeta { export interface IEndpoint { name: string; - exec: any; meta: IEndpointMeta; params: Schema; } @@ -735,7 +728,6 @@ export interface IEndpoint { const endpoints: IEndpoint[] = eps.map(([name, ep]) => { return { name: name, - exec: ep.default, meta: ep.meta || {}, params: ep.paramDef, }; diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 333746f423..480ae7166e 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -1,6 +1,8 @@ -import define from '../../define.js'; -import { AbuseUserReports } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AbuseUserReportsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -77,33 +79,43 @@ export const paramDef = { sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, state: { type: 'string', nullable: true, default: null }, - reporterOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "combined" }, - targetUserOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "combined" }, + reporterOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' }, + targetUserOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' }, forwarded: { type: 'boolean', default: false }, }, required: [], } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const query = makePaginationQuery(AbuseUserReports.createQueryBuilder('report'), ps.sinceId, ps.untilId); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.abuseUserReportsRepository) + private abuseUserReportsRepository: AbuseUserReportsRepository, - switch (ps.state) { - case 'resolved': query.andWhere('report.resolved = TRUE'); break; - case 'unresolved': query.andWhere('report.resolved = FALSE'); break; - } + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.abuseUserReportsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId); - switch (ps.reporterOrigin) { - case 'local': query.andWhere('report.reporterHost IS NULL'); break; - case 'remote': query.andWhere('report.reporterHost IS NOT NULL'); break; - } + switch (ps.state) { + case 'resolved': query.andWhere('report.resolved = TRUE'); break; + case 'unresolved': query.andWhere('report.resolved = FALSE'); break; + } - switch (ps.targetUserOrigin) { - case 'local': query.andWhere('report.targetUserHost IS NULL'); break; - case 'remote': query.andWhere('report.targetUserHost IS NOT NULL'); break; - } + switch (ps.reporterOrigin) { + case 'local': query.andWhere('report.reporterHost IS NULL'); break; + case 'remote': query.andWhere('report.reporterHost IS NOT NULL'); break; + } - const reports = await query.take(ps.limit).getMany(); + switch (ps.targetUserOrigin) { + case 'local': query.andWhere('report.targetUserHost IS NULL'); break; + case 'remote': query.andWhere('report.targetUserHost IS NOT NULL'); break; + } - return await AbuseUserReports.packMany(reports); -}); + const reports = await query.take(ps.limit).getMany(); + + return await this.abuseUserReportEntityService.packMany(reports); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index 5f89219991..1b173379a0 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -1,7 +1,11 @@ -import define from '../../../define.js'; -import { Users } from '@/models/index.js'; -import { signup } from '../../../common/signup.js'; +import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { SignupService } from '@/core/SignupService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { localUsernameSchema, passwordSchema } from '@/models/entities/User.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -22,31 +26,42 @@ export const meta = { export const paramDef = { type: 'object', properties: { - username: Users.localUsernameSchema, - password: Users.passwordSchema, + username: localUsernameSchema, + password: passwordSchema, }, required: ['username', 'password'], } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, _me) => { - const me = _me ? await Users.findOneByOrFail({ id: _me.id }) : null; - const noUsers = (await Users.countBy({ - host: IsNull(), - })) === 0; - if (!noUsers && !me?.isAdmin) throw new Error('access denied'); - - const { account, secret } = await signup({ - username: ps.username, - password: ps.password, - }); - - const res = await Users.pack(account, account, { - detail: true, - includeSecrets: true, - }); - - (res as any).token = secret; - - return res; -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private userEntityService: UserEntityService, + private signupService: SignupService, + ) { + super(meta, paramDef, async (ps, _me) => { + const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null; + const noUsers = (await this.usersRepository.countBy({ + host: IsNull(), + })) === 0; + if (!noUsers && !me?.isAdmin) throw new Error('access denied'); + + const { account, secret } = await this.signupService.signup({ + username: ps.username, + password: ps.password, + }); + + const res = await this.userEntityService.pack(account, account, { + detail: true, + includeSecrets: true, + }); + + (res as any).token = secret; + + return res; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts index 629d700582..2e0222f0c6 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -1,8 +1,10 @@ -import define from '../../../define.js'; -import { Users } from '@/models/index.js'; -import { doPostSuspend } from '@/services/suspend-user.js'; -import { publishUserEvent } from '@/services/stream.js'; -import { createDeleteAccountJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { QueueService } from '@/core/QueueService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { UserSuspendService } from '@/core/UserSuspendService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -20,40 +22,52 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user == null) { - throw new Error('user not found'); - } + private queueService: QueueService, + private globalEventService: GlobalEventService, + private userSuspendService: UserSuspendService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); - if (user.isAdmin) { - throw new Error('cannot suspend admin'); - } + if (user == null) { + throw new Error('user not found'); + } - if (user.isModerator) { - throw new Error('cannot suspend moderator'); - } + if (user.isAdmin) { + throw new Error('cannot suspend admin'); + } - if (Users.isLocalUser(user)) { - // 物理削除する前にDelete activityを送信する - await doPostSuspend(user).catch(e => {}); + if (user.isModerator) { + throw new Error('cannot suspend moderator'); + } - createDeleteAccountJob(user, { - soft: false, - }); - } else { - createDeleteAccountJob(user, { - soft: true, // リモートユーザーの削除は、完全にDBから物理削除してしまうと再度連合してきてアカウントが復活する可能性があるため、soft指定する - }); - } + if (this.userEntityService.isLocalUser(user)) { + // 物理削除する前にDelete activityを送信する + await this.userSuspendService.doPostSuspend(user).catch(err => {}); - await Users.update(user.id, { - isDeleted: true, - }); + this.queueService.createDeleteAccountJob(user, { + soft: false, + }); + } else { + this.queueService.createDeleteAccountJob(user, { + soft: true, // リモートユーザーの削除は、完全にDBから物理削除してしまうと再度連合してきてアカウントが復活する可能性があるため、soft指定する + }); + } - if (Users.isLocalUser(user)) { - // Terminate streaming - publishUserEvent(user.id, 'terminate', {}); + await this.usersRepository.update(user.id, { + isDeleted: true, + }); + + if (this.userEntityService.isLocalUser(user)) { + // Terminate streaming + this.globalEventService.publishUserEvent(user.id, 'terminate', {}); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts index ab2c50b50f..6b32391e8d 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import { Ads } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AdsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -24,16 +26,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - await Ads.insert({ - id: genId(), - createdAt: new Date(), - expiresAt: new Date(ps.expiresAt), - url: ps.url, - imageUrl: ps.imageUrl, - priority: ps.priority, - ratio: ps.ratio, - place: ps.place, - memo: ps.memo, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.adsRepository) + private adsRepository: AdsRepository, + + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + await this.adsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + expiresAt: new Date(ps.expiresAt), + url: ps.url, + imageUrl: ps.imageUrl, + priority: ps.priority, + ratio: ps.ratio, + place: ps.place, + memo: ps.memo, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts index 0ead2be005..7abefe156b 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { Ads } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AdsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -26,10 +28,18 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const ad = await Ads.findOneBy({ id: ps.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.adsRepository) + private adsRepository: AdsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const ad = await this.adsRepository.findOneBy({ id: ps.id }); - if (ad == null) throw new ApiError(meta.errors.noSuchAd); + if (ad == null) throw new ApiError(meta.errors.noSuchAd); - await Ads.delete(ad.id); -}); + await this.adsRepository.delete(ad.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts index 74f154f272..efece31bbf 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import { Ads } from '@/models/index.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AdsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -20,11 +22,21 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const query = makePaginationQuery(Ads.createQueryBuilder('ad'), ps.sinceId, ps.untilId) - .andWhere('ad.expiresAt > :now', { now: new Date() }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.adsRepository) + private adsRepository: AdsRepository, - const ads = await query.take(ps.limit).getMany(); + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId) + .andWhere('ad.expiresAt > :now', { now: new Date() }); - return ads; -}); + const ads = await query.take(ps.limit).getMany(); + + return ads; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts index 650f8670e3..098a593379 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { Ads } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AdsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -33,18 +35,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const ad = await Ads.findOneBy({ id: ps.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private adsRepository: AdsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const ad = await this.adsRepository.findOneBy({ id: ps.id }); - if (ad == null) throw new ApiError(meta.errors.noSuchAd); + if (ad == null) throw new ApiError(meta.errors.noSuchAd); - await Ads.update(ad.id, { - url: ps.url, - place: ps.place, - priority: ps.priority, - ratio: ps.ratio, - memo: ps.memo, - imageUrl: ps.imageUrl, - expiresAt: new Date(ps.expiresAt), - }); -}); + await this.adsRepository.update(ad.id, { + url: ps.url, + place: ps.place, + priority: ps.priority, + ratio: ps.ratio, + memo: ps.memo, + imageUrl: ps.imageUrl, + expiresAt: new Date(ps.expiresAt), + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index 33076b6d30..ee07170d62 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import { Announcements } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AnnouncementsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -55,15 +57,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const announcement = await Announcements.insert({ - id: genId(), - createdAt: new Date(), - updatedAt: null, - title: ps.title, - text: ps.text, - imageUrl: ps.imageUrl, - }).then(x => Announcements.findOneByOrFail(x.identifiers[0])); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.announcementsRepository) + private announcementsRepository: AnnouncementsRepository, - return Object.assign({}, announcement, { createdAt: announcement.createdAt.toISOString(), updatedAt: null }); -}); + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const announcement = await this.announcementsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + updatedAt: null, + title: ps.title, + text: ps.text, + imageUrl: ps.imageUrl, + }).then(x => this.announcementsRepository.findOneByOrFail(x.identifiers[0])); + + return Object.assign({}, announcement, { createdAt: announcement.createdAt.toISOString(), updatedAt: null }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts index c17765f4fc..9a67bdb1aa 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { Announcements } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AnnouncementsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -26,10 +28,18 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const announcement = await Announcements.findOneBy({ id: ps.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.announcementsRepository) + private announcementsRepository: AnnouncementsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const announcement = await this.announcementsRepository.findOneBy({ id: ps.id }); - if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); + if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); - await Announcements.delete(announcement.id); -}); + await this.announcementsRepository.delete(announcement.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 7a5758d75b..35c14abda2 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -1,7 +1,9 @@ -import { Announcements, AnnouncementReads } from '@/models/index.js'; -import { Announcement } from '@/models/entities/announcement.js'; -import define from '../../../define.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/index.js'; +import type { Announcement } from '@/models/entities/Announcement.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -64,26 +66,39 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const query = makePaginationQuery(Announcements.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.announcementsRepository) + private announcementsRepository: AnnouncementsRepository, - const announcements = await query.take(ps.limit).getMany(); + @Inject(DI.announcementReadsRepository) + private announcementReadsRepository: AnnouncementReadsRepository, - const reads = new Map(); + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); - for (const announcement of announcements) { - reads.set(announcement, await AnnouncementReads.countBy({ - announcementId: announcement.id, - })); - } + const announcements = await query.take(ps.limit).getMany(); + + const reads = new Map(); - return announcements.map(announcement => ({ - id: announcement.id, - createdAt: announcement.createdAt.toISOString(), - updatedAt: announcement.updatedAt?.toISOString() ?? null, - title: announcement.title, - text: announcement.text, - imageUrl: announcement.imageUrl, - reads: reads.get(announcement)!, - })); -}); + for (const announcement of announcements) { + reads.set(announcement, await this.announcementReadsRepository.countBy({ + announcementId: announcement.id, + })); + } + + return announcements.map(announcement => ({ + id: announcement.id, + createdAt: announcement.createdAt.toISOString(), + updatedAt: announcement.updatedAt?.toISOString() ?? null, + title: announcement.title, + text: announcement.text, + imageUrl: announcement.imageUrl, + reads: reads.get(announcement)!, + })); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index 61ce106d88..38358dff10 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { Announcements } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AnnouncementsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -29,15 +31,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const announcement = await Announcements.findOneBy({ id: ps.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.announcementsRepository) + private announcementsRepository: AnnouncementsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const announcement = await this.announcementsRepository.findOneBy({ id: ps.id }); - if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); + if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); - await Announcements.update(announcement.id, { - updatedAt: new Date(), - title: ps.title, - text: ps.text, - imageUrl: ps.imageUrl, - }); -}); + await this.announcementsRepository.update(announcement.id, { + updatedAt: new Date(), + title: ps.title, + text: ps.text, + imageUrl: ps.imageUrl, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/delete-account.ts b/packages/backend/src/server/api/endpoints/admin/delete-account.ts index 2d7ef2f236..c8b67fe1c0 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-account.ts @@ -1,6 +1,8 @@ -import { Users } from '@/models/index.js'; -import { deleteAccount } from '@/services/delete-account.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DeleteAccountService } from '@/core/DeleteAccountService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -21,11 +23,21 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const user = await Users.findOneByOrFail({ id: ps.userId }); - if (user.isDeleted) { - return; - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private deleteAccountService: DeleteAccountService, + ) { + super(meta, paramDef, async (ps) => { + const user = await this.usersRepository.findOneByOrFail({ id: ps.userId }); + if (user.isDeleted) { + return; + } - await deleteAccount(user); -}); + await this.deleteAccountService.deleteAccount(user); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts index dc1976624d..051e4c60fb 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts @@ -1,6 +1,8 @@ -import define from '../../define.js'; -import { deleteFile } from '@/services/drive/delete-file.js'; -import { DriveFiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DriveService } from '@/core/DriveService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -18,12 +20,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const files = await DriveFiles.findBy({ - userId: ps.userId, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - for (const file of files) { - deleteFile(file); + private driveService: DriveService, + ) { + super(meta, paramDef, async (ps, me) => { + const files = await this.driveFilesRepository.findBy({ + userId: ps.userId, + }); + + for (const file of files) { + this.driveService.deleteFile(file); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts index a4b29770e1..770bade06d 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; -import { Users } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { DI } from '@/di-symbols.js'; + export const meta = { tags: ['admin'], @@ -19,29 +21,39 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); - - if (user == null) { - throw new Error('user not found'); - } - - if (!Users.isLocalUser(user)) { - throw new Error('user is not local user'); - } - - /*if (user.isAdmin) { +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + if (!this.userEntityService.isLocalUser(user)) { + throw new Error('user is not local user'); + } + + /*if (user.isAdmin) { throw new Error('cannot suspend admin'); } if (user.isModerator) { throw new Error('cannot suspend moderator'); }*/ - await Users.update(user.id, { - driveCapacityOverrideMb: ps.overrideMb, - }); + await this.usersRepository.update(user.id, { + driveCapacityOverrideMb: ps.overrideMb, + }); - insertModerationLog(me, 'change-drive-capacity-override', { - targetId: user.id, - }); -}); + this.moderationLogService.insertModerationLog(me, 'change-drive-capacity-override', { + targetId: user.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts index bab149532e..2cc4e70e55 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts @@ -1,5 +1,6 @@ -import define from '../../../define.js'; -import { createCleanRemoteFilesJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { tags: ['admin'], @@ -15,6 +16,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - createCleanRemoteFilesJob(); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createCleanRemoteFilesJob(); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts index 3db942e6cd..3927a89f90 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts @@ -1,7 +1,9 @@ import { IsNull } from 'typeorm'; -import define from '../../../define.js'; -import { deleteFile } from '@/services/drive/delete-file.js'; -import { DriveFiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DriveService } from '@/core/DriveService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -17,12 +19,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const files = await DriveFiles.findBy({ - userId: IsNull(), - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - for (const file of files) { - deleteFile(file); + private driveService: DriveService, + ) { + super(meta, paramDef, async (ps, me) => { + const files = await this.driveFilesRepository.findBy({ + userId: IsNull(), + }); + + for (const file of files) { + this.driveService.deleteFile(file); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index ba32aac431..88529ab0aa 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -1,6 +1,8 @@ -import { DriveFiles } from '@/models/index.js'; -import define from '../../../define.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DriveFilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -39,32 +41,42 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (ps.userId) { - query.andWhere('file.userId = :userId', { userId: ps.userId }); - } else { - if (ps.origin === 'local') { - query.andWhere('file.userHost IS NULL'); - } else if (ps.origin === 'remote') { - query.andWhere('file.userHost IS NOT NULL'); - } + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId); - if (ps.hostname) { - query.andWhere('file.userHost = :hostname', { hostname: ps.hostname }); - } - } + if (ps.userId) { + query.andWhere('file.userId = :userId', { userId: ps.userId }); + } else { + if (ps.origin === 'local') { + query.andWhere('file.userHost IS NULL'); + } else if (ps.origin === 'remote') { + query.andWhere('file.userHost IS NOT NULL'); + } - if (ps.type) { - if (ps.type.endsWith('/*')) { - query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); - } else { - query.andWhere('file.type = :type', { type: ps.type }); - } - } + if (ps.hostname) { + query.andWhere('file.userHost = :hostname', { hostname: ps.hostname }); + } + } - const files = await query.take(ps.limit).getMany(); + if (ps.type) { + if (ps.type.endsWith('/*')) { + query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); + } else { + query.andWhere('file.type = :type', { type: ps.type }); + } + } - return await DriveFiles.packMany(files, { detail: true, withUser: true, self: true }); -}); + const files = await query.take(ps.limit).getMany(); + + return await this.driveFileEntityService.packMany(files, { detail: true, withUser: true, self: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index e9117a23c8..45ea9cdb50 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -1,5 +1,7 @@ -import { DriveFiles } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DriveFilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -169,25 +171,33 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const file = ps.fileId ? await DriveFiles.findOneBy({ id: ps.fileId }) : await DriveFiles.findOne({ - where: [{ - url: ps.url, - }, { - thumbnailUrl: ps.url, - }, { - webpublicUrl: ps.url, - }], - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const file = ps.fileId ? await this.driveFilesRepository.findOneBy({ id: ps.fileId }) : await this.driveFilesRepository.findOne({ + where: [{ + url: ps.url, + }, { + thumbnailUrl: ps.url, + }, { + webpublicUrl: ps.url, + }], + }); - if (file == null) { - throw new ApiError(meta.errors.noSuchFile); - } + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } - if (!me.isAdmin) { - delete file.requestIp; - delete file.requestHeaders; - } + if (!me.isAdmin) { + delete file.requestIp; + delete file.requestHeaders; + } - return file; -}); + return file; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts index 232fbbd573..0b6e744ef8 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts @@ -1,8 +1,8 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { In } from 'typeorm'; -import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, In } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -24,18 +24,31 @@ export const paramDef = { required: ['ids', 'aliases'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const emojis = await Emojis.findBy({ - id: In(ps.ids), - }); - - for (const emoji of emojis) { - await Emojis.update(emoji.id, { - updatedAt: new Date(), - aliases: [...new Set(emoji.aliases.concat(ps.aliases))], +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const emojis = await this.emojisRepository.findBy({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await this.emojisRepository.update(emoji.id, { + updatedAt: new Date(), + aliases: [...new Set(emoji.aliases.concat(ps.aliases))], + }); + } + + await this.db.queryResultCache!.remove(['meta_emojis']); }); } - - await db.queryResultCache!.remove(['meta_emojis']); -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index 67349c24e0..daa57e8eb2 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -1,11 +1,14 @@ -import define from '../../../define.js'; -import { Emojis, DriveFiles } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { ApiError } from '../../../error.js'; +import { Inject, Injectable } from '@nestjs/common'; import rndstr from 'rndstr'; -import { publishBroadcastStream } from '@/services/stream.js'; -import { db } from '@/db/postgre.js'; +import { DataSource } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository, EmojisRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { ApiError } from '../../../error.js'; export const meta = { tags: ['admin'], @@ -30,37 +33,58 @@ export const paramDef = { required: ['fileId'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const file = await DriveFiles.findOneBy({ id: ps.fileId }); - - if (file == null) throw new ApiError(meta.errors.noSuchFile); - - const name = file.name.split('.')[0].match(/^[a-z0-9_]+$/) ? file.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`; - - const emoji = await Emojis.insert({ - id: genId(), - updatedAt: new Date(), - name: name, - category: null, - host: null, - aliases: [], - originalUrl: file.url, - publicUrl: file.webpublicUrl ?? file.url, - type: file.webpublicType ?? file.type, - }).then(x => Emojis.findOneByOrFail(x.identifiers[0])); - - await db.queryResultCache!.remove(['meta_emojis']); - - publishBroadcastStream('emojiAdded', { - emoji: await Emojis.pack(emoji.id), - }); - - insertModerationLog(me, 'addEmoji', { - emojiId: emoji.id, - }); - - return { - id: emoji.id, - }; -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + + private emojiEntityService: EmojiEntityService, + private idService: IdService, + private globalEventService: GlobalEventService, + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); + + if (file == null) throw new ApiError(meta.errors.noSuchFile); + + const name = file.name.split('.')[0].match(/^[a-z0-9_]+$/) ? file.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`; + + const emoji = await this.emojisRepository.insert({ + id: this.idService.genId(), + updatedAt: new Date(), + name: name, + category: null, + host: null, + aliases: [], + originalUrl: file.url, + publicUrl: file.webpublicUrl ?? file.url, + type: file.webpublicType ?? file.type, + }).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0])); + + await this.db.queryResultCache!.remove(['meta_emojis']); + + this.globalEventService.publishBroadcastStream('emojiAdded', { + emoji: await this.emojiEntityService.pack(emoji.id), + }); + + this.moderationLogService.insertModerationLog(me, 'addEmoji', { + emojiId: emoji.id, + }); + + return { + id: emoji.id, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index 7010ade0d8..08d40834c1 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -1,11 +1,14 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import { DI } from '@/di-symbols.js'; +import { DriveService } from '@/core/DriveService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { ApiError } from '../../../error.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { uploadFromUrl } from '@/services/drive/upload-from-url.js'; -import { publishBroadcastStream } from '@/services/stream.js'; -import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -42,41 +45,59 @@ export const paramDef = { required: ['emojiId'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const emoji = await Emojis.findOneBy({ id: ps.emojiId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, - if (emoji == null) { - throw new ApiError(meta.errors.noSuchEmoji); - } + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, - let driveFile: DriveFile; + private emojiEntityService: EmojiEntityService, + private idService: IdService, + private globalEventService: GlobalEventService, + private driveService: DriveService, + ) { + super(meta, paramDef, async (ps, me) => { + const emoji = await this.emojisRepository.findOneBy({ id: ps.emojiId }); - try { - // Create file - driveFile = await uploadFromUrl({ url: emoji.originalUrl, user: null, force: true }); - } catch (e) { - throw new ApiError(); - } + if (emoji == null) { + throw new ApiError(meta.errors.noSuchEmoji); + } + + let driveFile: DriveFile; + + try { + // Create file + driveFile = await this.driveService.uploadFromUrl({ url: emoji.originalUrl, user: null, force: true }); + } catch (e) { + throw new ApiError(); + } - const copied = await Emojis.insert({ - id: genId(), - updatedAt: new Date(), - name: emoji.name, - host: null, - aliases: [], - originalUrl: driveFile.url, - publicUrl: driveFile.webpublicUrl ?? driveFile.url, - type: driveFile.webpublicType ?? driveFile.type, - }).then(x => Emojis.findOneByOrFail(x.identifiers[0])); - - await db.queryResultCache!.remove(['meta_emojis']); - - publishBroadcastStream('emojiAdded', { - emoji: await Emojis.pack(copied.id), - }); - - return { - id: copied.id, - }; -}); + const copied = await this.emojisRepository.insert({ + id: this.idService.genId(), + updatedAt: new Date(), + name: emoji.name, + host: null, + aliases: [], + originalUrl: driveFile.url, + publicUrl: driveFile.webpublicUrl ?? driveFile.url, + type: driveFile.webpublicType ?? driveFile.type, + }).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0])); + + await this.db.queryResultCache!.remove(['meta_emojis']); + + this.globalEventService.publishBroadcastStream('emojiAdded', { + emoji: await this.emojiEntityService.pack(copied.id), + }); + + return { + id: copied.id, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts index 93a6c4e4e2..81b095cb57 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts @@ -1,9 +1,9 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { In } from 'typeorm'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, In } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; export const meta = { tags: ['admin'], @@ -22,19 +22,34 @@ export const paramDef = { required: ['ids'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const emojis = await Emojis.findBy({ - id: In(ps.ids), - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const emojis = await this.emojisRepository.findBy({ + id: In(ps.ids), + }); - for (const emoji of emojis) { - await Emojis.delete(emoji.id); + for (const emoji of emojis) { + await this.emojisRepository.delete(emoji.id); - await db.queryResultCache!.remove(['meta_emojis']); + await this.db.queryResultCache!.remove(['meta_emojis']); - insertModerationLog(me, 'deleteEmoji', { - emoji: emoji, + this.moderationLogService.insertModerationLog(me, 'deleteEmoji', { + emoji: emoji, + }); + } }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts index 67dbf28d85..e4278dc33a 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -1,8 +1,10 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -27,17 +29,32 @@ export const paramDef = { required: ['id'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const emoji = await Emojis.findOneBy({ id: ps.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const emoji = await this.emojisRepository.findOneBy({ id: ps.id }); - if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji); + if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji); - await Emojis.delete(emoji.id); + await this.emojisRepository.delete(emoji.id); - await db.queryResultCache!.remove(['meta_emojis']); + await this.db.queryResultCache!.remove(['meta_emojis']); - insertModerationLog(me, 'deleteEmoji', { - emoji: emoji, - }); -}); + this.moderationLogService.insertModerationLog(me, 'deleteEmoji', { + emoji: emoji, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts index 3f03dc2da4..6fe492cb75 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts @@ -1,6 +1,6 @@ -import define from '../../../define.js'; -import { createImportCustomEmojisJob } from '@/queue/index.js'; -import ms from 'ms'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, @@ -17,6 +17,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - createImportCustomEmojisJob(user, ps.fileId); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createImportCustomEmojisJob(me, ps.fileId); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts index d16689a280..9d6fa53417 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts @@ -1,7 +1,10 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { toPuny } from '@/misc/convert-host.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -69,23 +72,35 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const q = makePaginationQuery(Emojis.createQueryBuilder('emoji'), ps.sinceId, ps.untilId); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, - if (ps.host == null) { - q.andWhere(`emoji.host IS NOT NULL`); - } else { - q.andWhere(`emoji.host = :host`, { host: toPuny(ps.host) }); - } + private utilityService: UtilityService, + private queryService: QueryService, + private emojiEntityService: EmojiEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId); - if (ps.query) { - q.andWhere('emoji.name like :query', { query: '%' + ps.query + '%' }); - } + if (ps.host == null) { + q.andWhere('emoji.host IS NOT NULL'); + } else { + q.andWhere('emoji.host = :host', { host: this.utilityService.toPuny(ps.host) }); + } - const emojis = await q - .orderBy('emoji.id', 'DESC') - .take(ps.limit) - .getMany(); + if (ps.query) { + q.andWhere('emoji.name like :query', { query: '%' + ps.query + '%' }); + } - return Emojis.packMany(emojis); -}); + const emojis = await q + .orderBy('emoji.id', 'DESC') + .take(ps.limit) + .getMany(); + + return this.emojiEntityService.packMany(emojis); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index 6192978fad..736d664cc3 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; -import { Emoji } from '@/models/entities/emoji.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import type { Emoji } from '@/models/entities/Emoji.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -63,27 +65,37 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const q = makePaginationQuery(Emojis.createQueryBuilder('emoji'), ps.sinceId, ps.untilId) - .andWhere(`emoji.host IS NULL`); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, - let emojis: Emoji[]; + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId) + .andWhere('emoji.host IS NULL'); - if (ps.query) { - //q.andWhere('emoji.name ILIKE :q', { q: `%${ps.query}%` }); - //const emojis = await q.take(ps.limit).getMany(); + let emojis: Emoji[]; - emojis = await q.getMany(); + if (ps.query) { + //q.andWhere('emoji.name ILIKE :q', { q: `%${ps.query}%` }); + //const emojis = await q.take(ps.limit).getMany(); - emojis = emojis.filter(emoji => - emoji.name.includes(ps.query!) || - emoji.aliases.some(a => a.includes(ps.query!)) || - emoji.category?.includes(ps.query!)); + emojis = await q.getMany(); - emojis.splice(ps.limit + 1); - } else { - emojis = await q.take(ps.limit).getMany(); - } + emojis = emojis.filter(emoji => + emoji.name.includes(ps.query!) || + emoji.aliases.some(a => a.includes(ps.query!)) || + emoji.category?.includes(ps.query!)); + + emojis.splice(ps.limit + 1); + } else { + emojis = await q.take(ps.limit).getMany(); + } - return Emojis.packMany(emojis); -}); + return this.emojiEntityService.packMany(emojis); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts index a4da40fffd..d6c70eaae7 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts @@ -1,8 +1,8 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { In } from 'typeorm'; -import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, In } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -24,18 +24,31 @@ export const paramDef = { required: ['ids', 'aliases'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const emojis = await Emojis.findBy({ - id: In(ps.ids), - }); - - for (const emoji of emojis) { - await Emojis.update(emoji.id, { - updatedAt: new Date(), - aliases: emoji.aliases.filter(x => !ps.aliases.includes(x)), +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const emojis = await this.emojisRepository.findBy({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await this.emojisRepository.update(emoji.id, { + updatedAt: new Date(), + aliases: emoji.aliases.filter(x => !ps.aliases.includes(x)), + }); + } + + await this.db.queryResultCache!.remove(['meta_emojis']); }); } - - await db.queryResultCache!.remove(['meta_emojis']); -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts index ae3b190f40..c438b7f9b7 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts @@ -1,8 +1,8 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { In } from 'typeorm'; -import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, In } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -24,14 +24,27 @@ export const paramDef = { required: ['ids', 'aliases'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - await Emojis.update({ - id: In(ps.ids), - }, { - updatedAt: new Date(), - aliases: ps.aliases, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + ) { + super(meta, paramDef, async (ps, me) => { + await this.emojisRepository.update({ + id: In(ps.ids), + }, { + updatedAt: new Date(), + aliases: ps.aliases, + }); - await db.queryResultCache!.remove(['meta_emojis']); -}); + await this.db.queryResultCache!.remove(['meta_emojis']); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts index cff58d6170..4a9b31fd28 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts @@ -1,8 +1,8 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { In } from 'typeorm'; -import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, In } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -26,14 +26,27 @@ export const paramDef = { required: ['ids'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - await Emojis.update({ - id: In(ps.ids), - }, { - updatedAt: new Date(), - category: ps.category, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + ) { + super(meta, paramDef, async (ps, me) => { + await this.emojisRepository.update({ + id: In(ps.ids), + }, { + updatedAt: new Date(), + category: ps.category, + }); - await db.queryResultCache!.remove(['meta_emojis']); -}); + await this.db.queryResultCache!.remove(['meta_emojis']); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index 5b547b3b79..e6eb9eb9a6 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -35,18 +37,31 @@ export const paramDef = { required: ['id', 'name', 'aliases'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const emoji = await Emojis.findOneBy({ id: ps.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const emoji = await this.emojisRepository.findOneBy({ id: ps.id }); - if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji); + if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji); - await Emojis.update(emoji.id, { - updatedAt: new Date(), - name: ps.name, - category: ps.category, - aliases: ps.aliases, - }); + await this.emojisRepository.update(emoji.id, { + updatedAt: new Date(), + name: ps.name, + category: ps.category, + aliases: ps.aliases, + }); - await db.queryResultCache!.remove(['meta_emojis']); -}); + await this.db.queryResultCache!.remove(['meta_emojis']); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts index da54201473..789838661c 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import { deleteFile } from '@/services/drive/delete-file.js'; -import { DriveFiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DriveService } from '@/core/DriveService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -18,12 +20,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const files = await DriveFiles.findBy({ - userHost: ps.host, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - for (const file of files) { - deleteFile(file); + private driveService: DriveService, + ) { + super(meta, paramDef, async (ps, me) => { + const files = await this.driveFilesRepository.findBy({ + userHost: ps.host, + }); + + for (const file of files) { + this.driveService.deleteFile(file); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts index cb2be5ab37..476b821523 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; -import { Instances } from '@/models/index.js'; -import { toPuny } from '@/misc/convert-host.js'; -import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { InstancesRepository } from '@/models/index.js'; +import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -19,12 +21,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const instance = await Instances.findOneBy({ host: toPuny(ps.host) }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, - if (instance == null) { - throw new Error('instance not found'); - } + private utilityService: UtilityService, + private fetchInstanceMetadataService: FetchInstanceMetadataService, + ) { + super(meta, paramDef, async (ps, me) => { + const instance = await this.instancesRepository.findOneBy({ host: this.utilityService.toPuny(ps.host) }); + + if (instance == null) { + throw new Error('instance not found'); + } - fetchInstanceMetadata(instance, true); -}); + this.fetchInstanceMetadataService.fetchInstanceMetadata(instance, true); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts index b7ee27db64..67165dc47e 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import deleteFollowing from '@/services/following/delete.js'; -import { Followings, Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { FollowingsRepository, UsersRepository } from '@/models/index.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -18,17 +20,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const followings = await Followings.findBy({ - followerHost: ps.host, - }); - - const pairs = await Promise.all(followings.map(f => Promise.all([ - Users.findOneByOrFail({ id: f.followerId }), - Users.findOneByOrFail({ id: f.followeeId }), - ]))); - - for (const pair of pairs) { - deleteFollowing(pair[0], pair[1]); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private followingsRepository: FollowingsRepository, + + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + const followings = await this.followingsRepository.findBy({ + followerHost: ps.host, + }); + + const pairs = await Promise.all(followings.map(f => Promise.all([ + this.usersRepository.findOneByOrFail({ id: f.followerId }), + this.usersRepository.findOneByOrFail({ id: f.followeeId }), + ]))); + + for (const pair of pairs) { + this.userFollowingService.unfollow(pair[0], pair[1]); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index 278131fb37..b9eade5b40 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import { Instances } from '@/models/index.js'; -import { toPuny } from '@/misc/convert-host.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { InstancesRepository } from '@/models/index.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -19,14 +21,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const instance = await Instances.findOneBy({ host: toPuny(ps.host) }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, - if (instance == null) { - throw new Error('instance not found'); - } + private utilityService: UtilityService, + ) { + super(meta, paramDef, async (ps, me) => { + const instance = await this.instancesRepository.findOneBy({ host: this.utilityService.toPuny(ps.host) }); + + if (instance == null) { + throw new Error('instance not found'); + } - Instances.update({ host: toPuny(ps.host) }, { - isSuspended: ps.isSuspended, - }); -}); + this.instancesRepository.update({ host: this.utilityService.toPuny(ps.host) }, { + isSuspended: ps.isSuspended, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts index dd16473f30..e53d0bfcea 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts @@ -1,5 +1,7 @@ -import define from '../../define.js'; -import { db } from '@/db/postgre.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -15,14 +17,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const stats = await db.query(`SELECT * FROM pg_indexes;`).then(recs => { - const res = [] as { tablename: string; indexname: string; }[]; - for (const rec of recs) { - res.push(rec); - } - return res; - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + ) { + super(meta, paramDef, async () => { + const stats = await this.db.query('SELECT * FROM pg_indexes;').then(recs => { + const res = [] as { tablename: string; indexname: string; }[]; + for (const rec of recs) { + res.push(rec); + } + return res; + }); - return stats; -}); + return stats; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts index aca2540fd5..41014cb167 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts @@ -1,5 +1,7 @@ -import { db } from '@/db/postgre.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -26,24 +28,31 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const sizes = await - db.query(` +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + ) { + super(meta, paramDef, async () => { + const sizes = await this.db.query(` SELECT relname AS "table", reltuples as "count", pg_total_relation_size(C.oid) AS "size" FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE nspname NOT IN ('pg_catalog', 'information_schema') AND C.relkind <> 'i' AND nspname !~ '^pg_toast';`) - .then(recs => { - const res = {} as Record; - for (const rec of recs) { - res[rec.table] = { - count: parseInt(rec.count, 10), - size: parseInt(rec.size, 10), - }; - } - return res; - }); + .then(recs => { + const res = {} as Record; + for (const rec of recs) { + res[rec.table] = { + count: parseInt(rec.count, 10), + size: parseInt(rec.size, 10), + }; + } + return res; + }); - return sizes; -}); + return sizes; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts index e8b9cb3b09..eddaade919 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts @@ -1,5 +1,7 @@ -import { UserIps } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserIpsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -17,15 +19,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const ips = await UserIps.find({ - where: { userId: ps.userId }, - order: { createdAt: 'DESC' }, - take: 30, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userIpsRepository) + private userIpsRepository: UserIpsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const ips = await this.userIpsRepository.find({ + where: { userId: ps.userId }, + order: { createdAt: 'DESC' }, + take: 30, + }); - return ips.map(x => ({ - ip: x.ip, - createdAt: x.createdAt.toISOString(), - })); -}); + return ips.map(x => ({ + ip: x.ip, + createdAt: x.createdAt.toISOString(), + })); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/invite.ts b/packages/backend/src/server/api/endpoints/admin/invite.ts index 7e950cf87b..5fe341e5ca 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite.ts @@ -1,7 +1,9 @@ import rndstr from 'rndstr'; -import define from '../../define.js'; -import { RegistrationTickets } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistrationTicketsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -31,19 +33,29 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const code = rndstr({ - length: 8, - chars: '2-9A-HJ-NP-Z', // [0-9A-Z] w/o [01IO] (32 patterns) - }); - - await RegistrationTickets.insert({ - id: genId(), - createdAt: new Date(), - code, - }); - - return { - code, - }; -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registrationTicketsRepository) + private registrationTicketsRepository: RegistrationTicketsRepository, + + private idService: IdService, + ) { + super(meta, paramDef, async () => { + const code = rndstr({ + length: 8, + chars: '2-9A-HJ-NP-Z', // [0-9A-Z] w/o [01IO] (32 patterns) + }); + + await this.registrationTicketsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + code, + }); + + return { + code, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 8746119687..615c0a0e70 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -1,7 +1,9 @@ -import config from '@/config/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; +import { Inject, Injectable } from '@nestjs/common'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import define from '../../define.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MetaService } from '@/core/MetaService.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['meta'], @@ -340,91 +342,101 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const instance = await fetchMeta(true); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, - return { - maintainerName: instance.maintainerName, - maintainerEmail: instance.maintainerEmail, - version: config.version, - name: instance.name, - uri: config.url, - description: instance.description, - langs: instance.langs, - tosUrl: instance.ToSUrl, - repositoryUrl: instance.repositoryUrl, - feedbackUrl: instance.feedbackUrl, - disableRegistration: instance.disableRegistration, - disableLocalTimeline: instance.disableLocalTimeline, - disableGlobalTimeline: instance.disableGlobalTimeline, - driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, - driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, - emailRequiredForSignup: instance.emailRequiredForSignup, - enableHcaptcha: instance.enableHcaptcha, - hcaptchaSiteKey: instance.hcaptchaSiteKey, - enableRecaptcha: instance.enableRecaptcha, - recaptchaSiteKey: instance.recaptchaSiteKey, - swPublickey: instance.swPublicKey, - themeColor: instance.themeColor, - mascotImageUrl: instance.mascotImageUrl, - bannerUrl: instance.bannerUrl, - errorImageUrl: instance.errorImageUrl, - iconUrl: instance.iconUrl, - backgroundImageUrl: instance.backgroundImageUrl, - logoImageUrl: instance.logoImageUrl, - maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため - defaultLightTheme: instance.defaultLightTheme, - defaultDarkTheme: instance.defaultDarkTheme, - enableEmail: instance.enableEmail, - enableTwitterIntegration: instance.enableTwitterIntegration, - enableGithubIntegration: instance.enableGithubIntegration, - enableDiscordIntegration: instance.enableDiscordIntegration, - enableServiceWorker: instance.enableServiceWorker, - translatorAvailable: instance.deeplAuthKey != null, - pinnedPages: instance.pinnedPages, - pinnedClipId: instance.pinnedClipId, - cacheRemoteFiles: instance.cacheRemoteFiles, - useStarForReactionFallback: instance.useStarForReactionFallback, - pinnedUsers: instance.pinnedUsers, - hiddenTags: instance.hiddenTags, - blockedHosts: instance.blockedHosts, - hcaptchaSecretKey: instance.hcaptchaSecretKey, - recaptchaSecretKey: instance.recaptchaSecretKey, - sensitiveMediaDetection: instance.sensitiveMediaDetection, - sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity, - setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically, - enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos, - proxyAccountId: instance.proxyAccountId, - twitterConsumerKey: instance.twitterConsumerKey, - twitterConsumerSecret: instance.twitterConsumerSecret, - githubClientId: instance.githubClientId, - githubClientSecret: instance.githubClientSecret, - discordClientId: instance.discordClientId, - discordClientSecret: instance.discordClientSecret, - summalyProxy: instance.summalyProxy, - email: instance.email, - smtpSecure: instance.smtpSecure, - smtpHost: instance.smtpHost, - smtpPort: instance.smtpPort, - smtpUser: instance.smtpUser, - smtpPass: instance.smtpPass, - swPrivateKey: instance.swPrivateKey, - useObjectStorage: instance.useObjectStorage, - objectStorageBaseUrl: instance.objectStorageBaseUrl, - objectStorageBucket: instance.objectStorageBucket, - objectStoragePrefix: instance.objectStoragePrefix, - objectStorageEndpoint: instance.objectStorageEndpoint, - objectStorageRegion: instance.objectStorageRegion, - objectStoragePort: instance.objectStoragePort, - objectStorageAccessKey: instance.objectStorageAccessKey, - objectStorageSecretKey: instance.objectStorageSecretKey, - objectStorageUseSSL: instance.objectStorageUseSSL, - objectStorageUseProxy: instance.objectStorageUseProxy, - objectStorageSetPublicRead: instance.objectStorageSetPublicRead, - objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle, - deeplAuthKey: instance.deeplAuthKey, - deeplIsPro: instance.deeplIsPro, - enableIpLogging: instance.enableIpLogging, - enableActiveEmailValidation: instance.enableActiveEmailValidation, - }; -}); + private metaService: MetaService, + ) { + super(meta, paramDef, async (ps, me) => { + const instance = await this.metaService.fetch(true); + + return { + maintainerName: instance.maintainerName, + maintainerEmail: instance.maintainerEmail, + version: this.config.version, + name: instance.name, + uri: this.config.url, + description: instance.description, + langs: instance.langs, + tosUrl: instance.ToSUrl, + repositoryUrl: instance.repositoryUrl, + feedbackUrl: instance.feedbackUrl, + disableRegistration: instance.disableRegistration, + disableLocalTimeline: instance.disableLocalTimeline, + disableGlobalTimeline: instance.disableGlobalTimeline, + driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, + driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, + emailRequiredForSignup: instance.emailRequiredForSignup, + enableHcaptcha: instance.enableHcaptcha, + hcaptchaSiteKey: instance.hcaptchaSiteKey, + enableRecaptcha: instance.enableRecaptcha, + recaptchaSiteKey: instance.recaptchaSiteKey, + swPublickey: instance.swPublicKey, + themeColor: instance.themeColor, + mascotImageUrl: instance.mascotImageUrl, + bannerUrl: instance.bannerUrl, + errorImageUrl: instance.errorImageUrl, + iconUrl: instance.iconUrl, + backgroundImageUrl: instance.backgroundImageUrl, + logoImageUrl: instance.logoImageUrl, + maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため + defaultLightTheme: instance.defaultLightTheme, + defaultDarkTheme: instance.defaultDarkTheme, + enableEmail: instance.enableEmail, + enableTwitterIntegration: instance.enableTwitterIntegration, + enableGithubIntegration: instance.enableGithubIntegration, + enableDiscordIntegration: instance.enableDiscordIntegration, + enableServiceWorker: instance.enableServiceWorker, + translatorAvailable: instance.deeplAuthKey != null, + pinnedPages: instance.pinnedPages, + pinnedClipId: instance.pinnedClipId, + cacheRemoteFiles: instance.cacheRemoteFiles, + useStarForReactionFallback: instance.useStarForReactionFallback, + pinnedUsers: instance.pinnedUsers, + hiddenTags: instance.hiddenTags, + blockedHosts: instance.blockedHosts, + hcaptchaSecretKey: instance.hcaptchaSecretKey, + recaptchaSecretKey: instance.recaptchaSecretKey, + sensitiveMediaDetection: instance.sensitiveMediaDetection, + sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity, + setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically, + enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos, + proxyAccountId: instance.proxyAccountId, + twitterConsumerKey: instance.twitterConsumerKey, + twitterConsumerSecret: instance.twitterConsumerSecret, + githubClientId: instance.githubClientId, + githubClientSecret: instance.githubClientSecret, + discordClientId: instance.discordClientId, + discordClientSecret: instance.discordClientSecret, + summalyProxy: instance.summalyProxy, + email: instance.email, + smtpSecure: instance.smtpSecure, + smtpHost: instance.smtpHost, + smtpPort: instance.smtpPort, + smtpUser: instance.smtpUser, + smtpPass: instance.smtpPass, + swPrivateKey: instance.swPrivateKey, + useObjectStorage: instance.useObjectStorage, + objectStorageBaseUrl: instance.objectStorageBaseUrl, + objectStorageBucket: instance.objectStorageBucket, + objectStoragePrefix: instance.objectStoragePrefix, + objectStorageEndpoint: instance.objectStorageEndpoint, + objectStorageRegion: instance.objectStorageRegion, + objectStoragePort: instance.objectStoragePort, + objectStorageAccessKey: instance.objectStorageAccessKey, + objectStorageSecretKey: instance.objectStorageSecretKey, + objectStorageUseSSL: instance.objectStorageUseSSL, + objectStorageUseProxy: instance.objectStorageUseProxy, + objectStorageSetPublicRead: instance.objectStorageSetPublicRead, + objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle, + deeplAuthKey: instance.deeplAuthKey, + deeplIsPro: instance.deeplIsPro, + enableIpLogging: instance.enableIpLogging, + enableActiveEmailValidation: instance.enableActiveEmailValidation, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts index 7b209c2d99..fe200da6ad 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import { Users } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -18,20 +20,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const user = await Users.findOneBy({ id: ps.userId }); - - if (user == null) { - throw new Error('user not found'); - } - - if (user.isAdmin) { - throw new Error('cannot mark as moderator if admin user'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + if (user.isAdmin) { + throw new Error('cannot mark as moderator if admin user'); + } + + await this.usersRepository.update(user.id, { + isModerator: true, + }); + + this.globalEventService.publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: true }); + }); } - - await Users.update(user.id, { - isModerator: true, - }); - - publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: true }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts index a01e9f3c69..3dc7158ba9 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import { Users } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -18,16 +20,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const user = await Users.findOneBy({ id: ps.userId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user == null) { - throw new Error('user not found'); - } + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } - await Users.update(user.id, { - isModerator: false, - }); + await this.usersRepository.update(user.id, { + isModerator: false, + }); - publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: false }); -}); + this.globalEventService.publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: false }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index 68a17867b2..a179f163df 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { PromoNotesRepository } from '@/models/index.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { getNote } from '../../../common/getters.js'; -import { PromoNotes } from '@/models/index.js'; export const meta = { tags: ['admin'], @@ -34,21 +36,31 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.promoNotesRepository) + private promoNotesRepository: PromoNotesRepository, - const exist = await PromoNotes.findOneBy({ noteId: note.id }); + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(e => { + if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw e; + }); - if (exist != null) { - throw new ApiError(meta.errors.alreadyPromoted); - } + const exist = await this.promoNotesRepository.findOneBy({ noteId: note.id }); + + if (exist != null) { + throw new ApiError(meta.errors.alreadyPromoted); + } - await PromoNotes.insert({ - noteId: note.id, - expiresAt: new Date(ps.expiresAt), - userId: note.userId, - }); -}); + await this.promoNotesRepository.insert({ + noteId: note.id, + expiresAt: new Date(ps.expiresAt), + userId: note.userId, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts index 8f015c280a..9129f53f06 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts @@ -1,6 +1,7 @@ -import define from '../../../define.js'; -import { destroy } from '@/queue/index.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { tags: ['admin'], @@ -16,8 +17,16 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - destroy(); +@Injectable() +export default class extends Endpoint { + constructor( + private moderationLogService: ModerationLogService, + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.destroy(); - insertModerationLog(me, 'clearQueue'); -}); + this.moderationLogService.insertModerationLog(me, 'clearQueue'); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts index 70f7d77de4..4b5be70d56 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts @@ -1,6 +1,7 @@ -import { deliverQueue } from '@/queue/queues.js'; import { URL } from 'node:url'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DeliverQueue } from '@/core/queue/QueueModule.js'; export const meta = { tags: ['admin'], @@ -39,21 +40,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const jobs = await deliverQueue.getJobs(['delayed']); - - const res = [] as [string, number][]; - - for (const job of jobs) { - const host = new URL(job.data.to).host; - if (res.find(x => x[0] === host)) { - res.find(x => x[0] === host)![1]++; - } else { - res.push([host, 1]); - } - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject('queue:deliver') public deliverQueue: DeliverQueue, + ) { + super(meta, paramDef, async (ps, me) => { + const jobs = await this.deliverQueue.getJobs(['delayed']); + + const res = [] as [string, number][]; - res.sort((a, b) => b[1] - a[1]); + for (const job of jobs) { + const host = new URL(job.data.to).host; + if (res.find(x => x[0] === host)) { + res.find(x => x[0] === host)![1]++; + } else { + res.push([host, 1]); + } + } - return res; -}); + res.sort((a, b) => b[1] - a[1]); + + return res; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts index 2235ce8f97..715974e917 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts @@ -1,6 +1,7 @@ import { URL } from 'node:url'; -import define from '../../../define.js'; -import { inboxQueue } from '@/queue/queues.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { InboxQueue } from '@/core/queue/QueueModule.js'; export const meta = { tags: ['admin'], @@ -39,21 +40,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const jobs = await inboxQueue.getJobs(['delayed']); - - const res = [] as [string, number][]; - - for (const job of jobs) { - const host = new URL(job.data.signature.keyId).host; - if (res.find(x => x[0] === host)) { - res.find(x => x[0] === host)![1]++; - } else { - res.push([host, 1]); - } - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject('queue:inbox') public inboxQueue: InboxQueue, + ) { + super(meta, paramDef, async (ps, me) => { + const jobs = await this.inboxQueue.getJobs(['delayed']); + + const res = [] as [string, number][]; - res.sort((a, b) => b[1] - a[1]); + for (const job of jobs) { + const host = new URL(job.data.signature.keyId).host; + if (res.find(x => x[0] === host)) { + res.find(x => x[0] === host)![1]++; + } else { + res.push([host, 1]); + } + } - return res; -}); + res.sort((a, b) => b[1] - a[1]); + + return res; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts index 988b5a5e35..f2ca81a8da 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts @@ -1,5 +1,6 @@ -import { deliverQueue, inboxQueue, dbQueue, objectStorageQueue } from '@/queue/queues.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/queue/QueueModule.js'; export const meta = { tags: ['admin'], @@ -38,16 +39,29 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const deliverJobCounts = await deliverQueue.getJobCounts(); - const inboxJobCounts = await inboxQueue.getJobCounts(); - const dbJobCounts = await dbQueue.getJobCounts(); - const objectStorageJobCounts = await objectStorageQueue.getJobCounts(); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject('queue:system') public systemQueue: SystemQueue, + @Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue, + @Inject('queue:deliver') public deliverQueue: DeliverQueue, + @Inject('queue:inbox') public inboxQueue: InboxQueue, + @Inject('queue:db') public dbQueue: DbQueue, + @Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue, + @Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue, + ) { + super(meta, paramDef, async (ps, me) => { + const deliverJobCounts = await this.deliverQueue.getJobCounts(); + const inboxJobCounts = await this.inboxQueue.getJobCounts(); + const dbJobCounts = await this.dbQueue.getJobCounts(); + const objectStorageJobCounts = await this.objectStorageQueue.getJobCounts(); - return { - deliver: deliverJobCounts, - inbox: inboxJobCounts, - db: dbJobCounts, - objectStorage: objectStorageJobCounts, - }; -}); + return { + deliver: deliverJobCounts, + inbox: inboxJobCounts, + db: dbJobCounts, + objectStorage: objectStorageJobCounts, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/relays/add.ts b/packages/backend/src/server/api/endpoints/admin/relays/add.ts index 348e9baca1..32ad79918f 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/add.ts @@ -1,6 +1,7 @@ import { URL } from 'node:url'; -import define from '../../../define.js'; -import { addRelay } from '@/services/relay.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RelayService } from '@/core/RelayService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -54,12 +55,19 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - try { - if (new URL(ps.inbox).protocol !== 'https:') throw 'https only'; - } catch { - throw new ApiError(meta.errors.invalidUrl); - } +@Injectable() +export default class extends Endpoint { + constructor( + private relayService: RelayService, + ) { + super(meta, paramDef, async (ps, me) => { + try { + if (new URL(ps.inbox).protocol !== 'https:') throw 'https only'; + } catch { + throw new ApiError(meta.errors.invalidUrl); + } - return await addRelay(ps.inbox); -}); + return await this.relayService.addRelay(ps.inbox); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/relays/list.ts b/packages/backend/src/server/api/endpoints/admin/relays/list.ts index 89ec651e61..079b351add 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/list.ts @@ -1,5 +1,6 @@ -import define from '../../../define.js'; -import { listRelay } from '@/services/relay.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RelayService } from '@/core/RelayService.js'; export const meta = { tags: ['admin'], @@ -46,6 +47,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - return await listRelay(); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private relayService: RelayService, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.relayService.listRelay(); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts index b59cf72c58..9dc4105d14 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts @@ -1,5 +1,6 @@ -import define from '../../../define.js'; -import { removeRelay } from '@/services/relay.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RelayService } from '@/core/RelayService.js'; export const meta = { tags: ['admin'], @@ -17,6 +18,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - return await removeRelay(ps.inbox); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private relayService: RelayService, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.relayService.removeRelay(ps.inbox); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index be4c2dceed..7446746b45 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import rndstr from 'rndstr'; -import { Users, UserProfiles } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -32,29 +34,40 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const user = await Users.findOneBy({ id: ps.userId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user == null) { - throw new Error('user not found'); - } + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); - if (user.isAdmin) { - throw new Error('cannot reset password of admin'); - } + if (user == null) { + throw new Error('user not found'); + } - const passwd = rndstr('a-zA-Z0-9', 8); + if (user.isAdmin) { + throw new Error('cannot reset password of admin'); + } - // Generate hash of password - const hash = bcrypt.hashSync(passwd); + const passwd = rndstr('a-zA-Z0-9', 8); - await UserProfiles.update({ - userId: user.id, - }, { - password: hash, - }); + // Generate hash of password + const hash = bcrypt.hashSync(passwd); - return { - password: passwd, - }; -}); + await this.userProfilesRepository.update({ + userId: user.id, + }, { + password: hash, + }); + + return { + password: passwd, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index 3edae4a85f..b5828ae9be 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -1,9 +1,10 @@ -import define from '../../define.js'; -import { AbuseUserReports, Users } from '@/models/index.js'; -import { getInstanceActor } from '@/services/instance-actor.js'; -import { deliver } from '@/queue/index.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import { renderFlag } from '@/remote/activitypub/renderer/flag.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; +import { InstanceActorService } from '@/core/InstanceActorService.js'; +import { QueueService } from '@/core/QueueService.js'; +import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -21,24 +22,41 @@ export const paramDef = { required: ['reportId'], } as const; -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const report = await AbuseUserReports.findOneByOrFail({ id: ps.reportId }); - - if (report == null) { - throw new Error('report not found'); - } +// TODO: ロジックをサービスに切り出す - if (ps.forward && report.targetUserHost != null) { - const actor = await getInstanceActor(); - const targetUser = await Users.findOneByOrFail({ id: report.targetUserId }); - - deliver(actor, renderActivity(renderFlag(actor, [targetUser.uri!], report.comment)), targetUser.inbox); +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.abuseUserReportsRepository) + private abuseUserReportsRepository: AbuseUserReportsRepository, + + private queueService: QueueService, + private instanceActorService: InstanceActorService, + private apRendererService: ApRendererService, + ) { + super(meta, paramDef, async (ps, me) => { + const report = await this.abuseUserReportsRepository.findOneBy({ id: ps.reportId }); + + if (report == null) { + throw new Error('report not found'); + } + + if (ps.forward && report.targetUserHost != null) { + const actor = await this.instanceActorService.getInstanceActor(); + const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId }); + + this.queueService.deliver(actor, this.apRendererService.renderActivity(this.apRendererService.renderFlag(actor, [targetUser.uri!], report.comment)), targetUser.inbox); + } + + await this.abuseUserReportsRepository.update(report.id, { + resolved: true, + assigneeId: me.id, + forwarded: ps.forward && report.targetUserHost != null, + }); + }); } - - await AbuseUserReports.update(report.id, { - resolved: true, - assigneeId: me.id, - forwarded: ps.forward && report.targetUserHost != null, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/send-email.ts b/packages/backend/src/server/api/endpoints/admin/send-email.ts index bbdd66e4c9..7434bf4c91 100644 --- a/packages/backend/src/server/api/endpoints/admin/send-email.ts +++ b/packages/backend/src/server/api/endpoints/admin/send-email.ts @@ -1,5 +1,6 @@ -import define from '../../define.js'; -import { sendEmail } from '@/services/send-email.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmailService } from '@/core/EmailService.js'; export const meta = { tags: ['admin'], @@ -19,6 +20,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - await sendEmail(ps.to, ps.subject, ps.text, ps.text); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private emailService: EmailService, + ) { + super(meta, paramDef, async (ps, me) => { + await this.emailService.sendEmail(ps.to, ps.subject, ps.text, ps.text); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts index 85c6fb82e7..9c576dffe9 100644 --- a/packages/backend/src/server/api/endpoints/admin/server-info.ts +++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts @@ -1,8 +1,10 @@ import * as os from 'node:os'; import si from 'systeminformation'; -import define from '../../define.js'; -import { redisClient } from '../../../../db/redis.js'; -import { db } from '@/db/postgre.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import Redis from 'ioredis'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -94,34 +96,46 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const memStats = await si.mem(); - const fsStats = await si.fsSize(); - const netInterface = await si.networkInterfaceDefault(); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, - const redisServerInfo = await redisClient.info('Server'); - const m = redisServerInfo.match(new RegExp('^redis_version:(.*)', 'm')); - const redis_version = m?.[1]; + @Inject(DI.redis) + private redisClient: Redis.Redis, - return { - machine: os.hostname(), - os: os.platform(), - node: process.version, - psql: await db.query('SHOW server_version').then(x => x[0].server_version), - redis: redis_version, - cpu: { - model: os.cpus()[0].model, - cores: os.cpus().length, - }, - mem: { - total: memStats.total, - }, - fs: { - total: fsStats[0].size, - used: fsStats[0].used, - }, - net: { - interface: netInterface, - }, - }; -}); + ) { + super(meta, paramDef, async () => { + const memStats = await si.mem(); + const fsStats = await si.fsSize(); + const netInterface = await si.networkInterfaceDefault(); + + const redisServerInfo = await this.redisClient.info('Server'); + const m = redisServerInfo.match(new RegExp('^redis_version:(.*)', 'm')); + const redis_version = m?.[1]; + + return { + machine: os.hostname(), + os: os.platform(), + node: process.version, + psql: await this.db.query('SHOW server_version').then(x => x[0].server_version), + redis: redis_version, + cpu: { + model: os.cpus()[0].model, + cores: os.cpus().length, + }, + mem: { + total: memStats.total, + }, + fs: { + total: fsStats[0].size, + used: fsStats[0].used, + }, + net: { + interface: netInterface, + }, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts index 3545536aa2..2424cac425 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts @@ -1,6 +1,8 @@ -import define from '../../define.js'; -import { ModerationLogs } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ModerationLogsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -59,10 +61,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const query = makePaginationQuery(ModerationLogs.createQueryBuilder('report'), ps.sinceId, ps.untilId); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.moderationLogsRepository) + private moderationLogsRepository: ModerationLogsRepository, - const reports = await query.take(ps.limit).getMany(); + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.moderationLogsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId); - return await ModerationLogs.packMany(reports); -}); + const reports = await query.take(ps.limit).getMany(); + + return await this.moderationLogEntityService.packMany(reports); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 0d866b3113..b50564210b 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -1,5 +1,7 @@ -import { Signins, UserProfiles, Users } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, SigninsRepository, UserProfilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -22,55 +24,69 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const [user, profile] = await Promise.all([ - Users.findOneBy({ id: ps.userId }), - UserProfiles.findOneBy({ userId: ps.userId }), - ]); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user == null || profile == null) { - throw new Error('user not found'); - } + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, - const _me = await Users.findOneByOrFail({ id: me.id }); - if ((_me.isModerator && !_me.isAdmin) && user.isAdmin) { - throw new Error('cannot show info of admin'); - } + @Inject(DI.signinsRepository) + private signinsRepository: SigninsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const [user, profile] = await Promise.all([ + this.usersRepository.findOneBy({ id: ps.userId }), + this.userProfilesRepository.findOneBy({ userId: ps.userId }), + ]); - if (!_me.isAdmin) { - return { - isModerator: user.isModerator, - isSilenced: user.isSilenced, - isSuspended: user.isSuspended, - }; - } + if (user == null || profile == null) { + throw new Error('user not found'); + } - const maskedKeys = ['accessToken', 'accessTokenSecret', 'refreshToken']; - Object.keys(profile.integrations).forEach(integration => { - maskedKeys.forEach(key => profile.integrations[integration][key] = ''); - }); + const _me = await this.usersRepository.findOneByOrFail({ id: me.id }); + if ((_me.isModerator && !_me.isAdmin) && user.isAdmin) { + throw new Error('cannot show info of admin'); + } - const signins = await Signins.findBy({ userId: user.id }); + if (!_me.isAdmin) { + return { + isModerator: user.isModerator, + isSilenced: user.isSilenced, + isSuspended: user.isSuspended, + }; + } - return { - email: profile.email, - emailVerified: profile.emailVerified, - autoAcceptFollowed: profile.autoAcceptFollowed, - noCrawle: profile.noCrawle, - alwaysMarkNsfw: profile.alwaysMarkNsfw, - autoSensitive: profile.autoSensitive, - carefulBot: profile.carefulBot, - injectFeaturedNote: profile.injectFeaturedNote, - receiveAnnouncementEmail: profile.receiveAnnouncementEmail, - integrations: profile.integrations, - mutedWords: profile.mutedWords, - mutedInstances: profile.mutedInstances, - mutingNotificationTypes: profile.mutingNotificationTypes, - isModerator: user.isModerator, - isSilenced: user.isSilenced, - isSuspended: user.isSuspended, - lastActiveDate: user.lastActiveDate, - moderationNote: profile.moderationNote, - signins, - }; -}); + const maskedKeys = ['accessToken', 'accessTokenSecret', 'refreshToken']; + Object.keys(profile.integrations).forEach(integration => { + maskedKeys.forEach(key => profile.integrations[integration][key] = ''); + }); + + const signins = await this.signinsRepository.findBy({ userId: user.id }); + + return { + email: profile.email, + emailVerified: profile.emailVerified, + autoAcceptFollowed: profile.autoAcceptFollowed, + noCrawle: profile.noCrawle, + alwaysMarkNsfw: profile.alwaysMarkNsfw, + autoSensitive: profile.autoSensitive, + carefulBot: profile.carefulBot, + injectFeaturedNote: profile.injectFeaturedNote, + receiveAnnouncementEmail: profile.receiveAnnouncementEmail, + integrations: profile.integrations, + mutedWords: profile.mutedWords, + mutedInstances: profile.mutedInstances, + mutingNotificationTypes: profile.mutingNotificationTypes, + isModerator: user.isModerator, + isSilenced: user.isSilenced, + isSuspended: user.isSuspended, + lastActiveDate: user.lastActiveDate, + moderationNote: profile.moderationNote, + signins, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 8e09e72d5b..8d11e3ea7a 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -1,5 +1,7 @@ -import { Users } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -38,46 +40,54 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Users.createQueryBuilder('user'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.usersRepository.createQueryBuilder('user'); - switch (ps.state) { - case 'available': query.where('user.isSuspended = FALSE'); break; - case 'admin': query.where('user.isAdmin = TRUE'); break; - case 'moderator': query.where('user.isModerator = TRUE'); break; - case 'adminOrModerator': query.where('user.isAdmin = TRUE OR user.isModerator = 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; - } + switch (ps.state) { + case 'available': query.where('user.isSuspended = FALSE'); break; + case 'admin': query.where('user.isAdmin = TRUE'); break; + case 'moderator': query.where('user.isModerator = TRUE'); break; + case 'adminOrModerator': query.where('user.isAdmin = TRUE OR user.isModerator = 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; + } - switch (ps.origin) { - case 'local': query.andWhere('user.host IS NULL'); break; - case 'remote': query.andWhere('user.host IS NOT NULL'); break; - } + switch (ps.origin) { + case 'local': query.andWhere('user.host IS NULL'); break; + case 'remote': query.andWhere('user.host IS NOT NULL'); break; + } - if (ps.username) { - query.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' }); - } + if (ps.username) { + query.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' }); + } - if (ps.hostname) { - query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() }); - } + if (ps.hostname) { + query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() }); + } - 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', 'NULLS LAST'); break; - case '-updatedAt': query.orderBy('user.updatedAt', 'ASC', 'NULLS FIRST'); break; - default: query.orderBy('user.id', 'ASC'); 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', 'NULLS LAST'); break; + case '-updatedAt': query.orderBy('user.updatedAt', 'ASC', 'NULLS FIRST'); break; + default: query.orderBy('user.id', 'ASC'); break; + } - query.take(ps.limit); - query.skip(ps.offset); + query.take(ps.limit); + query.skip(ps.offset); - const users = await query.getMany(); + const users = await query.getMany(); - return await Users.packMany(users, me, { detail: true }); -}); + return await this.userEntityService.packMany(users, me, { detail: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/silence-user.ts b/packages/backend/src/server/api/endpoints/admin/silence-user.ts index 17b9f3b5a0..bec8f7719e 100644 --- a/packages/backend/src/server/api/endpoints/admin/silence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/silence-user.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; -import { Users } from '@/models/index.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { publishInternalEvent } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { UsersRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -19,24 +21,35 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); - - if (user == null) { - throw new Error('user not found'); - } - - if (user.isAdmin) { - throw new Error('cannot silence admin'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private moderationLogService: ModerationLogService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + if (user.isAdmin) { + throw new Error('cannot silence admin'); + } + + await this.usersRepository.update(user.id, { + isSilenced: true, + }); + + this.globalEventService.publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: true }); + + this.moderationLogService.insertModerationLog(me, 'silence', { + targetId: user.id, + }); + }); } - - await Users.update(user.id, { - isSilenced: true, - }); - - publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: true }); - - insertModerationLog(me, 'silence', { - targetId: user.id, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index ed513eda08..fa057dadb6 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -1,10 +1,12 @@ -import define from '../../define.js'; -import deleteFollowing from '@/services/following/delete.js'; -import { Users, Followings, Notifications } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { doPostSuspend } from '@/services/suspend-user.js'; -import { publishUserEvent } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, FollowingsRepository, NotificationsRepository } from '@/models/index.js'; +import type { User } from '@/models/entities/User.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { UserSuspendService } from '@/core/UserSuspendService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -22,64 +24,83 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); - - if (user == null) { - throw new Error('user not found'); - } - - if (user.isAdmin) { - throw new Error('cannot suspend admin'); - } - - if (user.isModerator) { - throw new Error('cannot suspend moderator'); - } - - await Users.update(user.id, { - isSuspended: true, - }); - - insertModerationLog(me, 'suspend', { - targetId: user.id, - }); - - // Terminate streaming - if (Users.isLocalUser(user)) { - publishUserEvent(user.id, 'terminate', {}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + @Inject(DI.notificationsRepository) + private notificationsRepository: NotificationsRepository, + + private userFollowingService: UserFollowingService, + private userSuspendService: UserSuspendService, + private moderationLogService: ModerationLogService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + if (user.isAdmin) { + throw new Error('cannot suspend admin'); + } + + if (user.isModerator) { + throw new Error('cannot suspend moderator'); + } + + await this.usersRepository.update(user.id, { + isSuspended: true, + }); + + this.moderationLogService.insertModerationLog(me, 'suspend', { + targetId: user.id, + }); + + // Terminate streaming + if (this.userEntityService.isLocalUser(user)) { + this.globalEventService.publishUserEvent(user.id, 'terminate', {}); + } + + (async () => { + await this.userSuspendService.doPostSuspend(user).catch(e => {}); + await this.#unFollowAll(user).catch(e => {}); + await this.#readAllNotify(user).catch(e => {}); + })(); + }); } - (async () => { - await doPostSuspend(user).catch(e => {}); - await unFollowAll(user).catch(e => {}); - await readAllNotify(user).catch(e => {}); - })(); -}); - -async function unFollowAll(follower: User) { - const followings = await Followings.findBy({ - followerId: follower.id, - }); - - for (const following of followings) { - const followee = await Users.findOneBy({ - id: following.followeeId, + async #unFollowAll(follower: User) { + const followings = await this.followingsRepository.findBy({ + followerId: follower.id, }); - - if (followee == null) { - throw `Cant find followee ${following.followeeId}`; + + for (const following of followings) { + const followee = await this.usersRepository.findOneBy({ + id: following.followeeId, + }); + + if (followee == null) { + throw `Cant find followee ${following.followeeId}`; + } + + await this.userFollowingService.unfollow(follower, followee, true); } - - await deleteFollowing(follower, followee, true); } -} - -async function readAllNotify(notifier: User) { - await Notifications.update({ - notifierId: notifier.id, - isRead: false, - }, { - isRead: true, - }); + + async #readAllNotify(notifier: User) { + await this.notificationsRepository.update({ + notifierId: notifier.id, + isRead: false, + }, { + isRead: true, + }); + } } diff --git a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts index a4b373f5c7..b4671a2f41 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; -import { Users } from '@/models/index.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { publishInternalEvent } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -19,20 +21,31 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); - - if (user == null) { - throw new Error('user not found'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private moderationLogService: ModerationLogService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + await this.usersRepository.update(user.id, { + isSilenced: false, + }); + + this.globalEventService.publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: false }); + + this.moderationLogService.insertModerationLog(me, 'unsilence', { + targetId: user.id, + }); + }); } - - await Users.update(user.id, { - isSilenced: false, - }); - - publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: false }); - - insertModerationLog(me, 'unsilence', { - targetId: user.id, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index 5cf26251be..96283d251f 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; -import { Users } from '@/models/index.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { doPostUnsuspend } from '@/services/unsuspend-user.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { UserSuspendService } from '@/core/UserSuspendService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -19,20 +21,31 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); - - if (user == null) { - throw new Error('user not found'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private userSuspendService: UserSuspendService, + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + await this.usersRepository.update(user.id, { + isSuspended: false, + }); + + this.moderationLogService.insertModerationLog(me, 'unsuspend', { + targetId: user.id, + }); + + this.userSuspendService.doPostUnsuspend(user); + }); } - - await Users.update(user.id, { - isSuspended: false, - }); - - insertModerationLog(me, 'unsuspend', { - targetId: user.id, - }); - - doPostUnsuspend(user); -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index f14aa41050..968ed4d26d 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -1,8 +1,10 @@ -import { Meta } from '@/models/entities/meta.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { Meta } from '@/models/entities/Meta.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; -import { db } from '@/db/postgre.js'; -import define from '../../define.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -107,340 +109,350 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const set = {} as Partial; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const set = {} as Partial; - if (typeof ps.disableRegistration === 'boolean') { - set.disableRegistration = ps.disableRegistration; - } - - if (typeof ps.disableLocalTimeline === 'boolean') { - set.disableLocalTimeline = ps.disableLocalTimeline; - } - - if (typeof ps.disableGlobalTimeline === 'boolean') { - set.disableGlobalTimeline = ps.disableGlobalTimeline; - } - - if (typeof ps.useStarForReactionFallback === 'boolean') { - set.useStarForReactionFallback = ps.useStarForReactionFallback; - } + if (typeof ps.disableRegistration === 'boolean') { + set.disableRegistration = ps.disableRegistration; + } - if (Array.isArray(ps.pinnedUsers)) { - set.pinnedUsers = ps.pinnedUsers.filter(Boolean); - } - - if (Array.isArray(ps.hiddenTags)) { - set.hiddenTags = ps.hiddenTags.filter(Boolean); - } + if (typeof ps.disableLocalTimeline === 'boolean') { + set.disableLocalTimeline = ps.disableLocalTimeline; + } - if (Array.isArray(ps.blockedHosts)) { - set.blockedHosts = ps.blockedHosts.filter(Boolean); - } + if (typeof ps.disableGlobalTimeline === 'boolean') { + set.disableGlobalTimeline = ps.disableGlobalTimeline; + } - if (ps.themeColor !== undefined) { - set.themeColor = ps.themeColor; - } - - if (ps.mascotImageUrl !== undefined) { - set.mascotImageUrl = ps.mascotImageUrl; - } + if (typeof ps.useStarForReactionFallback === 'boolean') { + set.useStarForReactionFallback = ps.useStarForReactionFallback; + } - if (ps.bannerUrl !== undefined) { - set.bannerUrl = ps.bannerUrl; - } - - if (ps.iconUrl !== undefined) { - set.iconUrl = ps.iconUrl; - } - - if (ps.backgroundImageUrl !== undefined) { - set.backgroundImageUrl = ps.backgroundImageUrl; - } - - if (ps.logoImageUrl !== undefined) { - set.logoImageUrl = ps.logoImageUrl; - } - - if (ps.name !== undefined) { - set.name = ps.name; - } - - if (ps.description !== undefined) { - set.description = ps.description; - } - - if (ps.defaultLightTheme !== undefined) { - set.defaultLightTheme = ps.defaultLightTheme; - } - - if (ps.defaultDarkTheme !== undefined) { - set.defaultDarkTheme = ps.defaultDarkTheme; - } - - if (ps.localDriveCapacityMb !== undefined) { - set.localDriveCapacityMb = ps.localDriveCapacityMb; - } - - if (ps.remoteDriveCapacityMb !== undefined) { - set.remoteDriveCapacityMb = ps.remoteDriveCapacityMb; - } - - if (ps.cacheRemoteFiles !== undefined) { - set.cacheRemoteFiles = ps.cacheRemoteFiles; - } - - if (ps.emailRequiredForSignup !== undefined) { - set.emailRequiredForSignup = ps.emailRequiredForSignup; - } - - if (ps.enableHcaptcha !== undefined) { - set.enableHcaptcha = ps.enableHcaptcha; - } - - if (ps.hcaptchaSiteKey !== undefined) { - set.hcaptchaSiteKey = ps.hcaptchaSiteKey; - } - - if (ps.hcaptchaSecretKey !== undefined) { - set.hcaptchaSecretKey = ps.hcaptchaSecretKey; - } - - if (ps.enableRecaptcha !== undefined) { - set.enableRecaptcha = ps.enableRecaptcha; - } - - if (ps.recaptchaSiteKey !== undefined) { - set.recaptchaSiteKey = ps.recaptchaSiteKey; - } - - if (ps.recaptchaSecretKey !== undefined) { - set.recaptchaSecretKey = ps.recaptchaSecretKey; - } - - if (ps.sensitiveMediaDetection !== undefined) { - set.sensitiveMediaDetection = ps.sensitiveMediaDetection; - } - - if (ps.sensitiveMediaDetectionSensitivity !== undefined) { - set.sensitiveMediaDetectionSensitivity = ps.sensitiveMediaDetectionSensitivity; - } + if (Array.isArray(ps.pinnedUsers)) { + set.pinnedUsers = ps.pinnedUsers.filter(Boolean); + } - if (ps.setSensitiveFlagAutomatically !== undefined) { - set.setSensitiveFlagAutomatically = ps.setSensitiveFlagAutomatically; - } + if (Array.isArray(ps.hiddenTags)) { + set.hiddenTags = ps.hiddenTags.filter(Boolean); + } - if (ps.enableSensitiveMediaDetectionForVideos !== undefined) { - set.enableSensitiveMediaDetectionForVideos = ps.enableSensitiveMediaDetectionForVideos; - } + if (Array.isArray(ps.blockedHosts)) { + set.blockedHosts = ps.blockedHosts.filter(Boolean); + } - if (ps.proxyAccountId !== undefined) { - set.proxyAccountId = ps.proxyAccountId; - } + if (ps.themeColor !== undefined) { + set.themeColor = ps.themeColor; + } - if (ps.maintainerName !== undefined) { - set.maintainerName = ps.maintainerName; - } + if (ps.mascotImageUrl !== undefined) { + set.mascotImageUrl = ps.mascotImageUrl; + } - if (ps.maintainerEmail !== undefined) { - set.maintainerEmail = ps.maintainerEmail; - } + if (ps.bannerUrl !== undefined) { + set.bannerUrl = ps.bannerUrl; + } - if (Array.isArray(ps.langs)) { - set.langs = ps.langs.filter(Boolean); - } + if (ps.iconUrl !== undefined) { + set.iconUrl = ps.iconUrl; + } - if (Array.isArray(ps.pinnedPages)) { - set.pinnedPages = ps.pinnedPages.filter(Boolean); - } + if (ps.backgroundImageUrl !== undefined) { + set.backgroundImageUrl = ps.backgroundImageUrl; + } - if (ps.pinnedClipId !== undefined) { - set.pinnedClipId = ps.pinnedClipId; - } + if (ps.logoImageUrl !== undefined) { + set.logoImageUrl = ps.logoImageUrl; + } - if (ps.summalyProxy !== undefined) { - set.summalyProxy = ps.summalyProxy; - } + if (ps.name !== undefined) { + set.name = ps.name; + } - if (ps.enableTwitterIntegration !== undefined) { - set.enableTwitterIntegration = ps.enableTwitterIntegration; - } + if (ps.description !== undefined) { + set.description = ps.description; + } - if (ps.twitterConsumerKey !== undefined) { - set.twitterConsumerKey = ps.twitterConsumerKey; - } + if (ps.defaultLightTheme !== undefined) { + set.defaultLightTheme = ps.defaultLightTheme; + } - if (ps.twitterConsumerSecret !== undefined) { - set.twitterConsumerSecret = ps.twitterConsumerSecret; - } + if (ps.defaultDarkTheme !== undefined) { + set.defaultDarkTheme = ps.defaultDarkTheme; + } - if (ps.enableGithubIntegration !== undefined) { - set.enableGithubIntegration = ps.enableGithubIntegration; - } + if (ps.localDriveCapacityMb !== undefined) { + set.localDriveCapacityMb = ps.localDriveCapacityMb; + } - if (ps.githubClientId !== undefined) { - set.githubClientId = ps.githubClientId; - } + if (ps.remoteDriveCapacityMb !== undefined) { + set.remoteDriveCapacityMb = ps.remoteDriveCapacityMb; + } - if (ps.githubClientSecret !== undefined) { - set.githubClientSecret = ps.githubClientSecret; - } + if (ps.cacheRemoteFiles !== undefined) { + set.cacheRemoteFiles = ps.cacheRemoteFiles; + } - if (ps.enableDiscordIntegration !== undefined) { - set.enableDiscordIntegration = ps.enableDiscordIntegration; - } + if (ps.emailRequiredForSignup !== undefined) { + set.emailRequiredForSignup = ps.emailRequiredForSignup; + } - if (ps.discordClientId !== undefined) { - set.discordClientId = ps.discordClientId; - } + if (ps.enableHcaptcha !== undefined) { + set.enableHcaptcha = ps.enableHcaptcha; + } - if (ps.discordClientSecret !== undefined) { - set.discordClientSecret = ps.discordClientSecret; - } + if (ps.hcaptchaSiteKey !== undefined) { + set.hcaptchaSiteKey = ps.hcaptchaSiteKey; + } - if (ps.enableEmail !== undefined) { - set.enableEmail = ps.enableEmail; - } + if (ps.hcaptchaSecretKey !== undefined) { + set.hcaptchaSecretKey = ps.hcaptchaSecretKey; + } - if (ps.email !== undefined) { - set.email = ps.email; - } + if (ps.enableRecaptcha !== undefined) { + set.enableRecaptcha = ps.enableRecaptcha; + } - if (ps.smtpSecure !== undefined) { - set.smtpSecure = ps.smtpSecure; - } + if (ps.recaptchaSiteKey !== undefined) { + set.recaptchaSiteKey = ps.recaptchaSiteKey; + } - if (ps.smtpHost !== undefined) { - set.smtpHost = ps.smtpHost; - } + if (ps.recaptchaSecretKey !== undefined) { + set.recaptchaSecretKey = ps.recaptchaSecretKey; + } - if (ps.smtpPort !== undefined) { - set.smtpPort = ps.smtpPort; - } + if (ps.sensitiveMediaDetection !== undefined) { + set.sensitiveMediaDetection = ps.sensitiveMediaDetection; + } - if (ps.smtpUser !== undefined) { - set.smtpUser = ps.smtpUser; - } + if (ps.sensitiveMediaDetectionSensitivity !== undefined) { + set.sensitiveMediaDetectionSensitivity = ps.sensitiveMediaDetectionSensitivity; + } - if (ps.smtpPass !== undefined) { - set.smtpPass = ps.smtpPass; - } + if (ps.setSensitiveFlagAutomatically !== undefined) { + set.setSensitiveFlagAutomatically = ps.setSensitiveFlagAutomatically; + } - if (ps.errorImageUrl !== undefined) { - set.errorImageUrl = ps.errorImageUrl; - } + if (ps.enableSensitiveMediaDetectionForVideos !== undefined) { + set.enableSensitiveMediaDetectionForVideos = ps.enableSensitiveMediaDetectionForVideos; + } - if (ps.enableServiceWorker !== undefined) { - set.enableServiceWorker = ps.enableServiceWorker; - } + if (ps.proxyAccountId !== undefined) { + set.proxyAccountId = ps.proxyAccountId; + } - if (ps.swPublicKey !== undefined) { - set.swPublicKey = ps.swPublicKey; - } + if (ps.maintainerName !== undefined) { + set.maintainerName = ps.maintainerName; + } - if (ps.swPrivateKey !== undefined) { - set.swPrivateKey = ps.swPrivateKey; - } + if (ps.maintainerEmail !== undefined) { + set.maintainerEmail = ps.maintainerEmail; + } - if (ps.tosUrl !== undefined) { - set.ToSUrl = ps.tosUrl; - } + if (Array.isArray(ps.langs)) { + set.langs = ps.langs.filter(Boolean); + } - if (ps.repositoryUrl !== undefined) { - set.repositoryUrl = ps.repositoryUrl; - } + if (Array.isArray(ps.pinnedPages)) { + set.pinnedPages = ps.pinnedPages.filter(Boolean); + } - if (ps.feedbackUrl !== undefined) { - set.feedbackUrl = ps.feedbackUrl; - } + if (ps.pinnedClipId !== undefined) { + set.pinnedClipId = ps.pinnedClipId; + } - if (ps.useObjectStorage !== undefined) { - set.useObjectStorage = ps.useObjectStorage; - } + if (ps.summalyProxy !== undefined) { + set.summalyProxy = ps.summalyProxy; + } - if (ps.objectStorageBaseUrl !== undefined) { - set.objectStorageBaseUrl = ps.objectStorageBaseUrl; - } + if (ps.enableTwitterIntegration !== undefined) { + set.enableTwitterIntegration = ps.enableTwitterIntegration; + } - if (ps.objectStorageBucket !== undefined) { - set.objectStorageBucket = ps.objectStorageBucket; - } + if (ps.twitterConsumerKey !== undefined) { + set.twitterConsumerKey = ps.twitterConsumerKey; + } - if (ps.objectStoragePrefix !== undefined) { - set.objectStoragePrefix = ps.objectStoragePrefix; - } + if (ps.twitterConsumerSecret !== undefined) { + set.twitterConsumerSecret = ps.twitterConsumerSecret; + } - if (ps.objectStorageEndpoint !== undefined) { - set.objectStorageEndpoint = ps.objectStorageEndpoint; - } + if (ps.enableGithubIntegration !== undefined) { + set.enableGithubIntegration = ps.enableGithubIntegration; + } - if (ps.objectStorageRegion !== undefined) { - set.objectStorageRegion = ps.objectStorageRegion; - } + if (ps.githubClientId !== undefined) { + set.githubClientId = ps.githubClientId; + } - if (ps.objectStoragePort !== undefined) { - set.objectStoragePort = ps.objectStoragePort; - } + if (ps.githubClientSecret !== undefined) { + set.githubClientSecret = ps.githubClientSecret; + } - if (ps.objectStorageAccessKey !== undefined) { - set.objectStorageAccessKey = ps.objectStorageAccessKey; - } + if (ps.enableDiscordIntegration !== undefined) { + set.enableDiscordIntegration = ps.enableDiscordIntegration; + } - if (ps.objectStorageSecretKey !== undefined) { - set.objectStorageSecretKey = ps.objectStorageSecretKey; - } + if (ps.discordClientId !== undefined) { + set.discordClientId = ps.discordClientId; + } - if (ps.objectStorageUseSSL !== undefined) { - set.objectStorageUseSSL = ps.objectStorageUseSSL; - } + if (ps.discordClientSecret !== undefined) { + set.discordClientSecret = ps.discordClientSecret; + } - if (ps.objectStorageUseProxy !== undefined) { - set.objectStorageUseProxy = ps.objectStorageUseProxy; - } + if (ps.enableEmail !== undefined) { + set.enableEmail = ps.enableEmail; + } - if (ps.objectStorageSetPublicRead !== undefined) { - set.objectStorageSetPublicRead = ps.objectStorageSetPublicRead; - } + if (ps.email !== undefined) { + set.email = ps.email; + } - if (ps.objectStorageS3ForcePathStyle !== undefined) { - set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle; - } + if (ps.smtpSecure !== undefined) { + set.smtpSecure = ps.smtpSecure; + } - if (ps.deeplAuthKey !== undefined) { - if (ps.deeplAuthKey === '') { - set.deeplAuthKey = null; - } else { - set.deeplAuthKey = ps.deeplAuthKey; - } - } + if (ps.smtpHost !== undefined) { + set.smtpHost = ps.smtpHost; + } - if (ps.deeplIsPro !== undefined) { - set.deeplIsPro = ps.deeplIsPro; - } + if (ps.smtpPort !== undefined) { + set.smtpPort = ps.smtpPort; + } - if (ps.enableIpLogging !== undefined) { - set.enableIpLogging = ps.enableIpLogging; - } + if (ps.smtpUser !== undefined) { + set.smtpUser = ps.smtpUser; + } - if (ps.enableActiveEmailValidation !== undefined) { - set.enableActiveEmailValidation = ps.enableActiveEmailValidation; - } + if (ps.smtpPass !== undefined) { + set.smtpPass = ps.smtpPass; + } - await db.transaction(async transactionalEntityManager => { - const metas = await transactionalEntityManager.find(Meta, { - order: { - id: 'DESC', - }, + if (ps.errorImageUrl !== undefined) { + set.errorImageUrl = ps.errorImageUrl; + } + + if (ps.enableServiceWorker !== undefined) { + set.enableServiceWorker = ps.enableServiceWorker; + } + + if (ps.swPublicKey !== undefined) { + set.swPublicKey = ps.swPublicKey; + } + + if (ps.swPrivateKey !== undefined) { + set.swPrivateKey = ps.swPrivateKey; + } + + if (ps.tosUrl !== undefined) { + set.ToSUrl = ps.tosUrl; + } + + if (ps.repositoryUrl !== undefined) { + set.repositoryUrl = ps.repositoryUrl; + } + + if (ps.feedbackUrl !== undefined) { + set.feedbackUrl = ps.feedbackUrl; + } + + if (ps.useObjectStorage !== undefined) { + set.useObjectStorage = ps.useObjectStorage; + } + + if (ps.objectStorageBaseUrl !== undefined) { + set.objectStorageBaseUrl = ps.objectStorageBaseUrl; + } + + if (ps.objectStorageBucket !== undefined) { + set.objectStorageBucket = ps.objectStorageBucket; + } + + if (ps.objectStoragePrefix !== undefined) { + set.objectStoragePrefix = ps.objectStoragePrefix; + } + + if (ps.objectStorageEndpoint !== undefined) { + set.objectStorageEndpoint = ps.objectStorageEndpoint; + } + + if (ps.objectStorageRegion !== undefined) { + set.objectStorageRegion = ps.objectStorageRegion; + } + + if (ps.objectStoragePort !== undefined) { + set.objectStoragePort = ps.objectStoragePort; + } + + if (ps.objectStorageAccessKey !== undefined) { + set.objectStorageAccessKey = ps.objectStorageAccessKey; + } + + if (ps.objectStorageSecretKey !== undefined) { + set.objectStorageSecretKey = ps.objectStorageSecretKey; + } + + if (ps.objectStorageUseSSL !== undefined) { + set.objectStorageUseSSL = ps.objectStorageUseSSL; + } + + if (ps.objectStorageUseProxy !== undefined) { + set.objectStorageUseProxy = ps.objectStorageUseProxy; + } + + if (ps.objectStorageSetPublicRead !== undefined) { + set.objectStorageSetPublicRead = ps.objectStorageSetPublicRead; + } + + if (ps.objectStorageS3ForcePathStyle !== undefined) { + set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle; + } + + if (ps.deeplAuthKey !== undefined) { + if (ps.deeplAuthKey === '') { + set.deeplAuthKey = null; + } else { + set.deeplAuthKey = ps.deeplAuthKey; + } + } + + if (ps.deeplIsPro !== undefined) { + set.deeplIsPro = ps.deeplIsPro; + } + + if (ps.enableIpLogging !== undefined) { + set.enableIpLogging = ps.enableIpLogging; + } + + if (ps.enableActiveEmailValidation !== undefined) { + set.enableActiveEmailValidation = ps.enableActiveEmailValidation; + } + + await this.db.transaction(async transactionalEntityManager => { + const metas = await transactionalEntityManager.find(Meta, { + order: { + id: 'DESC', + }, + }); + + const meta = metas[0]; + + if (meta) { + await transactionalEntityManager.update(Meta, meta.id, set); + } else { + await transactionalEntityManager.save(Meta, set); + } + }); + + this.moderationLogService.insertModerationLog(me, 'updateMeta'); }); - - const meta = metas[0]; - - if (meta) { - await transactionalEntityManager.update(Meta, meta.id, set); - } else { - await transactionalEntityManager.save(Meta, set); - } - }); - - insertModerationLog(me, 'updateMeta'); -}); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts index fa21ab7833..1ea0e6aac4 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts @@ -1,5 +1,7 @@ -import { UserProfiles, Users } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -18,14 +20,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user == null) { - throw new Error('user not found'); - } + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } - await UserProfiles.update({ userId: user.id }, { - moderationNote: ps.text, - }); -}); + await this.userProfilesRepository.update({ userId: user.id }, { + moderationNote: ps.text, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/vacuum.ts b/packages/backend/src/server/api/endpoints/admin/vacuum.ts deleted file mode 100644 index 0546acfacb..0000000000 --- a/packages/backend/src/server/api/endpoints/admin/vacuum.ts +++ /dev/null @@ -1,36 +0,0 @@ -import define from '../../define.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { db } from '@/db/postgre.js'; - -export const meta = { - tags: ['admin'], - - requireCredential: true, - requireModerator: true, -} as const; - -export const paramDef = { - type: 'object', - properties: { - full: { type: 'boolean' }, - analyze: { type: 'boolean' }, - }, - required: ['full', 'analyze'], -} as const; - -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const params: string[] = []; - - if (ps.full) { - params.push('FULL'); - } - - if (ps.analyze) { - params.push('ANALYZE'); - } - - db.query('VACUUM ' + params.join(' ')); - - insertModerationLog(me, 'vacuum', ps); -}); diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index 23cb93c9a5..aa44dfd5dc 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -1,6 +1,8 @@ -import { Announcements, AnnouncementReads } from '@/models/index.js'; -import define from '../define.js'; -import { makePaginationQuery } from '../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; +import { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models'; export const meta = { tags: ['meta'], @@ -63,24 +65,37 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(Announcements.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.announcementsRepository) + private announcementsRepository: AnnouncementsRepository, - const announcements = await query.take(ps.limit).getMany(); + @Inject(DI.announcementReadsRepository) + private announcementReadsRepository: AnnouncementReadsRepository, - if (user) { - const reads = (await AnnouncementReads.findBy({ - userId: user.id, - })).map(x => x.announcementId); + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); - for (const announcement of announcements) { - (announcement as any).isRead = reads.includes(announcement.id); - } - } + const announcements = await query.take(ps.limit).getMany(); + + if (me) { + const reads = (await this.announcementReadsRepository.findBy({ + userId: me.id, + })).map(x => x.announcementId); - return (ps.withUnreads ? announcements.filter((a: any) => !a.isRead) : announcements).map((a) => ({ - ...a, - createdAt: a.createdAt.toISOString(), - updatedAt: a.updatedAt?.toISOString() ?? null, - })); -}); + for (const announcement of announcements) { + (announcement as any).isRead = reads.includes(announcement.id); + } + } + + return (ps.withUnreads ? announcements.filter((a: any) => !a.isRead) : announcements).map((a) => ({ + ...a, + createdAt: a.createdAt.toISOString(), + updatedAt: a.updatedAt?.toISOString() ?? null, + })); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 7a4923b944..56bd343d55 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -1,8 +1,11 @@ -import define from '../../define.js'; -import { genId } from '@/misc/gen-id.js'; -import { Antennas, UserLists, UserGroupJoinings } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import { UserListsRepository, UserGroupJoiningsRepository, AntennasRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['antennas'], @@ -61,48 +64,66 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - let userList; - let userGroupJoining; - - if (ps.src === 'list' && ps.userListId) { - userList = await UserLists.findOneBy({ - id: ps.userListId, - userId: user.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, - if (userList == null) { - throw new ApiError(meta.errors.noSuchUserList); - } - } else if (ps.src === 'group' && ps.userGroupId) { - userGroupJoining = await UserGroupJoinings.findOneBy({ - userGroupId: ps.userGroupId, - userId: user.id, - }); + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, - if (userGroupJoining == null) { - throw new ApiError(meta.errors.noSuchUserGroup); - } - } + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private antennaEntityService: AntennaEntityService, + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + let userList; + let userGroupJoining; + + if (ps.src === 'list' && ps.userListId) { + userList = await this.userListsRepository.findOneBy({ + id: ps.userListId, + userId: me.id, + }); + + if (userList == null) { + throw new ApiError(meta.errors.noSuchUserList); + } + } else if (ps.src === 'group' && ps.userGroupId) { + userGroupJoining = await this.userGroupJoiningsRepository.findOneBy({ + userGroupId: ps.userGroupId, + userId: me.id, + }); - const antenna = await Antennas.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - name: ps.name, - src: ps.src, - userListId: userList ? userList.id : null, - userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null, - keywords: ps.keywords, - excludeKeywords: ps.excludeKeywords, - users: ps.users, - caseSensitive: ps.caseSensitive, - withReplies: ps.withReplies, - withFile: ps.withFile, - notify: ps.notify, - }).then(x => Antennas.findOneByOrFail(x.identifiers[0])); - - publishInternalEvent('antennaCreated', antenna); - - return await Antennas.pack(antenna); -}); + if (userGroupJoining == null) { + throw new ApiError(meta.errors.noSuchUserGroup); + } + } + + const antenna = await this.antennasRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + name: ps.name, + src: ps.src, + userListId: userList ? userList.id : null, + userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null, + keywords: ps.keywords, + excludeKeywords: ps.excludeKeywords, + users: ps.users, + caseSensitive: ps.caseSensitive, + withReplies: ps.withReplies, + withFile: ps.withFile, + notify: ps.notify, + }).then(x => this.antennasRepository.findOneByOrFail(x.identifiers[0])); + + this.globalEventService.publishInternalEvent('antennaCreated', antenna); + + return await this.antennaEntityService.pack(antenna); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/antennas/delete.ts b/packages/backend/src/server/api/endpoints/antennas/delete.ts index ced34ba313..127aca0c33 100644 --- a/packages/backend/src/server/api/endpoints/antennas/delete.ts +++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AntennasRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Antennas } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['antennas'], @@ -28,17 +30,27 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const antenna = await Antennas.findOneBy({ - id: ps.antennaId, - userId: user.id, - }); - - if (antenna == null) { - throw new ApiError(meta.errors.noSuchAntenna); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, + + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const antenna = await this.antennasRepository.findOneBy({ + id: ps.antennaId, + userId: me.id, + }); + + if (antenna == null) { + throw new ApiError(meta.errors.noSuchAntenna); + } + + await this.antennasRepository.delete(antenna.id); + + this.globalEventService.publishInternalEvent('antennaDeleted', antenna); + }); } - - await Antennas.delete(antenna.id); - - publishInternalEvent('antennaDeleted', antenna); -}); +} diff --git a/packages/backend/src/server/api/endpoints/antennas/list.ts b/packages/backend/src/server/api/endpoints/antennas/list.ts index c519b452ef..bdc44895cc 100644 --- a/packages/backend/src/server/api/endpoints/antennas/list.ts +++ b/packages/backend/src/server/api/endpoints/antennas/list.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { Antennas } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AntennasRepository } from '@/models/index.js'; +import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['antennas', 'account'], @@ -26,10 +29,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const antennas = await Antennas.findBy({ - userId: me.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, - return await Promise.all(antennas.map(x => Antennas.pack(x))); -}); + private antennaEntityService: AntennaEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const antennas = await this.antennasRepository.findBy({ + userId: me.id, + }); + + return await Promise.all(antennas.map(x => this.antennaEntityService.pack(x))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 8aac55b4a0..eba42afe56 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -1,11 +1,11 @@ -import define from '../../define.js'; -import readNote from '@/services/note/read.js'; -import { Antennas, Notes, AntennaNotes } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NotesRepository, AntennaNotesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteReadService } from '@/core/NoteReadService.js'; +import { DI } from '@/di-symbols.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { ApiError } from '../../error.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; export const meta = { tags: ['antennas', 'account', 'notes'], @@ -47,43 +47,61 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const antenna = await Antennas.findOneBy({ - id: ps.antennaId, - userId: user.id, - }); - - if (antenna == null) { - throw new ApiError(meta.errors.noSuchAntenna); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - const query = makePaginationQuery(Notes.createQueryBuilder('note'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .innerJoin(AntennaNotes.metadata.targetName, 'antennaNote', 'antennaNote.noteId = note.id') - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') - .andWhere('antennaNote.antennaId = :antennaId', { antennaId: antenna.id }); - - generateVisibilityQuery(query, user); - generateMutedUserQuery(query, user); - generateBlockedUserQuery(query, user); - - const notes = await query - .take(ps.limit) - .getMany(); - - if (notes.length > 0) { - readNote(user.id, notes); - } + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, + + @Inject(DI.antennaNotesRepository) + private antennaNotesRepository: AntennaNotesRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private noteReadService: NoteReadService, + ) { + super(meta, paramDef, async (ps, me) => { + const antenna = await this.antennasRepository.findOneBy({ + id: ps.antennaId, + userId: me.id, + }); + + if (antenna == null) { + throw new ApiError(meta.errors.noSuchAntenna); + } - return await Notes.packMany(notes, user); -}); + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .innerJoin(this.antennaNotesRepository.metadata.targetName, 'antennaNote', 'antennaNote.noteId = note.id') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') + .andWhere('antennaNote.antennaId = :antennaId', { antennaId: antenna.id }); + + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + + const notes = await query + .take(ps.limit) + .getMany(); + + if (notes.length > 0) { + this.noteReadService.read(me.id, notes); + } + + return await this.noteEntityService.packMany(notes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/antennas/show.ts b/packages/backend/src/server/api/endpoints/antennas/show.ts index dd693789cb..8bd8ad124d 100644 --- a/packages/backend/src/server/api/endpoints/antennas/show.ts +++ b/packages/backend/src/server/api/endpoints/antennas/show.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AntennasRepository } from '@/models/index.js'; +import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Antennas } from '@/models/index.js'; export const meta = { tags: ['antennas', 'account'], @@ -33,16 +36,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the antenna - const antenna = await Antennas.findOneBy({ - id: ps.antennaId, - userId: me.id, - }); - - if (antenna == null) { - throw new ApiError(meta.errors.noSuchAntenna); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, + + private antennaEntityService: AntennaEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the antenna + const antenna = await this.antennasRepository.findOneBy({ + id: ps.antennaId, + userId: me.id, + }); + + if (antenna == null) { + throw new ApiError(meta.errors.noSuchAntenna); + } + + return await this.antennaEntityService.pack(antenna); + }); } - - return await Antennas.pack(antenna); -}); +} diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index edfedc1752..59bba04ee1 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -1,7 +1,10 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AntennasRepository, UserListsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Antennas, UserLists, UserGroupJoinings } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['antennas'], @@ -67,55 +70,72 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch the antenna - const antenna = await Antennas.findOneBy({ - id: ps.antennaId, - userId: user.id, - }); - - if (antenna == null) { - throw new ApiError(meta.errors.noSuchAntenna); - } - - let userList; - let userGroupJoining; - - if (ps.src === 'list' && ps.userListId) { - userList = await UserLists.findOneBy({ - id: ps.userListId, - userId: user.id, +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, + + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private antennaEntityService: AntennaEntityService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the antenna + const antenna = await this.antennasRepository.findOneBy({ + id: ps.antennaId, + userId: me.id, + }); + + if (antenna == null) { + throw new ApiError(meta.errors.noSuchAntenna); + } + + let userList; + let userGroupJoining; + + if (ps.src === 'list' && ps.userListId) { + userList = await this.userListsRepository.findOneBy({ + id: ps.userListId, + userId: me.id, + }); + + if (userList == null) { + throw new ApiError(meta.errors.noSuchUserList); + } + } else if (ps.src === 'group' && ps.userGroupId) { + userGroupJoining = await this.userGroupJoiningsRepository.findOneBy({ + userGroupId: ps.userGroupId, + userId: me.id, + }); + + if (userGroupJoining == null) { + throw new ApiError(meta.errors.noSuchUserGroup); + } + } + + await this.antennasRepository.update(antenna.id, { + name: ps.name, + src: ps.src, + userListId: userList ? userList.id : null, + userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null, + keywords: ps.keywords, + excludeKeywords: ps.excludeKeywords, + users: ps.users, + caseSensitive: ps.caseSensitive, + withReplies: ps.withReplies, + withFile: ps.withFile, + notify: ps.notify, + }); + + this.globalEventService.publishInternalEvent('antennaUpdated', await this.antennasRepository.findOneByOrFail({ id: antenna.id })); + + return await this.antennaEntityService.pack(antenna.id); }); - - if (userList == null) { - throw new ApiError(meta.errors.noSuchUserList); - } - } else if (ps.src === 'group' && ps.userGroupId) { - userGroupJoining = await UserGroupJoinings.findOneBy({ - userGroupId: ps.userGroupId, - userId: user.id, - }); - - if (userGroupJoining == null) { - throw new ApiError(meta.errors.noSuchUserGroup); - } } - - await Antennas.update(antenna.id, { - name: ps.name, - src: ps.src, - userListId: userList ? userList.id : null, - userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null, - keywords: ps.keywords, - excludeKeywords: ps.excludeKeywords, - users: ps.users, - caseSensitive: ps.caseSensitive, - withReplies: ps.withReplies, - withFile: ps.withFile, - notify: ps.notify, - }); - - publishInternalEvent('antennaUpdated', await Antennas.findOneByOrFail({ id: antenna.id })); - - return await Antennas.pack(antenna.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts index 0cbe7ebc67..3d4c85e50b 100644 --- a/packages/backend/src/server/api/endpoints/ap/get.ts +++ b/packages/backend/src/server/api/endpoints/ap/get.ts @@ -1,7 +1,8 @@ -import define from '../../define.js'; -import Resolver from '@/remote/activitypub/resolver.js'; -import { ApiError } from '../../error.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ApResolverService } from '@/core/remote/activitypub/ApResolverService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['federation'], @@ -31,8 +32,15 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const resolver = new Resolver(); - const object = await resolver.resolve(ps.uri); - return object; -}); +@Injectable() +export default class extends Endpoint { + constructor( + private apResolverService: ApResolverService, + ) { + super(meta, paramDef, async (ps, me) => { + const resolver = this.apResolverService.createResolver(); + const object = await resolver.resolve(ps.uri); + return object; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 6442a1412c..eaf93ee977 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -1,18 +1,21 @@ -import define from '../../define.js'; -import config from '@/config/index.js'; -import { createPerson } from '@/remote/activitypub/models/person.js'; -import { createNote } from '@/remote/activitypub/models/note.js'; -import DbResolver from '@/remote/activitypub/db-resolver.js'; -import Resolver from '@/remote/activitypub/resolver.js'; -import { ApiError } from '../../error.js'; -import { extractDbHost } from '@/misc/convert-host.js'; -import { Users, Notes } from '@/models/index.js'; -import { Note } from '@/models/entities/note.js'; -import { CacheableLocalUser, User } from '@/models/entities/user.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { isActor, isPost, getApId } from '@/remote/activitypub/type.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; -import { SchemaType } from '@/misc/schema.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, NotesRepository } from '@/models/index.js'; +import type { Note } from '@/models/entities/Note.js'; +import type { CacheableLocalUser, User } from '@/models/entities/User.js'; +import { isActor, isPost, getApId } from '@/core/remote/activitypub/type.js'; +import type { SchemaType } from '@/misc/schema.js'; +import { ApResolverService } from '@/core/remote/activitypub/ApResolverService.js'; +import { ApDbResolverService } from '@/core/remote/activitypub/ApDbResolverService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { ApPersonService } from '@/core/remote/activitypub/models/ApPersonService.js'; +import { ApNoteService } from '@/core/remote/activitypub/models/ApNoteService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['federation'], @@ -47,8 +50,8 @@ export const meta = { type: 'object', optional: false, nullable: false, ref: 'UserDetailedNotMe', - } - } + }, + }, }, { type: 'object', @@ -62,9 +65,9 @@ export const meta = { type: 'object', optional: false, nullable: false, ref: 'Note', - } - } - } + }, + }, + }, ], }, } as const; @@ -78,70 +81,88 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const object = await fetchAny(ps.uri, me); - if (object) { - return object; - } else { - throw new ApiError(meta.errors.noSuchObject); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private utilityService: UtilityService, + private userEntityService: UserEntityService, + private noteEntityService: NoteEntityService, + private metaService: MetaService, + private apResolverService: ApResolverService, + private apDbResolverService: ApDbResolverService, + private apPersonService: ApPersonService, + private apNoteService: ApNoteService, + ) { + super(meta, paramDef, async (ps, me) => { + const object = await this.#fetchAny(ps.uri, me); + if (object) { + return object; + } else { + throw new ApiError(meta.errors.noSuchObject); + } + }); } -}); -/*** - * URIからUserかNoteを解決する - */ -async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise | null> { + /*** + * URIからUserかNoteを解決する + */ + async #fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise | null> { // ブロックしてたら中断 - const fetchedMeta = await fetchMeta(); - if (fetchedMeta.blockedHosts.includes(extractDbHost(uri))) return null; - - const dbResolver = new DbResolver(); - - let local = await mergePack(me, ...await Promise.all([ - dbResolver.getUserFromApId(uri), - dbResolver.getNoteFromApId(uri), - ])); - if (local != null) return local; - - // リモートから一旦オブジェクトフェッチ - const resolver = new Resolver(); - const object = await resolver.resolve(uri) as any; - - // /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する - // これはDBに存在する可能性があるため再度DB検索 - if (uri !== object.id) { - local = await mergePack(me, ...await Promise.all([ - dbResolver.getUserFromApId(object.id), - dbResolver.getNoteFromApId(object.id), + const fetchedMeta = await this.metaService.fetch(); + if (fetchedMeta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) return null; + + let local = await this.#mergePack(me, ...await Promise.all([ + this.apDbResolverService.getUserFromApId(uri), + this.apDbResolverService.getNoteFromApId(uri), ])); if (local != null) return local; - } - return await mergePack( - me, - isActor(object) ? await createPerson(getApId(object)) : null, - isPost(object) ? await createNote(getApId(object), undefined, true) : null, - ); -} + // リモートから一旦オブジェクトフェッチ + const resolver = this.apResolverService.createResolver(); + const object = await resolver.resolve(uri) as any; + + // /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する + // これはDBに存在する可能性があるため再度DB検索 + if (uri !== object.id) { + local = await this.#mergePack(me, ...await Promise.all([ + this.apDbResolverService.getUserFromApId(object.id), + this.apDbResolverService.getNoteFromApId(object.id), + ])); + if (local != null) return local; + } -async function mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise | null> { - if (user != null) { - return { - type: 'User', - object: await Users.pack(user, me, { detail: true }), - }; - } else if (note != null) { - try { - const object = await Notes.pack(note, me, { detail: true }); + return await this.#mergePack( + me, + isActor(object) ? await this.apPersonService.createPerson(getApId(object)) : null, + isPost(object) ? await this.apNoteService.createNote(getApId(object), undefined, true) : null, + ); + } + async #mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise | null> { + if (user != null) { return { - type: 'Note', - object, + type: 'User', + object: await this.userEntityService.pack(user, me, { detail: true }), }; - } catch (e) { - return null; + } else if (note != null) { + try { + const object = await this.noteEntityService.pack(note, me, { detail: true }); + + return { + type: 'Note', + object, + }; + } catch (e) { + return null; + } } - } - return null; + return null; + } } diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index a0a7350822..f52d18f7fe 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -1,8 +1,11 @@ -import define from '../../define.js'; -import { Apps } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { unique } from '@/prelude/array.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AppsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { unique } from '@/misc/prelude/array.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; +import { AppEntityService } from '@/core/entities/AppEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['app'], @@ -30,27 +33,38 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Generate secret - const secret = secureRndstr(32, true); - - // for backward compatibility - const permission = unique(ps.permission.map(v => v.replace(/^(.+)(\/|-)(read|write)$/, '$3:$1'))); - - // Create account - const app = await Apps.insert({ - id: genId(), - createdAt: new Date(), - userId: user ? user.id : null, - name: ps.name, - description: ps.description, - permission, - callbackUrl: ps.callbackUrl, - secret: secret, - }).then(x => Apps.findOneByOrFail(x.identifiers[0])); - - return await Apps.pack(app, null, { - detail: true, - includeSecret: true, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.appsRepository) + private appsRepository: AppsRepository, + + private appEntityService: AppEntityService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + // Generate secret + const secret = secureRndstr(32, true); + + // for backward compatibility + const permission = unique(ps.permission.map(v => v.replace(/^(.+)(\/|-)(read|write)$/, '$3:$1'))); + + // Create account + const app = await this.appsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me ? me.id : null, + name: ps.name, + description: ps.description, + permission, + callbackUrl: ps.callbackUrl, + secret: secret, + }).then(x => this.appsRepository.findOneByOrFail(x.identifiers[0])); + + return await this.appEntityService.pack(app, null, { + detail: true, + includeSecret: true, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/app/show.ts b/packages/backend/src/server/api/endpoints/app/show.ts index 451969d971..f94fed5344 100644 --- a/packages/backend/src/server/api/endpoints/app/show.ts +++ b/packages/backend/src/server/api/endpoints/app/show.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AppsRepository } from '@/models/index.js'; +import { AppEntityService } from '@/core/entities/AppEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Apps } from '@/models/index.js'; export const meta = { tags: ['app'], @@ -29,18 +32,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user, token) => { - const isSecure = user != null && token == null; - - // Lookup app - const ap = await Apps.findOneBy({ id: ps.appId }); - - if (ap == null) { - throw new ApiError(meta.errors.noSuchApp); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.appsRepository) + private appsRepository: AppsRepository, + + private appEntityService: AppEntityService, + ) { + super(meta, paramDef, async (ps, user, token) => { + const isSecure = user != null && token == null; + + // Lookup app + const ap = await this.appsRepository.findOneBy({ id: ps.appId }); + + if (ap == null) { + throw new ApiError(meta.errors.noSuchApp); + } + + return await this.appEntityService.pack(ap, user, { + detail: true, + includeSecret: isSecure && (ap.userId === user!.id), + }); + }); } - - return await Apps.pack(ap, user, { - detail: true, - includeSecret: isSecure && (ap.userId === user!.id), - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index b5c06792bb..6032b59bef 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -1,9 +1,11 @@ import * as crypto from 'node:crypto'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { AuthSessions, AccessTokens, Apps } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AuthSessionsRepository, AppsRepository, AccessTokensRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['auth'], @@ -30,49 +32,65 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch token - const session = await AuthSessions - .findOneBy({ token: ps.token }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.appsRepository) + private appsRepository: AppsRepository, - if (session == null) { - throw new ApiError(meta.errors.noSuchSession); - } + @Inject(DI.authSessionsRepository) + private authSessionsRepository: AuthSessionsRepository, + + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, + + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch token + const session = await this.authSessionsRepository + .findOneBy({ token: ps.token }); + + if (session == null) { + throw new ApiError(meta.errors.noSuchSession); + } + + // Generate access token + const accessToken = secureRndstr(32, true); - // Generate access token - const accessToken = secureRndstr(32, true); - - // Fetch exist access token - const exist = await AccessTokens.findOneBy({ - appId: session.appId, - userId: user.id, - }); - - if (exist == null) { - // Lookup app - const app = await Apps.findOneByOrFail({ id: session.appId }); - - // Generate Hash - const sha256 = crypto.createHash('sha256'); - sha256.update(accessToken + app.secret); - const hash = sha256.digest('hex'); - - const now = new Date(); - - // Insert access token doc - await AccessTokens.insert({ - id: genId(), - createdAt: now, - lastUsedAt: now, - appId: session.appId, - userId: user.id, - token: accessToken, - hash: hash, + // Fetch exist access token + const exist = await this.accessTokensRepository.findOneBy({ + appId: session.appId, + userId: me.id, + }); + + if (exist == null) { + // Lookup app + const app = await this.appsRepository.findOneByOrFail({ id: session.appId }); + + // Generate Hash + const sha256 = crypto.createHash('sha256'); + sha256.update(accessToken + app.secret); + const hash = sha256.digest('hex'); + + const now = new Date(); + + // Insert access token doc + await this.accessTokensRepository.insert({ + id: this.idService.genId(), + createdAt: now, + lastUsedAt: now, + appId: session.appId, + userId: me.id, + token: accessToken, + hash: hash, + }); + } + + // Update session + await this.authSessionsRepository.update(session.id, { + userId: me.id, + }); }); } - - // Update session - await AuthSessions.update(session.id, { - userId: user.id, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts index 717c3e5086..7f8325dbbd 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -1,9 +1,11 @@ import { v4 as uuid } from 'uuid'; -import config from '@/config/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AppsRepository, AuthSessionsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { Apps, AuthSessions } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; export const meta = { tags: ['auth'], @@ -44,29 +46,45 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - // Lookup app - const app = await Apps.findOneBy({ - secret: ps.appSecret, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, - if (app == null) { - throw new ApiError(meta.errors.noSuchApp); - } + @Inject(DI.appsRepository) + private appsRepository: AppsRepository, + + @Inject(DI.authSessionsRepository) + private authSessionsRepository: AuthSessionsRepository, + + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + // Lookup app + const app = await this.appsRepository.findOneBy({ + secret: ps.appSecret, + }); - // Generate token - const token = uuid(); + if (app == null) { + throw new ApiError(meta.errors.noSuchApp); + } - // Create session token document - const doc = await AuthSessions.insert({ - id: genId(), - createdAt: new Date(), - appId: app.id, - token: token, - }).then(x => AuthSessions.findOneByOrFail(x.identifiers[0])); + // Generate token + const token = uuid(); - return { - token: doc.token, - url: `${config.authUrl}/${doc.token}`, - }; -}); + // Create session token document + const doc = await this.authSessionsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + appId: app.id, + token: token, + }).then(x => this.authSessionsRepository.findOneByOrFail(x.identifiers[0])); + + return { + token: doc.token, + url: `${this.config.authUrl}/${doc.token}`, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/auth/session/show.ts b/packages/backend/src/server/api/endpoints/auth/session/show.ts index 3f3a4d1427..dff4c74340 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/show.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/show.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AuthSessionsRepository } from '@/models/index.js'; +import { AuthSessionEntityService } from '@/core/entities/AuthSessionEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { AuthSessions } from '@/models/index.js'; export const meta = { tags: ['auth'], @@ -46,15 +49,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Lookup session - const session = await AuthSessions.findOneBy({ - token: ps.token, - }); - - if (session == null) { - throw new ApiError(meta.errors.noSuchSession); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.authSessionsRepository) + private authSessionsRepository: AuthSessionsRepository, + + private authSessionEntityService: AuthSessionEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Lookup session + const session = await this.authSessionsRepository.findOneBy({ + token: ps.token, + }); - return await AuthSessions.pack(session, user); -}); + if (session == null) { + throw new ApiError(meta.errors.noSuchSession); + } + + return await this.authSessionEntityService.pack(session, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts index 89884ed38a..9c9f13f502 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, AppsRepository, AccessTokensRepository, AuthSessionsRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { Apps, AuthSessions, AccessTokens, Users } from '@/models/index.js'; export const meta = { tags: ['auth'], @@ -55,43 +58,62 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - // Lookup app - const app = await Apps.findOneBy({ - secret: ps.appSecret, - }); - - if (app == null) { - throw new ApiError(meta.errors.noSuchApp); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.appsRepository) + private appsRepository: AppsRepository, + + @Inject(DI.authSessionsRepository) + private authSessionsRepository: AuthSessionsRepository, + + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, + + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Lookup app + const app = await this.appsRepository.findOneBy({ + secret: ps.appSecret, + }); + + if (app == null) { + throw new ApiError(meta.errors.noSuchApp); + } + + // Fetch token + const session = await this.authSessionsRepository.findOneBy({ + token: ps.token, + appId: app.id, + }); + + if (session == null) { + throw new ApiError(meta.errors.noSuchSession); + } + + if (session.userId == null) { + throw new ApiError(meta.errors.pendingSession); + } + + // Lookup access token + const accessToken = await this.accessTokensRepository.findOneByOrFail({ + appId: app.id, + userId: session.userId, + }); + + // Delete session + this.authSessionsRepository.delete(session.id); + + return { + accessToken: accessToken.token, + user: await this.userEntityService.pack(session.userId, null, { + detail: true, + }), + }; + }); } - - // Fetch token - const session = await AuthSessions.findOneBy({ - token: ps.token, - appId: app.id, - }); - - if (session == null) { - throw new ApiError(meta.errors.noSuchSession); - } - - if (session.userId == null) { - throw new ApiError(meta.errors.pendingSession); - } - - // Lookup access token - const accessToken = await AccessTokens.findOneByOrFail({ - appId: app.id, - userId: session.userId, - }); - - // Delete session - AuthSessions.delete(session.id); - - return { - accessToken: accessToken.token, - user: await Users.pack(session.userId, null, { - detail: true, - }), - }; -}); +} diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index 0540e6ab0f..33614a1554 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -1,9 +1,12 @@ import ms from 'ms'; -import create from '@/services/blocking/create.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, BlockingsRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { UserBlockingService } from '@/core/UserBlockingService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { Blockings, NoteWatchings, Users } from '@/models/index.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['account'], @@ -53,38 +56,48 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const blocker = await Users.findOneByOrFail({ id: user.id }); - - // 自分自身 - if (user.id === ps.userId) { - throw new ApiError(meta.errors.blockeeIsYourself); - } - - // Get blockee - const blockee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Check if already blocking - const exist = await Blockings.findOneBy({ - blockerId: blocker.id, - blockeeId: blockee.id, - }); - - if (exist != null) { - throw new ApiError(meta.errors.alreadyBlocking); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private userBlockingService: UserBlockingService, + ) { + super(meta, paramDef, async (ps, me) => { + const blocker = await this.usersRepository.findOneByOrFail({ id: me.id }); + + // 自分自身 + if (me.id === ps.userId) { + throw new ApiError(meta.errors.blockeeIsYourself); + } + + // Get blockee + const blockee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check if already blocking + const exist = await this.blockingsRepository.findOneBy({ + blockerId: blocker.id, + blockeeId: blockee.id, + }); + + if (exist != null) { + throw new ApiError(meta.errors.alreadyBlocking); + } + + await this.userBlockingService.block(blocker, blockee); + + return await this.userEntityService.pack(blockee.id, blocker, { + detail: true, + }); + }); } - - await create(blocker, blockee); - - NoteWatchings.delete({ - userId: blocker.id, - noteUserId: blockee.id, - }); - - return await Users.pack(blockee.id, blocker, { - detail: true, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index 77e17b3ba9..f2cc28e922 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -1,9 +1,12 @@ import ms from 'ms'; -import deleteBlocking from '@/services/blocking/delete.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, BlockingsRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { UserBlockingService } from '@/core/UserBlockingService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { Blockings, Users } from '@/models/index.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['account'], @@ -53,34 +56,49 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const blocker = await Users.findOneByOrFail({ id: user.id }); - - // Check if the blockee is yourself - if (user.id === ps.userId) { - throw new ApiError(meta.errors.blockeeIsYourself); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private userBlockingService: UserBlockingService, + ) { + super(meta, paramDef, async (ps, me) => { + const blocker = await this.usersRepository.findOneByOrFail({ id: me.id }); + + // Check if the blockee is yourself + if (me.id === ps.userId) { + throw new ApiError(meta.errors.blockeeIsYourself); + } + + // Get blockee + const blockee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check not blocking + const exist = await this.blockingsRepository.findOneBy({ + blockerId: blocker.id, + blockeeId: blockee.id, + }); + + if (exist == null) { + throw new ApiError(meta.errors.notBlocking); + } + + // Delete blocking + await this.userBlockingService.unblock(blocker, blockee); + + return await this.userEntityService.pack(blockee.id, blocker, { + detail: true, + }); + }); } - - // Get blockee - const blockee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Check not blocking - const exist = await Blockings.findOneBy({ - blockerId: blocker.id, - blockeeId: blockee.id, - }); - - if (exist == null) { - throw new ApiError(meta.errors.notBlocking); - } - - // Delete blocking - await deleteBlocking(blocker, blockee); - - return await Users.pack(blockee.id, blocker, { - detail: true, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/blocking/list.ts b/packages/backend/src/server/api/endpoints/blocking/list.ts index 29095ebe21..4f5e11cd68 100644 --- a/packages/backend/src/server/api/endpoints/blocking/list.ts +++ b/packages/backend/src/server/api/endpoints/blocking/list.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Blockings } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { BlockingsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { BlockingEntityService } from '@/core/entities/BlockingEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], @@ -31,13 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(Blockings.createQueryBuilder('blocking'), ps.sinceId, ps.untilId) - .andWhere(`blocking.blockerId = :meId`, { meId: me.id }); - - const blockings = await query - .take(ps.limit) - .getMany(); - - return await Blockings.packMany(blockings, me); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + private blockingEntityService: BlockingEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.blockingsRepository.createQueryBuilder('blocking'), ps.sinceId, ps.untilId) + .andWhere('blocking.blockerId = :meId', { meId: me.id }); + + const blockings = await query + .take(ps.limit) + .getMany(); + + return await this.blockingEntityService.packMany(blockings, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index 94dcfe5023..21979884f9 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -1,8 +1,11 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelsRepository, DriveFilesRepository } from '@/models/index.js'; +import type { Channel } from '@/models/entities/Channel.js'; +import { IdService } from '@/core/IdService.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Channels, DriveFiles } from '@/models/index.js'; -import { Channel } from '@/models/entities/channel.js'; -import { genId } from '@/misc/gen-id.js'; export const meta = { tags: ['channels'], @@ -37,27 +40,41 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - let banner = null; - if (ps.bannerId != null) { - banner = await DriveFiles.findOneBy({ - id: ps.bannerId, - userId: user.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (banner == null) { - throw new ApiError(meta.errors.noSuchFile); - } - } + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + private idService: IdService, + private channelEntityService: ChannelEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + let banner = null; + if (ps.bannerId != null) { + banner = await this.driveFilesRepository.findOneBy({ + id: ps.bannerId, + userId: me.id, + }); + + if (banner == null) { + throw new ApiError(meta.errors.noSuchFile); + } + } - const channel = await Channels.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - name: ps.name, - description: ps.description || null, - bannerId: banner ? banner.id : null, - } as Channel).then(x => Channels.findOneByOrFail(x.identifiers[0])); - - return await Channels.pack(channel, user); -}); + const channel = await this.channelsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + name: ps.name, + description: ps.description ?? null, + bannerId: banner ? banner.id : null, + } as Channel).then(x => this.channelsRepository.findOneByOrFail(x.identifiers[0])); + + return await this.channelEntityService.pack(channel, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/featured.ts b/packages/backend/src/server/api/endpoints/channels/featured.ts index 73980c0fad..0c3f9509d1 100644 --- a/packages/backend/src/server/api/endpoints/channels/featured.ts +++ b/packages/backend/src/server/api/endpoints/channels/featured.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { Channels } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelsRepository } from '@/models/index.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['channels'], @@ -24,12 +27,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Channels.createQueryBuilder('channel') - .where('channel.lastNotedAt IS NOT NULL') - .orderBy('channel.lastNotedAt', 'DESC'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, - const channels = await query.take(10).getMany(); + private channelEntityService: ChannelEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.channelsRepository.createQueryBuilder('channel') + .where('channel.lastNotedAt IS NOT NULL') + .orderBy('channel.lastNotedAt', 'DESC'); - return await Promise.all(channels.map(x => Channels.pack(x, me))); -}); + const channels = await query.take(10).getMany(); + + return await Promise.all(channels.map(x => this.channelEntityService.pack(x, me))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index 895ffed0bd..6c6b498a94 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -1,8 +1,10 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelFollowingsRepository, ChannelsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Channels, ChannelFollowings } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { publishUserEvent } from '@/services/stream.js'; export const meta = { tags: ['channels'], @@ -29,21 +31,35 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const channel = await Channels.findOneBy({ - id: ps.channelId, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, - if (channel == null) { - throw new ApiError(meta.errors.noSuchChannel); - } + @Inject(DI.channelFollowingsRepository) + private channelFollowingsRepository: ChannelFollowingsRepository, + + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const channel = await Channels.findOneBy({ + id: ps.channelId, + }); - await ChannelFollowings.insert({ - id: genId(), - createdAt: new Date(), - followerId: user.id, - followeeId: channel.id, - }); + if (channel == null) { + throw new ApiError(meta.errors.noSuchChannel); + } - publishUserEvent(user.id, 'followChannel', channel); -}); + await this.channelFollowingsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + followerId: me.id, + followeeId: channel.id, + }); + + this.globalEventService.publishUserEvent(me.id, 'followChannel', channel); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/followed.ts b/packages/backend/src/server/api/endpoints/channels/followed.ts index e4aa4d1614..5a8ab26af9 100644 --- a/packages/backend/src/server/api/endpoints/channels/followed.ts +++ b/packages/backend/src/server/api/endpoints/channels/followed.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Channels, ChannelFollowings } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelFollowingsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['channels', 'account'], @@ -31,13 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(ChannelFollowings.createQueryBuilder(), ps.sinceId, ps.untilId) - .andWhere({ followerId: me.id }); - - const followings = await query - .take(ps.limit) - .getMany(); - - return await Promise.all(followings.map(x => Channels.pack(x.followeeId, me))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.channelFollowingsRepository) + private channelFollowingsRepository: ChannelFollowingsRepository, + + private channelEntityService: ChannelEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.channelFollowingsRepository.createQueryBuilder(), ps.sinceId, ps.untilId) + .andWhere({ followerId: me.id }); + + const followings = await query + .take(ps.limit) + .getMany(); + + return await Promise.all(followings.map(x => this.channelEntityService.pack(x.followeeId, me))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/owned.ts b/packages/backend/src/server/api/endpoints/channels/owned.ts index ed7e41cac2..8b8b5819e6 100644 --- a/packages/backend/src/server/api/endpoints/channels/owned.ts +++ b/packages/backend/src/server/api/endpoints/channels/owned.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Channels } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['channels', 'account'], @@ -31,13 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(Channels.createQueryBuilder(), ps.sinceId, ps.untilId) - .andWhere({ userId: me.id }); - - const channels = await query - .take(ps.limit) - .getMany(); - - return await Promise.all(channels.map(x => Channels.pack(x, me))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + private channelEntityService: ChannelEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder(), ps.sinceId, ps.untilId) + .andWhere({ userId: me.id }); + + const channels = await query + .take(ps.limit) + .getMany(); + + return await Promise.all(channels.map(x => this.channelEntityService.pack(x, me))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts index 87665a9865..54ae31790b 100644 --- a/packages/backend/src/server/api/endpoints/channels/show.ts +++ b/packages/backend/src/server/api/endpoints/channels/show.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelsRepository } from '@/models/index.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Channels } from '@/models/index.js'; export const meta = { tags: ['channels'], @@ -31,14 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const channel = await Channels.findOneBy({ - id: ps.channelId, - }); - - if (channel == null) { - throw new ApiError(meta.errors.noSuchChannel); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + private channelEntityService: ChannelEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const channel = await this.channelsRepository.findOneBy({ + id: ps.channelId, + }); + + if (channel == null) { + throw new ApiError(meta.errors.noSuchChannel); + } + + return await this.channelEntityService.pack(channel, me); + }); } - - return await Channels.pack(channel, me); -}); +} diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index deaa299013..1c7f1360b9 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -1,8 +1,11 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelsRepository, NotesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Notes, Channels } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { activeUsersChart } from '@/services/chart/index.js'; export const meta = { tags: ['notes', 'channels'], @@ -42,35 +45,50 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const channel = await Channels.findOneBy({ - id: ps.channelId, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - if (channel == null) { - throw new ApiError(meta.errors.noSuchChannel); - } + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private activeUsersChart: ActiveUsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + const channel = await this.channelsRepository.findOneBy({ + id: ps.channelId, + }); - //#region Construct query - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere('note.channelId = :channelId', { channelId: channel.id }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') - .leftJoinAndSelect('note.channel', 'channel'); - //#endregion + if (channel == null) { + throw new ApiError(meta.errors.noSuchChannel); + } - const timeline = await query.take(ps.limit).getMany(); + //#region Construct query + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('note.channelId = :channelId', { channelId: channel.id }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') + .leftJoinAndSelect('note.channel', 'channel'); + //#endregion - if (user) activeUsersChart.read(user); + const timeline = await query.take(ps.limit).getMany(); - return await Notes.packMany(timeline, user); -}); + if (me) this.activeUsersChart.read(me); + + return await this.noteEntityService.packMany(timeline, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts index e065d897a5..b464c55097 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelFollowingsRepository, ChannelsRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Channels, ChannelFollowings } from '@/models/index.js'; -import { publishUserEvent } from '@/services/stream.js'; export const meta = { tags: ['channels'], @@ -28,19 +30,32 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const channel = await Channels.findOneBy({ - id: ps.channelId, - }); - - if (channel == null) { - throw new ApiError(meta.errors.noSuchChannel); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + @Inject(DI.channelFollowingsRepository) + private channelFollowingsRepository: ChannelFollowingsRepository, + + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const channel = await this.channelsRepository.findOneBy({ + id: ps.channelId, + }); + + if (channel == null) { + throw new ApiError(meta.errors.noSuchChannel); + } + + await this.channelFollowingsRepository.delete({ + followerId: me.id, + followeeId: channel.id, + }); + + this.globalEventService.publishUserEvent(me.id, 'unfollowChannel', channel); + }); } - - await ChannelFollowings.delete({ - followerId: user.id, - followeeId: channel.id, - }); - - publishUserEvent(user.id, 'unfollowChannel', channel); -}); +} diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index 13104f324f..ba62e9d371 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository, ChannelsRepository } from '@/models/index.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Channels, DriveFiles } from '@/models/index.js'; export const meta = { tags: ['channels'], @@ -48,39 +51,52 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const channel = await Channels.findOneBy({ - id: ps.channelId, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, - if (channel == null) { - throw new ApiError(meta.errors.noSuchChannel); - } + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (channel.userId !== me.id) { - throw new ApiError(meta.errors.accessDenied); - } + private channelEntityService: ChannelEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const channel = await this.channelsRepository.findOneBy({ + id: ps.channelId, + }); - // eslint:disable-next-line:no-unnecessary-initializer - let banner = undefined; - if (ps.bannerId != null) { - banner = await DriveFiles.findOneBy({ - id: ps.bannerId, - userId: me.id, - }); + if (channel == null) { + throw new ApiError(meta.errors.noSuchChannel); + } - if (banner == null) { - throw new ApiError(meta.errors.noSuchFile); - } - } else if (ps.bannerId === null) { - banner = null; - } + if (channel.userId !== me.id) { + throw new ApiError(meta.errors.accessDenied); + } - await Channels.update(channel.id, { - ...(ps.name !== undefined ? { name: ps.name } : {}), - ...(ps.description !== undefined ? { description: ps.description } : {}), - ...(banner ? { bannerId: banner.id } : {}), - }); + // eslint:disable-next-line:no-unnecessary-initializer + let banner = undefined; + if (ps.bannerId != null) { + banner = await this.driveFilesRepository.findOneBy({ + id: ps.bannerId, + userId: me.id, + }); - return await Channels.pack(channel.id, me); -}); + if (banner == null) { + throw new ApiError(meta.errors.noSuchFile); + } + } else if (ps.bannerId === null) { + banner = null; + } + + await this.channelsRepository.update(channel.id, { + ...(ps.name !== undefined ? { name: ps.name } : {}), + ...(ps.description !== undefined ? { description: ps.description } : {}), + ...(banner ? { bannerId: banner.id } : {}), + }); + + return await this.channelEntityService.pack(channel.id, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/active-users.ts b/packages/backend/src/server/api/endpoints/charts/active-users.ts index ea23794296..862ef89268 100644 --- a/packages/backend/src/server/api/endpoints/charts/active-users.ts +++ b/packages/backend/src/server/api/endpoints/charts/active-users.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { schema } from '@/core/chart/charts/entities/active-users.js'; export const meta = { tags: ['charts', 'users'], - res: getJsonSchema(activeUsersChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -22,6 +24,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await activeUsersChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private activeUsersChart: ActiveUsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.activeUsersChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/ap-request.ts b/packages/backend/src/server/api/endpoints/charts/ap-request.ts index 06dee250ee..1d5b8f05f8 100644 --- a/packages/backend/src/server/api/endpoints/charts/ap-request.ts +++ b/packages/backend/src/server/api/endpoints/charts/ap-request.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { apRequestChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import ApRequestChart from '@/core/chart/charts/ap-request.js'; +import { schema } from '@/core/chart/charts/entities/ap-request.js'; export const meta = { tags: ['charts'], - res: getJsonSchema(apRequestChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -22,6 +24,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await apRequestChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private apRequestChart: ApRequestChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.apRequestChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/drive.ts b/packages/backend/src/server/api/endpoints/charts/drive.ts index dd2c2d6838..ec28fa75de 100644 --- a/packages/backend/src/server/api/endpoints/charts/drive.ts +++ b/packages/backend/src/server/api/endpoints/charts/drive.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { driveChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import DriveChart from '@/core/chart/charts/drive.js'; +import { schema } from '@/core/chart/charts/entities/drive.js'; export const meta = { tags: ['charts', 'drive'], - res: getJsonSchema(driveChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -22,6 +24,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await driveChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private driveChart: DriveChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.driveChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/federation.ts b/packages/backend/src/server/api/endpoints/charts/federation.ts index 8c35b3c46d..6c24cbbb77 100644 --- a/packages/backend/src/server/api/endpoints/charts/federation.ts +++ b/packages/backend/src/server/api/endpoints/charts/federation.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { federationChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import FederationChart from '@/core/chart/charts/federation.js'; +import { schema } from '@/core/chart/charts/entities/federation.js'; export const meta = { tags: ['charts'], - res: getJsonSchema(federationChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -22,6 +24,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await federationChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private federationChart: FederationChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.federationChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/hashtag.ts b/packages/backend/src/server/api/endpoints/charts/hashtag.ts index 77e24a62c3..71e5bab766 100644 --- a/packages/backend/src/server/api/endpoints/charts/hashtag.ts +++ b/packages/backend/src/server/api/endpoints/charts/hashtag.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { hashtagChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import HashtagChart from '@/core/chart/charts/hashtag.js'; +import { schema } from '@/core/chart/charts/entities/hashtag.js'; export const meta = { tags: ['charts', 'hashtags'], - res: getJsonSchema(hashtagChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -23,6 +25,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await hashtagChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.tag); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private hashtagChart: HashtagChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.hashtagChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.tag); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/instance.ts b/packages/backend/src/server/api/endpoints/charts/instance.ts index 817d51ad01..a6a538ea5c 100644 --- a/packages/backend/src/server/api/endpoints/charts/instance.ts +++ b/packages/backend/src/server/api/endpoints/charts/instance.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { instanceChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import InstanceChart from '@/core/chart/charts/instance.js'; +import { schema } from '@/core/chart/charts/entities/instance.js'; export const meta = { tags: ['charts'], - res: getJsonSchema(instanceChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -23,6 +25,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await instanceChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.host); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private instanceChart: InstanceChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.instanceChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.host); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/notes.ts b/packages/backend/src/server/api/endpoints/charts/notes.ts index 951adf5408..8d03f2eaf1 100644 --- a/packages/backend/src/server/api/endpoints/charts/notes.ts +++ b/packages/backend/src/server/api/endpoints/charts/notes.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { notesChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import NotesChart from '@/core/chart/charts/notes.js'; +import { schema } from '@/core/chart/charts/entities/notes.js'; export const meta = { tags: ['charts', 'notes'], - res: getJsonSchema(notesChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -22,6 +24,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await notesChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private notesChart: NotesChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.notesChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/user/drive.ts b/packages/backend/src/server/api/endpoints/charts/user/drive.ts index f165b40224..87d56f38b7 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/drive.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/drive.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { perUserDriveChart } from '@/services/chart/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js'; +import { schema } from '@/core/chart/charts/entities/per-user-drive.js'; export const meta = { tags: ['charts', 'drive', 'users'], - res: getJsonSchema(perUserDriveChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -23,6 +25,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await perUserDriveChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private perUserDriveChart: PerUserDriveChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.perUserDriveChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/user/following.ts b/packages/backend/src/server/api/endpoints/charts/user/following.ts index f5d42e21c2..7a61544aea 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/following.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/following.ts @@ -1,11 +1,13 @@ -import define from '../../../define.js'; -import { getJsonSchema } from '@/services/chart/core.js'; -import { perUserFollowingChart } from '@/services/chart/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { getJsonSchema } from '@/core/chart/core.js'; +import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; +import { schema } from '@/core/chart/charts/entities/per-user-following.js'; export const meta = { tags: ['charts', 'users', 'following'], - res: getJsonSchema(perUserFollowingChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -23,6 +25,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await perUserFollowingChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private perUserFollowingChart: PerUserFollowingChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.perUserFollowingChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/user/notes.ts b/packages/backend/src/server/api/endpoints/charts/user/notes.ts index aefe550d43..fdc385191f 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/notes.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/notes.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { perUserNotesChart } from '@/services/chart/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import PerUserNotesChart from '@/core/chart/charts/per-user-notes.js'; +import { schema } from '@/core/chart/charts/entities/per-user-notes.js'; export const meta = { tags: ['charts', 'users', 'notes'], - res: getJsonSchema(perUserNotesChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -23,6 +25,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await perUserNotesChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private perUserNotesChart: PerUserNotesChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.perUserNotesChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts index 6bc6b56bf0..f0f3e520da 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { perUserReactionsChart } from '@/services/chart/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import PerUserReactionsChart from '@/core/chart/charts/per-user-reactions.js'; +import { schema } from '@/core/chart/charts/entities/per-user-reactions.js'; export const meta = { tags: ['charts', 'users', 'reactions'], - res: getJsonSchema(perUserReactionsChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -23,6 +25,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await perUserReactionsChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private perUserReactionsChart: PerUserReactionsChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.perUserReactionsChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/users.ts b/packages/backend/src/server/api/endpoints/charts/users.ts index 338e8fd338..d09f2512e5 100644 --- a/packages/backend/src/server/api/endpoints/charts/users.ts +++ b/packages/backend/src/server/api/endpoints/charts/users.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { usersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import UsersChart from '@/core/chart/charts/users.js'; +import { schema } from '@/core/chart/charts/entities/users.js'; export const meta = { tags: ['charts', 'users'], - res: getJsonSchema(usersChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -22,6 +24,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await usersChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private usersChart: UsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.usersChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index 5d72f5c1bf..c733d28657 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -1,8 +1,10 @@ -import define from '../../define.js'; -import { ClipNotes, Clips } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; +import { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { ApiError } from '../../error.js'; -import { genId } from '@/misc/gen-id.js'; -import { getNote } from '../../common/getters.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['account', 'notes', 'clips'], @@ -42,33 +44,47 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.findOneBy({ - id: ps.clipId, - userId: user.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); - } + @Inject(DI.clipNotesRepository) + private clipNotesRepository: ClipNotesRepository, - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); + private idService: IdService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const clip = await this.clipsRepository.findOneBy({ + id: ps.clipId, + userId: me.id, + }); - const exist = await ClipNotes.findOneBy({ - noteId: note.id, - clipId: clip.id, - }); + if (clip == null) { + throw new ApiError(meta.errors.noSuchClip); + } - if (exist != null) { - throw new ApiError(meta.errors.alreadyClipped); - } + const note = await this.getterService.getNote(ps.noteId).catch(e => { + if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw e; + }); + + const exist = await this.clipNotesRepository.findOneBy({ + noteId: note.id, + clipId: clip.id, + }); - await ClipNotes.insert({ - id: genId(), - noteId: note.id, - clipId: clip.id, - }); -}); + if (exist != null) { + throw new ApiError(meta.errors.alreadyClipped); + } + + await this.clipNotesRepository.insert({ + id: this.idService.genId(), + noteId: note.id, + clipId: clip.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index 4afe4222a1..8eca3d66d1 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { genId } from '@/misc/gen-id.js'; -import { Clips } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import { ClipsRepository } from '@/models/index.js'; +import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['clips'], @@ -27,15 +30,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - name: ps.name, - isPublic: ps.isPublic, - description: ps.description, - }).then(x => Clips.findOneByOrFail(x.identifiers[0])); - - return await Clips.pack(clip); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + + private clipEntityService: ClipEntityService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const clip = await this.clipsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + name: ps.name, + isPublic: ps.isPublic, + description: ps.description, + }).then(x => this.clipsRepository.findOneByOrFail(x.identifiers[0])); + + return await this.clipEntityService.pack(clip); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/clips/delete.ts b/packages/backend/src/server/api/endpoints/clips/delete.ts index b6c0eb702a..ea361ae9c0 100644 --- a/packages/backend/src/server/api/endpoints/clips/delete.ts +++ b/packages/backend/src/server/api/endpoints/clips/delete.ts @@ -1,6 +1,8 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ClipsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Clips } from '@/models/index.js'; export const meta = { tags: ['clips'], @@ -27,15 +29,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.findOneBy({ - id: ps.clipId, - userId: user.id, - }); - - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const clip = await this.clipsRepository.findOneBy({ + id: ps.clipId, + userId: me.id, + }); + + if (clip == null) { + throw new ApiError(meta.errors.noSuchClip); + } + + await this.clipsRepository.delete(clip.id); + }); } - - await Clips.delete(clip.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index 378811eba0..b57affd1c4 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { Clips } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ClipsRepository } from '@/models/index.js'; +import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['clips', 'account'], @@ -26,10 +29,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const clips = await Clips.findBy({ - userId: me.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, - return await Promise.all(clips.map(x => Clips.pack(x))); -}); + private clipEntityService: ClipEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const clips = await this.clipsRepository.findBy({ + userId: me.id, + }); + + return await Promise.all(clips.map(x => this.clipEntityService.pack(x))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index 4ace747efe..4282498931 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -1,10 +1,10 @@ -import define from '../../define.js'; -import { ClipNotes, Clips, Notes } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NotesRepository, ClipsRepository, ClipNotesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; export const meta = { tags: ['account', 'notes', 'clips'], @@ -44,43 +44,60 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.findOneBy({ - id: ps.clipId, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); - } + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - if (!clip.isPublic && (user == null || (clip.userId !== user.id))) { - throw new ApiError(meta.errors.noSuchClip); - } + @Inject(DI.clipNotesRepository) + private clipNotesRepository: ClipNotesRepository, - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .innerJoin(ClipNotes.metadata.targetName, 'clipNote', 'clipNote.noteId = note.id') - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') - .andWhere('clipNote.clipId = :clipId', { clipId: clip.id }); - - if (user) { - generateVisibilityQuery(query, user); - generateMutedUserQuery(query, user); - generateBlockedUserQuery(query, user); - } + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const clip = await this.clipsRepository.findOneBy({ + id: ps.clipId, + }); + + if (clip == null) { + throw new ApiError(meta.errors.noSuchClip); + } - const notes = await query - .take(ps.limit) - .getMany(); + if (!clip.isPublic && (me == null || (clip.userId !== me.id))) { + throw new ApiError(meta.errors.noSuchClip); + } - return await Notes.packMany(notes, user); -}); + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .innerJoin(this.clipNotesRepository.metadata.targetName, 'clipNote', 'clipNote.noteId = note.id') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') + .andWhere('clipNote.clipId = :clipId', { clipId: clip.id }); + + if (me) { + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + } + + const notes = await query + .take(ps.limit) + .getMany(); + + return await this.noteEntityService.packMany(notes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/clips/remove-note.ts b/packages/backend/src/server/api/endpoints/clips/remove-note.ts index 8b90e31f65..3fc60e3639 100644 --- a/packages/backend/src/server/api/endpoints/clips/remove-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/remove-note.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; -import { ClipNotes, Clips } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getNote } from '../../common/getters.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['account', 'notes', 'clips'], @@ -35,23 +37,36 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.findOneBy({ - id: ps.clipId, - userId: user.id, - }); - - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + + @Inject(DI.clipNotesRepository) + private clipNotesRepository: ClipNotesRepository, + + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const clip = await this.clipsRepository.findOneBy({ + id: ps.clipId, + userId: me.id, + }); - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); + if (clip == null) { + throw new ApiError(meta.errors.noSuchClip); + } - await ClipNotes.delete({ - noteId: note.id, - clipId: clip.id, - }); -}); + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + await this.clipNotesRepository.delete({ + noteId: note.id, + clipId: clip.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts index c3d73c168d..4e93540054 100644 --- a/packages/backend/src/server/api/endpoints/clips/show.ts +++ b/packages/backend/src/server/api/endpoints/clips/show.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ClipsRepository } from '@/models/index.js'; +import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Clips } from '@/models/index.js'; export const meta = { tags: ['clips', 'account'], @@ -33,19 +36,29 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the clip - const clip = await Clips.findOneBy({ - id: ps.clipId, - }); - - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, - if (!clip.isPublic && (me == null || (clip.userId !== me.id))) { - throw new ApiError(meta.errors.noSuchClip); - } + private clipEntityService: ClipEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the clip + const clip = await this.clipsRepository.findOneBy({ + id: ps.clipId, + }); + + if (clip == null) { + throw new ApiError(meta.errors.noSuchClip); + } - return await Clips.pack(clip); -}); + if (!clip.isPublic && (me == null || (clip.userId !== me.id))) { + throw new ApiError(meta.errors.noSuchClip); + } + + return await this.clipEntityService.pack(clip); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts index b67d844f6e..9880505d06 100644 --- a/packages/backend/src/server/api/endpoints/clips/update.ts +++ b/packages/backend/src/server/api/endpoints/clips/update.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ClipsRepository } from '@/models/index.js'; +import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Clips } from '@/models/index.js'; export const meta = { tags: ['clips'], @@ -36,22 +39,32 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch the clip - const clip = await Clips.findOneBy({ - id: ps.clipId, - userId: user.id, - }); - - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + + private clipEntityService: ClipEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the clip + const clip = await this.clipsRepository.findOneBy({ + id: ps.clipId, + userId: me.id, + }); - await Clips.update(clip.id, { - name: ps.name, - description: ps.description, - isPublic: ps.isPublic, - }); + if (clip == null) { + throw new ApiError(meta.errors.noSuchClip); + } - return await Clips.pack(clip.id); -}); + await this.clipsRepository.update(clip.id, { + name: ps.name, + description: ps.description, + isPublic: ps.isPublic, + }); + + return await this.clipEntityService.pack(clip.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts index 82497adefa..6f40225f15 100644 --- a/packages/backend/src/server/api/endpoints/drive.ts +++ b/packages/backend/src/server/api/endpoints/drive.ts @@ -1,6 +1,7 @@ -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { DriveFiles } from '@/models/index.js'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; export const meta = { tags: ['drive', 'account'], @@ -32,14 +33,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const instance = await fetchMeta(true); - - // Calculate drive usage - const usage = await DriveFiles.calcDriveUsageOf(user.id); - - return { - capacity: 1024 * 1024 * (user.driveCapacityOverrideMb || instance.localDriveCapacityMb), - usage: usage, - }; -}); +@Injectable() +export default class extends Endpoint { + constructor( + private metaService: MetaService, + private driveFileEntityService: DriveFileEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const instance = await this.metaService.fetch(true); + + // Calculate drive usage + const usage = await this.driveFileEntityService.calcDriveUsageOf(me.id); + + return { + capacity: 1024 * 1024 * (me.driveCapacityOverrideMb ?? instance.localDriveCapacityMb), + usage: usage, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index 40e6c16c9c..56055d1340 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { DriveFiles } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -33,25 +36,36 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId) - .andWhere('file.userId = :userId', { userId: user.id }); - - if (ps.folderId) { - query.andWhere('file.folderId = :folderId', { folderId: ps.folderId }); - } else { - query.andWhere('file.folderId IS NULL'); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (ps.type) { - if (ps.type.endsWith('/*')) { - query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); - } else { - query.andWhere('file.type = :type', { type: ps.type }); - } - } + private driveFileEntityService: DriveFileEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId) + .andWhere('file.userId = :userId', { userId: me.id }); + + if (ps.folderId) { + query.andWhere('file.folderId = :folderId', { folderId: ps.folderId }); + } else { + query.andWhere('file.folderId IS NULL'); + } - const files = await query.take(ps.limit).getMany(); + if (ps.type) { + if (ps.type.endsWith('/*')) { + query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); + } else { + query.andWhere('file.type = :type', { type: ps.type }); + } + } - return await DriveFiles.packMany(files, { detail: false, self: true }); -}); + const files = await query.take(ps.limit).getMany(); + + return await this.driveFileEntityService.packMany(files, { detail: false, self: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index 415a8cc693..9f11eb8b53 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NotesRepository, DriveFilesRepository } from '@/models/index.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFiles, Notes } from '@/models/index.js'; export const meta = { tags: ['drive', 'notes'], @@ -39,22 +42,35 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch file - const file = await DriveFiles.findOneBy({ - id: ps.fileId, - userId: user.id, - }); - - if (file == null) { - throw new ApiError(meta.errors.noSuchFile); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch file + const file = await this.driveFilesRepository.findOneBy({ + id: ps.fileId, + userId: me.id, + }); - const notes = await Notes.createQueryBuilder('note') - .where(':file = ANY(note.fileIds)', { file: file.id }) - .getMany(); + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } - return await Notes.packMany(notes, user, { - detail: true, - }); -}); + const notes = await this.notesRepository.createQueryBuilder('note') + .where(':file = ANY(note.fileIds)', { file: file.id }) + .getMany(); + + return await this.noteEntityService.packMany(notes, me, { + detail: true, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index bbae9bf4e4..176031d808 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { DriveFiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -25,11 +27,19 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOneBy({ - md5: ps.md5, - userId: user.id, - }); - - return file != null; -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ + md5: ps.md5, + userId: me.id, + }); + + return file != null; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index ddcbd62889..bff83876d7 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -1,11 +1,13 @@ import ms from 'ms'; -import { addFile } from '@/services/drive/add-file.js'; -import { DriveFiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DriveFilesRepository } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import define from '../../../define.js'; -import { apiLogger } from '../../../logger.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DriveService } from '@/core/DriveService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -64,48 +66,60 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user, _, file, cleanup, ip, headers) => { - // Get 'name' parameter - let name = ps.name || file.originalname; - if (name !== undefined && name !== null) { - name = name.trim(); - if (name.length === 0) { - name = null; - } else if (name === 'blob') { - name = null; - } else if (!DriveFiles.validateFileName(name)) { - throw new ApiError(meta.errors.invalidFileName); - } - } else { - name = null; - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - const meta = await fetchMeta(); + private driveFileEntityService: DriveFileEntityService, + private metaService: MetaService, + private driveService: DriveService, + ) { + super(meta, paramDef, async (ps, me, _, file, cleanup, ip, headers) => { + // Get 'name' parameter + let name = ps.name ?? file.originalname; + if (name !== undefined && name !== null) { + name = name.trim(); + if (name.length === 0) { + name = null; + } else if (name === 'blob') { + name = null; + } else if (!this.driveFileEntityService.validateFileName(name)) { + throw new ApiError(meta.errors.invalidFileName); + } + } else { + name = null; + } - try { - // Create file - const driveFile = await addFile({ - user, - path: file.path, - name, - comment: ps.comment, - folderId: ps.folderId, - force: ps.force, - sensitive: ps.isSensitive, - requestIp: meta.enableIpLogging ? ip : null, - requestHeaders: meta.enableIpLogging ? headers : null, - }); - return await DriveFiles.pack(driveFile, { self: true }); - } catch (e) { - if (e instanceof Error || typeof e === 'string') { - apiLogger.error(e); - } - if (e instanceof IdentifiableError) { - if (e.id === '282f77bf-5816-4f72-9264-aa14d8261a21') throw new ApiError(meta.errors.inappropriate); - if (e.id === 'c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6') throw new ApiError(meta.errors.noFreeSpace); - } - throw new ApiError(); - } finally { + const meta = await this.metaService.fetch(); + + try { + // Create file + const driveFile = await this.driveService.addFile({ + user: me, + path: file.path, + name, + comment: ps.comment, + folderId: ps.folderId, + force: ps.force, + sensitive: ps.isSensitive, + requestIp: meta.enableIpLogging ? ip : null, + requestHeaders: meta.enableIpLogging ? headers : null, + }); + return await this.driveFileEntityService.pack(driveFile, { self: true }); + } catch (err) { + if (err instanceof Error || typeof err === 'string') { + console.error(err); + } + if (err instanceof IdentifiableError) { + if (err.id === '282f77bf-5816-4f72-9264-aa14d8261a21') throw new ApiError(meta.errors.inappropriate); + if (err.id === 'c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6') throw new ApiError(meta.errors.noFreeSpace); + } + throw new ApiError(); + } finally { cleanup!(); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index 6108ae7da9..9d2ea6011a 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -1,8 +1,10 @@ -import { deleteFile } from '@/services/drive/delete-file.js'; -import { publishDriveStream } from '@/services/stream.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DriveService } from '@/core/DriveService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFiles, Users } from '@/models/index.js'; export const meta = { tags: ['drive'], @@ -37,20 +39,31 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOneBy({ id: ps.fileId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (file == null) { - throw new ApiError(meta.errors.noSuchFile); - } + private driveService: DriveService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); - if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { - throw new ApiError(meta.errors.accessDenied); - } + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } - // Delete - await deleteFile(file); + if ((!me.isAdmin && !me.isModerator) && (file.userId !== me.id)) { + throw new ApiError(meta.errors.accessDenied); + } - // Publish fileDeleted event - publishDriveStream(user.id, 'fileDeleted', file.id); -}); + // Delete + await this.driveService.deleteFile(file); + + // Publish fileDeleted event + this.globalEventService.publishDriveStream(me.id, 'fileDeleted', file.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts index f2bc7348c6..6299ca8f6b 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts @@ -1,5 +1,8 @@ -import { DriveFiles } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DriveFilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -30,11 +33,21 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const files = await DriveFiles.findBy({ - md5: ps.md5, - userId: user.id, - }); - - return await DriveFiles.packMany(files, { self: true }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private driveFileEntityService: DriveFileEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const files = await this.driveFilesRepository.findBy({ + md5: ps.md5, + userId: me.id, + }); + + return await this.driveFileEntityService.packMany(files, { self: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts index 245fb45a65..e4cd5213dd 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; -import { DriveFiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -32,12 +35,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const files = await DriveFiles.findBy({ - name: ps.name, - userId: user.id, - folderId: ps.folderId ?? IsNull(), - }); - - return await Promise.all(files.map(file => DriveFiles.pack(file, { self: true }))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private driveFileEntityService: DriveFileEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const files = await this.driveFilesRepository.findBy({ + name: ps.name, + userId: me.id, + folderId: ps.folderId ?? IsNull(), + }); + + return await Promise.all(files.map(file => this.driveFileEntityService.pack(file, { self: true }))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index 2c604c54c8..bae4d7d66f 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -1,6 +1,9 @@ -import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFiles, Users } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -52,34 +55,44 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - let file: DriveFile | null = null; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (ps.fileId) { - file = await DriveFiles.findOneBy({ id: ps.fileId }); - } else if (ps.url) { - file = await DriveFiles.findOne({ - where: [{ - url: ps.url, - }, { - webpublicUrl: ps.url, - }, { - thumbnailUrl: ps.url, - }], - }); - } + private driveFileEntityService: DriveFileEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + let file: DriveFile | null = null; - if (file == null) { - throw new ApiError(meta.errors.noSuchFile); - } + if (ps.fileId) { + file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); + } else if (ps.url) { + file = await this.driveFilesRepository.findOne({ + where: [{ + url: ps.url, + }, { + webpublicUrl: ps.url, + }, { + thumbnailUrl: ps.url, + }], + }); + } - if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { - throw new ApiError(meta.errors.accessDenied); - } + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } - return await DriveFiles.pack(file, { - detail: true, - withUser: true, - self: true, - }); -}); + if ((!me.isAdmin && !me.isModerator) && (file.userId !== me.id)) { + throw new ApiError(meta.errors.accessDenied); + } + + return await this.driveFileEntityService.pack(file, { + detail: true, + withUser: true, + self: true, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index fa2ec8519c..03e3663f08 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -1,7 +1,10 @@ -import { publishDriveStream } from '@/services/stream.js'; -import { DriveFiles, DriveFolders, Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; -import define from '../../../define.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -59,54 +62,68 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOneBy({ id: ps.fileId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.driveFoldersRepository) + private driveFoldersRepository: DriveFoldersRepository, + + private driveFileEntityService: DriveFileEntityService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); + + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } - if (file == null) { - throw new ApiError(meta.errors.noSuchFile); - } + if ((!me.isAdmin && !me.isModerator) && (file.userId !== me.id)) { + throw new ApiError(meta.errors.accessDenied); + } - if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { - throw new ApiError(meta.errors.accessDenied); - } + if (ps.name) file.name = ps.name; + if (!this.driveFileEntityService.validateFileName(file.name)) { + throw new ApiError(meta.errors.invalidFileName); + } - if (ps.name) file.name = ps.name; - if (!DriveFiles.validateFileName(file.name)) { - throw new ApiError(meta.errors.invalidFileName); - } + if (ps.comment !== undefined) file.comment = ps.comment; - if (ps.comment !== undefined) file.comment = ps.comment; + if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive; - if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive; + if (ps.folderId !== undefined) { + if (ps.folderId === null) { + file.folderId = null; + } else { + const folder = await this.driveFoldersRepository.findOneBy({ + id: ps.folderId, + userId: me.id, + }); - if (ps.folderId !== undefined) { - if (ps.folderId === null) { - file.folderId = null; - } else { - const folder = await DriveFolders.findOneBy({ - id: ps.folderId, - userId: user.id, - }); + if (folder == null) { + throw new ApiError(meta.errors.noSuchFolder); + } - if (folder == null) { - throw new ApiError(meta.errors.noSuchFolder); + file.folderId = folder.id; + } } - file.folderId = folder.id; - } - } - - await DriveFiles.update(file.id, { - name: file.name, - comment: file.comment, - folderId: file.folderId, - isSensitive: file.isSensitive, - }); + await this.driveFilesRepository.update(file.id, { + name: file.name, + comment: file.comment, + folderId: file.folderId, + isSensitive: file.isSensitive, + }); - const fileObj = await DriveFiles.pack(file, { self: true }); + const fileObj = await this.driveFileEntityService.pack(file, { self: true }); - // Publish fileUpdated event - publishDriveStream(user.id, 'fileUpdated', fileObj); + // Publish fileUpdated event + this.globalEventService.publishDriveStream(me.id, 'fileUpdated', fileObj); - return fileObj; -}); + return fileObj; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index eb8071c3c9..f4f8df3c2b 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -1,9 +1,12 @@ import ms from 'ms'; -import { uploadFromUrl } from '@/services/drive/upload-from-url.js'; -import { DriveFiles } from '@/models/index.js'; -import { publishMainStream } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DriveFilesRepository } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; -import define from '../../../define.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DriveService } from '@/core/DriveService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -34,13 +37,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user, _1, _2, _3, ip, headers) => { - uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment, requestIp: ip, requestHeaders: headers }).then(file => { - DriveFiles.pack(file, { self: true }).then(packedFile => { - publishMainStream(user.id, 'urlUploadFinished', { - marker: ps.marker, - file: packedFile, +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private driveFileEntityService: DriveFileEntityService, + private driveService: DriveService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, user, _1, _2, _3, ip, headers) => { + this.driveService.uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment, requestIp: ip, requestHeaders: headers }).then(file => { + this.driveFileEntityService.pack(file, { self: true }).then(packedFile => { + this.globalEventService.publishMainStream(user.id, 'urlUploadFinished', { + marker: ps.marker, + file: packedFile, + }); + }); }); }); - }); -}); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/folders.ts b/packages/backend/src/server/api/endpoints/drive/folders.ts index d4d530ba9e..703dc83ecd 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { DriveFolders } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFoldersRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -32,17 +35,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - 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'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFoldersRepository) + private driveFoldersRepository: DriveFoldersRepository, + + private driveFolderEntityService: DriveFolderEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.driveFoldersRepository.createQueryBuilder('folder'), ps.sinceId, ps.untilId) + .andWhere('folder.userId = :userId', { userId: me.id }); + + if (ps.folderId) { + query.andWhere('folder.parentId = :parentId', { parentId: ps.folderId }); + } else { + query.andWhere('folder.parentId IS NULL'); + } + + const folders = await query.take(ps.limit).getMany(); + + return await Promise.all(folders.map(folder => this.driveFolderEntityService.pack(folder))); + }); } - - const folders = await query.take(ps.limit).getMany(); - - return await Promise.all(folders.map(folder => DriveFolders.pack(folder))); -}); +} diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts index 3d7f514c85..7604eaf489 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts @@ -1,8 +1,11 @@ -import { publishDriveStream } from '@/services/stream.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFoldersRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFolders } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; export const meta = { tags: ['drive'], @@ -29,41 +32,53 @@ export const meta = { export const paramDef = { type: 'object', properties: { - name: { type: 'string', default: "Untitled", maxLength: 200 }, + name: { type: 'string', default: 'Untitled', maxLength: 200 }, parentId: { type: 'string', format: 'misskey:id', nullable: true }, }, required: [], } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // If the parent folder is specified - let parent = null; - if (ps.parentId) { - // Fetch parent folder - parent = await DriveFolders.findOneBy({ - id: ps.parentId, - userId: user.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFoldersRepository) + private driveFoldersRepository: DriveFoldersRepository, - if (parent == null) { - throw new ApiError(meta.errors.noSuchFolder); - } - } + private driveFolderEntityService: DriveFolderEntityService, + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // If the parent folder is specified + let parent = null; + if (ps.parentId) { + // Fetch parent folder + parent = await this.driveFoldersRepository.findOneBy({ + id: ps.parentId, + userId: me.id, + }); + + if (parent == null) { + throw new ApiError(meta.errors.noSuchFolder); + } + } - // Create folder - const folder = await DriveFolders.insert({ - id: genId(), - createdAt: new Date(), - name: ps.name, - parentId: parent !== null ? parent.id : null, - userId: user.id, - }).then(x => DriveFolders.findOneByOrFail(x.identifiers[0])); + // Create folder + const folder = await this.driveFoldersRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + name: ps.name, + parentId: parent !== null ? parent.id : null, + userId: me.id, + }).then(x => this.driveFoldersRepository.findOneByOrFail(x.identifiers[0])); - const folderObj = await DriveFolders.pack(folder); + const folderObj = await this.driveFolderEntityService.pack(folder); - // Publish folderCreated event - publishDriveStream(user.id, 'folderCreated', folderObj); + // Publish folderCreated event + this.globalEventService.publishDriveStream(me.id, 'folderCreated', folderObj); - return folderObj; -}); + return folderObj; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts index ab9d411ec0..dcbaecf8af 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; -import { publishDriveStream } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFoldersRepository, DriveFilesRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFolders, DriveFiles } from '@/models/index.js'; export const meta = { tags: ['drive'], @@ -34,28 +36,41 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Get folder - const folder = await DriveFolders.findOneBy({ - id: ps.folderId, - userId: user.id, - }); - - if (folder == null) { - throw new ApiError(meta.errors.noSuchFolder); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - const [childFoldersCount, childFilesCount] = await Promise.all([ - DriveFolders.countBy({ parentId: folder.id }), - DriveFiles.countBy({ folderId: folder.id }), - ]); + @Inject(DI.driveFoldersRepository) + private driveFoldersRepository: DriveFoldersRepository, - if (childFoldersCount !== 0 || childFilesCount !== 0) { - throw new ApiError(meta.errors.hasChildFilesOrFolders); - } + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Get folder + const folder = await this.driveFoldersRepository.findOneBy({ + id: ps.folderId, + userId: me.id, + }); + + if (folder == null) { + throw new ApiError(meta.errors.noSuchFolder); + } - await DriveFolders.delete(folder.id); + const [childFoldersCount, childFilesCount] = await Promise.all([ + this.driveFoldersRepository.countBy({ parentId: folder.id }), + this.driveFilesRepository.countBy({ folderId: folder.id }), + ]); - // Publish folderCreated event - publishDriveStream(user.id, 'folderDeleted', folder.id); -}); + if (childFoldersCount !== 0 || childFilesCount !== 0) { + throw new ApiError(meta.errors.hasChildFilesOrFolders); + } + + await this.driveFoldersRepository.delete(folder.id); + + // Publish folderCreated event + this.globalEventService.publishDriveStream(me.id, 'folderDeleted', folder.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/folders/find.ts b/packages/backend/src/server/api/endpoints/drive/folders/find.ts index 1feab273a1..96a87344a9 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/find.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; -import { DriveFolders } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFoldersRepository } from '@/models/index.js'; +import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -30,12 +33,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const folders = await DriveFolders.findBy({ - name: ps.name, - userId: user.id, - parentId: ps.parentId ?? IsNull(), - }); - - return await Promise.all(folders.map(folder => DriveFolders.pack(folder))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFoldersRepository) + private driveFoldersRepository: DriveFoldersRepository, + + private driveFolderEntityService: DriveFolderEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const folders = await this.driveFoldersRepository.findBy({ + name: ps.name, + userId: me.id, + parentId: ps.parentId ?? IsNull(), + }); + + return await Promise.all(folders.map(folder => this.driveFolderEntityService.pack(folder))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/folders/show.ts b/packages/backend/src/server/api/endpoints/drive/folders/show.ts index 1e7aa2b16c..4c25bc705c 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/show.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFoldersRepository } from '@/models/index.js'; +import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFolders } from '@/models/index.js'; export const meta = { tags: ['drive'], @@ -33,18 +36,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Get folder - const folder = await DriveFolders.findOneBy({ - id: ps.folderId, - userId: user.id, - }); - - if (folder == null) { - throw new ApiError(meta.errors.noSuchFolder); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFoldersRepository) + private driveFoldersRepository: DriveFoldersRepository, + + private driveFolderEntityService: DriveFolderEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Get folder + const folder = await this.driveFoldersRepository.findOneBy({ + id: ps.folderId, + userId: me.id, + }); + + if (folder == null) { + throw new ApiError(meta.errors.noSuchFolder); + } + + return await this.driveFolderEntityService.pack(folder, { + detail: true, + }); + }); } - - return await DriveFolders.pack(folder, { - detail: true, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts index 1aa2e84292..4fcd37bbbf 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts @@ -1,7 +1,10 @@ -import { publishDriveStream } from '@/services/stream.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFoldersRepository } from '@/models/index.js'; +import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFolders } from '@/models/index.js'; export const meta = { tags: ['drive'], @@ -48,71 +51,82 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch folder - const folder = await DriveFolders.findOneBy({ - id: ps.folderId, - userId: user.id, - }); - - if (folder == null) { - throw new ApiError(meta.errors.noSuchFolder); - } - - if (ps.name) folder.name = ps.name; - - if (ps.parentId !== undefined) { - 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 DriveFolders.findOneBy({ - id: ps.parentId, - userId: user.id, +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFoldersRepository) + private driveFoldersRepository: DriveFoldersRepository, + + private driveFolderEntityService: DriveFolderEntityService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch folder + const folder = await this.driveFoldersRepository.findOneBy({ + id: ps.folderId, + userId: me.id, }); - if (parent == null) { - throw new ApiError(meta.errors.noSuchParentFolder); + if (folder == null) { + throw new ApiError(meta.errors.noSuchFolder); } - // Check if the circular reference will occur - async function checkCircle(folderId: string): Promise { - // Fetch folder - const folder2 = await DriveFolders.findOneBy({ - id: folderId, - }); - - if (folder2!.id === folder!.id) { - return true; - } else if (folder2!.parentId) { - return await checkCircle(folder2!.parentId); - } else { - return false; - } - } + if (ps.name) folder.name = ps.name; - if (parent.parentId !== null) { - if (await checkCircle(parent.parentId)) { + if (ps.parentId !== undefined) { + 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 this.driveFoldersRepository.findOneBy({ + id: ps.parentId, + userId: me.id, + }); + + if (parent == null) { + throw new ApiError(meta.errors.noSuchParentFolder); + } + + // Check if the circular reference will occur + const checkCircle = async (folderId: string): Promise => { + // Fetch folder + const folder2 = await this.driveFoldersRepository.findOneBy({ + id: folderId, + }); + + if (folder2!.id === folder!.id) { + return true; + } else if (folder2!.parentId) { + return await checkCircle(folder2!.parentId); + } else { + return false; + } + }; + + if (parent.parentId !== null) { + if (await checkCircle(parent.parentId)) { + throw new ApiError(meta.errors.recursiveNesting); + } + } + + folder.parentId = parent.id; } } - folder.parentId = parent.id; - } - } - - // Update - DriveFolders.update(folder.id, { - name: folder.name, - parentId: folder.parentId, - }); + // Update + this.driveFoldersRepository.update(folder.id, { + name: folder.name, + parentId: folder.parentId, + }); - const folderObj = await DriveFolders.pack(folder); + const folderObj = await this.driveFolderEntityService.pack(folder); - // Publish folderUpdated event - publishDriveStream(user.id, 'folderUpdated', folderObj); + // Publish folderUpdated event + this.globalEventService.publishDriveStream(me.id, 'folderUpdated', folderObj); - return folderObj; -}); + return folderObj; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/stream.ts b/packages/backend/src/server/api/endpoints/drive/stream.ts index 99e8d024fb..aba73c2092 100644 --- a/packages/backend/src/server/api/endpoints/drive/stream.ts +++ b/packages/backend/src/server/api/endpoints/drive/stream.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { DriveFiles } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -32,19 +35,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId) - .andWhere('file.userId = :userId', { userId: user.id }); - - if (ps.type) { - if (ps.type.endsWith('/*')) { - query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); - } else { - query.andWhere('file.type = :type', { type: ps.type }); - } - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private driveFileEntityService: DriveFileEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId) + .andWhere('file.userId = :userId', { userId: me.id }); - const files = await query.take(ps.limit).getMany(); + if (ps.type) { + if (ps.type.endsWith('/*')) { + query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); + } else { + query.andWhere('file.type = :type', { type: ps.type }); + } + } - return await DriveFiles.packMany(files, { detail: false, self: true }); -}); + const files = await query.take(ps.limit).getMany(); + + return await this.driveFileEntityService.packMany(files, { detail: false, self: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/email-address/available.ts b/packages/backend/src/server/api/endpoints/email-address/available.ts index 07064ce9fa..8a497a514e 100644 --- a/packages/backend/src/server/api/endpoints/email-address/available.ts +++ b/packages/backend/src/server/api/endpoints/email-address/available.ts @@ -1,5 +1,6 @@ -import define from '../../define.js'; -import { validateEmailForAccount } from '@/services/validate-email-for-account.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmailService } from '@/core/EmailService.js'; export const meta = { tags: ['users'], @@ -31,6 +32,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await validateEmailForAccount(ps.emailAddress); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private emailService: EmailService, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.emailService.validateEmailForAccount(ps.emailAddress); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts index c174126779..2141dfbeb0 100644 --- a/packages/backend/src/server/api/endpoints/endpoint.ts +++ b/packages/backend/src/server/api/endpoints/endpoint.ts @@ -1,4 +1,5 @@ -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; import endpoints from '../endpoints.js'; export const meta = { @@ -16,13 +17,19 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const ep = endpoints.find(x => x.name === ps.endpoint); - if (ep == null) return null; - return { - params: Object.entries(ep.params.properties || {}).map(([k, v]) => ({ - name: k, - type: v.type.charAt(0).toUpperCase() + v.type.slice(1), - })), - }; -}); +@Injectable() +export default class extends Endpoint { + constructor( + ) { + super(meta, paramDef, async (ps) => { + const ep = endpoints.find(x => x.name === ps.endpoint); + if (ep == null) return null; + return { + params: Object.entries(ep.params.properties || {}).map(([k, v]) => ({ + name: k, + type: v.type.charAt(0).toUpperCase() + v.type.slice(1), + })), + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/endpoints.ts b/packages/backend/src/server/api/endpoints/endpoints.ts index b20da96eb3..91fc3ec98d 100644 --- a/packages/backend/src/server/api/endpoints/endpoints.ts +++ b/packages/backend/src/server/api/endpoints/endpoints.ts @@ -1,4 +1,5 @@ -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; import endpoints from '../endpoints.js'; export const meta = { @@ -29,6 +30,12 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - return endpoints.map(x => x.name); -}); +@Injectable() +export default class extends Endpoint { + constructor( + ) { + super(meta, paramDef, async () => { + return endpoints.map(x => x.name); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts index 5fe622932d..ead6b037cc 100644 --- a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts +++ b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts @@ -1,6 +1,7 @@ import ms from 'ms'; -import { createExportCustomEmojisJob } from '@/queue/index.js'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, @@ -18,6 +19,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - createExportCustomEmojisJob(user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportCustomEmojisJob(me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/federation/followers.ts b/packages/backend/src/server/api/endpoints/federation/followers.ts index 7b1197d1e5..e5222fcbfd 100644 --- a/packages/backend/src/server/api/endpoints/federation/followers.ts +++ b/packages/backend/src/server/api/endpoints/federation/followers.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Followings } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { FollowingsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['federation'], @@ -30,13 +33,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) - .andWhere(`following.followeeHost = :host`, { host: ps.host }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - const followings = await query - .take(ps.limit) - .getMany(); + private followingEntityService: FollowingEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId) + .andWhere('following.followeeHost = :host', { host: ps.host }); - return await Followings.packMany(followings, me, { populateFollowee: true }); -}); + const followings = await query + .take(ps.limit) + .getMany(); + + return await this.followingEntityService.packMany(followings, me, { populateFollowee: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/federation/following.ts b/packages/backend/src/server/api/endpoints/federation/following.ts index ed1f142d88..a20c5a31b3 100644 --- a/packages/backend/src/server/api/endpoints/federation/following.ts +++ b/packages/backend/src/server/api/endpoints/federation/following.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Followings } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { FollowingsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['federation'], @@ -30,13 +33,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) - .andWhere(`following.followerHost = :host`, { host: ps.host }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - const followings = await query - .take(ps.limit) - .getMany(); + private followingEntityService: FollowingEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId) + .andWhere('following.followerHost = :host', { host: ps.host }); - return await Followings.packMany(followings, me, { populateFollowee: true }); -}); + const followings = await query + .take(ps.limit) + .getMany(); + + return await this.followingEntityService.packMany(followings, me, { populateFollowee: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 07e5c07c6a..e7f8cefff5 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -1,7 +1,9 @@ -import config from '@/config/index.js'; -import define from '../../define.js'; -import { Instances } from '@/models/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { InstancesRepository } from '@/models/index.js'; +import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['federation'], @@ -37,82 +39,93 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Instances.createQueryBuilder('instance'); - - switch (ps.sort) { - case '+pubSub': query.orderBy('instance.followingCount', 'DESC').orderBy('instance.followersCount', 'DESC'); break; - case '-pubSub': query.orderBy('instance.followingCount', 'ASC').orderBy('instance.followersCount', 'ASC'); break; - case '+notes': query.orderBy('instance.notesCount', 'DESC'); break; - case '-notes': query.orderBy('instance.notesCount', 'ASC'); break; - case '+users': query.orderBy('instance.usersCount', 'DESC'); break; - case '-users': query.orderBy('instance.usersCount', 'ASC'); break; - case '+following': query.orderBy('instance.followingCount', 'DESC'); break; - case '-following': query.orderBy('instance.followingCount', 'ASC'); break; - case '+followers': query.orderBy('instance.followersCount', 'DESC'); break; - case '-followers': 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; - - default: query.orderBy('instance.id', 'DESC'); break; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, + + private instanceEntityService: InstanceEntityService, + private metaService: MetaService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.instancesRepository.createQueryBuilder('instance'); + + switch (ps.sort) { + case '+pubSub': query.orderBy('instance.followingCount', 'DESC').orderBy('instance.followersCount', 'DESC'); break; + case '-pubSub': query.orderBy('instance.followingCount', 'ASC').orderBy('instance.followersCount', 'ASC'); break; + case '+notes': query.orderBy('instance.notesCount', 'DESC'); break; + case '-notes': query.orderBy('instance.notesCount', 'ASC'); break; + case '+users': query.orderBy('instance.usersCount', 'DESC'); break; + case '-users': query.orderBy('instance.usersCount', 'ASC'); break; + case '+following': query.orderBy('instance.followingCount', 'DESC'); break; + case '-following': query.orderBy('instance.followingCount', 'ASC'); break; + case '+followers': query.orderBy('instance.followersCount', 'DESC'); break; + case '-followers': 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; + + default: query.orderBy('instance.id', 'DESC'); break; + } + + if (typeof ps.blocked === 'boolean') { + const meta = await this.metaService.fetch(true); + if (ps.blocked) { + query.andWhere('instance.host IN (:...blocks)', { blocks: meta.blockedHosts }); + } else { + query.andWhere('instance.host NOT IN (:...blocks)', { blocks: meta.blockedHosts }); + } + } + + if (typeof ps.notResponding === 'boolean') { + if (ps.notResponding) { + query.andWhere('instance.isNotResponding = TRUE'); + } else { + query.andWhere('instance.isNotResponding = FALSE'); + } + } + + if (typeof ps.suspended === 'boolean') { + if (ps.suspended) { + query.andWhere('instance.isSuspended = TRUE'); + } else { + query.andWhere('instance.isSuspended = FALSE'); + } + } + + if (typeof ps.federating === 'boolean') { + if (ps.federating) { + query.andWhere('((instance.followingCount > 0) OR (instance.followersCount > 0))'); + } else { + query.andWhere('((instance.followingCount = 0) AND (instance.followersCount = 0))'); + } + } + + if (typeof ps.subscribing === 'boolean') { + if (ps.subscribing) { + query.andWhere('instance.followersCount > 0'); + } else { + query.andWhere('instance.followersCount = 0'); + } + } + + if (typeof ps.publishing === 'boolean') { + if (ps.publishing) { + query.andWhere('instance.followingCount > 0'); + } else { + query.andWhere('instance.followingCount = 0'); + } + } + + if (ps.host) { + query.andWhere('instance.host like :host', { host: '%' + ps.host.toLowerCase() + '%' }); + } + + const instances = await query.take(ps.limit).skip(ps.offset).getMany(); + + return await this.instanceEntityService.packMany(instances); + }); } - - if (typeof ps.blocked === 'boolean') { - const meta = await fetchMeta(true); - if (ps.blocked) { - query.andWhere('instance.host IN (:...blocks)', { blocks: meta.blockedHosts }); - } else { - query.andWhere('instance.host NOT IN (:...blocks)', { blocks: meta.blockedHosts }); - } - } - - if (typeof ps.notResponding === 'boolean') { - if (ps.notResponding) { - query.andWhere('instance.isNotResponding = TRUE'); - } else { - query.andWhere('instance.isNotResponding = FALSE'); - } - } - - if (typeof ps.suspended === 'boolean') { - if (ps.suspended) { - query.andWhere('instance.isSuspended = TRUE'); - } else { - query.andWhere('instance.isSuspended = FALSE'); - } - } - - if (typeof ps.federating === 'boolean') { - if (ps.federating) { - query.andWhere('((instance.followingCount > 0) OR (instance.followersCount > 0))'); - } else { - query.andWhere('((instance.followingCount = 0) AND (instance.followersCount = 0))'); - } - } - - if (typeof ps.subscribing === 'boolean') { - if (ps.subscribing) { - query.andWhere('instance.followersCount > 0'); - } else { - query.andWhere('instance.followersCount = 0'); - } - } - - if (typeof ps.publishing === 'boolean') { - if (ps.publishing) { - query.andWhere('instance.followingCount > 0'); - } else { - query.andWhere('instance.followingCount = 0'); - } - } - - if (ps.host) { - query.andWhere('instance.host like :host', { host: '%' + ps.host.toLowerCase() + '%' }); - } - - const instances = await query.take(ps.limit).skip(ps.offset).getMany(); - - return await Instances.packMany(instances); -}); +} diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts index 2fbb8a15cb..f855b54537 100644 --- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts +++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Instances } from '@/models/index.js'; -import { toPuny } from '@/misc/convert-host.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { InstancesRepository } from '@/models/index.js'; +import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['federation'], @@ -26,9 +29,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const instance = await Instances - .findOneBy({ host: toPuny(ps.host) }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, - return instance ? await Instances.pack(instance) : null; -}); + private utilityService: UtilityService, + private instanceEntityService: InstanceEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const instance = await this.instancesRepository + .findOneBy({ host: this.utilityService.toPuny(ps.host) }); + + return instance ? await this.instanceEntityService.pack(instance) : null; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/federation/stats.ts b/packages/backend/src/server/api/endpoints/federation/stats.ts index e02c7b97e0..d07a08637f 100644 --- a/packages/backend/src/server/api/endpoints/federation/stats.ts +++ b/packages/backend/src/server/api/endpoints/federation/stats.ts @@ -1,7 +1,10 @@ import { IsNull, MoreThan, Not } from 'typeorm'; -import { Followings, Instances } from '@/models/index.js'; -import { awaitAll } from '@/prelude/await-all.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { FollowingsRepository, InstancesRepository } from '@/models/index.js'; +import { awaitAll } from '@/misc/prelude/await-all.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['federation'], @@ -21,45 +24,58 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const [topSubInstances, topPubInstances, allSubCount, allPubCount] = await Promise.all([ - Instances.find({ - where: { - followersCount: MoreThan(0), - }, - order: { - followersCount: 'DESC', - }, - take: ps.limit, - }), - Instances.find({ - where: { - followingCount: MoreThan(0), - }, - order: { - followingCount: 'DESC', - }, - take: ps.limit, - }), - Followings.count({ - where: { - followeeHost: Not(IsNull()), - }, - }), - Followings.count({ - where: { - followerHost: Not(IsNull()), - }, - }), - ]); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, - const gotSubCount = topSubInstances.map(x => x.followersCount).reduce((a, b) => a + b, 0); - const gotPubCount = topPubInstances.map(x => x.followingCount).reduce((a, b) => a + b, 0); + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - return await awaitAll({ - topSubInstances: Instances.packMany(topSubInstances), - otherFollowersCount: Math.max(0, allSubCount - gotSubCount), - topPubInstances: Instances.packMany(topPubInstances), - otherFollowingCount: Math.max(0, allPubCount - gotPubCount), - }); -}); + private instanceEntityService: InstanceEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const [topSubInstances, topPubInstances, allSubCount, allPubCount] = await Promise.all([ + this.instancesRepository.find({ + where: { + followersCount: MoreThan(0), + }, + order: { + followersCount: 'DESC', + }, + take: ps.limit, + }), + this.instancesRepository.find({ + where: { + followingCount: MoreThan(0), + }, + order: { + followingCount: 'DESC', + }, + take: ps.limit, + }), + this.followingsRepository.count({ + where: { + followeeHost: Not(IsNull()), + }, + }), + this.followingsRepository.count({ + where: { + followerHost: Not(IsNull()), + }, + }), + ]); + + const gotSubCount = topSubInstances.map(x => x.followersCount).reduce((a, b) => a + b, 0); + const gotPubCount = topPubInstances.map(x => x.followingCount).reduce((a, b) => a + b, 0); + + return await awaitAll({ + topSubInstances: this.instanceEntityService.packMany(topSubInstances), + otherFollowersCount: Math.max(0, allSubCount - gotSubCount), + topPubInstances: this.instanceEntityService.packMany(topPubInstances), + otherFollowingCount: Math.max(0, allPubCount - gotPubCount), + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts index 409cc7695e..57497bbf62 100644 --- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts +++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts @@ -1,6 +1,7 @@ -import define from '../../define.js'; -import { getRemoteUser } from '../../common/getters.js'; -import { updatePerson } from '@/remote/activitypub/models/person.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ApPersonService } from '@/core/remote/activitypub/models/ApPersonService.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['federation'], @@ -17,7 +18,15 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const user = await getRemoteUser(ps.userId); - await updatePerson(user.uri!); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private getterService: GetterService, + private apPersonService: ApPersonService, + ) { + super(meta, paramDef, async (ps) => { + const user = await this.getterService.getRemoteUser(ps.userId); + await this.apPersonService.updatePerson(user.uri!); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts index 65ad9f88d3..0400cacd02 100644 --- a/packages/backend/src/server/api/endpoints/federation/users.ts +++ b/packages/backend/src/server/api/endpoints/federation/users.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Users } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['federation'], @@ -30,13 +33,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(Users.createQueryBuilder('user'), ps.sinceId, ps.untilId) - .andWhere(`user.host = :host`, { host: ps.host }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - const users = await query - .take(ps.limit) - .getMany(); + private userEntityService: UserEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.usersRepository.createQueryBuilder('user'), ps.sinceId, ps.untilId) + .andWhere('user.host = :host', { host: ps.host }); - return await Users.packMany(users, me, { detail: true }); -}); + const users = await query + .take(ps.limit) + .getMany(); + + return await this.userEntityService.packMany(users, me, { detail: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/fetch-rss.ts b/packages/backend/src/server/api/endpoints/fetch-rss.ts index 05fa22a9e4..9e6a3cc717 100644 --- a/packages/backend/src/server/api/endpoints/fetch-rss.ts +++ b/packages/backend/src/server/api/endpoints/fetch-rss.ts @@ -1,7 +1,9 @@ import Parser from 'rss-parser'; -import { getResponse } from '@/misc/fetch.js'; -import config from '@/config/index.js'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; const rssParser = new Parser(); @@ -22,18 +24,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const res = await getResponse({ - url: ps.url, - method: 'GET', - headers: Object.assign({ - 'User-Agent': config.userAgent, - Accept: 'application/rss+xml, */*', - }), - timeout: 5000, - }); - - const text = await res.text(); - - return rssParser.parseString(text); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, + + private httpRequestService: HttpRequestService, + ) { + super(meta, paramDef, async (ps, me) => { + const res = await this.httpRequestService.getResponse({ + url: ps.url, + method: 'GET', + headers: Object.assign({ + 'User-Agent': config.userAgent, + Accept: 'application/rss+xml, */*', + }), + timeout: 5000, + }); + + const text = await res.text(); + + return rssParser.parseString(text); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 02a030cd5e..3a06c63d52 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -1,10 +1,13 @@ import ms from 'ms'; -import create from '@/services/following/create.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { Followings, Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['following', 'users'], @@ -66,39 +69,54 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const follower = user; - - // 自分自身 - if (user.id === ps.userId) { - throw new ApiError(meta.errors.followeeIsYourself); - } - - // Get followee - const followee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Check if already following - const exist = await Followings.findOneBy({ - followerId: follower.id, - followeeId: followee.id, - }); - - if (exist != null) { - throw new ApiError(meta.errors.alreadyFollowing); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + const follower = me; + + // 自分自身 + if (me.id === ps.userId) { + throw new ApiError(meta.errors.followeeIsYourself); + } + + // Get followee + const followee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check if already following + const exist = await this.followingsRepository.findOneBy({ + followerId: follower.id, + followeeId: followee.id, + }); + + if (exist != null) { + throw new ApiError(meta.errors.alreadyFollowing); + } + + try { + await this.userFollowingService.follow(follower, followee); + } catch (e) { + if (e instanceof IdentifiableError) { + if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError(meta.errors.blocking); + if (e.id === '3338392a-f764-498d-8855-db939dcf8c48') throw new ApiError(meta.errors.blocked); + } + throw e; + } + + return await this.userEntityService.pack(followee.id, me); + }); } - - try { - await create(follower, followee); - } catch (e) { - if (e instanceof IdentifiableError) { - if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError(meta.errors.blocking); - if (e.id === '3338392a-f764-498d-8855-db939dcf8c48') throw new ApiError(meta.errors.blocked); - } - throw e; - } - - return await Users.pack(followee.id, user); -}); +} diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 2f41b16e9a..07366bc821 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -1,9 +1,12 @@ import ms from 'ms'; -import deleteFollowing from '@/services/following/delete.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { Followings, Users } from '@/models/index.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['following', 'users'], @@ -53,31 +56,46 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const follower = user; - - // Check if the followee is yourself - if (user.id === ps.userId) { - throw new ApiError(meta.errors.followeeIsYourself); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + const follower = me; + + // Check if the followee is yourself + if (me.id === ps.userId) { + throw new ApiError(meta.errors.followeeIsYourself); + } + + // Get followee + const followee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check not following + const exist = await this.followingsRepository.findOneBy({ + followerId: follower.id, + followeeId: followee.id, + }); + + if (exist == null) { + throw new ApiError(meta.errors.notFollowing); + } + + await this.userFollowingService.unfollow(follower, followee); + + return await this.userEntityService.pack(followee.id, me); + }); } - - // Get followee - const followee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Check not following - const exist = await Followings.findOneBy({ - followerId: follower.id, - followeeId: followee.id, - }); - - if (exist == null) { - throw new ApiError(meta.errors.notFollowing); - } - - await deleteFollowing(follower, followee); - - return await Users.pack(followee.id, user); -}); +} diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index 18ec5affe8..8285189d66 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -1,9 +1,12 @@ import ms from 'ms'; -import deleteFollowing from '@/services/following/delete.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { Followings, Users } from '@/models/index.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['following', 'users'], @@ -53,31 +56,46 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const followee = user; - - // Check if the follower is yourself - if (user.id === ps.userId) { - throw new ApiError(meta.errors.followerIsYourself); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + const followee = me; + + // Check if the follower is yourself + if (me.id === ps.userId) { + throw new ApiError(meta.errors.followerIsYourself); + } + + // Get follower + const follower = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check not following + const exist = await this.followingsRepository.findOneBy({ + followerId: follower.id, + followeeId: followee.id, + }); + + if (exist == null) { + throw new ApiError(meta.errors.notFollowing); + } + + await this.userFollowingService.unfollow(follower, followee); + + return await this.userEntityService.pack(followee.id, me); + }); } - - // Get follower - const follower = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Check not following - const exist = await Followings.findOneBy({ - followerId: follower.id, - followeeId: followee.id, - }); - - if (exist == null) { - throw new ApiError(meta.errors.notFollowing); - } - - await deleteFollowing(follower, followee); - - return await Users.pack(followee.id, user); -}); +} diff --git a/packages/backend/src/server/api/endpoints/following/requests/accept.ts b/packages/backend/src/server/api/endpoints/following/requests/accept.ts index e5df55375e..5f082c087a 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/accept.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/accept.ts @@ -1,7 +1,8 @@ -import acceptFollowRequest from '@/services/following/requests/accept.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; export const meta = { tags: ['following', 'account'], @@ -33,17 +34,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch follower - const follower = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - await acceptFollowRequest(user, follower).catch(e => { - if (e.id === '8884c2dd-5795-4ac9-b27e-6a01d38190f9') throw new ApiError(meta.errors.noFollowRequest); - throw e; - }); - - return; -}); +@Injectable() +export default class extends Endpoint { + constructor( + private getterService: GetterService, + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch follower + const follower = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + await this.userFollowingService.acceptFollowRequest(me, follower).catch(err => { + if (err.id === '8884c2dd-5795-4ac9-b27e-6a01d38190f9') throw new ApiError(meta.errors.noFollowRequest); + throw err; + }); + + return; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts index 80d37fb075..0b79a80649 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts @@ -1,9 +1,13 @@ -import cancelFollowRequest from '@/services/following/requests/cancel.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; -import { Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { FollowingsRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../../error.js'; export const meta = { tags: ['following', 'account'], @@ -42,21 +46,33 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch followee - const followee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - try { - await cancelFollowRequest(followee, user); - } catch (e) { - if (e instanceof IdentifiableError) { - if (e.id === '17447091-ce07-46dd-b331-c1fd4f15b1e7') throw new ApiError(meta.errors.followRequestNotFound); - } - throw e; - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - return await Users.pack(followee.id, user); -}); + private userEntityService: UserEntityService, + private getterService: GetterService, + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch followee + const followee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + try { + await this.userFollowingService.cancelFollowRequest(followee, me); + } catch (err) { + if (err instanceof IdentifiableError) { + if (err.id === '17447091-ce07-46dd-b331-c1fd4f15b1e7') throw new ApiError(meta.errors.followRequestNotFound); + } + throw err; + } + + return await this.userEntityService.pack(followee.id, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/following/requests/list.ts b/packages/backend/src/server/api/endpoints/following/requests/list.ts index a8f42c481d..c36d4a077f 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/list.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts @@ -1,5 +1,8 @@ -import define from '../../../define.js'; -import { FollowRequests } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { FollowRequestsRepository } from '@/models/index.js'; +import { FollowRequestEntityService } from '@/core/entities/FollowRequestEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['following', 'account'], @@ -42,10 +45,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const reqs = await FollowRequests.findBy({ - followeeId: user.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.followRequestsRepository) + private followRequestsRepository: FollowRequestsRepository, - return await Promise.all(reqs.map(req => FollowRequests.pack(req))); -}); + private followRequestEntityService: FollowRequestEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const reqs = await this.followRequestsRepository.findBy({ + followeeId: me.id, + }); + + return await Promise.all(reqs.map(req => this.followRequestEntityService.pack(req))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/following/requests/reject.ts b/packages/backend/src/server/api/endpoints/following/requests/reject.ts index cebe604284..663c659bf3 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/reject.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/reject.ts @@ -1,7 +1,8 @@ -import { rejectFollowRequest } from '@/services/following/reject.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; export const meta = { tags: ['following', 'account'], @@ -28,14 +29,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch follower - const follower = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - await rejectFollowRequest(user, follower); - - return; -}); +@Injectable() +export default class extends Endpoint { + constructor( + private getterService: GetterService, + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch follower + const follower = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + await this.userFollowingService.rejectFollowRequest(me, follower); + + return; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/featured.ts b/packages/backend/src/server/api/endpoints/gallery/featured.ts index e6acd36911..3b892ef522 100644 --- a/packages/backend/src/server/api/endpoints/gallery/featured.ts +++ b/packages/backend/src/server/api/endpoints/gallery/featured.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { GalleryPosts } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository } from '@/models/index.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['gallery'], @@ -24,13 +27,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = GalleryPosts.createQueryBuilder('post') - .andWhere('post.createdAt > :date', { date: new Date(Date.now() - (1000 * 60 * 60 * 24 * 3)) }) - .andWhere('post.likedCount > 0') - .orderBy('post.likedCount', 'DESC'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - const posts = await query.take(10).getMany(); + private galleryPostEntityService: GalleryPostEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.galleryPostsRepository.createQueryBuilder('post') + .andWhere('post.createdAt > :date', { date: new Date(Date.now() - (1000 * 60 * 60 * 24 * 3)) }) + .andWhere('post.likedCount > 0') + .orderBy('post.likedCount', 'DESC'); - return await GalleryPosts.packMany(posts, me); -}); + const posts = await query.take(10).getMany(); + + return await this.galleryPostEntityService.packMany(posts, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/popular.ts b/packages/backend/src/server/api/endpoints/gallery/popular.ts index c4c8982fcc..551ea64835 100644 --- a/packages/backend/src/server/api/endpoints/gallery/popular.ts +++ b/packages/backend/src/server/api/endpoints/gallery/popular.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { GalleryPosts } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository } from '@/models/index.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['gallery'], @@ -24,12 +27,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = GalleryPosts.createQueryBuilder('post') - .andWhere('post.likedCount > 0') - .orderBy('post.likedCount', 'DESC'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - const posts = await query.take(10).getMany(); + private galleryPostEntityService: GalleryPostEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.galleryPostsRepository.createQueryBuilder('post') + .andWhere('post.likedCount > 0') + .orderBy('post.likedCount', 'DESC'); - return await GalleryPosts.packMany(posts, me); -}); + const posts = await query.take(10).getMany(); + + return await this.galleryPostEntityService.packMany(posts, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/posts.ts b/packages/backend/src/server/api/endpoints/gallery/posts.ts index 428ba9cc71..4afcbce816 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { GalleryPosts } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['gallery'], @@ -27,11 +30,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId) - .innerJoinAndSelect('post.user', 'user'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - const posts = await query.take(ps.limit).getMany(); + private galleryPostEntityService: GalleryPostEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId) + .innerJoinAndSelect('post.user', 'user'); - return await GalleryPosts.packMany(posts, me); -}); + const posts = await query.take(ps.limit).getMany(); + + return await this.galleryPostEntityService.packMany(posts, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index 8074a3b34f..9e8bcac131 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -1,10 +1,13 @@ import ms from 'ms'; -import define from '../../../define.js'; -import { DriveFiles, GalleryPosts } from '@/models/index.js'; -import { genId } from '../../../../../misc/gen-id.js'; -import { GalleryPost } from '@/models/entities/gallery-post.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js'; +import { GalleryPost } from '@/models/entities/GalleryPost.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import { IdService } from '@/core/IdService.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; export const meta = { tags: ['gallery'], @@ -43,28 +46,42 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const files = (await Promise.all(ps.fileIds.map(fileId => - DriveFiles.findOneBy({ - id: fileId, - userId: user.id, - }) - ))).filter((file): file is DriveFile => file != null); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - if (files.length === 0) { - throw new Error(); - } + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private galleryPostEntityService: GalleryPostEntityService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const files = (await Promise.all(ps.fileIds.map(fileId => + this.driveFilesRepository.findOneBy({ + id: fileId, + userId: me.id, + }), + ))).filter((file): file is DriveFile => file != null); - const post = await GalleryPosts.insert(new GalleryPost({ - id: genId(), - createdAt: new Date(), - updatedAt: new Date(), - title: ps.title, - description: ps.description, - userId: user.id, - isSensitive: ps.isSensitive, - fileIds: files.map(file => file.id), - })).then(x => GalleryPosts.findOneByOrFail(x.identifiers[0])); + if (files.length === 0) { + throw new Error(); + } - return await GalleryPosts.pack(post, user); -}); + const post = await this.galleryPostsRepository.insert(new GalleryPost({ + id: this.idService.genId(), + createdAt: new Date(), + updatedAt: new Date(), + title: ps.title, + description: ps.description, + userId: me.id, + isSensitive: ps.isSensitive, + fileIds: files.map(file => file.id), + })).then(x => this.galleryPostsRepository.findOneByOrFail(x.identifiers[0])); + + return await this.galleryPostEntityService.pack(post, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts index b00ee0e2ae..ad5f95c853 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { GalleryPosts } from '@/models/index.js'; export const meta = { tags: ['gallery'], @@ -27,15 +29,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const post = await GalleryPosts.findOneBy({ - id: ps.postId, - userId: user.id, - }); - - if (post == null) { - throw new ApiError(meta.errors.noSuchPost); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const post = await this.galleryPostsRepository.findOneBy({ + id: ps.postId, + userId: me.id, + }); + + if (post == null) { + throw new ApiError(meta.errors.noSuchPost); + } + + await this.galleryPostsRepository.delete(post.id); + }); } - - await GalleryPosts.delete(post.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index b858114aec..8aca98119b 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryLikesRepository, GalleryPostsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { GalleryPosts, GalleryLikes } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; export const meta = { tags: ['gallery'], @@ -40,33 +42,46 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const post = await GalleryPosts.findOneBy({ id: ps.postId }); - if (post == null) { - throw new ApiError(meta.errors.noSuchPost); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - if (post.userId === user.id) { - throw new ApiError(meta.errors.yourPost); - } + @Inject(DI.galleryLikesRepository) + private galleryLikesRepository: GalleryLikesRepository, - // if already liked - const exist = await GalleryLikes.findOneBy({ - postId: post.id, - userId: user.id, - }); + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const post = await this.galleryPostsRepository.findOneBy({ id: ps.postId }); + if (post == null) { + throw new ApiError(meta.errors.noSuchPost); + } - if (exist != null) { - throw new ApiError(meta.errors.alreadyLiked); - } + if (post.userId === me.id) { + throw new ApiError(meta.errors.yourPost); + } - // Create like - await GalleryLikes.insert({ - id: genId(), - createdAt: new Date(), - postId: post.id, - userId: user.id, - }); + // if already liked + const exist = await this.galleryLikesRepository.findOneBy({ + postId: post.id, + userId: me.id, + }); - GalleryPosts.increment({ id: post.id }, 'likedCount', 1); -}); + if (exist != null) { + throw new ApiError(meta.errors.alreadyLiked); + } + + // Create like + await this.galleryLikesRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + postId: post.id, + userId: me.id, + }); + + this.galleryPostsRepository.increment({ id: post.id }, 'likedCount', 1); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts index 4f6dafd7cb..723906d60f 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository } from '@/models/index.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { GalleryPosts } from '@/models/index.js'; export const meta = { tags: ['gallery'], @@ -31,14 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const post = await GalleryPosts.findOneBy({ - id: ps.postId, - }); - - if (post == null) { - throw new ApiError(meta.errors.noSuchPost); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, + + private galleryPostEntityService: GalleryPostEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const post = await this.galleryPostsRepository.findOneBy({ + id: ps.postId, + }); + + if (post == null) { + throw new ApiError(meta.errors.noSuchPost); + } + + return await this.galleryPostEntityService.pack(post, me); + }); } - - return await GalleryPosts.pack(post, me); -}); +} diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts index d136239e5e..d878582998 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository, GalleryLikesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { GalleryPosts, GalleryLikes } from '@/models/index.js'; export const meta = { tags: ['gallery'], @@ -33,23 +35,34 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const post = await GalleryPosts.findOneBy({ id: ps.postId }); - if (post == null) { - throw new ApiError(meta.errors.noSuchPost); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - const exist = await GalleryLikes.findOneBy({ - postId: post.id, - userId: user.id, - }); + @Inject(DI.galleryLikesRepository) + private galleryLikesRepository: GalleryLikesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const post = await this.galleryPostsRepository.findOneBy({ id: ps.postId }); + if (post == null) { + throw new ApiError(meta.errors.noSuchPost); + } - if (exist == null) { - throw new ApiError(meta.errors.notLiked); - } + const exist = await this.galleryLikesRepository.findOneBy({ + postId: post.id, + userId: me.id, + }); - // Delete like - await GalleryLikes.delete(exist.id); + if (exist == null) { + throw new ApiError(meta.errors.notLiked); + } - GalleryPosts.decrement({ id: post.id }, 'likedCount', 1); -}); + // Delete like + await this.galleryLikesRepository.delete(exist.id); + + this.galleryPostsRepository.decrement({ id: post.id }, 'likedCount', 1); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index 82fe38078e..1900afaeb6 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -1,9 +1,12 @@ import ms from 'ms'; -import define from '../../../define.js'; -import { DriveFiles, GalleryPosts } from '@/models/index.js'; -import { GalleryPost } from '@/models/entities/gallery-post.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js'; +import { GalleryPost } from '@/models/entities/GalleryPost.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; export const meta = { tags: ['gallery'], @@ -43,30 +46,43 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const files = (await Promise.all(ps.fileIds.map(fileId => - DriveFiles.findOneBy({ - id: fileId, - userId: user.id, - }) - ))).filter((file): file is DriveFile => file != null); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - if (files.length === 0) { - throw new Error(); - } + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private galleryPostEntityService: GalleryPostEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const files = (await Promise.all(ps.fileIds.map(fileId => + this.driveFilesRepository.findOneBy({ + id: fileId, + userId: me.id, + }), + ))).filter((file): file is DriveFile => file != null); - await GalleryPosts.update({ - id: ps.postId, - userId: user.id, - }, { - updatedAt: new Date(), - title: ps.title, - description: ps.description, - isSensitive: ps.isSensitive, - fileIds: files.map(file => file.id), - }); + if (files.length === 0) { + throw new Error(); + } - const post = await GalleryPosts.findOneByOrFail({ id: ps.postId }); + await this.galleryPostsRepository.update({ + id: ps.postId, + userId: me.id, + }, { + updatedAt: new Date(), + title: ps.title, + description: ps.description, + isSensitive: ps.isSensitive, + fileIds: files.map(file => file.id), + }); - return await GalleryPosts.pack(post, user); -}); + const post = await this.galleryPostsRepository.findOneByOrFail({ id: ps.postId }); + + return await this.galleryPostEntityService.pack(post, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index 56c5502978..2d9bf29dd9 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -1,7 +1,9 @@ import { MoreThan } from 'typeorm'; +import { Inject, Injectable } from '@nestjs/common'; import { USER_ONLINE_THRESHOLD } from '@/const.js'; -import { Users } from '@/models/index.js'; -import define from '../define.js'; +import { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['meta'], @@ -16,12 +18,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const count = await Users.countBy({ - lastActiveDate: MoreThan(new Date(Date.now() - USER_ONLINE_THRESHOLD)), - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + ) { + super(meta, paramDef, async () => { + const count = await this.usersRepository.countBy({ + lastActiveDate: MoreThan(new Date(Date.now() - USER_ONLINE_THRESHOLD)), + }); - return { - count, - }; -}); + return { + count, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/hashtags/list.ts b/packages/backend/src/server/api/endpoints/hashtags/list.ts index 50e36386cf..a7e7e6ba35 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/list.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/list.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { Hashtags } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { HashtagsRepository } from '@/models/index.js'; +import { HashtagEntityService } from '@/core/entities/HashtagEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['hashtags'], @@ -30,39 +33,49 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Hashtags.createQueryBuilder('tag'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.hashtagsRepository) + private hashtagsRepository: HashtagsRepository, - if (ps.attachedToUserOnly) query.andWhere('tag.attachedUsersCount != 0'); - if (ps.attachedToLocalUserOnly) query.andWhere('tag.attachedLocalUsersCount != 0'); - if (ps.attachedToRemoteUserOnly) query.andWhere('tag.attachedRemoteUsersCount != 0'); + private hashtagEntityService: HashtagEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.hashtagsRepository.createQueryBuilder('tag'); - 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; - } + 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', - ]); + query.select([ + 'tag.name', + 'tag.mentionedUsersCount', + 'tag.mentionedLocalUsersCount', + 'tag.mentionedRemoteUsersCount', + 'tag.attachedUsersCount', + 'tag.attachedLocalUsersCount', + 'tag.attachedRemoteUsersCount', + ]); - const tags = await query.take(ps.limit).getMany(); + const tags = await query.take(ps.limit).getMany(); - return Hashtags.packMany(tags); -}); + return this.hashtagEntityService.packMany(tags); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts index c289844775..3fb77bef9b 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/search.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts @@ -1,5 +1,7 @@ -import define from '../../define.js'; -import { Hashtags } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { HashtagsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['hashtags'], @@ -27,14 +29,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const hashtags = await Hashtags.createQueryBuilder('tag') - .where('tag.name like :q', { q: ps.query.toLowerCase() + '%' }) - .orderBy('tag.count', 'DESC') - .groupBy('tag.id') - .take(ps.limit) - .skip(ps.offset) - .getMany(); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.hashtagsRepository) + private hashtagsRepository: HashtagsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const hashtags = await this.hashtagsRepository.createQueryBuilder('tag') + .where('tag.name like :q', { q: ps.query.toLowerCase() + '%' }) + .orderBy('tag.count', 'DESC') + .groupBy('tag.id') + .take(ps.limit) + .skip(ps.offset) + .getMany(); - return hashtags.map(tag => tag.name); -}); + return hashtags.map(tag => tag.name); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/hashtags/show.ts b/packages/backend/src/server/api/endpoints/hashtags/show.ts index 5b78f6ac7f..59170f6d0e 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/show.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/show.ts @@ -1,7 +1,10 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Hashtags } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { HashtagsRepository } from '@/models/index.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; +import { HashtagEntityService } from '@/core/entities/HashtagEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['hashtags'], @@ -32,11 +35,21 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const hashtag = await Hashtags.findOneBy({ name: normalizeForSearch(ps.tag) }); - if (hashtag == null) { - throw new ApiError(meta.errors.noSuchHashtag); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.hashtagsRepository) + private hashtagsRepository: HashtagsRepository, - return await Hashtags.pack(hashtag); -}); + private hashtagEntityService: HashtagEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const hashtag = await this.hashtagsRepository.findOneBy({ name: normalizeForSearch(ps.tag) }); + if (hashtag == null) { + throw new ApiError(meta.errors.noSuchHashtag); + } + + return await this.hashtagEntityService.pack(hashtag); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/hashtags/trend.ts b/packages/backend/src/server/api/endpoints/hashtags/trend.ts index 9cdbc8941c..7e483ea214 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/trend.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/trend.ts @@ -1,10 +1,12 @@ import { Brackets } from 'typeorm'; -import define from '../../define.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Notes } from '@/models/index.js'; -import { Note } from '@/models/entities/note.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NotesRepository } from '@/models/index.js'; +import type { Note } from '@/models/entities/Note.js'; import { safeForSql } from '@/misc/safe-for-sql.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DI } from '@/di-symbols.js'; /* トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要 @@ -60,94 +62,104 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const instance = await fetchMeta(true); - const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t)); - - const now = new Date(); // 5分単位で丸めた現在日時 - now.setMinutes(Math.round(now.getMinutes() / 5) * 5, 0, 0); - - const tagNotes = await Notes.createQueryBuilder('note') - .where(`note.createdAt > :date`, { date: new Date(now.getTime() - rangeA) }) - .andWhere(new Brackets(qb => { qb - .where(`note.visibility = 'public'`) - .orWhere(`note.visibility = 'home'`); - })) - .andWhere(`note.tags != '{}'`) - .select(['note.tags', 'note.userId']) - .cache(60000) // 1 min - .getMany(); - - if (tagNotes.length === 0) { - return []; - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private metaService: MetaService, + ) { + super(meta, paramDef, async () => { + const instance = await this.metaService.fetch(true); + const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t)); + + const now = new Date(); // 5分単位で丸めた現在日時 + now.setMinutes(Math.round(now.getMinutes() / 5) * 5, 0, 0); + + const tagNotes = await this.notesRepository.createQueryBuilder('note') + .where('note.createdAt > :date', { date: new Date(now.getTime() - rangeA) }) + .andWhere(new Brackets(qb => { qb + .where('note.visibility = \'public\'') + .orWhere('note.visibility = \'home\''); + })) + .andWhere('note.tags != \'{}\'') + .select(['note.tags', 'note.userId']) + .cache(60000) // 1 min + .getMany(); + + if (tagNotes.length === 0) { + return []; + } - const tags: { + const tags: { name: string; users: Note['userId'][]; }[] = []; - 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); + 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], + }); + } } - } else { - tags.push({ - name: tag, - users: [note.userId], - }); } - } - } - // タグを人気順に並べ替え - const hots = tags - .sort((a, b) => b.users.length - a.users.length) - .map(tag => tag.name) - .slice(0, max); - - //#region 2(または3)で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する - const countPromises: Promise[] = []; - - const range = 20; - - // 10分 - const interval = 1000 * 60 * 10; - - for (let i = 0; i < range; i++) { - countPromises.push(Promise.all(hots.map(tag => Notes.createQueryBuilder('note') - .select('count(distinct note.userId)') - .where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`) - .andWhere('note.createdAt < :lt', { lt: new Date(now.getTime() - (interval * i)) }) - .andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - (interval * (i + 1))) }) - .cache(60000) // 1 min - .getRawOne() - .then(x => parseInt(x.count, 10)) - ))); - } + // タグを人気順に並べ替え + const hots = tags + .sort((a, b) => b.users.length - a.users.length) + .map(tag => tag.name) + .slice(0, max); + + //#region 2(または3)で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する + const countPromises: Promise[] = []; + + const range = 20; + + // 10分 + const interval = 1000 * 60 * 10; + + for (let i = 0; i < range; i++) { + countPromises.push(Promise.all(hots.map(tag => this.notesRepository.createQueryBuilder('note') + .select('count(distinct note.userId)') + .where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`) + .andWhere('note.createdAt < :lt', { lt: new Date(now.getTime() - (interval * i)) }) + .andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - (interval * (i + 1))) }) + .cache(60000) // 1 min + .getRawOne() + .then(x => parseInt(x.count, 10)), + ))); + } - const countsLog = await Promise.all(countPromises); - //#endregion - - const totalCounts = await Promise.all(hots.map(tag => Notes.createQueryBuilder('note') - .select('count(distinct note.userId)') - .where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`) - .andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - rangeA) }) - .cache(60000 * 60) // 60 min - .getRawOne() - .then(x => parseInt(x.count, 10)) - )); - - const stats = hots.map((tag, i) => ({ - tag, - chart: countsLog.map(counts => counts[i]), - usersCount: totalCounts[i], - })); - - return stats; -}); + const countsLog = await Promise.all(countPromises); + //#endregion + + const totalCounts = await Promise.all(hots.map(tag => this.notesRepository.createQueryBuilder('note') + .select('count(distinct note.userId)') + .where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`) + .andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - rangeA) }) + .cache(60000 * 60) // 60 min + .getRawOne() + .then(x => parseInt(x.count, 10)), + )); + + const stats = hots.map((tag, i) => ({ + tag, + chart: countsLog.map(counts => counts[i]), + usersCount: totalCounts[i], + })); + + return stats; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index a5df21a7e3..10a88fbefa 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: false, @@ -24,39 +27,49 @@ export const paramDef = { tag: { type: 'string' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] }, - state: { type: 'string', enum: ['all', 'alive'], default: "all" }, - origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" }, + state: { type: 'string', enum: ['all', 'alive'], default: 'all' }, + origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' }, }, required: ['tag', 'sort'], } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Users.createQueryBuilder('user') - .where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.usersRepository.createQueryBuilder('user') + .where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) }); - const recent = new Date(Date.now() - (1000 * 60 * 60 * 24 * 5)); + const recent = new Date(Date.now() - (1000 * 60 * 60 * 24 * 5)); - if (ps.state === 'alive') { - query.andWhere('user.updatedAt > :date', { date: recent }); - } + if (ps.state === 'alive') { + query.andWhere('user.updatedAt > :date', { date: recent }); + } - if (ps.origin === 'local') { - query.andWhere('user.host IS NULL'); - } else if (ps.origin === 'remote') { - query.andWhere('user.host IS NOT NULL'); - } + if (ps.origin === 'local') { + query.andWhere('user.host IS NULL'); + } else if (ps.origin === 'remote') { + query.andWhere('user.host IS NOT 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; - } + 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 query.take(ps.limit).getMany(); + const users = await query.take(ps.limit).getMany(); - return await Users.packMany(users, me, { detail: true }); -}); + return await this.userEntityService.packMany(users, me, { detail: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index 22aedfeee8..815b3168b4 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -1,5 +1,8 @@ -import { Users } from '@/models/index.js'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], @@ -20,12 +23,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user, token) => { - const isSecure = token == null; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - // ここで渡ってきている user はキャッシュされていて古い可能性もあるので id だけ渡す - return await Users.pack(user.id, user, { - detail: true, - includeSecrets: isSecure, - }); -}); + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, user, token) => { + const isSecure = token == null; + + // ここで渡ってきている user はキャッシュされていて古い可能性もあるので id だけ渡す + return await this.userEntityService.pack(user.id, user, { + detail: true, + includeSecrets: isSecure, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts index 35806b2bc3..bcf3931b04 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -1,6 +1,8 @@ import * as speakeasy from 'speakeasy'; -import define from '../../../define.js'; -import { UserProfiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserProfilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -17,27 +19,35 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const token = ps.token.replace(/\s/g, ''); - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - if (profile.twoFactorTempSecret == null) { - throw new Error('二段階認証の設定が開始されていません'); - } - - const verified = (speakeasy as any).totp.verify({ - secret: profile.twoFactorTempSecret, - encoding: 'base32', - token: token, - }); - - if (!verified) { - throw new Error('not verified'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const token = ps.token.replace(/\s/g, ''); + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + + if (profile.twoFactorTempSecret == null) { + throw new Error('二段階認証の設定が開始されていません'); + } + + const verified = (speakeasy as any).totp.verify({ + secret: profile.twoFactorTempSecret, + encoding: 'base32', + token: token, + }); + + if (!verified) { + throw new Error('not verified'); + } + + await this.userProfilesRepository.update(me.id, { + twoFactorSecret: profile.twoFactorTempSecret, + twoFactorEnabled: true, + }); + }); } - - await UserProfiles.update(user.id, { - twoFactorSecret: profile.twoFactorTempSecret, - twoFactorEnabled: true, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index 1afb34bfda..f2f4c2044e 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -1,19 +1,16 @@ -import bcrypt from 'bcryptjs'; import { promisify } from 'node:util'; +import bcrypt from 'bcryptjs'; import * as cbor from 'cbor'; -import define from '../../../define.js'; -import { - UserProfiles, - UserSecurityKeys, - AttestationChallenges, - Users, -} from '@/models/index.js'; -import config from '@/config/index.js'; -import { procedures, hash } from '../../../2fa.js'; -import { publishMainStream } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; +import { AttestationChallengesRepository, UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; const cborDecodeFirst = promisify(cbor.decodeFirst) as any; -const rpIdHashReal = hash(Buffer.from(config.hostname, 'utf-8')); export const meta = { requireCredential: true, @@ -34,110 +31,135 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); - - if (!same) { - throw new Error('incorrect password'); - } - - if (!profile.twoFactorEnabled) { - throw new Error('2fa not enabled'); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, - const clientData = JSON.parse(ps.clientDataJSON); + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, - if (clientData.type !== 'webauthn.create') { - throw new Error('not a creation attestation'); - } - if (clientData.origin !== config.scheme + '://' + config.host) { - throw new Error('origin mismatch'); - } - - const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, 'utf-8')); - - const attestation = await cborDecodeFirst(ps.attestationObject); - - const rpIdHash = attestation.authData.slice(0, 32); - if (!rpIdHashReal.equals(rpIdHash)) { - throw new Error('rpIdHash mismatch'); - } + @Inject(DI.userSecurityKeysRepository) + private userSecurityKeysRepository: UserSecurityKeysRepository, - const flags = attestation.authData[32]; + @Inject(DI.attestationChallengesRepository) + private attestationChallengesRepository: AttestationChallengesRepository, - // eslint:disable-next-line:no-bitwise - if (!(flags & 1)) { - throw new Error('user not present'); - } - - const authData = Buffer.from(attestation.authData); - const credentialIdLength = authData.readUInt16BE(53); - const credentialId = authData.slice(55, 55 + credentialIdLength); - const publicKeyData = authData.slice(55 + credentialIdLength); - const publicKey: Map = await cborDecodeFirst(publicKeyData); - if (publicKey.get(3) !== -7) { - throw new Error('alg mismatch'); - } - - if (!(procedures as any)[attestation.fmt]) { - throw new Error('unsupported fmt'); - } - - const verificationData = (procedures as any)[attestation.fmt].verify({ - attStmt: attestation.attStmt, - authenticatorData: authData, - clientDataHash: clientDataJSONHash, - credentialId, - publicKey, - rpIdHash, - }); - if (!verificationData.valid) throw new Error('signature invalid'); - - const attestationChallenge = await AttestationChallenges.findOneBy({ - userId: user.id, - id: ps.challengeId, - registrationChallenge: true, - challenge: hash(clientData.challenge).toString('hex'), - }); - - if (!attestationChallenge) { - throw new Error('non-existent challenge'); - } - - await AttestationChallenges.delete({ - userId: user.id, - id: ps.challengeId, - }); - - // Expired challenge (> 5min old) - if ( - new Date().getTime() - attestationChallenge.createdAt.getTime() >= - 5 * 60 * 1000 + private userEntityService: UserEntityService, + private globalEventService: GlobalEventService, + private twoFactorAuthenticationService: TwoFactorAuthenticationService, ) { - throw new Error('expired challenge'); + super(meta, paramDef, async (ps, me) => { + const rpIdHashReal = this.twoFactorAuthenticationService.hash(Buffer.from(this.config.hostname, 'utf-8')); + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + if (!profile.twoFactorEnabled) { + throw new Error('2fa not enabled'); + } + + const clientData = JSON.parse(ps.clientDataJSON); + + if (clientData.type !== 'webauthn.create') { + throw new Error('not a creation attestation'); + } + if (clientData.origin !== this.config.scheme + '://' + this.config.host) { + throw new Error('origin mismatch'); + } + + const clientDataJSONHash = this.twoFactorAuthenticationService.hash(Buffer.from(ps.clientDataJSON, 'utf-8')); + + const attestation = await cborDecodeFirst(ps.attestationObject); + + const rpIdHash = attestation.authData.slice(0, 32); + if (!rpIdHashReal.equals(rpIdHash)) { + throw new Error('rpIdHash mismatch'); + } + + const flags = attestation.authData[32]; + + // eslint:disable-next-line:no-bitwise + if (!(flags & 1)) { + throw new Error('user not present'); + } + + const authData = Buffer.from(attestation.authData); + const credentialIdLength = authData.readUInt16BE(53); + const credentialId = authData.slice(55, 55 + credentialIdLength); + const publicKeyData = authData.slice(55 + credentialIdLength); + const publicKey: Map = await cborDecodeFirst(publicKeyData); + if (publicKey.get(3) !== -7) { + throw new Error('alg mismatch'); + } + + const procedures = this.twoFactorAuthenticationService.getProcedures(); + + if (!(procedures as any)[attestation.fmt]) { + throw new Error('unsupported fmt'); + } + + const verificationData = (procedures as any)[attestation.fmt].verify({ + attStmt: attestation.attStmt, + authenticatorData: authData, + clientDataHash: clientDataJSONHash, + credentialId, + publicKey, + rpIdHash, + }); + if (!verificationData.valid) throw new Error('signature invalid'); + + const attestationChallenge = await this.attestationChallengesRepository.findOneBy({ + userId: me.id, + id: ps.challengeId, + registrationChallenge: true, + challenge: this.twoFactorAuthenticationService.hash(clientData.challenge).toString('hex'), + }); + + if (!attestationChallenge) { + throw new Error('non-existent challenge'); + } + + await this.attestationChallengesRepository.delete({ + userId: me.id, + id: ps.challengeId, + }); + + // Expired challenge (> 5min old) + if ( + new Date().getTime() - attestationChallenge.createdAt.getTime() >= + 5 * 60 * 1000 + ) { + throw new Error('expired challenge'); + } + + const credentialIdString = credentialId.toString('hex'); + + await this.userSecurityKeysRepository.insert({ + userId: me.id, + id: credentialIdString, + lastUsed: new Date(), + name: ps.name, + publicKey: verificationData.publicKey.toString('hex'), + }); + + // Publish meUpdated event + this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { + detail: true, + includeSecrets: true, + })); + + return { + id: credentialIdString, + name: ps.name, + }; + }); } - - const credentialIdString = credentialId.toString('hex'); - - await UserSecurityKeys.insert({ - userId: user.id, - id: credentialIdString, - lastUsed: new Date(), - name: ps.name, - publicKey: verificationData.publicKey.toString('hex'), - }); - - // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, { - detail: true, - includeSecrets: true, - })); - - return { - id: credentialIdString, - name: ps.name, - }; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts index 4bfa24f97f..3eb9f43c2b 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { UserProfiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserProfilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -16,8 +18,16 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - await UserProfiles.update(user.id, { - usePasswordLessLogin: ps.value, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + await this.userProfilesRepository.update(me.id, { + usePasswordLessLogin: ps.value, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index e906b82043..df37db4c6a 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -1,10 +1,12 @@ -import bcrypt from 'bcryptjs'; -import define from '../../../define.js'; -import { UserProfiles, AttestationChallenges } from '@/models/index.js'; import { promisify } from 'node:util'; import * as crypto from 'node:crypto'; -import { genId } from '@/misc/gen-id.js'; -import { hash } from '../../../2fa.js'; +import bcrypt from 'bcryptjs'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserProfilesRepository, AttestationChallengesRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; +import { DI } from '@/di-symbols.js'; const randomBytes = promisify(crypto.randomBytes); @@ -23,39 +25,53 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + @Inject(DI.attestationChallengesRepository) + private attestationChallengesRepository: AttestationChallengesRepository, - if (!same) { - throw new Error('incorrect password'); - } + private idService: IdService, + private twoFactorAuthenticationService: TwoFactorAuthenticationService, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); - if (!profile.twoFactorEnabled) { - throw new Error('2fa not enabled'); - } + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } - // 32 byte challenge - const entropy = await randomBytes(32); - const challenge = entropy.toString('base64') - .replace(/=/g, '') - .replace(/\+/g, '-') - .replace(/\//g, '_'); - - const challengeId = genId(); - - await AttestationChallenges.insert({ - userId: user.id, - id: challengeId, - challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'), - createdAt: new Date(), - registrationChallenge: true, - }); - - return { - challengeId, - challenge, - }; -}); + if (!profile.twoFactorEnabled) { + throw new Error('2fa not enabled'); + } + + // 32 byte challenge + const entropy = await randomBytes(32); + const challenge = entropy.toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + + const challengeId = this.idService.genId(); + + await this.attestationChallengesRepository.insert({ + userId: me.id, + id: challengeId, + challenge: this.twoFactorAuthenticationService.hash(Buffer.from(challenge, 'utf-8')).toString('hex'), + createdAt: new Date(), + registrationChallenge: true, + }); + + return { + challengeId, + challenge, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index 33f5717728..e20911f35e 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -1,9 +1,11 @@ import bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; import * as QRCode from 'qrcode'; -import config from '@/config/index.js'; -import { UserProfiles } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserProfilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; +import { Config } from '@/config.js'; export const meta = { requireCredential: true, @@ -20,39 +22,50 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); - if (!same) { - throw new Error('incorrect password'); - } + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + // Generate user's secret key + const secret = speakeasy.generateSecret({ + length: 32, + }); - // Generate user's secret key - const secret = speakeasy.generateSecret({ - length: 32, - }); - - await UserProfiles.update(user.id, { - twoFactorTempSecret: secret.base32, - }); - - // Get the data URL of the authenticator URL - const url = speakeasy.otpauthURL({ - secret: secret.base32, - encoding: 'base32', - label: user.username, - issuer: config.host, - }); - const dataUrl = await QRCode.toDataURL(url); - - return { - qr: dataUrl, - url, - secret: secret.base32, - label: user.username, - issuer: config.host, - }; -}); + await this.userProfilesRepository.update(me.id, { + twoFactorTempSecret: secret.base32, + }); + + // Get the data URL of the authenticator URL + const url = speakeasy.otpauthURL({ + secret: secret.base32, + encoding: 'base32', + label: me.username, + issuer: this.config.host, + }); + const dataUrl = await QRCode.toDataURL(url); + + return { + qr: dataUrl, + url, + secret: secret.base32, + label: me.username, + issuer: this.config.host, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index eb2f75308d..1889dd7893 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -1,7 +1,11 @@ import bcrypt from 'bcryptjs'; -import define from '../../../define.js'; -import { UserProfiles, UserSecurityKeys, Users } from '@/models/index.js'; -import { publishMainStream } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -19,27 +23,41 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); - - if (!same) { - throw new Error('incorrect password'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userSecurityKeysRepository) + private userSecurityKeysRepository: UserSecurityKeysRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private userEntityService: UserEntityService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + // Make sure we only delete the user's own creds + await this.userSecurityKeysRepository.delete({ + userId: me.id, + id: ps.credentialId, + }); + + // Publish meUpdated event + this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { + detail: true, + includeSecrets: true, + })); + + return {}; + }); } - - // Make sure we only delete the user's own creds - await UserSecurityKeys.delete({ - userId: user.id, - id: ps.credentialId, - }); - - // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, { - detail: true, - includeSecrets: true, - })); - - return {}; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index 45e7a98639..4607e5d981 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -1,6 +1,8 @@ import bcrypt from 'bcryptjs'; -import define from '../../../define.js'; -import { UserProfiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserProfilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -17,18 +19,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); - if (!same) { - throw new Error('incorrect password'); - } + if (!same) { + throw new Error('incorrect password'); + } - await UserProfiles.update(user.id, { - twoFactorSecret: null, - twoFactorEnabled: false, - }); -}); + await this.userProfilesRepository.update(me.id, { + twoFactorSecret: null, + twoFactorEnabled: false, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index eca9558847..8d5851659b 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -1,5 +1,7 @@ -import define from '../../define.js'; -import { AccessTokens } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AccessTokensRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -16,25 +18,33 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = AccessTokens.createQueryBuilder('token') - .where('token.userId = :userId', { userId: user.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.accessTokensRepository.createQueryBuilder('token') + .where('token.userId = :userId', { userId: me.id }); - switch (ps.sort) { - case '+createdAt': query.orderBy('token.createdAt', 'DESC'); break; - case '-createdAt': query.orderBy('token.createdAt', 'ASC'); break; - case '+lastUsedAt': query.orderBy('token.lastUsedAt', 'DESC'); break; - case '-lastUsedAt': query.orderBy('token.lastUsedAt', 'ASC'); break; - default: query.orderBy('token.id', 'ASC'); break; - } + switch (ps.sort) { + case '+createdAt': query.orderBy('token.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('token.createdAt', 'ASC'); break; + case '+lastUsedAt': query.orderBy('token.lastUsedAt', 'DESC'); break; + case '-lastUsedAt': query.orderBy('token.lastUsedAt', 'ASC'); break; + default: query.orderBy('token.id', 'ASC'); break; + } - const tokens = await query.getMany(); + const tokens = await query.getMany(); - return await Promise.all(tokens.map(token => ({ - id: token.id, - name: token.name, - createdAt: token.createdAt, - lastUsedAt: token.lastUsedAt, - permission: token.permission, - }))); -}); + return await Promise.all(tokens.map(token => ({ + id: token.id, + name: token.name, + createdAt: token.createdAt, + lastUsedAt: token.lastUsedAt, + permission: token.permission, + }))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts index 68bd103a6d..a5592d20de 100644 --- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts +++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { AccessTokens, Apps } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AccessTokensRepository } from '@/models/index.js'; +import { AppEntityService } from '@/core/entities/AppEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -12,26 +15,36 @@ export const paramDef = { properties: { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, offset: { type: 'integer', default: 0 }, - sort: { type: 'string', enum: ['desc', 'asc'], default: "desc" }, + sort: { type: 'string', enum: ['desc', 'asc'], default: 'desc' }, }, required: [], } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Get tokens - const tokens = await AccessTokens.find({ - where: { - userId: user.id, - }, - take: ps.limit, - skip: ps.offset, - order: { - id: ps.sort === 'asc' ? 1 : -1, - }, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, - return await Promise.all(tokens.map(token => Apps.pack(token.appId, user, { - detail: true, - }))); -}); + private appEntityService: AppEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Get tokens + const tokens = await this.accessTokensRepository.find({ + where: { + userId: me.id, + }, + take: ps.limit, + skip: ps.offset, + order: { + id: ps.sort === 'asc' ? 1 : -1, + }, + }); + + return await Promise.all(tokens.map(token => this.appEntityService.pack(token.appId, me, { + detail: true, + }))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index f9f6a33a80..cc5b712ecf 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -1,6 +1,8 @@ import bcrypt from 'bcryptjs'; -import define from '../../define.js'; -import { UserProfiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserProfilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -18,21 +20,29 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - // Compare password - const same = await bcrypt.compare(ps.currentPassword, profile.password!); - - if (!same) { - throw new Error('incorrect password'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + + // Compare password + const same = await bcrypt.compare(ps.currentPassword, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + // Generate hash of password + const salt = await bcrypt.genSalt(8); + const hash = await bcrypt.hash(ps.newPassword, salt); + + await this.userProfilesRepository.update(me.id, { + password: hash, + }); + }); } - - // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(ps.newPassword, salt); - - await UserProfiles.update(user.id, { - password: hash, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index ede4a9d03b..a1804599df 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -1,7 +1,9 @@ import bcrypt from 'bcryptjs'; -import { UserProfiles, Users } from '@/models/index.js'; -import { deleteAccount } from '@/services/delete-account.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DeleteAccountService } from '@/core/DeleteAccountService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -18,19 +20,32 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - const userDetailed = await Users.findOneByOrFail({ id: user.id }); - if (userDetailed.isDeleted) { - return; - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, - if (!same) { - throw new Error('incorrect password'); - } + private deleteAccountService: DeleteAccountService, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + const userDetailed = await this.usersRepository.findOneByOrFail({ id: me.id }); + if (userDetailed.isDeleted) { + return; + } + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); - await deleteAccount(user); -}); + if (!same) { + throw new Error('incorrect password'); + } + + await this.deleteAccountService.deleteAccount(me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/export-blocking.ts b/packages/backend/src/server/api/endpoints/i/export-blocking.ts index aed4c2e0a3..770708e685 100644 --- a/packages/backend/src/server/api/endpoints/i/export-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/export-blocking.ts @@ -1,6 +1,7 @@ -import define from '../../define.js'; -import { createExportBlockingJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, @@ -18,6 +19,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - createExportBlockingJob(user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportBlockingJob(me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/export-following.ts b/packages/backend/src/server/api/endpoints/i/export-following.ts index 058d77b3c2..fcaa59b12d 100644 --- a/packages/backend/src/server/api/endpoints/i/export-following.ts +++ b/packages/backend/src/server/api/endpoints/i/export-following.ts @@ -1,6 +1,7 @@ -import define from '../../define.js'; -import { createExportFollowingJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, @@ -21,6 +22,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - createExportFollowingJob(user, ps.excludeMuting, ps.excludeInactive); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportFollowingJob(me, ps.excludeMuting, ps.excludeInactive); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/export-mute.ts b/packages/backend/src/server/api/endpoints/i/export-mute.ts index c0216fac0c..37bef0a117 100644 --- a/packages/backend/src/server/api/endpoints/i/export-mute.ts +++ b/packages/backend/src/server/api/endpoints/i/export-mute.ts @@ -1,6 +1,7 @@ -import define from '../../define.js'; -import { createExportMuteJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, @@ -18,6 +19,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - createExportMuteJob(user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportMuteJob(me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/export-notes.ts b/packages/backend/src/server/api/endpoints/i/export-notes.ts index 4b85a45554..9d2505e403 100644 --- a/packages/backend/src/server/api/endpoints/i/export-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/export-notes.ts @@ -1,6 +1,7 @@ -import define from '../../define.js'; -import { createExportNotesJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, @@ -18,6 +19,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - createExportNotesJob(user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportNotesJob(me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts index fa5c1f5e5a..0f8e4bca76 100644 --- a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts @@ -1,6 +1,7 @@ -import define from '../../define.js'; -import { createExportUserListsJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, @@ -18,6 +19,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - createExportUserListsJob(user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportUserListsJob(me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts index 3c420e4d0f..350abd9f7b 100644 --- a/packages/backend/src/server/api/endpoints/i/favorites.ts +++ b/packages/backend/src/server/api/endpoints/i/favorites.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { NoteFavorites } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteFavoritesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteFavoriteEntityService } from '@/core/entities/NoteFavoriteEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'notes', 'favorites'], @@ -31,14 +34,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(NoteFavorites.createQueryBuilder('favorite'), ps.sinceId, ps.untilId) - .andWhere(`favorite.userId = :meId`, { meId: user.id }) - .leftJoinAndSelect('favorite.note', 'note'); - - const favorites = await query - .take(ps.limit) - .getMany(); - - return await NoteFavorites.packMany(favorites, user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.noteFavoritesRepository) + private noteFavoritesRepository: NoteFavoritesRepository, + + private noteFavoriteEntityService: NoteFavoriteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.noteFavoritesRepository.createQueryBuilder('favorite'), ps.sinceId, ps.untilId) + .andWhere('favorite.userId = :meId', { meId: me.id }) + .leftJoinAndSelect('favorite.note', 'note'); + + const favorites = await query + .take(ps.limit) + .getMany(); + + return await this.noteFavoriteEntityService.packMany(favorites, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts index a38383f30e..ff6bcc01ab 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; -import { GalleryLikes } from '@/models/index.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryLikesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { GalleryLikeEntityService } from '@/core/entities/GalleryLikeEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'gallery'], @@ -27,7 +30,7 @@ export const meta = { ref: 'GalleryPost', }, }, - } + }, }, } as const; @@ -42,14 +45,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(GalleryLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId) - .andWhere(`like.userId = :meId`, { meId: user.id }) - .leftJoinAndSelect('like.post', 'post'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryLikesRepository) + private galleryLikesRepository: GalleryLikesRepository, - const likes = await query - .take(ps.limit) - .getMany(); + private galleryLikeEntityService: GalleryLikeEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.galleryLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId) + .andWhere('like.userId = :meId', { meId: me.id }) + .leftJoinAndSelect('like.post', 'post'); - return await GalleryLikes.packMany(likes, user); -}); + const likes = await query + .take(ps.limit) + .getMany(); + + return await this.galleryLikeEntityService.packMany(likes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts index b4edb5f73d..927be51f79 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; -import { GalleryPosts } from '@/models/index.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'gallery'], @@ -31,13 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId) - .andWhere(`post.userId = :meId`, { meId: user.id }); - - const posts = await query - .take(ps.limit) - .getMany(); - - return await GalleryPosts.packMany(posts, user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, + + private galleryPostEntityService: GalleryPostEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId) + .andWhere('post.userId = :meId', { meId: me.id }); + + const posts = await query + .take(ps.limit) + .getMany(); + + return await this.galleryPostEntityService.packMany(posts, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts index e7d7518c5b..0695abdd85 100644 --- a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts +++ b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts @@ -1,5 +1,7 @@ -import define from '../../define.js'; -import { MutedNotes } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MutedNotesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], @@ -27,11 +29,19 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - return { - count: await MutedNotes.countBy({ - userId: user.id, - reason: 'word', - }), - }; -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.mutedNotesRepository) + private mutedNotesRepository: MutedNotesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + return { + count: await this.mutedNotesRepository.countBy({ + userId: me.id, + reason: 'word', + }), + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index 0bcbf37ddd..bfba1fc36f 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -1,8 +1,10 @@ -import define from '../../define.js'; -import { createImportBlockingJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { DriveFiles } from '@/models/index.js'; export const meta = { secure: true, @@ -49,13 +51,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOneBy({ id: ps.fileId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (file == null) throw new ApiError(meta.errors.noSuchFile); - //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); + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); - createImportBlockingJob(user, file.id); -}); + if (file == null) throw new ApiError(meta.errors.noSuchFile); + //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); + + this.queueService.createImportBlockingJob(me, file.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index ee2abbea19..c7cb2e0330 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -1,8 +1,10 @@ -import define from '../../define.js'; -import { createImportFollowingJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { DriveFiles } from '@/models/index.js'; export const meta = { secure: true, @@ -48,13 +50,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOneBy({ id: ps.fileId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (file == null) throw new ApiError(meta.errors.noSuchFile); - //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); + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); - createImportFollowingJob(user, file.id); -}); + if (file == null) throw new ApiError(meta.errors.noSuchFile); + //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); + + this.queueService.createImportFollowingJob(me, file.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index b3b3b39238..060c37c13f 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -1,8 +1,10 @@ -import define from '../../define.js'; -import { createImportMutingJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { DriveFiles } from '@/models/index.js'; export const meta = { secure: true, @@ -49,13 +51,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOneBy({ id: ps.fileId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (file == null) throw new ApiError(meta.errors.noSuchFile); - //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); + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); - createImportMutingJob(user, file.id); -}); + if (file == null) throw new ApiError(meta.errors.noSuchFile); + //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); + + this.queueService.createImportMutingJob(me, file.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index 64f5ec05fd..a5e17283e5 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -1,8 +1,10 @@ -import define from '../../define.js'; -import { createImportUserListsJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { DriveFiles } from '@/models/index.js'; export const meta = { secure: true, @@ -48,13 +50,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOneBy({ id: ps.fileId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (file == null) throw new ApiError(meta.errors.noSuchFile); - //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); + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); - createImportUserListsJob(user, file.id); -}); + if (file == null) throw new ApiError(meta.errors.noSuchFile); + //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); + + this.queueService.createImportUserListsJob(me, file.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index 2b343dabdd..96927dad49 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -1,10 +1,13 @@ import { Brackets } from 'typeorm'; -import { Notifications, Followings, Mutings, Users, UserProfiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotificationsRepository } from '@/models/index.js'; import { notificationTypes } from '@/types.js'; -import read from '@/services/note/read.js'; -import { readNotification } from '../../common/read-notification.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteReadService } from '@/core/NoteReadService.js'; +import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js'; +import { NotificationService } from '@/core/NotificationService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'notifications'], @@ -49,96 +52,121 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // includeTypes が空の場合はクエリしない - if (ps.includeTypes && ps.includeTypes.length === 0) { - return []; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.notificationsRepository) + private notificationsRepository: NotificationsRepository, + + private notificationEntityService: NotificationEntityService, + private notificationService: NotificationService, + private queryService: QueryService, + private noteReadService: NoteReadService, + ) { + super(meta, paramDef, async (ps, me) => { + // includeTypes が空の場合はクエリしない + if (ps.includeTypes && ps.includeTypes.length === 0) { + return []; + } + // excludeTypes に全指定されている場合はクエリしない + if (notificationTypes.every(type => ps.excludeTypes?.includes(type))) { + return []; + } + const followingQuery = this.followingsRepository.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); + + const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') + .select('muting.muteeId') + .where('muting.muterId = :muterId', { muterId: me.id }); + + const mutingInstanceQuery = this.userProfilesRepository.createQueryBuilder('user_profile') + .select('user_profile.mutedInstances') + .where('user_profile.userId = :muterId', { muterId: me.id }); + + const suspendedQuery = this.usersRepository.createQueryBuilder('users') + .select('users.id') + .where('users.isSuspended = TRUE'); + + const query = this.queryService.makePaginationQuery(this.notificationsRepository.createQueryBuilder('notification'), ps.sinceId, ps.untilId) + .andWhere('notification.notifieeId = :meId', { meId: me.id }) + .leftJoinAndSelect('notification.notifier', 'notifier') + .leftJoinAndSelect('notification.note', 'note') + .leftJoinAndSelect('notifier.avatar', 'notifierAvatar') + .leftJoinAndSelect('notifier.banner', 'notifierBanner') + .leftJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + + // muted users + query.andWhere(new Brackets(qb => { qb + .where(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`) + .orWhere('notification.notifierId IS NULL'); + })); + query.setParameters(mutingQuery.getParameters()); + + // muted instances + query.andWhere(new Brackets(qb => { qb + .andWhere('notifier.host IS NULL') + .orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`); + })); + query.setParameters(mutingInstanceQuery.getParameters()); + + // suspended users + query.andWhere(new Brackets(qb => { qb + .where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`) + .orWhere('notification.notifierId IS NULL'); + })); + + if (ps.following) { + query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: me.id }); + query.setParameters(followingQuery.getParameters()); + } + + if (ps.includeTypes && ps.includeTypes.length > 0) { + query.andWhere('notification.type IN (:...includeTypes)', { includeTypes: ps.includeTypes }); + } else if (ps.excludeTypes && ps.excludeTypes.length > 0) { + query.andWhere('notification.type NOT IN (:...excludeTypes)', { excludeTypes: ps.excludeTypes }); + } + + if (ps.unreadOnly) { + query.andWhere('notification.isRead = false'); + } + + const notifications = await query.take(ps.limit).getMany(); + + // Mark all as read + if (notifications.length > 0 && ps.markAsRead) { + this.notificationService.readNotification(me.id, notifications.map(x => x.id)); + } + + const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!); + + if (notes.length > 0) { + this.noteReadService.read(me.id, notes); + } + + return await this.notificationEntityService.packMany(notifications, me.id); + }); } - // excludeTypes に全指定されている場合はクエリしない - if (notificationTypes.every(type => ps.excludeTypes?.includes(type))) { - return []; - } - const followingQuery = Followings.createQueryBuilder('following') - .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: user.id }); - - const mutingQuery = Mutings.createQueryBuilder('muting') - .select('muting.muteeId') - .where('muting.muterId = :muterId', { muterId: user.id }); - - const mutingInstanceQuery = UserProfiles.createQueryBuilder('user_profile') - .select('user_profile.mutedInstances') - .where('user_profile.userId = :muterId', { muterId: user.id }); - - const suspendedQuery = Users.createQueryBuilder('users') - .select('users.id') - .where('users.isSuspended = TRUE'); - - const query = makePaginationQuery(Notifications.createQueryBuilder('notification'), ps.sinceId, ps.untilId) - .andWhere('notification.notifieeId = :meId', { meId: user.id }) - .leftJoinAndSelect('notification.notifier', 'notifier') - .leftJoinAndSelect('notification.note', 'note') - .leftJoinAndSelect('notifier.avatar', 'notifierAvatar') - .leftJoinAndSelect('notifier.banner', 'notifierBanner') - .leftJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - - // muted users - query.andWhere(new Brackets(qb => { qb - .where(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`) - .orWhere('notification.notifierId IS NULL'); - })); - query.setParameters(mutingQuery.getParameters()); - - // muted instances - query.andWhere(new Brackets(qb => { qb - .andWhere('notifier.host IS NULL') - .orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`); - })); - query.setParameters(mutingInstanceQuery.getParameters()); - - // suspended users - query.andWhere(new Brackets(qb => { qb - .where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`) - .orWhere('notification.notifierId IS NULL'); - })); - - if (ps.following) { - query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: user.id }); - query.setParameters(followingQuery.getParameters()); - } - - if (ps.includeTypes && ps.includeTypes.length > 0) { - query.andWhere('notification.type IN (:...includeTypes)', { includeTypes: ps.includeTypes }); - } else if (ps.excludeTypes && ps.excludeTypes.length > 0) { - query.andWhere('notification.type NOT IN (:...excludeTypes)', { excludeTypes: ps.excludeTypes }); - } - - if (ps.unreadOnly) { - query.andWhere('notification.isRead = false'); - } - - const notifications = await query.take(ps.limit).getMany(); - - // Mark all as read - if (notifications.length > 0 && ps.markAsRead) { - readNotification(user.id, notifications.map(x => x.id)); - } - - const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!); - - if (notes.length > 0) { - read(user.id, notes); - } - - return await Notifications.packMany(notifications, user.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/page-likes.ts b/packages/backend/src/server/api/endpoints/i/page-likes.ts index 71e326e2f0..9a909eedf4 100644 --- a/packages/backend/src/server/api/endpoints/i/page-likes.ts +++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { PageLikes } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { PageLikesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { PageLikeEntityService } from '@/core/entities/PageLikeEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'pages'], @@ -26,7 +29,7 @@ export const meta = { ref: 'Page', }, }, - } + }, }, } as const; @@ -41,14 +44,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(PageLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId) - .andWhere(`like.userId = :meId`, { meId: user.id }) - .leftJoinAndSelect('like.page', 'page'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pageLikesRepository) + private pageLikesRepository: PageLikesRepository, - const likes = await query - .take(ps.limit) - .getMany(); + private pageLikeEntityService: PageLikeEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.pageLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId) + .andWhere('like.userId = :meId', { meId: me.id }) + .leftJoinAndSelect('like.page', 'page'); - return PageLikes.packMany(likes, user); -}); + const likes = await query + .take(ps.limit) + .getMany(); + + return this.pageLikeEntityService.packMany(likes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts index f28aed3fd4..7c4e4a6c7d 100644 --- a/packages/backend/src/server/api/endpoints/i/pages.ts +++ b/packages/backend/src/server/api/endpoints/i/pages.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Pages } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { PagesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { PageEntityService } from '@/core/entities/PageEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'pages'], @@ -31,13 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId) - .andWhere(`page.userId = :meId`, { meId: user.id }); - - const pages = await query - .take(ps.limit) - .getMany(); - - return await Pages.packMany(pages); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + + private pageEntityService: PageEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.pagesRepository.createQueryBuilder('page'), ps.sinceId, ps.untilId) + .andWhere('page.userId = :meId', { meId: me.id }); + + const pages = await query + .take(ps.limit) + .getMany(); + + return await this.pageEntityService.packMany(pages); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts index 67b7026be1..f31b0dc35e 100644 --- a/packages/backend/src/server/api/endpoints/i/pin.ts +++ b/packages/backend/src/server/api/endpoints/i/pin.ts @@ -1,7 +1,9 @@ -import { addPinned } from '@/services/i/pin.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UsersRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { NotePiningService } from '@/core/NotePiningService.js'; import { ApiError } from '../../error.js'; -import { Users } from '@/models/index.js'; export const meta = { tags: ['account', 'notes'], @@ -46,15 +48,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - await addPinned(user, ps.noteId).catch(e => { - if (e.id === '70c4e51f-5bea-449c-a030-53bee3cce202') throw new ApiError(meta.errors.noSuchNote); - if (e.id === '15a018eb-58e5-4da1-93be-330fcc5e4e1a') throw new ApiError(meta.errors.pinLimitExceeded); - if (e.id === '23f0cf4e-59a3-4276-a91d-61a5891c1514') throw new ApiError(meta.errors.alreadyPinned); - throw e; - }); - - return await Users.pack(user.id, user, { - detail: true, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private userEntityService: UserEntityService, + private notePiningService: NotePiningService, + ) { + super(meta, paramDef, async (ps, me) => { + await this.notePiningService.addPinned(me, ps.noteId).catch(err => { + if (err.id === '70c4e51f-5bea-449c-a030-53bee3cce202') throw new ApiError(meta.errors.noSuchNote); + if (err.id === '15a018eb-58e5-4da1-93be-330fcc5e4e1a') throw new ApiError(meta.errors.pinLimitExceeded); + if (err.id === '23f0cf4e-59a3-4276-a91d-61a5891c1514') throw new ApiError(meta.errors.alreadyPinned); + throw err; + }); + + return await this.userEntityService.pack(me.id, me, { + detail: true, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts index 7ff6409caf..36c3566f55 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts @@ -1,6 +1,8 @@ -import { publishMainStream } from '@/services/stream.js'; -import define from '../../define.js'; -import { MessagingMessages, UserGroupJoinings } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'messaging'], @@ -17,25 +19,38 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Update documents - await MessagingMessages.update({ - recipientId: user.id, - isRead: false, - }, { - isRead: true, - }); - - const joinings = await UserGroupJoinings.findBy({ userId: user.id }); - - await Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder().update() - .set({ - reads: (() => `array_append("reads", '${user.id}')`) as any, - }) - .where(`groupId = :groupId`, { groupId: j.userGroupId }) - .andWhere('userId != :userId', { userId: user.id }) - .andWhere('NOT (:userId = ANY(reads))', { userId: user.id }) - .execute())); - - publishMainStream(user.id, 'readAllMessagingMessages'); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Update documents + await this.messagingMessagesRepository.update({ + recipientId: me.id, + isRead: false, + }, { + isRead: true, + }); + + const joinings = await this.userGroupJoiningsRepository.findBy({ userId: me.id }); + + await Promise.all(joinings.map(j => this.messagingMessagesRepository.createQueryBuilder().update() + .set({ + reads: (() => `array_append("reads", '${me.id}')`) as any, + }) + .where('groupId = :groupId', { groupId: j.userGroupId }) + .andWhere('userId != :userId', { userId: me.id }) + .andWhere('NOT (:userId = ANY(reads))', { userId: me.id }) + .execute())); + + this.globalEventService.publishMainStream(me.id, 'readAllMessagingMessages'); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts index 49f3deb331..b4bb83c6eb 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts @@ -1,6 +1,8 @@ -import { publishMainStream } from '@/services/stream.js'; -import define from '../../define.js'; -import { NoteUnreads } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteUnreadsRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], @@ -17,13 +19,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Remove documents - await NoteUnreads.delete({ - userId: user.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.noteUnreadsRepository) + private noteUnreadsRepository: NoteUnreadsRepository, - // 全て既読になったイベントを発行 - publishMainStream(user.id, 'readAllUnreadMentions'); - publishMainStream(user.id, 'readAllUnreadSpecifiedNotes'); -}); + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Remove documents + await this.noteUnreadsRepository.delete({ + userId: me.id, + }); + + // 全て既読になったイベントを発行 + this.globalEventService.publishMainStream(me.id, 'readAllUnreadMentions'); + this.globalEventService.publishMainStream(me.id, 'readAllUnreadSpecifiedNotes'); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts index 45b6e98c86..5a7909674f 100644 --- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts +++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts @@ -1,8 +1,12 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { genId } from '@/misc/gen-id.js'; -import { AnnouncementReads, Announcements, Users } from '@/models/index.js'; -import { publishMainStream } from '@/services/stream.js'; export const meta = { tags: ['account'], @@ -29,33 +33,48 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Check if announcement exists - const announcement = await Announcements.findOneBy({ id: ps.announcementId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.announcementsRepository) + private announcementsRepository: AnnouncementsRepository, - if (announcement == null) { - throw new ApiError(meta.errors.noSuchAnnouncement); - } + @Inject(DI.announcementReadsRepository) + private announcementReadsRepository: AnnouncementReadsRepository, - // Check if already read - const read = await AnnouncementReads.findOneBy({ - announcementId: ps.announcementId, - userId: user.id, - }); + private userEntityService: UserEntityService, + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Check if announcement exists + const announcement = await this.announcementsRepository.findOneBy({ id: ps.announcementId }); - if (read != null) { - return; - } + if (announcement == null) { + throw new ApiError(meta.errors.noSuchAnnouncement); + } + + // Check if already read + const read = await this.announcementReadsRepository.findOneBy({ + announcementId: ps.announcementId, + userId: me.id, + }); + + if (read != null) { + return; + } - // Create read - await AnnouncementReads.insert({ - id: genId(), - createdAt: new Date(), - announcementId: ps.announcementId, - userId: user.id, - }); + // Create read + await this.announcementReadsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + announcementId: ps.announcementId, + userId: me.id, + }); - if (!await Users.getHasUnreadAnnouncement(user.id)) { - publishMainStream(user.id, 'readAllAnnouncements'); + if (!await this.userEntityService.getHasUnreadAnnouncement(me.id)) { + this.globalEventService.publishMainStream(me.id, 'readAllAnnouncements'); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts index af929b04e8..7796fd97cb 100644 --- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -1,8 +1,10 @@ import bcrypt from 'bcryptjs'; -import { publishInternalEvent, publishMainStream, publishUserEvent } from '@/services/stream.js'; -import generateUserToken from '../../common/generate-native-user-token.js'; -import define from '../../define.js'; -import { Users, UserProfiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import generateUserToken from '@/misc/generate-native-user-token.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -19,31 +21,44 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const freshUser = await Users.findOneByOrFail({ id: user.id }); - const oldToken = freshUser.token; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const freshUser = await this.usersRepository.findOneByOrFail({ id: me.id }); + const oldToken = freshUser.token; - if (!same) { - throw new Error('incorrect password'); - } + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); - const newToken = generateUserToken(); + if (!same) { + throw new Error('incorrect password'); + } - await Users.update(user.id, { - token: newToken, - }); + const newToken = generateUserToken(); - // Publish event - publishInternalEvent('userTokenRegenerated', { id: user.id, oldToken, newToken }); - publishMainStream(user.id, 'myTokenRegenerated'); + await this.usersRepository.update(me.id, { + token: newToken, + }); - // Terminate streaming - setTimeout(() => { - publishUserEvent(user.id, 'terminate', {}); - }, 5000); -}); + // Publish event + this.globalEventService.publishInternalEvent('userTokenRegenerated', { id: me.id, oldToken, newToken }); + this.globalEventService.publishMainStream(me.id, 'myTokenRegenerated'); + + // Terminate streaming + setTimeout(() => { + this.globalEventService.publishUserEvent(me.id, 'terminate', {}); + }, 5000); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts index d0b16dbc48..3b4db5fae3 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -18,19 +20,27 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }) - .andWhere('item.scope = :scope', { scope: ps.scope }); - - const items = await query.getMany(); - - const res = {} as Record; - - for (const item of items) { - res[item.key] = item.value; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const items = await query.getMany(); + + const res = {} as Record; + + for (const item of items) { + res[item.key] = item.value; + } + + return res; + }); } - - return res; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts index cc5d5a8c6f..d24dff95b0 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -28,21 +30,29 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }) - .andWhere('item.key = :key', { key: ps.key }) - .andWhere('item.scope = :scope', { scope: ps.scope }); - - const item = await query.getOne(); - - if (item == null) { - throw new ApiError(meta.errors.noSuchKey); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }) + .andWhere('item.key = :key', { key: ps.key }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const item = await query.getOne(); + + if (item == null) { + throw new ApiError(meta.errors.noSuchKey); + } + + return { + updatedAt: item.updatedAt, + value: item.value, + }; + }); } - - return { - updatedAt: item.updatedAt, - value: item.value, - }; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts index a79319744c..98d94a4c02 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -28,18 +30,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }) - .andWhere('item.key = :key', { key: ps.key }) - .andWhere('item.scope = :scope', { scope: ps.scope }); - - const item = await query.getOne(); - - if (item == null) { - throw new ApiError(meta.errors.noSuchKey); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }) + .andWhere('item.key = :key', { key: ps.key }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const item = await query.getOne(); + + if (item == null) { + throw new ApiError(meta.errors.noSuchKey); + } + + return item.value; + }); } - - return item.value; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts index ac209c06a6..d1a05d9d06 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -18,19 +20,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }) - .andWhere('item.scope = :scope', { scope: ps.scope }); - - const items = await query.getMany(); - - const res = {} as Record; - - for (const item of items) { - const type = typeof item.value; - res[item.key] = +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const items = await query.getMany(); + + const res = {} as Record; + + for (const item of items) { + const type = typeof item.value; + res[item.key] = item.value === null ? 'null' : Array.isArray(item.value) ? 'array' : type === 'number' ? 'number' : @@ -38,7 +46,9 @@ export default define(meta, paramDef, async (ps, user) => { type === 'boolean' ? 'boolean' : type === 'object' ? 'object' : null as never; - } + } - return res; -}); + return res; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys.ts b/packages/backend/src/server/api/endpoints/i/registry/keys.ts index 5ea1a9d344..6df5f4ecc3 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -18,14 +20,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .select('item.key') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }) - .andWhere('item.scope = :scope', { scope: ps.scope }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .select('item.key') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }) + .andWhere('item.scope = :scope', { scope: ps.scope }); - const items = await query.getMany(); + const items = await query.getMany(); - return items.map(x => x.key); -}); + return items.map(x => x.key); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/remove.ts b/packages/backend/src/server/api/endpoints/i/registry/remove.ts index 92473654c6..b5870f099d 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/remove.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/remove.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -28,18 +30,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }) - .andWhere('item.key = :key', { key: ps.key }) - .andWhere('item.scope = :scope', { scope: ps.scope }); - - const item = await query.getOne(); - - if (item == null) { - throw new ApiError(meta.errors.noSuchKey); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }) + .andWhere('item.key = :key', { key: ps.key }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const item = await query.getOne(); + + if (item == null) { + throw new ApiError(meta.errors.noSuchKey); + } + + await this.registryItemsRepository.remove(item); + }); } - - await RegistryItems.remove(item); -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts index de4b313e25..58085ddbc5 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -14,20 +16,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .select('item.scope') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }); - - const items = await query.getMany(); - - const res = [] as string[][]; - - for (const item of items) { - if (res.some(scope => scope.join('.') === item.scope.join('.'))) continue; - res.push(item.scope); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .select('item.scope') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }); + + const items = await query.getMany(); + + const res = [] as string[][]; + + for (const item of items) { + if (res.some(scope => scope.join('.') === item.scope.join('.'))) continue; + res.push(item.scope); + } + + return res; + }); } - - return res; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/set.ts b/packages/backend/src/server/api/endpoints/i/registry/set.ts index d380b428a3..585aac2e01 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/set.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/set.ts @@ -1,7 +1,9 @@ -import { publishMainStream } from '@/services/stream.js'; -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -22,37 +24,48 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }) - .andWhere('item.key = :key', { key: ps.key }) - .andWhere('item.scope = :scope', { scope: ps.scope }); - - const existingItem = await query.getOne(); - - if (existingItem) { - await RegistryItems.update(existingItem.id, { - updatedAt: new Date(), - value: ps.value, - }); - } else { - await RegistryItems.insert({ - id: genId(), - createdAt: new Date(), - updatedAt: new Date(), - userId: user.id, - domain: null, - scope: ps.scope, - key: ps.key, - value: ps.value, +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }) + .andWhere('item.key = :key', { key: ps.key }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const existingItem = await query.getOne(); + + if (existingItem) { + await this.registryItemsRepository.update(existingItem.id, { + updatedAt: new Date(), + value: ps.value, + }); + } else { + await this.registryItemsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + updatedAt: new Date(), + userId: me.id, + domain: null, + scope: ps.scope, + key: ps.key, + value: ps.value, + }); + } + + // TODO: サードパーティアプリが傍受出来てしまうのでどうにかする + this.globalEventService.publishMainStream(me.id, 'registryUpdated', { + scope: ps.scope, + key: ps.key, + value: ps.value, + }); }); } - - // TODO: サードパーティアプリが傍受出来てしまうのでどうにかする - publishMainStream(user.id, 'registryUpdated', { - scope: ps.scope, - key: ps.key, - value: ps.value, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts index c692453794..86a82e6a6c 100644 --- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -1,6 +1,8 @@ -import define from '../../define.js'; -import { AccessTokens } from '@/models/index.js'; -import { publishUserEvent } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AccessTokensRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -17,16 +19,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const token = await AccessTokens.findOneBy({ id: ps.tokenId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, - if (token) { - await AccessTokens.delete({ - id: ps.tokenId, - userId: user.id, - }); + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const token = await this.accessTokensRepository.findOneBy({ id: ps.tokenId }); + + if (token) { + await this.accessTokensRepository.delete({ + id: ps.tokenId, + userId: me.id, + }); - // Terminate streaming - publishUserEvent(user.id, 'terminate'); + // Terminate streaming + this.globalEventService.publishUserEvent(me.id, 'terminate'); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/signin-history.ts b/packages/backend/src/server/api/endpoints/i/signin-history.ts index ca37411662..410cd72065 100644 --- a/packages/backend/src/server/api/endpoints/i/signin-history.ts +++ b/packages/backend/src/server/api/endpoints/i/signin-history.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Signins } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { SigninsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { SigninEntityService } from '@/core/entities/SigninEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -19,11 +22,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(Signins.createQueryBuilder('signin'), ps.sinceId, ps.untilId) - .andWhere(`signin.userId = :meId`, { meId: user.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.signinsRepository) + private signinsRepository: SigninsRepository, - const history = await query.take(ps.limit).getMany(); + private signinEntityService: SigninEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.signinsRepository.createQueryBuilder('signin'), ps.sinceId, ps.untilId) + .andWhere('signin.userId = :meId', { meId: me.id }); - return await Promise.all(history.map(record => Signins.pack(record))); -}); + const history = await query.take(ps.limit).getMany(); + + return await Promise.all(history.map(record => this.signinEntityService.pack(record))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/unpin.ts b/packages/backend/src/server/api/endpoints/i/unpin.ts index 9912689da5..9a735e1168 100644 --- a/packages/backend/src/server/api/endpoints/i/unpin.ts +++ b/packages/backend/src/server/api/endpoints/i/unpin.ts @@ -1,7 +1,9 @@ -import { removePinned } from '@/services/i/pin.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UsersRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { NotePiningService } from '@/core/NotePiningService.js'; import { ApiError } from '../../error.js'; -import { Users } from '@/models/index.js'; export const meta = { tags: ['account', 'notes'], @@ -34,13 +36,21 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - await removePinned(user, ps.noteId).catch(e => { - if (e.id === 'b302d4cf-c050-400a-bbb3-be208681f40c') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - return await Users.pack(user.id, user, { - detail: true, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private userEntityService: UserEntityService, + private notePiningService: NotePiningService, + ) { + super(meta, paramDef, async (ps, me) => { + await this.notePiningService.removePinned(me, ps.noteId).catch(err => { + if (err.id === 'b302d4cf-c050-400a-bbb3-be208681f40c') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + return await this.userEntityService.pack(me.id, me, { + detail: true, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 3318078523..719cc14f09 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -1,13 +1,15 @@ -import { publishMainStream } from '@/services/stream.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; import rndstr from 'rndstr'; -import config from '@/config/index.js'; import ms from 'ms'; import bcrypt from 'bcryptjs'; -import { Users, UserProfiles } from '@/models/index.js'; -import { sendEmail } from '@/services/send-email.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { EmailService } from '@/core/EmailService.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ApiError } from '../../error.js'; -import { validateEmailForAccount } from '@/services/validate-email-for-account.js'; export const meta = { requireCredential: true, @@ -44,50 +46,68 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); - - if (!same) { - throw new ApiError(meta.errors.incorrectPassword); - } - - if (ps.email != null) { - const available = await validateEmailForAccount(ps.email); - if (!available) { - throw new ApiError(meta.errors.unavailable); - } - } - - await UserProfiles.update(user.id, { - email: ps.email, - emailVerified: false, - emailVerifyCode: null, - }); - - const iObj = await Users.pack(user.id, user, { - detail: true, - includeSecrets: true, - }); - - // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', iObj); - - if (ps.email != null) { - const code = rndstr('a-z0-9', 16); - - await UserProfiles.update(user.id, { - emailVerifyCode: code, +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private userEntityService: UserEntityService, + private emailService: EmailService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new ApiError(meta.errors.incorrectPassword); + } + + if (ps.email != null) { + const available = await this.emailService.validateEmailForAccount(ps.email); + if (!available) { + throw new ApiError(meta.errors.unavailable); + } + } + + await this.userProfilesRepository.update(me.id, { + email: ps.email, + emailVerified: false, + emailVerifyCode: null, + }); + + const iObj = await this.userEntityService.pack(me.id, me, { + detail: true, + includeSecrets: true, + }); + + // Publish meUpdated event + this.globalEventService.publishMainStream(me.id, 'meUpdated', iObj); + + if (ps.email != null) { + const code = rndstr('a-z0-9', 16); + + await this.userProfilesRepository.update(me.id, { + emailVerifyCode: code, + }); + + const link = `${this.config.url}/verify-email/${code}`; + + this.emailService.sendEmail(ps.email, 'Email verification', + `To verify email, please click this link:
${link}`, + `To verify email, please click this link: ${link}`); + } + + return iObj; }); - - const link = `${config.url}/verify-email/${code}`; - - sendEmail(ps.email, 'Email verification', - `To verify email, please click this link:
${link}`, - `To verify email, please click this link: ${link}`); } - - return iObj; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 3c2f1cea0d..4b904d4696 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -1,19 +1,23 @@ import RE2 from 're2'; import * as mfm from 'mfm-js'; -import { publishMainStream, publishUserEvent } from '@/services/stream.js'; -import acceptAllFollowRequests from '@/services/following/requests/accept-all.js'; -import { publishToFollowers } from '@/services/i/update.js'; +import { Inject, Injectable } from '@nestjs/common'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractHashtags } from '@/misc/extract-hashtags.js'; -import { updateUsertags } from '@/services/update-hashtag.js'; -import { Users, DriveFiles, UserProfiles, Pages } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import { UserProfile } from '@/models/entities/user-profile.js'; +import { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/index.js'; +import type { User } from '@/models/entities/User.js'; +import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/entities/User.js'; +import type { UserProfile } from '@/models/entities/UserProfile.js'; import { notificationTypes } from '@/types.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { langmap } from '@/misc/langmap.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { AccountUpdateService } from '@/core/AccountUpdateService.js'; +import { HashtagService } from '@/core/HashtagService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import define from '../../define.js'; export const meta = { tags: ['account'], @@ -70,10 +74,10 @@ export const meta = { export const paramDef = { type: 'object', properties: { - name: { ...Users.nameSchema, nullable: true }, - description: { ...Users.descriptionSchema, nullable: true }, - location: { ...Users.locationSchema, nullable: true }, - birthday: { ...Users.birthdaySchema, nullable: true }, + name: { ...nameSchema, nullable: true }, + description: { ...descriptionSchema, nullable: true }, + location: { ...locationSchema, nullable: true }, + birthday: { ...birthdaySchema, nullable: true }, lang: { type: 'string', enum: [null, ...Object.keys(langmap)], nullable: true }, avatarId: { type: 'string', format: 'misskey:id', nullable: true }, bannerId: { type: 'string', format: 'misskey:id', nullable: true }, @@ -122,134 +126,157 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, _user, token) => { - const user = await Users.findOneByOrFail({ id: _user.id }); - const isSecure = token == null; - - const updates = {} as Partial; - const profileUpdates = {} as Partial; - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - if (ps.name !== undefined) updates.name = ps.name; - if (ps.description !== undefined) profileUpdates.description = ps.description; - if (ps.lang !== undefined) profileUpdates.lang = ps.lang; - if (ps.location !== undefined) profileUpdates.location = ps.location; - if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday; - if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility; - if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId; - if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId; - if (ps.mutedWords !== undefined) { - // validate regular expression syntax - ps.mutedWords.filter(x => !Array.isArray(x)).forEach(x => { - const regexp = x.match(/^\/(.+)\/(.*)$/); - if (!regexp) throw new ApiError(meta.errors.invalidRegexp); - - try { - new RE2(regexp[1], regexp[2]); - } catch (err) { - throw new ApiError(meta.errors.invalidRegexp); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + + private userEntityService: UserEntityService, + private globalEventService: GlobalEventService, + private userFollowingService: UserFollowingService, + private accountUpdateService: AccountUpdateService, + private hashtagService: HashtagService, + ) { + super(meta, paramDef, async (ps, _user, token) => { + const user = await this.usersRepository.findOneByOrFail({ id: _user.id }); + const isSecure = token == null; + + const updates = {} as Partial; + const profileUpdates = {} as Partial; + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + if (ps.name !== undefined) updates.name = ps.name; + if (ps.description !== undefined) profileUpdates.description = ps.description; + if (ps.lang !== undefined) profileUpdates.lang = ps.lang; + if (ps.location !== undefined) profileUpdates.location = ps.location; + if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday; + if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility; + if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId; + if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId; + if (ps.mutedWords !== undefined) { + // validate regular expression syntax + ps.mutedWords.filter(x => !Array.isArray(x)).forEach(x => { + const regexp = x.match(/^\/(.+)\/(.*)$/); + if (!regexp) throw new ApiError(meta.errors.invalidRegexp); + + try { + new RE2(regexp[1], regexp[2]); + } catch (err) { + throw new ApiError(meta.errors.invalidRegexp); + } + }); + + profileUpdates.mutedWords = ps.mutedWords; + profileUpdates.enableWordMute = ps.mutedWords.length > 0; + } + if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances; + if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes as typeof notificationTypes[number][]; + if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked; + if (typeof ps.isExplorable === 'boolean') updates.isExplorable = ps.isExplorable; + if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus; + if (typeof ps.publicReactions === 'boolean') profileUpdates.publicReactions = ps.publicReactions; + if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot; + if (typeof ps.showTimelineReplies === 'boolean') updates.showTimelineReplies = ps.showTimelineReplies; + if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot; + if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; + if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle; + if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat; + if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; + if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail; + if (typeof ps.alwaysMarkNsfw === 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw; + if (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive; + if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes; + + if (ps.avatarId) { + const avatar = await this.driveFilesRepository.findOneBy({ id: ps.avatarId }); + + if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar); + if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage); } - }); - - profileUpdates.mutedWords = ps.mutedWords; - profileUpdates.enableWordMute = ps.mutedWords.length > 0; - } - if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances; - if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes as typeof notificationTypes[number][]; - if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked; - if (typeof ps.isExplorable === 'boolean') updates.isExplorable = ps.isExplorable; - if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus; - if (typeof ps.publicReactions === 'boolean') profileUpdates.publicReactions = ps.publicReactions; - if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot; - if (typeof ps.showTimelineReplies === 'boolean') updates.showTimelineReplies = ps.showTimelineReplies; - if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot; - if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; - if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle; - if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat; - if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; - if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail; - if (typeof ps.alwaysMarkNsfw === 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw; - if (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive; - if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes; - - if (ps.avatarId) { - const avatar = await DriveFiles.findOneBy({ id: ps.avatarId }); - - 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 (ps.bannerId) { - const banner = await DriveFiles.findOneBy({ id: ps.bannerId }); + if (ps.bannerId) { + const banner = await this.driveFilesRepository.findOneBy({ id: ps.bannerId }); - 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 == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner); + if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage); + } - if (ps.pinnedPageId) { - const page = await Pages.findOneBy({ id: ps.pinnedPageId }); + if (ps.pinnedPageId) { + const page = await this.pagesRepository.findOneBy({ id: ps.pinnedPageId }); - if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage); + if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage); - profileUpdates.pinnedPageId = page.id; - } else if (ps.pinnedPageId === null) { - profileUpdates.pinnedPageId = null; - } + profileUpdates.pinnedPageId = page.id; + } else if (ps.pinnedPageId === null) { + profileUpdates.pinnedPageId = null; + } - if (ps.fields) { - profileUpdates.fields = ps.fields - .filter(x => typeof x.name === 'string' && x.name !== '' && typeof x.value === 'string' && x.value !== '') - .map(x => { - return { name: x.name, value: x.value }; - }); - } + if (ps.fields) { + profileUpdates.fields = ps.fields + .filter(x => typeof x.name === 'string' && x.name !== '' && typeof x.value === 'string' && x.value !== '') + .map(x => { + return { name: x.name, value: x.value }; + }); + } - //#region emojis/tags + //#region emojis/tags - let emojis = [] as string[]; - let tags = [] as string[]; + let emojis = [] as string[]; + let tags = [] as string[]; - const newName = updates.name === undefined ? user.name : updates.name; - const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description; + const newName = updates.name === undefined ? user.name : updates.name; + const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description; - if (newName != null) { - const tokens = mfm.parseSimple(newName); - emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!)); - } + if (newName != null) { + const tokens = mfm.parseSimple(newName); + emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!)); + } - if (newDescription != null) { - const tokens = mfm.parse(newDescription); - emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!)); - tags = extractHashtags(tokens!).map(tag => normalizeForSearch(tag)).splice(0, 32); - } + if (newDescription != null) { + const tokens = mfm.parse(newDescription); + emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!)); + tags = extractHashtags(tokens!).map(tag => normalizeForSearch(tag)).splice(0, 32); + } - updates.emojis = emojis; - updates.tags = tags; + updates.emojis = emojis; + updates.tags = tags; - // ハッシュタグ更新 - updateUsertags(user, tags); - //#endregion + // ハッシュタグ更新 + this.hashtagService.updateUsertags(user, tags); + //#endregion - if (Object.keys(updates).length > 0) await Users.update(user.id, updates); - if (Object.keys(profileUpdates).length > 0) await UserProfiles.update(user.id, profileUpdates); + if (Object.keys(updates).length > 0) await this.usersRepository.update(user.id, updates); + if (Object.keys(profileUpdates).length > 0) await this.userProfilesRepository.update(user.id, profileUpdates); - const iObj = await Users.pack(user.id, user, { - detail: true, - includeSecrets: isSecure, - }); + const iObj = await this.userEntityService.pack(user.id, user, { + detail: true, + includeSecrets: isSecure, + }); - // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', iObj); - publishUserEvent(user.id, 'updateUserProfile', await UserProfiles.findOneBy({ userId: user.id })); + // Publish meUpdated event + this.globalEventService.publishMainStream(user.id, 'meUpdated', iObj); + this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', await this.userProfilesRepository.findOneBy({ userId: user.id })); - // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 - if (user.isLocked && ps.isLocked === false) { - acceptAllFollowRequests(user); - } + // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 + if (user.isLocked && ps.isLocked === false) { + this.userFollowingService.acceptAllFollowRequests(user); + } - // フォロワーにUpdateを配信 - publishToFollowers(user.id); + // フォロワーにUpdateを配信 + this.accountUpdateService.publishToFollowers(user.id); - return iObj; -}); + return iObj; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts index 1d7e4a16b3..6dd1626bb8 100644 --- a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts +++ b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { UserGroupInvitations } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserGroupInvitationsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UserGroupInvitationEntityService } from '@/core/entities/UserGroupInvitationEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'groups'], @@ -42,14 +45,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(UserGroupInvitations.createQueryBuilder('invitation'), ps.sinceId, ps.untilId) - .andWhere(`invitation.userId = :meId`, { meId: user.id }) - .leftJoinAndSelect('invitation.userGroup', 'user_group'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupInvitationsRepository) + private userGroupInvitationsRepository: UserGroupInvitationsRepository, - const invitations = await query - .take(ps.limit) - .getMany(); + private userGroupInvitationEntityService: UserGroupInvitationEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.userGroupInvitationsRepository.createQueryBuilder('invitation'), ps.sinceId, ps.untilId) + .andWhere('invitation.userId = :meId', { meId: me.id }) + .leftJoinAndSelect('invitation.userGroup', 'user_group'); - return await UserGroupInvitations.packMany(invitations); -}); + const invitations = await query + .take(ps.limit) + .getMany(); + + return await this.userGroupInvitationEntityService.packMany(invitations); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts index 2e2fd00b8c..016b1b5d6a 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -1,8 +1,10 @@ -import define from '../../../define.js'; -import { genId } from '@/misc/gen-id.js'; -import { Webhooks } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; -import { webhookEventTypes } from '@/models/entities/webhook.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import { WebhooksRepository } from '@/models/index.js'; +import { webhookEventTypes } from '@/models/entities/Webhook.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['webhooks'], @@ -25,19 +27,32 @@ export const paramDef = { required: ['name', 'url', 'secret', 'on'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const webhook = await Webhooks.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - name: ps.name, - url: ps.url, - secret: ps.secret, - on: ps.on, - }).then(x => Webhooks.findOneByOrFail(x.identifiers[0])); - - publishInternalEvent('webhookCreated', webhook); - - return webhook; -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.webhooksRepository) + private webhooksRepository: WebhooksRepository, + + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const webhook = await this.webhooksRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + name: ps.name, + url: ps.url, + secret: ps.secret, + on: ps.on, + }).then(x => this.webhooksRepository.findOneByOrFail(x.identifiers[0])); + + this.globalEventService.publishInternalEvent('webhookCreated', webhook); + + return webhook; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts index 2821eaa5f1..53b553b43e 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { WebhooksRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { Webhooks } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['webhooks'], @@ -27,18 +29,30 @@ export const paramDef = { required: ['webhookId'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const webhook = await Webhooks.findOneBy({ - id: ps.webhookId, - userId: user.id, - }); - - if (webhook == null) { - throw new ApiError(meta.errors.noSuchWebhook); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.webhooksRepository) + private webhooksRepository: WebhooksRepository, + + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const webhook = await this.webhooksRepository.findOneBy({ + id: ps.webhookId, + userId: me.id, + }); + + if (webhook == null) { + throw new ApiError(meta.errors.noSuchWebhook); + } + + await this.webhooksRepository.delete(webhook.id); + + this.globalEventService.publishInternalEvent('webhookDeleted', webhook); + }); } - - await Webhooks.delete(webhook.id); - - publishInternalEvent('webhookDeleted', webhook); -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts index 54e4563732..8e4aff45dd 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { Webhooks } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { WebhooksRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['webhooks', 'account'], @@ -16,10 +18,18 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const webhooks = await Webhooks.findBy({ - userId: me.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.webhooksRepository) + private webhooksRepository: WebhooksRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const webhooks = await this.webhooksRepository.findBy({ + userId: me.id, + }); - return webhooks; -}); + return webhooks; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts index 02fa1edb5e..622c2ade98 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { WebhooksRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { Webhooks } from '@/models/index.js'; export const meta = { tags: ['webhooks'], @@ -27,15 +29,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const webhook = await Webhooks.findOneBy({ - id: ps.webhookId, - userId: user.id, - }); - - if (webhook == null) { - throw new ApiError(meta.errors.noSuchWebhook); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.webhooksRepository) + private webhooksRepository: WebhooksRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const webhook = await this.webhooksRepository.findOneBy({ + id: ps.webhookId, + userId: me.id, + }); + + if (webhook == null) { + throw new ApiError(meta.errors.noSuchWebhook); + } + + return webhook; + }); } - - return webhook; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts index f87b9753fb..3a0ef1a526 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts @@ -1,8 +1,10 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { WebhooksRepository } from '@/models/index.js'; +import { webhookEventTypes } from '@/models/entities/Webhook.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { Webhooks } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; -import { webhookEventTypes } from '@/models/entities/webhook.js'; export const meta = { tags: ['webhooks'], @@ -36,24 +38,36 @@ export const paramDef = { required: ['webhookId', 'name', 'url', 'secret', 'on', 'active'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const webhook = await Webhooks.findOneBy({ - id: ps.webhookId, - userId: user.id, - }); - - if (webhook == null) { - throw new ApiError(meta.errors.noSuchWebhook); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.webhooksRepository) + private webhooksRepository: WebhooksRepository, + + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const webhook = await this.webhooksRepository.findOneBy({ + id: ps.webhookId, + userId: me.id, + }); - await Webhooks.update(webhook.id, { - name: ps.name, - url: ps.url, - secret: ps.secret, - on: ps.on, - active: ps.active, - }); + if (webhook == null) { + throw new ApiError(meta.errors.noSuchWebhook); + } - publishInternalEvent('webhookUpdated', webhook); -}); + await this.webhooksRepository.update(webhook.id, { + name: ps.name, + url: ps.url, + secret: ps.secret, + on: ps.on, + active: ps.active, + }); + + this.globalEventService.publishInternalEvent('webhookUpdated', webhook); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/messaging/history.ts b/packages/backend/src/server/api/endpoints/messaging/history.ts index ea0600d0e4..da3ba59df9 100644 --- a/packages/backend/src/server/api/endpoints/messaging/history.ts +++ b/packages/backend/src/server/api/endpoints/messaging/history.ts @@ -1,7 +1,10 @@ -import define from '../../define.js'; -import { MessagingMessage } from '@/models/entities/messaging-message.js'; -import { MessagingMessages, Mutings, UserGroupJoinings } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; +import { MutingsRepository, UserGroupJoiningsRepository, MessagingMessagesRepository } from '@/models/index.js'; +import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['messaging'], @@ -31,61 +34,77 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const mute = await Mutings.findBy({ - muterId: user.id, - }); - - const groups = ps.group ? await UserGroupJoinings.findBy({ - userId: user.id, - }).then(xs => xs.map(x => x.userGroupId)) : []; - - if (ps.group && groups.length === 0) { - return []; - } - - const history: MessagingMessage[] = []; - - for (let i = 0; i < ps.limit; i++) { - const found = ps.group - ? history.map(m => m.groupId!) - : history.map(m => (m.userId === user.id) ? m.recipientId! : m.userId!); - - const query = MessagingMessages.createQueryBuilder('message') - .orderBy('message.createdAt', 'DESC'); - - if (ps.group) { - query.where(`message.groupId IN (:...groups)`, { groups: groups }); - - if (found.length > 0) { - query.andWhere(`message.groupId NOT IN (:...found)`, { found: found }); - } - } else { - query.where(new Brackets(qb => { qb - .where(`message.userId = :userId`, { userId: user.id }) - .orWhere(`message.recipientId = :userId`, { userId: user.id }); - })); - query.andWhere(`message.groupId IS NULL`); - - if (found.length > 0) { - query.andWhere(`message.userId NOT IN (:...found)`, { found: found }); - query.andWhere(`message.recipientId NOT IN (:...found)`, { found: found }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private messagingMessageEntityService: MessagingMessageEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const mute = await this.mutingsRepository.findBy({ + muterId: me.id, + }); + + const groups = ps.group ? await this.userGroupJoiningsRepository.findBy({ + userId: me.id, + }).then(xs => xs.map(x => x.userGroupId)) : []; + + if (ps.group && groups.length === 0) { + return []; } - 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 history: MessagingMessage[] = []; + + for (let i = 0; i < ps.limit; i++) { + const found = ps.group + ? history.map(m => m.groupId!) + : history.map(m => (m.userId === me.id) ? m.recipientId! : m.userId!); + + const query = this.messagingMessagesRepository.createQueryBuilder('message') + .orderBy('message.createdAt', 'DESC'); + + if (ps.group) { + query.where('message.groupId IN (:...groups)', { groups: groups }); + + if (found.length > 0) { + query.andWhere('message.groupId NOT IN (:...found)', { found: found }); + } + } else { + query.where(new Brackets(qb => { qb + .where('message.userId = :userId', { userId: me.id }) + .orWhere('message.recipientId = :userId', { userId: me.id }); + })); + query.andWhere('message.groupId IS NULL'); + + 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); + } else { + break; + } } - } - const message = await query.getOne(); - - if (message) { - history.push(message); - } else { - break; - } + return await Promise.all(history.map(h => this.messagingMessageEntityService.pack(h.id, me))); + }); } - - return await Promise.all(history.map(h => MessagingMessages.pack(h.id, user))); -}); +} diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts index dbf1f6c868..6579b03987 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts @@ -1,10 +1,15 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { MessagingMessages, UserGroups, UserGroupJoinings, Users } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; -import { readUserMessagingMessage, readGroupMessagingMessage, deliverReadActivity } from '../../common/read-messaging-message.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UsersRepository } from '@/models/index.js'; +import { UserGroupsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; +import { MessagingService } from '@/core/MessagingService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['messaging'], @@ -69,73 +74,93 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - if (ps.userId != null) { - // Fetch recipient (user) - const recipient = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId) - .andWhere(new Brackets(qb => { qb - .where(new Brackets(qb => { qb - .where('message.userId = :meId') - .andWhere('message.recipientId = :recipientId'); - })) - .orWhere(new Brackets(qb => { qb - .where('message.userId = :recipientId') - .andWhere('message.recipientId = :meId'); - })); - })) - .setParameter('meId', user.id) - .setParameter('recipientId', recipient.id); - - const messages = await query.take(ps.limit).getMany(); - - // Mark all as read - if (ps.markAsRead) { - readUserMessagingMessage(user.id, recipient.id, messages.filter(m => m.recipientId === user.id).map(x => x.id)); - - // リモートユーザーとのメッセージだったら既読配信 - if (Users.isLocalUser(user) && Users.isRemoteUser(recipient)) { - deliverReadActivity(user, recipient, messages); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + @Inject(DI.userGroupsRepository) + private userGroupRepository: UserGroupsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private messagingMessageEntityService: MessagingMessageEntityService, + private messagingService: MessagingService, + private userEntityService: UserEntityService, + private queryService: QueryService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + if (ps.userId != null) { + // Fetch recipient (user) + const recipient = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + const query = this.queryService.makePaginationQuery(this.messagingMessagesRepository.createQueryBuilder('message'), ps.sinceId, ps.untilId) + .andWhere(new Brackets(qb => { qb + .where(new Brackets(qb => { qb + .where('message.userId = :meId') + .andWhere('message.recipientId = :recipientId'); + })) + .orWhere(new Brackets(qb => { qb + .where('message.userId = :recipientId') + .andWhere('message.recipientId = :meId'); + })); + })) + .setParameter('meId', me.id) + .setParameter('recipientId', recipient.id); + + const messages = await query.take(ps.limit).getMany(); + + // Mark all as read + if (ps.markAsRead) { + this.messagingService.readUserMessagingMessage(me.id, recipient.id, messages.filter(m => m.recipientId === me.id).map(x => x.id)); + + // リモートユーザーとのメッセージだったら既読配信 + if (this.userEntityService.isLocalUser(me) && this.userEntityService.isRemoteUser(recipient)) { + this.messagingService.deliverReadActivity(me, recipient, messages); + } + } + + return await Promise.all(messages.map(message => this.messagingMessageEntityService.pack(message, me, { + populateRecipient: false, + }))); + } else if (ps.groupId != null) { + // Fetch recipient (group) + const recipientGroup = await this.userGroupRepository.findOneBy({ id: ps.groupId }); + + if (recipientGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } + + // check joined + const joining = await this.userGroupJoiningsRepository.findOneBy({ + userId: me.id, + userGroupId: recipientGroup.id, + }); + + if (joining == null) { + throw new ApiError(meta.errors.groupAccessDenied); + } + + const query = this.queryService.makePaginationQuery(this.messagingMessagesRepository.createQueryBuilder('message'), ps.sinceId, ps.untilId) + .andWhere('message.groupId = :groupId', { groupId: recipientGroup.id }); + + const messages = await query.take(ps.limit).getMany(); + + // Mark all as read + if (ps.markAsRead) { + this.messagingService.readGroupMessagingMessage(me.id, recipientGroup.id, messages.map(x => x.id)); + } + + return await Promise.all(messages.map(message => this.messagingMessageEntityService.pack(message, me, { + populateGroup: false, + }))); } - } - - return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, { - populateRecipient: false, - }))); - } else if (ps.groupId != null) { - // Fetch recipient (group) - const recipientGroup = await UserGroups.findOneBy({ id: ps.groupId }); - - if (recipientGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); - } - - // check joined - const joining = await UserGroupJoinings.findOneBy({ - userId: user.id, - userGroupId: recipientGroup.id, }); - - if (joining == null) { - throw new ApiError(meta.errors.groupAccessDenied); - } - - const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId) - .andWhere(`message.groupId = :groupId`, { groupId: recipientGroup.id }); - - const messages = await query.take(ps.limit).getMany(); - - // Mark all as read - if (ps.markAsRead) { - readGroupMessagingMessage(user.id, recipientGroup.id, messages.map(x => x.id)); - } - - return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, { - populateGroup: false, - }))); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts index 405af5ec17..e02afcbcfd 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts @@ -1,10 +1,12 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { BlockingsRepository, UserGroupJoiningsRepository, DriveFilesRepository, UserGroupsRepository } from '@/models/index.js'; +import type { User } from '@/models/entities/User.js'; +import type { UserGroup } from '@/models/entities/UserGroup.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { MessagingService } from '@/core/MessagingService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; -import { MessagingMessages, DriveFiles, UserGroups, UserGroupJoinings, Blockings } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { createMessage } from '@/services/messages/create.js'; export const meta = { tags: ['messaging'], @@ -87,65 +89,85 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - let recipientUser: User | null; - let recipientGroup: UserGroup | null; - - if (ps.userId != null) { - // Myself - if (ps.userId === user.id) { - throw new ApiError(meta.errors.recipientIsYourself); - } - - // Fetch recipient (user) - recipientUser = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private getterService: GetterService, + private messagingService: MessagingService, + ) { + super(meta, paramDef, async (ps, me) => { + let recipientUser: User | null; + let recipientGroup: UserGroup | null; + + if (ps.userId != null) { + // Myself + if (ps.userId === me.id) { + throw new ApiError(meta.errors.recipientIsYourself); + } + + // Fetch recipient (user) + recipientUser = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check blocking + const block = await this.blockingsRepository.findOneBy({ + blockerId: recipientUser.id, + blockeeId: me.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } else if (ps.groupId != null) { + // Fetch recipient (group) + recipientGroup = await this.userGroupsRepository.findOneBy({ id: ps.groupId! }); + + if (recipientGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } + + // check joined + const joining = await this.userGroupJoiningsRepository.findOneBy({ + userId: me.id, + userGroupId: recipientGroup.id, + }); + + if (joining == null) { + throw new ApiError(meta.errors.groupAccessDenied); + } + } + + let file = null; + if (ps.fileId != null) { + file = await this.driveFilesRepository.findOneBy({ + id: ps.fileId, + userId: me.id, + }); + + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } + } + + // テキストが無いかつ添付ファイルも無かったらエラー + if (ps.text == null && file == null) { + throw new ApiError(meta.errors.contentRequired); + } + + return await this.messagingService.createMessage(me, recipientUser, recipientGroup, ps.text, file); }); - - // Check blocking - const block = await Blockings.findOneBy({ - blockerId: recipientUser.id, - blockeeId: user.id, - }); - if (block) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } else if (ps.groupId != null) { - // Fetch recipient (group) - recipientGroup = await UserGroups.findOneBy({ id: ps.groupId! }); - - if (recipientGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); - } - - // check joined - const joining = await UserGroupJoinings.findOneBy({ - userId: user.id, - userGroupId: recipientGroup.id, - }); - - if (joining == null) { - throw new ApiError(meta.errors.groupAccessDenied); - } } - - let file = null; - if (ps.fileId != null) { - file = await DriveFiles.findOneBy({ - id: ps.fileId, - userId: user.id, - }); - - if (file == null) { - throw new ApiError(meta.errors.noSuchFile); - } - } - - // テキストが無いかつ添付ファイルも無かったらエラー - if (ps.text == null && file == null) { - throw new ApiError(meta.errors.contentRequired); - } - - return await createMessage(user, recipientUser, recipientGroup, ps.text, file); -}); +} diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts index f66d75873c..5baecb9114 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts @@ -1,8 +1,10 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MessagingMessagesRepository } from '@/models/index.js'; +import { MessagingService } from '@/core/MessagingService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { MessagingMessages } from '@/models/index.js'; -import { deleteMessage } from '@/services/messages/delete.js'; export const meta = { tags: ['messaging'], @@ -35,15 +37,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const message = await MessagingMessages.findOneBy({ - id: ps.messageId, - userId: user.id, - }); - - if (message == null) { - throw new ApiError(meta.errors.noSuchMessage); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + private messagingService: MessagingService, + ) { + super(meta, paramDef, async (ps, me) => { + const message = await this.messagingMessagesRepository.findOneBy({ + id: ps.messageId, + userId: me.id, + }); + + if (message == null) { + throw new ApiError(meta.errors.noSuchMessage); + } + + await this.messagingService.deleteMessage(message); + }); } - - await deleteMessage(message); -}); +} diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts index db12ae922c..6e66cafe1e 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MessagingMessagesRepository } from '@/models/index.js'; +import { MessagingService } from '@/core/MessagingService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { MessagingMessages } from '@/models/index.js'; -import { readUserMessagingMessage, readGroupMessagingMessage } from '../../../common/read-messaging-message.js'; export const meta = { tags: ['messaging'], @@ -28,22 +30,32 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const message = await MessagingMessages.findOneBy({ id: ps.messageId }); - - if (message == null) { - throw new ApiError(meta.errors.noSuchMessage); - } - - if (message.recipientId) { - await readUserMessagingMessage(user.id, message.userId, [message.id]).catch(e => { - if (e.id === 'e140a4bf-49ce-4fb6-b67c-b78dadf6b52f') throw new ApiError(meta.errors.noSuchMessage); - throw e; - }); - } else if (message.groupId) { - await readGroupMessagingMessage(user.id, message.groupId, [message.id]).catch(e => { - if (e.id === '930a270c-714a-46b2-b776-ad27276dc569') throw new ApiError(meta.errors.noSuchMessage); - throw e; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + private messagingService: MessagingService, + ) { + super(meta, paramDef, async (ps, me) => { + const message = await this.messagingMessagesRepository.findOneBy({ id: ps.messageId }); + + if (message == null) { + throw new ApiError(meta.errors.noSuchMessage); + } + + if (message.recipientId) { + await this.messagingService.readUserMessagingMessage(me.id, message.userId, [message.id]).catch(err => { + if (err.id === 'e140a4bf-49ce-4fb6-b67c-b78dadf6b52f') throw new ApiError(meta.errors.noSuchMessage); + throw err; + }); + } else if (message.groupId) { + await this.messagingService.readGroupMessagingMessage(me.id, message.groupId, [message.id]).catch(err => { + if (err.id === '930a270c-714a-46b2-b776-ad27276dc569') throw new ApiError(meta.errors.noSuchMessage); + throw err; + }); + } }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 5b624842c3..9a6258d7dd 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -1,10 +1,14 @@ import { IsNull, MoreThan } from 'typeorm'; -import config from '@/config/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Ads, Emojis, Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { AdsRepository, EmojisRepository, UsersRepository } from '@/models/index.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import define from '../define.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['meta'], @@ -26,7 +30,6 @@ export const meta = { version: { type: 'string', optional: false, nullable: false, - example: config.version, }, name: { type: 'string', @@ -304,111 +307,132 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const instance = await fetchMeta(true); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - const emojis = await Emojis.find({ - where: { - host: IsNull(), - }, - order: { - category: 'ASC', - name: 'ASC', - }, - cache: { - id: 'meta_emojis', - milliseconds: 3600000, // 1 hour - }, - }); + @Inject(DI.adsRepository) + private adsRepository: AdsRepository, - const ads = await Ads.find({ - where: { - expiresAt: MoreThan(new Date()), - }, - }); + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, - const response: any = { - maintainerName: instance.maintainerName, - maintainerEmail: instance.maintainerEmail, + private userEntityService: UserEntityService, + private emojiEntityService: EmojiEntityService, + private metaService: MetaService, + ) { + super(meta, paramDef, async (ps, me) => { + const instance = await this.metaService.fetch(true); - version: config.version, + const emojis = await this.emojisRepository.find({ + where: { + host: IsNull(), + }, + order: { + category: 'ASC', + name: 'ASC', + }, + cache: { + id: 'meta_emojis', + milliseconds: 3600000, // 1 hour + }, + }); - name: instance.name, - uri: config.url, - description: instance.description, - langs: instance.langs, - tosUrl: instance.ToSUrl, - repositoryUrl: instance.repositoryUrl, - feedbackUrl: instance.feedbackUrl, - disableRegistration: instance.disableRegistration, - disableLocalTimeline: instance.disableLocalTimeline, - disableGlobalTimeline: instance.disableGlobalTimeline, - driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, - driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, - emailRequiredForSignup: instance.emailRequiredForSignup, - enableHcaptcha: instance.enableHcaptcha, - hcaptchaSiteKey: instance.hcaptchaSiteKey, - enableRecaptcha: instance.enableRecaptcha, - recaptchaSiteKey: instance.recaptchaSiteKey, - swPublickey: instance.swPublicKey, - themeColor: instance.themeColor, - mascotImageUrl: instance.mascotImageUrl, - bannerUrl: instance.bannerUrl, - errorImageUrl: instance.errorImageUrl, - iconUrl: instance.iconUrl, - backgroundImageUrl: instance.backgroundImageUrl, - logoImageUrl: instance.logoImageUrl, - maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため - emojis: await Emojis.packMany(emojis), - defaultLightTheme: instance.defaultLightTheme, - defaultDarkTheme: instance.defaultDarkTheme, - ads: ads.map(ad => ({ - id: ad.id, - url: ad.url, - place: ad.place, - ratio: ad.ratio, - imageUrl: ad.imageUrl, - })), - enableEmail: instance.enableEmail, + const ads = await this.adsRepository.find({ + where: { + expiresAt: MoreThan(new Date()), + }, + }); - enableTwitterIntegration: instance.enableTwitterIntegration, - enableGithubIntegration: instance.enableGithubIntegration, - enableDiscordIntegration: instance.enableDiscordIntegration, + const response: any = { + maintainerName: instance.maintainerName, + maintainerEmail: instance.maintainerEmail, - enableServiceWorker: instance.enableServiceWorker, + version: this.config.version, - translatorAvailable: instance.deeplAuthKey != null, + name: instance.name, + uri: this.config.url, + description: instance.description, + langs: instance.langs, + tosUrl: instance.ToSUrl, + repositoryUrl: instance.repositoryUrl, + feedbackUrl: instance.feedbackUrl, + disableRegistration: instance.disableRegistration, + disableLocalTimeline: instance.disableLocalTimeline, + disableGlobalTimeline: instance.disableGlobalTimeline, + driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, + driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, + emailRequiredForSignup: instance.emailRequiredForSignup, + enableHcaptcha: instance.enableHcaptcha, + hcaptchaSiteKey: instance.hcaptchaSiteKey, + enableRecaptcha: instance.enableRecaptcha, + recaptchaSiteKey: instance.recaptchaSiteKey, + swPublickey: instance.swPublicKey, + themeColor: instance.themeColor, + mascotImageUrl: instance.mascotImageUrl, + bannerUrl: instance.bannerUrl, + errorImageUrl: instance.errorImageUrl, + iconUrl: instance.iconUrl, + backgroundImageUrl: instance.backgroundImageUrl, + logoImageUrl: instance.logoImageUrl, + maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため + emojis: await this.emojiEntityService.packMany(emojis), + defaultLightTheme: instance.defaultLightTheme, + defaultDarkTheme: instance.defaultDarkTheme, + ads: ads.map(ad => ({ + id: ad.id, + url: ad.url, + place: ad.place, + ratio: ad.ratio, + imageUrl: ad.imageUrl, + })), + enableEmail: instance.enableEmail, - ...(ps.detail ? { - pinnedPages: instance.pinnedPages, - pinnedClipId: instance.pinnedClipId, - cacheRemoteFiles: instance.cacheRemoteFiles, - requireSetup: (await Users.countBy({ - host: IsNull(), - })) === 0, - } : {}), - }; + enableTwitterIntegration: instance.enableTwitterIntegration, + enableGithubIntegration: instance.enableGithubIntegration, + enableDiscordIntegration: instance.enableDiscordIntegration, - if (ps.detail) { - const proxyAccount = instance.proxyAccountId ? await Users.pack(instance.proxyAccountId).catch(() => null) : null; + enableServiceWorker: instance.enableServiceWorker, - response.proxyAccountName = proxyAccount ? proxyAccount.username : null; - response.features = { - registration: !instance.disableRegistration, - localTimeLine: !instance.disableLocalTimeline, - globalTimeLine: !instance.disableGlobalTimeline, - emailRequiredForSignup: instance.emailRequiredForSignup, - elasticsearch: config.elasticsearch ? true : false, - hcaptcha: instance.enableHcaptcha, - recaptcha: instance.enableRecaptcha, - objectStorage: instance.useObjectStorage, - twitter: instance.enableTwitterIntegration, - github: instance.enableGithubIntegration, - discord: instance.enableDiscordIntegration, - serviceWorker: instance.enableServiceWorker, - miauth: true, - }; - } + translatorAvailable: instance.deeplAuthKey != null, + + ...(ps.detail ? { + pinnedPages: instance.pinnedPages, + pinnedClipId: instance.pinnedClipId, + cacheRemoteFiles: instance.cacheRemoteFiles, + requireSetup: (await this.usersRepository.countBy({ + host: IsNull(), + })) === 0, + } : {}), + }; - return response; -}); + if (ps.detail) { + const proxyAccount = instance.proxyAccountId ? await this.userEntityService.pack(instance.proxyAccountId).catch(() => null) : null; + + response.proxyAccountName = proxyAccount ? proxyAccount.username : null; + response.features = { + registration: !instance.disableRegistration, + localTimeLine: !instance.disableLocalTimeline, + globalTimeLine: !instance.disableGlobalTimeline, + emailRequiredForSignup: instance.emailRequiredForSignup, + elasticsearch: this.config.elasticsearch ? true : false, + hcaptcha: instance.enableHcaptcha, + recaptcha: instance.enableRecaptcha, + objectStorage: instance.useObjectStorage, + twitter: instance.enableTwitterIntegration, + github: instance.enableGithubIntegration, + discord: instance.enableDiscordIntegration, + serviceWorker: instance.enableServiceWorker, + miauth: true, + }; + } + + return response; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts index 73ecdaeb03..d8eb89c0e6 100644 --- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts +++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; -import { AccessTokens } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AccessTokensRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['auth'], @@ -37,28 +39,38 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Generate access token - const accessToken = secureRndstr(32, true); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, - const now = new Date(); + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + // Generate access token + const accessToken = secureRndstr(32, true); - // Insert access token doc - await AccessTokens.insert({ - id: genId(), - createdAt: now, - lastUsedAt: now, - session: ps.session, - userId: user.id, - token: accessToken, - hash: accessToken, - name: ps.name, - description: ps.description, - iconUrl: ps.iconUrl, - permission: ps.permission, - }); + const now = new Date(); - return { - token: accessToken, - }; -}); + // Insert access token doc + await this.accessTokensRepository.insert({ + id: this.idService.genId(), + createdAt: now, + lastUsedAt: now, + session: ps.session, + userId: me.id, + token: accessToken, + hash: accessToken, + name: ps.name, + description: ps.description, + iconUrl: ps.iconUrl, + permission: ps.permission, + }); + + return { + token: accessToken, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 7e857e6731..cbdd001185 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -1,10 +1,12 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import { MutingsRepository } from '@/models/index.js'; +import type { Muting } from '@/models/entities/Muting.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { genId } from '@/misc/gen-id.js'; -import { Mutings, NoteWatchings } from '@/models/index.js'; -import { Muting } from '@/models/entities/muting.js'; -import { publishUserEvent } from '@/services/stream.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['account'], @@ -48,47 +50,54 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const muter = user; - - // 自分自身 - if (user.id === ps.userId) { - throw new ApiError(meta.errors.muteeIsYourself); - } - - // Get mutee - const mutee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Check if already muting - const exist = await Mutings.findOneBy({ - muterId: muter.id, - muteeId: mutee.id, - }); - - if (exist != null) { - throw new ApiError(meta.errors.alreadyMuting); - } - - if (ps.expiresAt && ps.expiresAt <= Date.now()) { - return; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, + + private globalEventService: GlobalEventService, + private getterService: GetterService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const muter = me; + + // 自分自身 + if (me.id === ps.userId) { + throw new ApiError(meta.errors.muteeIsYourself); + } + + // Get mutee + const mutee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check if already muting + const exist = await this.mutingsRepository.findOneBy({ + muterId: muter.id, + muteeId: mutee.id, + }); + + if (exist != null) { + throw new ApiError(meta.errors.alreadyMuting); + } + + if (ps.expiresAt && ps.expiresAt <= Date.now()) { + return; + } + + // Create mute + await this.mutingsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, + muterId: muter.id, + muteeId: mutee.id, + } as Muting); + + this.globalEventService.publishUserEvent(me.id, 'mute', mutee); + }); } - - // Create mute - await Mutings.insert({ - id: genId(), - createdAt: new Date(), - expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, - muterId: muter.id, - muteeId: mutee.id, - } as Muting); - - publishUserEvent(user.id, 'mute', mutee); - - NoteWatchings.delete({ - userId: muter.id, - noteUserId: mutee.id, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts index 0b173dbe24..c7098059d5 100644 --- a/packages/backend/src/server/api/endpoints/mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/mute/delete.ts @@ -1,8 +1,10 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MutingsRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { Mutings } from '@/models/index.js'; -import { publishUserEvent } from '@/services/stream.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['account'], @@ -41,34 +43,45 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const muter = user; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, - // Check if the mutee is yourself - if (user.id === ps.userId) { - throw new ApiError(meta.errors.muteeIsYourself); - } + private globalEventService: GlobalEventService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const muter = me; - // Get mutee - const mutee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); + // Check if the mutee is yourself + if (me.id === ps.userId) { + throw new ApiError(meta.errors.muteeIsYourself); + } - // Check not muting - const exist = await Mutings.findOneBy({ - muterId: muter.id, - muteeId: mutee.id, - }); + // Get mutee + const mutee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); - if (exist == null) { - throw new ApiError(meta.errors.notMuting); - } + // Check not muting + const exist = await this.mutingsRepository.findOneBy({ + muterId: muter.id, + muteeId: mutee.id, + }); - // Delete mute - await Mutings.delete({ - id: exist.id, - }); + if (exist == null) { + throw new ApiError(meta.errors.notMuting); + } - publishUserEvent(user.id, 'unmute', mutee); -}); + // Delete mute + await this.mutingsRepository.delete({ + id: exist.id, + }); + + this.globalEventService.publishUserEvent(me.id, 'unmute', mutee); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/mute/list.ts b/packages/backend/src/server/api/endpoints/mute/list.ts index 31283cf4c1..11c05eb795 100644 --- a/packages/backend/src/server/api/endpoints/mute/list.ts +++ b/packages/backend/src/server/api/endpoints/mute/list.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { Mutings } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MutingsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { MutingEntityService } from '@/core/entities/MutingEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], @@ -31,13 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(Mutings.createQueryBuilder('muting'), ps.sinceId, ps.untilId) - .andWhere(`muting.muterId = :meId`, { meId: me.id }); - - const mutings = await query - .take(ps.limit) - .getMany(); - - return await Mutings.packMany(mutings, me); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, + + private mutingEntityService: MutingEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.mutingsRepository.createQueryBuilder('muting'), ps.sinceId, ps.untilId) + .andWhere('muting.muterId = :meId', { meId: me.id }); + + const mutings = await query + .take(ps.limit) + .getMany(); + + return await this.mutingEntityService.packMany(mutings, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/my/apps.ts b/packages/backend/src/server/api/endpoints/my/apps.ts index 85b75c15df..90cd53a133 100644 --- a/packages/backend/src/server/api/endpoints/my/apps.ts +++ b/packages/backend/src/server/api/endpoints/my/apps.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { Apps } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AppsRepository } from '@/models/index.js'; +import { AppEntityService } from '@/core/entities/AppEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'app'], @@ -27,18 +30,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = { - userId: user.id, - }; - - const apps = await Apps.find({ - where: query, - take: ps.limit, - skip: ps.offset, - }); - - return await Promise.all(apps.map(app => Apps.pack(app, user, { - detail: true, - }))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.appsRepository) + private appsRepository: AppsRepository, + + private appEntityService: AppEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = { + userId: me.id, + }; + + const apps = await this.appsRepository.find({ + where: query, + take: ps.limit, + skip: ps.offset, + }); + + return await Promise.all(apps.map(app => this.appEntityService.pack(app, me, { + detail: true, + }))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts index 015b0338e3..288e195316 100644 --- a/packages/backend/src/server/api/endpoints/notes.ts +++ b/packages/backend/src/server/api/endpoints/notes.ts @@ -1,6 +1,9 @@ -import { Notes } from '@/models/index.js'; -import define from '../define.js'; -import { makePaginationQuery } from '../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -32,48 +35,59 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere('note.visibility = \'public\'') - .andWhere('note.localOnly = FALSE') - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - if (ps.local) { - query.andWhere('note.userHost IS NULL'); + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere('note.visibility = \'public\'') + .andWhere('note.localOnly = FALSE') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + + if (ps.local) { + query.andWhere('note.userHost IS NULL'); + } + + if (ps.reply !== undefined) { + query.andWhere(ps.reply ? 'note.replyId IS NOT NULL' : 'note.replyId IS NULL'); + } + + if (ps.renote !== undefined) { + query.andWhere(ps.renote ? 'note.renoteId IS NOT NULL' : 'note.renoteId IS NULL'); + } + + if (ps.withFiles !== undefined) { + query.andWhere(ps.withFiles ? 'note.fileIds != \'{}\'' : 'note.fileIds = \'{}\''); + } + + if (ps.poll !== undefined) { + query.andWhere(ps.poll ? 'note.hasPoll = TRUE' : 'note.hasPoll = FALSE'); + } + + // TODO + //if (bot != undefined) { + // query.isBot = bot; + //} + + const notes = await query.take(ps.limit).getMany(); + + return await this.noteEntityService.packMany(notes); + }); } - - if (ps.reply !== undefined) { - query.andWhere(ps.reply ? 'note.replyId IS NOT NULL' : 'note.replyId IS NULL'); - } - - if (ps.renote !== undefined) { - query.andWhere(ps.renote ? 'note.renoteId IS NOT NULL' : 'note.renoteId IS NULL'); - } - - if (ps.withFiles !== undefined) { - query.andWhere(ps.withFiles ? 'note.fileIds != \'{}\'' : 'note.fileIds = \'{}\''); - } - - if (ps.poll !== undefined) { - query.andWhere(ps.poll ? 'note.hasPoll = TRUE' : 'note.hasPoll = FALSE'); - } - - // TODO - //if (bot != undefined) { - // query.isBot = bot; - //} - - const notes = await query.take(ps.limit).getMany(); - - return await Notes.packMany(notes); -}); +} diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index efc109105c..86f90e049f 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -1,10 +1,10 @@ import { Brackets } from 'typeorm'; -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -34,38 +34,49 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, 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 }) +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere(new Brackets(qb => { qb - .where('note.text IS NOT NULL') - .orWhere('note.fileIds != \'{}\'') - .orWhere('note.hasPoll = TRUE'); - })); - })); - })) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .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'); + })); + })); + })) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - generateVisibilityQuery(query, user); - if (user) { - generateMutedUserQuery(query, user); - generateBlockedUserQuery(query, user); - } + this.queryService.generateVisibilityQuery(query, me); + if (me) { + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + } - const notes = await query.take(ps.limit).getMany(); + const notes = await query.take(ps.limit).getMany(); - return await Notes.packMany(notes, user); -}); + return await this.noteEntityService.packMany(notes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index e79f8563e8..7d893f32a1 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -1,8 +1,11 @@ import { In } from 'typeorm'; -import { ClipNotes, Clips } from '@/models/index.js'; -import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['clips', 'notes'], @@ -37,20 +40,34 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - const clipNotes = await ClipNotes.findBy({ - noteId: note.id, - }); - - const clips = await Clips.findBy({ - id: In(clipNotes.map(x => x.clipId)), - isPublic: true, - }); - - return await Promise.all(clips.map(x => Clips.pack(x))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + + @Inject(DI.clipNotesRepository) + private clipNotesRepository: ClipNotesRepository, + + private clipEntityService: ClipEntityService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + const clipNotes = await this.clipNotesRepository.findBy({ + noteId: note.id, + }); + + const clips = await this.clipsRepository.findBy({ + id: In(clipNotes.map(x => x.clipId)), + isPublic: true, + }); + + return await Promise.all(clips.map(x => this.clipEntityService.pack(x))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index b731d18248..2f8324ed62 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -1,8 +1,11 @@ -import { Note } from '@/models/entities/note.js'; -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { Note } from '@/models/entities/Note.js'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getNote } from '../../common/getters.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['notes'], @@ -39,36 +42,47 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - const conversation: Note[] = []; - let i = 0; + private noteEntityService: NoteEntityService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); - async function get(id: any) { - i++; - const p = await Notes.findOneBy({ id }); - if (p == null) return; + const conversation: Note[] = []; + let i = 0; - if (i > ps.offset!) { - conversation.push(p); - } + const get = async (id: any) => { + i++; + const p = await this.notesRepository.findOneBy({ id }); + if (p == null) return; - if (conversation.length === ps.limit) { - return; - } + if (i > ps.offset!) { + conversation.push(p); + } - if (p.replyId) { - await get(p.replyId); - } - } + if (conversation.length === ps.limit) { + return; + } - if (note.replyId) { - await get(note.replyId); - } + if (p.replyId) { + await get(p.replyId); + } + }; - return await Notes.packMany(conversation, user); -}); + if (note.replyId) { + await get(note.replyId); + } + + return await this.noteEntityService.packMany(conversation, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index a133294169..30b7a889fc 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -1,15 +1,18 @@ import ms from 'ms'; import { In } from 'typeorm'; -import create from '@/services/note/create.js'; -import { User } from '@/models/entities/user.js'; -import { Users, DriveFiles, Notes, Channels, Blockings } from '@/models/index.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { Note } from '@/models/entities/note.js'; -import { Channel } from '@/models/entities/channel.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { User } from '@/models/entities/User.js'; +import { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesRepository, ChannelsRepository } from '@/models/index.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { Note } from '@/models/entities/Note.js'; +import type { Channel } from '@/models/entities/Channel.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { NoteCreateService } from '@/core/NoteCreateService.js'; +import { DI } from '@/di-symbols.js'; import { noteVisibilities } from '../../../../types.js'; import { ApiError } from '../../error.js'; -import define from '../../define.js'; export const meta = { tags: ['notes'], @@ -161,115 +164,138 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - let visibleUsers: User[] = []; - if (ps.visibleUserIds) { - visibleUsers = await Users.findBy({ - id: In(ps.visibleUserIds), - }); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - let files: DriveFile[] = []; - const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; - if (fileIds != null) { - files = await DriveFiles.createQueryBuilder('file') - .where('file.userId = :userId AND file.id IN (:...fileIds)', { - userId: user.id, - fileIds, - }) - .orderBy('array_position(ARRAY[:...fileIds], "id"::text)') - .setParameters({ fileIds }) - .getMany(); - } + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - let renote: Note | null = null; - if (ps.renoteId != null) { - // Fetch renote to note - renote = await Notes.findOneBy({ id: ps.renoteId }); + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, - if (renote == null) { - throw new ApiError(meta.errors.noSuchRenoteTarget); - } else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) { - throw new ApiError(meta.errors.cannotReRenote); - } + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - // Check blocking - if (renote.userId !== user.id) { - const block = await Blockings.findOneBy({ - blockerId: renote.userId, - blockeeId: user.id, - }); - if (block) { - throw new ApiError(meta.errors.youHaveBeenBlocked); + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + private noteEntityService: NoteEntityService, + private noteCreateService: NoteCreateService, + ) { + super(meta, paramDef, async (ps, me) => { + let visibleUsers: User[] = []; + if (ps.visibleUserIds) { + visibleUsers = await this.usersRepository.findBy({ + id: In(ps.visibleUserIds), + }); } - } - } - let reply: Note | null = null; - if (ps.replyId != null) { - // Fetch reply - reply = await Notes.findOneBy({ id: ps.replyId }); + let files: DriveFile[] = []; + const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; + if (fileIds != null) { + files = await this.driveFilesRepository.createQueryBuilder('file') + .where('file.userId = :userId AND file.id IN (:...fileIds)', { + userId: me.id, + fileIds, + }) + .orderBy('array_position(ARRAY[:...fileIds], "id"::text)') + .setParameters({ fileIds }) + .getMany(); + } - if (reply == null) { - throw new ApiError(meta.errors.noSuchReplyTarget); - } else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) { - throw new ApiError(meta.errors.cannotReplyToPureRenote); - } + let renote: Note | null = null; + if (ps.renoteId != null) { + // Fetch renote to note + renote = await this.notesRepository.findOneBy({ id: ps.renoteId }); - // Check blocking - if (reply.userId !== user.id) { - const block = await Blockings.findOneBy({ - blockerId: reply.userId, - blockeeId: user.id, - }); - if (block) { - throw new ApiError(meta.errors.youHaveBeenBlocked); + if (renote == null) { + throw new ApiError(meta.errors.noSuchRenoteTarget); + } else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) { + throw new ApiError(meta.errors.cannotReRenote); + } + + // Check blocking + if (renote.userId !== me.id) { + const block = await this.blockingsRepository.findOneBy({ + blockerId: renote.userId, + blockeeId: me.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } } - } - } - if (ps.poll) { - if (typeof ps.poll.expiresAt === 'number') { - if (ps.poll.expiresAt < Date.now()) { - throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); + let reply: Note | null = null; + if (ps.replyId != null) { + // Fetch reply + reply = await this.notesRepository.findOneBy({ id: ps.replyId }); + + if (reply == null) { + throw new ApiError(meta.errors.noSuchReplyTarget); + } else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) { + throw new ApiError(meta.errors.cannotReplyToPureRenote); + } + + // Check blocking + if (reply.userId !== me.id) { + const block = await this.blockingsRepository.findOneBy({ + blockerId: reply.userId, + blockeeId: me.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } } - } else if (typeof ps.poll.expiredAfter === 'number') { - ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter; - } - } - let channel: Channel | null = null; - if (ps.channelId != null) { - channel = await Channels.findOneBy({ id: ps.channelId }); + if (ps.poll) { + if (typeof ps.poll.expiresAt === 'number') { + if (ps.poll.expiresAt < Date.now()) { + throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); + } + } else if (typeof ps.poll.expiredAfter === 'number') { + ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter; + } + } - if (channel == null) { - throw new ApiError(meta.errors.noSuchChannel); - } - } + let channel: Channel | null = null; + if (ps.channelId != null) { + channel = await this.channelsRepository.findOneBy({ id: ps.channelId }); - // 投稿を作成 - const note = await create(user, { - createdAt: new Date(), - files: files, - poll: ps.poll ? { - choices: ps.poll.choices, - multiple: ps.poll.multiple || false, - expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, - } : undefined, - text: ps.text || undefined, - reply, - renote, - cw: ps.cw, - localOnly: ps.localOnly, - visibility: ps.visibility, - visibleUsers, - channel, - apMentions: ps.noExtractMentions ? [] : undefined, - apHashtags: ps.noExtractHashtags ? [] : undefined, - apEmojis: ps.noExtractEmojis ? [] : undefined, - }); + if (channel == null) { + throw new ApiError(meta.errors.noSuchChannel); + } + } + + // 投稿を作成 + const note = await this.noteCreateService.create(me, { + createdAt: new Date(), + files: files, + poll: ps.poll ? { + choices: ps.poll.choices, + multiple: ps.poll.multiple || false, + expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, + } : undefined, + text: ps.text ?? undefined, + reply, + renote, + cw: ps.cw, + localOnly: ps.localOnly, + visibility: ps.visibility, + visibleUsers, + channel, + apMentions: ps.noExtractMentions ? [] : undefined, + apHashtags: ps.noExtractHashtags ? [] : undefined, + apEmojis: ps.noExtractEmojis ? [] : undefined, + }); - return { - createdNote: await Notes.pack(note, user), - }; -}); + return { + createdNote: await this.noteEntityService.pack(note, me), + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index c23ceeb5bf..4769c8bdf1 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -1,9 +1,11 @@ import ms from 'ms'; -import deleteNote from '@/services/note/delete.js'; -import { Users } from '@/models/index.js'; -import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteDeleteService } from '@/core/NoteDeleteService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['notes'], @@ -42,16 +44,27 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - if ((!user.isAdmin && !user.isModerator) && (note.userId !== user.id)) { - throw new ApiError(meta.errors.accessDenied); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private getterService: GetterService, + private noteDeleteService: NoteDeleteService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); - // この操作を行うのが投稿者とは限らない(例えばモデレーター)ため - await deleteNote(await Users.findOneByOrFail({ id: note.userId }), note); -}); + if ((!me.isAdmin && !me.isModerator) && (note.userId !== me.id)) { + throw new ApiError(meta.errors.accessDenied); + } + + // この操作を行うのが投稿者とは限らない(例えばモデレーター)ため + await this.noteDeleteService.delete(await this.usersRepository.findOneByOrFail({ id: note.userId }), note); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index 097371a425..bfdd1acd22 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -1,8 +1,10 @@ -import { NoteFavorites } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NoteFavoritesRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { getNote } from '../../../common/getters.js'; export const meta = { tags: ['notes', 'favorites'], @@ -35,28 +37,39 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Get favoritee - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - // if already favorited - const exist = await NoteFavorites.findOneBy({ - noteId: note.id, - userId: user.id, - }); - - if (exist != null) { - throw new ApiError(meta.errors.alreadyFavorited); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.noteFavoritesRepository) + private noteFavoritesRepository: NoteFavoritesRepository, + + private idService: IdService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + // Get favoritee + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + // if already favorited + const exist = await this.noteFavoritesRepository.findOneBy({ + noteId: note.id, + userId: me.id, + }); - // Create favorite - await NoteFavorites.insert({ - id: genId(), - createdAt: new Date(), - noteId: note.id, - userId: user.id, - }); -}); + if (exist != null) { + throw new ApiError(meta.errors.alreadyFavorited); + } + + // Create favorite + await this.noteFavoritesRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + noteId: note.id, + userId: me.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index 82ef4fa197..6b3a02b101 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -1,7 +1,9 @@ -import { NoteFavorites } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { DI } from '@/di-symbols.js'; +import { NoteFavoritesRepository } from '@/models/index.js'; import { ApiError } from '../../../error.js'; -import { getNote } from '../../../common/getters.js'; export const meta = { tags: ['notes', 'favorites'], @@ -34,23 +36,33 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Get favoritee - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - // if already favorited - const exist = await NoteFavorites.findOneBy({ - noteId: note.id, - userId: user.id, - }); - - if (exist == null) { - throw new ApiError(meta.errors.notFavorited); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.noteFavoritesRepository) + private noteFavoritesRepository: NoteFavoritesRepository, + + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + // Get favoritee + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + // if already favorited + const exist = await this.noteFavoritesRepository.findOneBy({ + noteId: note.id, + userId: me.id, + }); - // Delete favorite - await NoteFavorites.delete(exist.id); -}); + if (exist == null) { + throw new ApiError(meta.errors.notFavorited); + } + + // Delete favorite + await this.noteFavoritesRepository.delete(exist.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index dd9cc581aa..9985f9d257 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -1,7 +1,9 @@ -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -29,39 +31,50 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const max = 30; - const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - const query = Notes.createQueryBuilder('note') - .addSelect('note.score') - .where('note.userHost IS NULL') - .andWhere('note.score > 0') - .andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) }) - .andWhere('note.visibility = \'public\'') - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const max = 30; + const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで - if (user) generateMutedUserQuery(query, user); - if (user) generateBlockedUserQuery(query, user); + const query = this.notesRepository.createQueryBuilder('note') + .addSelect('note.score') + .where('note.userHost IS NULL') + .andWhere('note.score > 0') + .andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) }) + .andWhere('note.visibility = \'public\'') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - let notes = await query - .orderBy('note.score', 'DESC') - .take(max) - .getMany(); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); - notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + let notes = await query + .orderBy('note.score', 'DESC') + .take(max) + .getMany(); - notes = notes.slice(ps.offset, ps.offset + ps.limit); + notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); - return await Notes.packMany(notes, user); -}); + notes = notes.slice(ps.offset, ps.offset + ps.limit); + + return await this.noteEntityService.packMany(notes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 925318f544..73b5afa40a 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -1,13 +1,12 @@ -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Notes } from '@/models/index.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateRepliesQuery } from '../../common/generate-replies-query.js'; -import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; export const meta = { tags: ['notes'], @@ -49,50 +48,63 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const m = await fetchMeta(); - if (m.disableGlobalTimeline) { - if (user == null || (!user.isAdmin && !user.isModerator)) { - throw new ApiError(meta.errors.gtlDisabled); - } - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - //#region Construct query - const query = makePaginationQuery(Notes.createQueryBuilder('note'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere('note.visibility = \'public\'') - .andWhere('note.channelId IS NULL') - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private metaService: MetaService, + private activeUsersChart: ActiveUsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + const m = await this.metaService.fetch(); + if (m.disableGlobalTimeline) { + if (me == null || (!me.isAdmin && !me.isModerator)) { + throw new ApiError(meta.errors.gtlDisabled); + } + } - generateRepliesQuery(query, user); - if (user) { - generateMutedUserQuery(query, user); - generateMutedNoteQuery(query, user); - generateBlockedUserQuery(query, user); - } + //#region Construct query + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('note.visibility = \'public\'') + .andWhere('note.channelId IS NULL') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); - } - //#endregion + this.queryService.generateRepliesQuery(query, me); + if (me) { + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateMutedNoteQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + } - const timeline = await query.take(ps.limit).getMany(); + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + //#endregion - process.nextTick(() => { - if (user) { - activeUsersChart.read(user); - } - }); + const timeline = await query.take(ps.limit).getMany(); - return await Notes.packMany(timeline, user); -}); + process.nextTick(() => { + if (me) { + this.activeUsersChart.read(me); + } + }); + + return await this.noteEntityService.packMany(timeline, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 2dc98c4c9f..c6458223eb 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -1,16 +1,13 @@ import { Brackets } from 'typeorm'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Followings, Notes } from '@/models/index.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { MetaService } from '@/core/MetaService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateRepliesQuery } from '../../common/generate-replies-query.js'; -import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js'; -import { generateChannelQuery } from '../../common/generate-channel-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; export const meta = { tags: ['notes'], @@ -57,83 +54,99 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const m = await fetchMeta(); - if (m.disableLocalTimeline && (!user.isAdmin && !user.isModerator)) { - throw new ApiError(meta.errors.stlDisabled); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private metaService: MetaService, + private activeUsersChart: ActiveUsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + const m = await this.metaService.fetch(); + if (m.disableLocalTimeline && (!me.isAdmin && !me.isModerator)) { + throw new ApiError(meta.errors.stlDisabled); + } + + //#region Construct query + const followingQuery = this.followingsRepository.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); + + const query = this.queryService.makePaginationQuery(this.notesRepository.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: me.id }) + .orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)'); + })) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') + .setParameters(followingQuery.getParameters()); + + this.queryService.generateChannelQuery(query, me); + this.queryService.generateRepliesQuery(query, me); + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateMutedNoteQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + + if (ps.includeMyRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.userId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.includeRenotedMyNotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.includeLocalRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserHost IS NOT NULL'); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + //#endregion + + const timeline = await query.take(ps.limit).getMany(); + + process.nextTick(() => { + this.activeUsersChart.read(me); + }); + + return await this.noteEntityService.packMany(timeline, me); + }); } - - //#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)'); - })) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') - .setParameters(followingQuery.getParameters()); - - generateChannelQuery(query, user); - generateRepliesQuery(query, user); - generateVisibilityQuery(query, user); - generateMutedUserQuery(query, user); - generateMutedNoteQuery(query, user); - generateBlockedUserQuery(query, user); - - if (ps.includeMyRenotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.userId != :meId', { meId: user.id }); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } - - if (ps.includeRenotedMyNotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.renoteUserId != :meId', { meId: user.id }); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } - - if (ps.includeLocalRenotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.renoteUserHost IS NOT NULL'); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } - - if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); - } - //#endregion - - const timeline = await query.take(ps.limit).getMany(); - - process.nextTick(() => { - activeUsersChart.read(user); - }); - - return await Notes.packMany(timeline, user); -}); +} diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index aac2a3749c..7b8859639d 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -1,16 +1,14 @@ import { Brackets } from 'typeorm'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Notes, Users } from '@/models/index.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateRepliesQuery } from '../../common/generate-replies-query.js'; -import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js'; -import { generateChannelQuery } from '../../common/generate-channel-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; export const meta = { tags: ['notes'], @@ -56,64 +54,77 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const m = await fetchMeta(); - if (m.disableLocalTimeline) { - if (user == null || (!user.isAdmin && !user.isModerator)) { - throw new ApiError(meta.errors.ltlDisabled); - } - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - //#region Construct query - const query = makePaginationQuery(Notes.createQueryBuilder('note'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)') - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private metaService: MetaService, + private activeUsersChart: ActiveUsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + const m = await this.metaService.fetch(); + if (m.disableLocalTimeline) { + if (me == null || (!me.isAdmin && !me.isModerator)) { + throw new ApiError(meta.errors.ltlDisabled); + } + } - generateChannelQuery(query, user); - generateRepliesQuery(query, user); - generateVisibilityQuery(query, user); - if (user) generateMutedUserQuery(query, user); - if (user) generateMutedNoteQuery(query, user); - if (user) generateBlockedUserQuery(query, user); + //#region Construct query + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); - } + this.queryService.generateChannelQuery(query, me); + this.queryService.generateRepliesQuery(query, me); + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateMutedNoteQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); - 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); - qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } - })); - if (ps.excludeNsfw) { - query.andWhere('note.cw IS NULL'); - query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)'); - } - } - //#endregion + 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); + qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); + } + })); - const timeline = await query.take(ps.limit).getMany(); + if (ps.excludeNsfw) { + query.andWhere('note.cw IS NULL'); + query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)'); + } + } + //#endregion + + const timeline = await query.take(ps.limit).getMany(); - process.nextTick(() => { - if (user) { - activeUsersChart.read(user); - } - }); + process.nextTick(() => { + if (me) { + this.activeUsersChart.read(me); + } + }); - return await Notes.packMany(timeline, user); -}); + return await this.noteEntityService.packMany(timeline, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index 9b41544523..9b2dabc88b 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -1,12 +1,12 @@ import { Brackets } from 'typeorm'; -import read from '@/services/note/read.js'; -import { Notes, Followings } from '@/models/index.js'; -import define from '../../define.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; -import { generateMutedNoteThreadQuery } from '../../common/generate-muted-note-thread-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { NoteReadService } from '@/core/NoteReadService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -37,45 +37,60 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const followingQuery = Followings.createQueryBuilder('following') - .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: user.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere(new Brackets(qb => { qb - .where(`'{"${user.id}"}' <@ note.mentions`) - .orWhere(`'{"${user.id}"}' <@ note.visibleUserIds`); - })) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - generateVisibilityQuery(query, user); - generateMutedUserQuery(query, user); - generateMutedNoteThreadQuery(query, user); - generateBlockedUserQuery(query, user); + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private noteReadService: NoteReadService, + ) { + super(meta, paramDef, async (ps, me) => { + const followingQuery = this.followingsRepository.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); - if (ps.visibility) { - query.andWhere('note.visibility = :visibility', { visibility: ps.visibility }); - } + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere(new Brackets(qb => { qb + .where(`'{"${me.id}"}' <@ note.mentions`) + .orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`); + })) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - if (ps.following) { - query.andWhere(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id }); - query.setParameters(followingQuery.getParameters()); - } + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateMutedNoteThreadQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + + if (ps.visibility) { + query.andWhere('note.visibility = :visibility', { visibility: ps.visibility }); + } - const mentions = await query.take(ps.limit).getMany(); + if (ps.following) { + query.andWhere(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: me.id }); + query.setParameters(followingQuery.getParameters()); + } - read(user.id, mentions); + const mentions = await query.take(ps.limit).getMany(); - return await Notes.packMany(mentions, user); -}); + this.noteReadService.read(me.id, mentions); + + return await this.noteEntityService.packMany(mentions, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index 5a04d68f3e..11bfdbba0f 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -1,6 +1,9 @@ import { Brackets, In } from 'typeorm'; -import { Polls, Mutings, Notes, PollVotes } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository, MutingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -28,56 +31,75 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - 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() }); - })); - - //#region exclude arleady voted polls - const votedQuery = PollVotes.createQueryBuilder('vote') - .select('vote.noteId') - .where('vote.userId = :meId', { meId: user.id }); - - query - .andWhere(`poll.noteId NOT IN (${ votedQuery.getQuery() })`); - - 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 - .orderBy('poll.noteId', 'DESC') - .take(ps.limit) - .skip(ps.offset) - .getMany(); - - if (polls.length === 0) return []; - - const notes = await Notes.find({ - where: { - id: In(polls.map(poll => poll.noteId)), - }, - order: { - createdAt: 'DESC', - }, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + @Inject(DI.pollVotesRepository) + private pollVotesRepository: PollVotesRepository, + + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, + + private noteEntityService: NoteEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.pollsRepository.createQueryBuilder('poll') + .where('poll.userHost IS NULL') + .andWhere('poll.userId != :meId', { meId: me.id }) + .andWhere('poll.noteVisibility = \'public\'') + .andWhere(new Brackets(qb => { qb + .where('poll.expiresAt IS NULL') + .orWhere('poll.expiresAt > :now', { now: new Date() }); + })); + + //#region exclude arleady voted polls + const votedQuery = this.pollVotesRepository.createQueryBuilder('vote') + .select('vote.noteId') + .where('vote.userId = :meId', { meId: me.id }); + + query + .andWhere(`poll.noteId NOT IN (${ votedQuery.getQuery() })`); + + query.setParameters(votedQuery.getParameters()); + //#endregion + + //#region mute + const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') + .select('muting.muteeId') + .where('muting.muterId = :muterId', { muterId: me.id }); + + query + .andWhere(`poll.userId NOT IN (${ mutingQuery.getQuery() })`); + + query.setParameters(mutingQuery.getParameters()); + //#endregion + + const polls = await query + .orderBy('poll.noteId', 'DESC') + .take(ps.limit) + .skip(ps.offset) + .getMany(); + + if (polls.length === 0) return []; + + const notes = await this.notesRepository.find({ + where: { + id: In(polls.map(poll => poll.noteId)), + }, + order: { + createdAt: 'DESC', + }, + }); - return await Notes.packMany(notes, user, { - detail: true, - }); -}); + return await this.noteEntityService.packMany(notes, me, { + detail: true, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 45a832cbd2..76f07528d7 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -1,16 +1,17 @@ import { Not } from 'typeorm'; -import { publishNoteStream } from '@/services/stream.js'; -import { createNotification } from '@/services/create-notification.js'; -import { deliver } from '@/queue/index.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderVote from '@/remote/activitypub/renderer/vote.js'; -import { deliverQuestionUpdate } from '@/services/note/polls/update.js'; -import { PollVotes, NoteWatchings, Users, Polls, Blockings } from '@/models/index.js'; -import { IRemoteUser } from '@/models/entities/user.js'; -import { genId } from '@/misc/gen-id.js'; -import { getNote } from '../../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, BlockingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; +import type { IRemoteUser } from '@/models/entities/User.js'; +import { IdService } from '@/core/IdService.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { QueueService } from '@/core/QueueService.js'; +import { PollService } from '@/core/PollService.js'; +import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { CreateNotificationService } from '@/core/CreateNotificationService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import define from '../../../define.js'; export const meta = { tags: ['notes'], @@ -67,103 +68,116 @@ export const paramDef = { required: ['noteId', 'choice'], } as const; -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const createdAt = new Date(); +// TODO: ロジックをサービスに切り出す - // Get votee - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + @Inject(DI.pollVotesRepository) + private pollVotesRepository: PollVotesRepository, + + private idService: IdService, + private getterService: GetterService, + private queueService: QueueService, + private pollService: PollService, + private apRendererService: ApRendererService, + private globalEventService: GlobalEventService, + private createNotificationService: CreateNotificationService, + ) { + super(meta, paramDef, async (ps, me) => { + const createdAt = new Date(); + + // Get votee + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); - if (!note.hasPoll) { - throw new ApiError(meta.errors.noPoll); - } + if (!note.hasPoll) { + throw new ApiError(meta.errors.noPoll); + } - // Check blocking - if (note.userId !== user.id) { - const block = await Blockings.findOneBy({ - blockerId: note.userId, - blockeeId: user.id, - }); - if (block) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } + // Check blocking + if (note.userId !== me.id) { + const block = await this.blockingsRepository.findOneBy({ + blockerId: note.userId, + blockeeId: me.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } - const poll = await Polls.findOneByOrFail({ noteId: note.id }); + const poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id }); - if (poll.expiresAt && poll.expiresAt < createdAt) { - throw new ApiError(meta.errors.alreadyExpired); - } + if (poll.expiresAt && poll.expiresAt < createdAt) { + throw new ApiError(meta.errors.alreadyExpired); + } - if (poll.choices[ps.choice] == null) { - throw new ApiError(meta.errors.invalidChoice); - } + if (poll.choices[ps.choice] == null) { + throw new ApiError(meta.errors.invalidChoice); + } - // if already voted - const exist = await PollVotes.findBy({ - noteId: note.id, - userId: user.id, - }); + // if already voted + const exist = await this.pollVotesRepository.findBy({ + noteId: note.id, + userId: me.id, + }); - if (exist.length) { - if (poll.multiple) { - if (exist.some(x => x.choice === ps.choice)) { - throw new ApiError(meta.errors.alreadyVoted); + if (exist.length) { + if (poll.multiple) { + if (exist.some(x => x.choice === ps.choice)) { + throw new ApiError(meta.errors.alreadyVoted); + } + } else { + throw new ApiError(meta.errors.alreadyVoted); + } } - } else { - throw new ApiError(meta.errors.alreadyVoted); - } - } - // Create vote - const vote = await PollVotes.insert({ - id: genId(), - createdAt, - noteId: note.id, - userId: user.id, - choice: ps.choice, - }).then(x => PollVotes.findOneByOrFail(x.identifiers[0])); - - // 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}'`); - - publishNoteStream(note.id, 'pollVoted', { - choice: ps.choice, - userId: user.id, - }); - - // Notify - createNotification(note.userId, 'pollVote', { - notifierId: user.id, - noteId: note.id, - choice: ps.choice, - }); - - // Fetch watchers - NoteWatchings.findBy({ - noteId: note.id, - userId: Not(user.id), - }).then(watchers => { - for (const watcher of watchers) { - createNotification(watcher.userId, 'pollVote', { - notifierId: user.id, + // Create vote + const vote = await this.pollVotesRepository.insert({ + id: this.idService.genId(), + createdAt, noteId: note.id, + userId: me.id, choice: ps.choice, + }).then(x => this.pollVotesRepository.findOneByOrFail(x.identifiers[0])); + + // Increment votes count + const index = ps.choice + 1; // In SQL, array index is 1 based + await this.pollsRepository.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`); + + this.globalEventService.publishNoteStream(note.id, 'pollVoted', { + choice: ps.choice, + userId: me.id, }); - } - }); - // リモート投票の場合リプライ送信 - if (note.userHost != null) { - const pollOwner = await Users.findOneByOrFail({ id: note.userId }) as IRemoteUser; + // Notify + this.createNotificationService.createNotification(note.userId, 'pollVote', { + notifierId: me.id, + noteId: note.id, + choice: ps.choice, + }); - deliver(user, renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox); - } + // リモート投票の場合リプライ送信 + if (note.userHost != null) { + const pollOwner = await this.usersRepository.findOneByOrFail({ id: note.userId }) as IRemoteUser; + + this.queueService.deliver(me, this.apRendererService.renderActivity(await this.apRendererService.renderVote(me, vote, note, poll, pollOwner)), pollOwner.inbox); + } - // リモートフォロワーにUpdate配信 - deliverQuestionUpdate(note.id); -}); + // リモートフォロワーにUpdate配信 + this.pollService.deliverQuestionUpdate(note.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index 15a62d394d..d57950f012 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -1,8 +1,12 @@ -import { DeepPartial, FindOptionsWhere } from 'typeorm'; -import { NoteReactions } from '@/models/index.js'; -import { NoteReaction } from '@/models/entities/note-reaction.js'; -import define from '../../define.js'; +import { DeepPartial } from 'typeorm'; +import { Inject, Injectable } from '@nestjs/common'; +import { NoteReactionsRepository } from '@/models/index.js'; +import type { NoteReaction } from '@/models/entities/NoteReaction.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import type { FindOptionsWhere } from 'typeorm'; export const meta = { tags: ['notes', 'reactions'], @@ -45,28 +49,38 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = { - noteId: ps.noteId, - } as FindOptionsWhere; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.noteReactionsRepository) + private noteReactionsRepository: NoteReactionsRepository, - if (ps.type) { - // ローカルリアクションはホスト名が . とされているが - // DB 上ではそうではないので、必要に応じて変換 - const suffix = '@.:'; - const type = ps.type.endsWith(suffix) ? ps.type.slice(0, ps.type.length - suffix.length) + ':' : ps.type; - query.reaction = type; - } + private noteReactionEntityService: NoteReactionEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = { + noteId: ps.noteId, + } as FindOptionsWhere; - const reactions = await NoteReactions.find({ - where: query, - take: ps.limit, - skip: ps.offset, - order: { - id: -1, - }, - relations: ['user', 'user.avatar', 'user.banner', 'note'], - }); + if (ps.type) { + // ローカルリアクションはホスト名が . とされているが + // DB 上ではそうではないので、必要に応じて変換 + const suffix = '@.:'; + const type = ps.type.endsWith(suffix) ? ps.type.slice(0, ps.type.length - suffix.length) + ':' : ps.type; + query.reaction = type; + } - return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, user))); -}); + const reactions = await this.noteReactionsRepository.find({ + where: query, + take: ps.limit, + skip: ps.offset, + order: { + id: -1, + }, + relations: ['user', 'user.avatar', 'user.banner', 'note'], + }); + + return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts index 07e52a9266..2af734307d 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts @@ -1,6 +1,7 @@ -import createReaction from '@/services/note/reaction/create.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { ReactionService } from '@/core/ReactionService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -41,15 +42,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - await createReaction(user, note, ps.reaction).catch(e => { - if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted); - if (e.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError(meta.errors.youHaveBeenBlocked); - throw e; - }); - return; -}); +@Injectable() +export default class extends Endpoint { + constructor( + private getterService: GetterService, + private reactionService: ReactionService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + await this.reactionService.create(me, note, ps.reaction).catch(err => { + if (err.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted); + if (err.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError(meta.errors.youHaveBeenBlocked); + throw err; + }); + return; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts index c13cafa21d..31ed962922 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts @@ -1,7 +1,8 @@ import ms from 'ms'; -import deleteReaction from '@/services/note/reaction/delete.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { ReactionService } from '@/core/ReactionService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -41,13 +42,21 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - await deleteReaction(user, note).catch(e => { - if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') throw new ApiError(meta.errors.notReacted); - throw e; - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private getterService: GetterService, + private reactionService: ReactionService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + await this.reactionService.delete(me, note).catch(err => { + if (err.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') throw new ApiError(meta.errors.notReacted); + throw err; + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 28be360763..57b7aeae0d 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -1,11 +1,11 @@ -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['notes'], @@ -43,31 +43,43 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere('note.renoteId = :renoteId', { renoteId: note.id }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); - generateVisibilityQuery(query, user); - if (user) generateMutedUserQuery(query, user); - if (user) generateBlockedUserQuery(query, user); + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere('note.renoteId = :renoteId', { renoteId: note.id }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - const renotes = await query.take(ps.limit).getMany(); + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); - return await Notes.packMany(renotes, user); -}); + const renotes = await query.take(ps.limit).getMany(); + + return await this.noteEntityService.packMany(renotes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index ab0018f58e..7020d0c681 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -1,9 +1,9 @@ -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -33,26 +33,37 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere('note.replyId = :replyId', { replyId: ps.noteId }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - - generateVisibilityQuery(query, user); - if (user) generateMutedUserQuery(query, user); - if (user) generateBlockedUserQuery(query, user); - - const timeline = await query.take(ps.limit).getMany(); - - return await Notes.packMany(timeline, user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere('note.replyId = :replyId', { replyId: ps.noteId }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); + + const timeline = await query.take(ps.limit).getMany(); + + return await this.noteEntityService.packMany(timeline, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index 777de7221c..0727c9af6c 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -1,12 +1,12 @@ import { Brackets } from 'typeorm'; -import { Notes } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; import { safeForSql } from '@/misc/safe-for-sql.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes', 'hashtags'], @@ -66,75 +66,86 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - generateVisibilityQuery(query, me); - if (me) generateMutedUserQuery(query, me); - if (me) generateBlockedUserQuery(query, me); + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - try { - if (ps.tag) { - if (!safeForSql(ps.tag)) throw 'Injection'; - query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`); - } else { - query.andWhere(new Brackets(qb => { - for (const tags of ps.query!) { - qb.orWhere(new Brackets(qb => { - for (const tag of tags) { - if (!safeForSql(tag)) throw 'Injection'; - qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`); + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); + + try { + if (ps.tag) { + if (!safeForSql(ps.tag)) throw 'Injection'; + query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`); + } else { + query.andWhere(new Brackets(qb => { + for (const tags of ps.query!) { + qb.orWhere(new Brackets(qb => { + for (const tag of tags) { + if (!safeForSql(tag)) throw 'Injection'; + qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`); + } + })); } })); } - })); - } - } catch (e) { - if (e === 'Injection') return []; - throw e; - } + } catch (e) { + if (e === 'Injection') return []; + throw e; + } - if (ps.reply != null) { - if (ps.reply) { - query.andWhere('note.replyId IS NOT NULL'); - } else { - query.andWhere('note.replyId IS NULL'); - } - } + if (ps.reply != null) { + if (ps.reply) { + query.andWhere('note.replyId IS NOT NULL'); + } else { + query.andWhere('note.replyId IS NULL'); + } + } - if (ps.renote != null) { - if (ps.renote) { - query.andWhere('note.renoteId IS NOT NULL'); - } else { - query.andWhere('note.renoteId IS NULL'); - } - } + if (ps.renote != null) { + if (ps.renote) { + query.andWhere('note.renoteId IS NOT NULL'); + } else { + query.andWhere('note.renoteId IS NULL'); + } + } - if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); - } + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } - if (ps.poll != null) { - if (ps.poll) { - query.andWhere('note.hasPoll = TRUE'); - } else { - query.andWhere('note.hasPoll = FALSE'); - } - } + if (ps.poll != null) { + if (ps.poll) { + query.andWhere('note.hasPoll = TRUE'); + } else { + query.andWhere('note.hasPoll = FALSE'); + } + } - // Search notes - const notes = await query.take(ps.limit).getMany(); + // Search notes + const notes = await query.take(ps.limit).getMany(); - return await Notes.packMany(notes, me); -}); + return await this.noteEntityService.packMany(notes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index 4e2cdae801..484cfc1128 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -1,12 +1,11 @@ import { In } from 'typeorm'; -import { Notes } from '@/models/index.js'; -import config from '@/config/index.js'; -import es from '../../../../db/elasticsearch.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -46,97 +45,51 @@ export const paramDef = { required: ['query'], } as const; -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - if (es == null) { - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId); - - if (ps.userId) { - query.andWhere('note.userId = :userId', { userId: ps.userId }); - } else if (ps.channelId) { - query.andWhere('note.channelId = :channelId', { channelId: ps.channelId }); - } - - query - .andWhere('note.text ILIKE :q', { q: `%${ps.query}%` }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - - generateVisibilityQuery(query, me); - if (me) generateMutedUserQuery(query, me); - if (me) generateBlockedUserQuery(query, me); - - const notes = await query.take(ps.limit).getMany(); - - return await Notes.packMany(notes, me); - } else { - const userQuery = ps.userId != null ? [{ - term: { - userId: ps.userId, - }, - }] : []; - - const hostQuery = ps.userId == null ? - ps.host === null ? [{ - bool: { - must_not: { - exists: { - field: 'userHost', - }, - }, - }, - }] : ps.host !== undefined ? [{ - term: { - userHost: ps.host, - }, - }] : [] - : []; +// TODO: ロジックをサービスに切り出す - const result = await es.search({ - index: config.elasticsearch.index || 'misskey_note', - body: { - size: ps.limit, - from: ps.offset, - query: { - bool: { - must: [{ - simple_query_string: { - fields: ['text'], - query: ps.query.toLowerCase(), - default_operator: 'and', - }, - }, ...hostQuery, ...userQuery], - }, - }, - sort: [{ - _doc: 'desc', - }], - }, - }); - - const hits = result.body.hits.hits.map((hit: any) => hit._id); - - if (hits.length === 0) return []; - - // Fetch found notes - const notes = await Notes.find({ - where: { - id: In(hits), - }, - order: { - id: -1, - }, +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId); + + if (ps.userId) { + query.andWhere('note.userId = :userId', { userId: ps.userId }); + } else if (ps.channelId) { + query.andWhere('note.channelId = :channelId', { channelId: ps.channelId }); + } + + query + .andWhere('note.text ILIKE :q', { q: `%${ps.query}%` }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); + + const notes = await query.take(ps.limit).getMany(); + + return await this.noteEntityService.packMany(notes, me); }); - - return await Notes.packMany(notes, me); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index 5cd74bd2ca..c3f5b9dfb0 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -1,7 +1,10 @@ -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['notes'], @@ -32,13 +35,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - return await Notes.pack(note, user, { - detail: true, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + return await this.noteEntityService.pack(note, me, { + detail: true, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index 01afa5add2..7756d39f7c 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -1,5 +1,7 @@ -import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository, NoteThreadMutingsRepository, NoteFavoritesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -35,36 +37,42 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await Notes.findOneByOrFail({ id: ps.noteId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - const [favorite, watching, threadMuting] = await Promise.all([ - NoteFavorites.count({ - where: { - userId: user.id, - noteId: note.id, - }, - take: 1, - }), - NoteWatchings.count({ - where: { - userId: user.id, - noteId: note.id, - }, - take: 1, - }), - NoteThreadMutings.count({ - where: { - userId: user.id, - threadId: note.threadId || note.id, - }, - take: 1, - }), - ]); + @Inject(DI.noteThreadMutingsRepository) + private noteThreadMutingsRepository: NoteThreadMutingsRepository, + + @Inject(DI.noteFavoritesRepository) + private noteFavoritesRepository: NoteFavoritesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.notesRepository.findOneByOrFail({ id: ps.noteId }); + + const [favorite, threadMuting] = await Promise.all([ + this.noteFavoritesRepository.count({ + where: { + userId: me.id, + noteId: note.id, + }, + take: 1, + }), + this.noteThreadMutingsRepository.count({ + where: { + userId: me.id, + threadId: note.threadId || note.id, + }, + take: 1, + }), + ]); - return { - isFavorited: favorite !== 0, - isWatching: watching !== 0, - isMutedThread: threadMuting !== 0, - }; -}); + return { + isFavorited: favorite !== 0, + isMutedThread: threadMuting !== 0, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index cf360526d3..1c83adddff 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -1,8 +1,10 @@ -import { Notes, NoteThreadMutings } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import readNote from '@/services/note/read.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository, NoteThreadMutingsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { NoteReadService } from '@/core/NoteReadService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -30,26 +32,41 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - const mutedNotes = await Notes.find({ - where: [{ - id: note.threadId || note.id, - }, { - threadId: note.threadId || note.id, - }], - }); - - await readNote(user.id, mutedNotes); - - await NoteThreadMutings.insert({ - id: genId(), - createdAt: new Date(), - threadId: note.threadId || note.id, - userId: user.id, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.noteThreadMutingsRepository) + private noteThreadMutingsRepository: NoteThreadMutingsRepository, + + private getterService: GetterService, + private noteReadService: NoteReadService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + const mutedNotes = await this.notesRepository.find({ + where: [{ + id: note.threadId || note.id, + }, { + threadId: note.threadId || note.id, + }], + }); + + await this.noteReadService.read(me.id, mutedNotes); + + await this.noteThreadMutingsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + threadId: note.threadId || note.id, + userId: me.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts index ac310d0fe6..1f896734d1 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -1,6 +1,8 @@ -import { NoteThreadMutings } from '@/models/index.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NoteThreadMutingsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -28,14 +30,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - await NoteThreadMutings.delete({ - threadId: note.threadId || note.id, - userId: user.id, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.noteThreadMutingsRepository) + private noteThreadMutingsRepository: NoteThreadMutingsRepository, + + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + await this.noteThreadMutingsRepository.delete({ + threadId: note.threadId || note.id, + userId: me.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 22f4925175..53a1ae1348 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -1,14 +1,12 @@ import { Brackets } from 'typeorm'; -import { Notes, Followings } from '@/models/index.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateRepliesQuery } from '../../common/generate-replies-query.js'; -import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js'; -import { generateChannelQuery } from '../../common/generate-channel-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -47,85 +45,100 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const hasFollowing = (await Followings.count({ - where: { - followerId: user.id, - }, - take: 1, - })) !== 0; - - //#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 = :meId', { meId: user.id }); - if (hasFollowing) qb.orWhere(`note.userId IN (${ followingQuery.getQuery() })`); - })) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') - .setParameters(followingQuery.getParameters()); - - generateChannelQuery(query, user); - generateRepliesQuery(query, user); - generateVisibilityQuery(query, user); - generateMutedUserQuery(query, user); - generateMutedNoteQuery(query, user); - generateBlockedUserQuery(query, user); - - if (ps.includeMyRenotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.userId != :meId', { meId: user.id }); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - if (ps.includeRenotedMyNotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.renoteUserId != :meId', { meId: user.id }); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - if (ps.includeLocalRenotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.renoteUserHost IS NOT NULL'); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private activeUsersChart: ActiveUsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + const hasFollowing = (await this.followingsRepository.count({ + where: { + followerId: me.id, + }, + take: 1, + })) !== 0; - if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); - } - //#endregion + //#region Construct query + const followingQuery = this.followingsRepository.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); + + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere(new Brackets(qb => { qb + .where('note.userId = :meId', { meId: me.id }); + if (hasFollowing) qb.orWhere(`note.userId IN (${ followingQuery.getQuery() })`); + })) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') + .setParameters(followingQuery.getParameters()); - const timeline = await query.take(ps.limit).getMany(); + this.queryService.generateChannelQuery(query, me); + this.queryService.generateRepliesQuery(query, me); + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateMutedNoteQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); - process.nextTick(() => { - activeUsersChart.read(user); - }); + if (ps.includeMyRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.userId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } - return await Notes.packMany(timeline, user); -}); + if (ps.includeRenotedMyNotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.includeLocalRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserHost IS NOT NULL'); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + //#endregion + + const timeline = await query.take(ps.limit).getMany(); + + process.nextTick(() => { + this.activeUsersChart.read(me); + }); + + return await this.noteEntityService.packMany(timeline, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index 5e40e7106f..c24f1e401e 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -1,12 +1,15 @@ import { URLSearchParams } from 'node:url'; import fetch from 'node-fetch'; -import config from '@/config/index.js'; -import { getAgentByUrl } from '@/misc/fetch.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Notes } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; import { ApiError } from '../../error.js'; -import { getNote } from '../../common/getters.js'; -import define from '../../define.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['notes'], @@ -37,58 +40,74 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - if (!(await Notes.isVisibleForMe(note, user ? user.id : null))) { - return 204; // TODO: 良い感じのエラー返す - } - - if (note.text == null) { - return 204; - } - - const instance = await fetchMeta(); - - if (instance.deeplAuthKey == null) { - return 204; // TODO: 良い感じのエラー返す - } - - let targetLang = ps.targetLang; - if (targetLang.includes('-')) targetLang = targetLang.split('-')[0]; - - const params = new URLSearchParams(); - params.append('auth_key', instance.deeplAuthKey); - params.append('text', note.text); - params.append('target_lang', targetLang); - - const endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate'; - - const res = await fetch(endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': config.userAgent, - Accept: 'application/json, */*', - }, - body: params, - // TODO - //timeout: 10000, - agent: getAgentByUrl, - }); - - const json = (await res.json()) as { +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private getterService: GetterService, + private metaService: MetaService, + private httpRequestService: HttpRequestService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + if (!(await this.noteEntityService.isVisibleForMe(note, me ? me.id : null))) { + return 204; // TODO: 良い感じのエラー返す + } + + if (note.text == null) { + return 204; + } + + const instance = await this.metaService.fetch(); + + if (instance.deeplAuthKey == null) { + return 204; // TODO: 良い感じのエラー返す + } + + let targetLang = ps.targetLang; + if (targetLang.includes('-')) targetLang = targetLang.split('-')[0]; + + const params = new URLSearchParams(); + params.append('auth_key', instance.deeplAuthKey); + params.append('text', note.text); + params.append('target_lang', targetLang); + + const endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate'; + + const res = await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': config.userAgent, + Accept: 'application/json, */*', + }, + body: params, + // TODO + //timeout: 10000, + agent: (url) => this.httpRequestService.getAgentByUrl(url), + }); + + const json = (await res.json()) as { translations: { detected_source_language: string; text: string; }[]; }; - return { - sourceLang: json.translations[0].detected_source_language, - text: json.translations[0].text, - }; -}); + return { + sourceLang: json.translations[0].detected_source_language, + text: json.translations[0].text, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index 3fba0efe0c..c0048888b4 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -1,9 +1,11 @@ import ms from 'ms'; -import deleteNote from '@/services/note/delete.js'; -import { Notes, Users } from '@/models/index.js'; -import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteDeleteService } from '@/core/NoteDeleteService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['notes'], @@ -36,18 +38,32 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - const renotes = await Notes.findBy({ - userId: user.id, - renoteId: note.id, - }); - - for (const note of renotes) { - deleteNote(await Users.findOneByOrFail({ id: user.id }), note); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private getterService: GetterService, + private noteDeleteService: NoteDeleteService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + const renotes = await this.notesRepository.findBy({ + userId: me.id, + renoteId: note.id, + }); + + for (const note of renotes) { + this.noteDeleteService.delete(await this.usersRepository.findOneByOrFail({ id: me.id }), note); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index e603a8f625..87a464578c 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -1,10 +1,12 @@ import { Brackets } from 'typeorm'; -import { UserLists, UserListJoinings, Notes } from '@/models/index.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; export const meta = { tags: ['notes', 'lists'], @@ -52,72 +54,90 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const list = await UserLists.findOneBy({ - id: ps.listId, - userId: user.id, - }); - - if (list == null) { - throw new ApiError(meta.errors.noSuchList); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private activeUsersChart: ActiveUsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + const list = await this.userListsRepository.findOneBy({ + id: ps.listId, + userId: me.id, + }); + + if (list == null) { + throw new ApiError(meta.errors.noSuchList); + } + + //#region Construct query + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .innerJoin(this.userListJoiningsRepository.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') + .andWhere('userListJoining.userListId = :userListId', { userListId: list.id }); + + this.queryService.generateVisibilityQuery(query, me); + + if (ps.includeMyRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.userId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.includeRenotedMyNotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.includeLocalRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserHost IS NOT NULL'); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + //#endregion + + const timeline = await query.take(ps.limit).getMany(); + + this.activeUsersChart.read(me); + + return await this.noteEntityService.packMany(timeline, me); + }); } - - //#region Construct query - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .innerJoin(UserListJoinings.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId') - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') - .andWhere('userListJoining.userListId = :userListId', { userListId: list.id }); - - generateVisibilityQuery(query, user); - - if (ps.includeMyRenotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.userId != :meId', { meId: user.id }); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } - - if (ps.includeRenotedMyNotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.renoteUserId != :meId', { meId: user.id }); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } - - if (ps.includeLocalRenotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.renoteUserHost IS NOT NULL'); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } - - if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); - } - //#endregion - - const timeline = await query.take(ps.limit).getMany(); - - activeUsersChart.read(user); - - return await Notes.packMany(timeline, user); -}); +} diff --git a/packages/backend/src/server/api/endpoints/notes/watching/create.ts b/packages/backend/src/server/api/endpoints/notes/watching/create.ts deleted file mode 100644 index 7d482b0732..0000000000 --- a/packages/backend/src/server/api/endpoints/notes/watching/create.ts +++ /dev/null @@ -1,38 +0,0 @@ -import watch from '@/services/note/watch.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; -import { ApiError } from '../../../error.js'; - -export const meta = { - tags: ['notes'], - - requireCredential: true, - - kind: 'write:account', - - errors: { - noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: 'ea0e37a6-90a3-4f58-ba6b-c328ca206fc7', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - noteId: { type: 'string', format: 'misskey:id' }, - }, - required: ['noteId'], -} as const; - -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - await watch(user.id, note); -}); diff --git a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts deleted file mode 100644 index 2c1a2e5fbd..0000000000 --- a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts +++ /dev/null @@ -1,38 +0,0 @@ -import unwatch from '@/services/note/unwatch.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; -import { ApiError } from '../../../error.js'; - -export const meta = { - tags: ['notes'], - - requireCredential: true, - - kind: 'write:account', - - errors: { - noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '09b3695c-f72c-4731-a428-7cff825fc82e', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - noteId: { type: 'string', format: 'misskey:id' }, - }, - required: ['noteId'], -} as const; - -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - await unwatch(user.id, note); -}); diff --git a/packages/backend/src/server/api/endpoints/notifications/create.ts b/packages/backend/src/server/api/endpoints/notifications/create.ts index 80d513d8da..3427a3eb5c 100644 --- a/packages/backend/src/server/api/endpoints/notifications/create.ts +++ b/packages/backend/src/server/api/endpoints/notifications/create.ts @@ -1,5 +1,6 @@ -import { createNotification } from '@/services/create-notification.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { CreateNotificationService } from '@/core/CreateNotificationService.js'; export const meta = { tags: ['notifications'], @@ -23,11 +24,18 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user, token) => { - createNotification(user.id, 'app', { - appAccessTokenId: token ? token.id : null, - customBody: ps.body, - customHeader: ps.header, - customIcon: ps.icon, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private createNotificationService: CreateNotificationService, + ) { + super(meta, paramDef, async (ps, user, token) => { + this.createNotificationService.createNotification(user.id, 'app', { + appAccessTokenId: token ? token.id : null, + customBody: ps.body, + customHeader: ps.header, + customIcon: ps.icon, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts index d169afbb35..3d1eb2b39c 100644 --- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts @@ -1,7 +1,9 @@ -import { publishMainStream } from '@/services/stream.js'; -import { pushNotification } from '@/services/push-notification.js'; -import { Notifications } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotificationsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { PushNotificationService } from '@/core/PushNotificationService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notifications', 'account'], @@ -18,16 +20,27 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Update documents - await Notifications.update({ - notifieeId: user.id, - isRead: false, - }, { - isRead: true, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notificationsRepository) + private notificationsRepository: NotificationsRepository, - // 全ての通知を読みましたよというイベントを発行 - publishMainStream(user.id, 'readAllNotifications'); - pushNotification(user.id, 'readAllNotifications', undefined); -}); + private globalEventService: GlobalEventService, + private pushNotificationService: PushNotificationService, + ) { + super(meta, paramDef, async (ps, me) => { + // Update documents + await this.notificationsRepository.update({ + notifieeId: me.id, + isRead: false, + }, { + isRead: true, + }); + + // 全ての通知を読みましたよというイベントを発行 + this.globalEventService.publishMainStream(me.id, 'readAllNotifications'); + this.pushNotificationService.pushNotification(me.id, 'readAllNotifications', undefined); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts index 7bce525a55..cdf8d09f9e 100644 --- a/packages/backend/src/server/api/endpoints/notifications/read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/read.ts @@ -1,5 +1,6 @@ -import define from '../../define.js'; -import { readNotification } from '../../common/read-notification.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NotificationService } from '@/core/NotificationService.js'; export const meta = { tags: ['notifications', 'account'], @@ -43,7 +44,14 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - if ('notificationId' in ps) return readNotification(user.id, [ps.notificationId]); - return readNotification(user.id, ps.notificationIds); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private notificationService: NotificationService, + ) { + super(meta, paramDef, async (ps, me) => { + if ('notificationId' in ps) return this.notificationService.readNotification(me.id, [ps.notificationId]); + return this.notificationService.readNotification(me.id, ps.notificationIds); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts index 6dd3ede85a..1b0299c3c6 100644 --- a/packages/backend/src/server/api/endpoints/page-push.ts +++ b/packages/backend/src/server/api/endpoints/page-push.ts @@ -1,6 +1,10 @@ -import { publishMainStream } from '@/services/stream.js'; -import { Users, Pages } from '@/models/index.js'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PagesRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../error.js'; export const meta = { @@ -27,19 +31,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOneBy({ id: ps.pageId }); - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + + private userEntityService: UserEntityService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); + if (page == null) { + throw new ApiError(meta.errors.noSuchPage); + } - publishMainStream(page.userId, 'pageEvent', { - pageId: ps.pageId, - event: ps.event, - var: ps.var, - userId: user.id, - user: await Users.pack(user.id, { id: page.userId }, { - detail: true, - }), - }); -}); + this.globalEventService.publishMainStream(page.userId, 'pageEvent', { + pageId: ps.pageId, + event: ps.event, + var: ps.var, + userId: me.id, + user: await this.userEntityService.pack(me.id, { id: page.userId }, { + detail: true, + }), + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index b008cde84e..ac80849aa0 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -1,8 +1,11 @@ import ms from 'ms'; -import { Pages, DriveFiles } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { Page } from '@/models/entities/page.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DriveFilesRepository, PagesRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { Page } from '@/models/entities/Page.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { PageEntityService } from '@/core/entities/PageEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -59,45 +62,59 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - let eyeCatchingImage = null; - if (ps.eyeCatchingImageId != null) { - eyeCatchingImage = await DriveFiles.findOneBy({ - id: ps.eyeCatchingImageId, - userId: user.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, - if (eyeCatchingImage == null) { - throw new ApiError(meta.errors.noSuchFile); - } - } + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private pageEntityService: PageEntityService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + let eyeCatchingImage = null; + if (ps.eyeCatchingImageId != null) { + eyeCatchingImage = await this.driveFilesRepository.findOneBy({ + id: ps.eyeCatchingImageId, + userId: me.id, + }); - await Pages.findBy({ - userId: user.id, - name: ps.name, - }).then(result => { - if (result.length > 0) { - throw new ApiError(meta.errors.nameAlreadyExists); - } - }); + if (eyeCatchingImage == null) { + throw new ApiError(meta.errors.noSuchFile); + } + } - const page = await Pages.insert(new Page({ - id: genId(), - createdAt: new Date(), - updatedAt: new Date(), - title: ps.title, - name: ps.name, - summary: ps.summary, - content: ps.content, - variables: ps.variables, - script: ps.script, - eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null, - userId: user.id, - visibility: 'public', - alignCenter: ps.alignCenter, - hideTitleWhenPinned: ps.hideTitleWhenPinned, - font: ps.font, - })).then(x => Pages.findOneByOrFail(x.identifiers[0])); + await this.pagesRepository.findBy({ + userId: me.id, + name: ps.name, + }).then(result => { + if (result.length > 0) { + throw new ApiError(meta.errors.nameAlreadyExists); + } + }); - return await Pages.pack(page); -}); + const page = await this.pagesRepository.insert(new Page({ + id: this.idService.genId(), + createdAt: new Date(), + updatedAt: new Date(), + title: ps.title, + name: ps.name, + summary: ps.summary, + content: ps.content, + variables: ps.variables, + script: ps.script, + eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null, + userId: me.id, + visibility: 'public', + alignCenter: ps.alignCenter, + hideTitleWhenPinned: ps.hideTitleWhenPinned, + font: ps.font, + })).then(x => this.pagesRepository.findOneByOrFail(x.identifiers[0])); + + return await this.pageEntityService.pack(page); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index a7708e6585..4e97755761 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -1,5 +1,7 @@ -import { Pages } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PagesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -33,14 +35,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOneBy({ id: ps.pageId }); - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); + if (page == null) { + throw new ApiError(meta.errors.noSuchPage); + } + if (page.userId !== me.id) { + throw new ApiError(meta.errors.accessDenied); + } + + await this.pagesRepository.delete(page.id); + }); } - if (page.userId !== user.id) { - throw new ApiError(meta.errors.accessDenied); - } - - await Pages.delete(page.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/pages/featured.ts b/packages/backend/src/server/api/endpoints/pages/featured.ts index 5a149a626e..3e3dbb0832 100644 --- a/packages/backend/src/server/api/endpoints/pages/featured.ts +++ b/packages/backend/src/server/api/endpoints/pages/featured.ts @@ -1,5 +1,8 @@ -import { Pages } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PagesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { PageEntityService } from '@/core/entities/PageEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['pages'], @@ -24,13 +27,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Pages.createQueryBuilder('page') - .where('page.visibility = \'public\'') - .andWhere('page.likedCount > 0') - .orderBy('page.likedCount', 'DESC'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, - const pages = await query.take(10).getMany(); + private pageEntityService: PageEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.pagesRepository.createQueryBuilder('page') + .where('page.visibility = \'public\'') + .andWhere('page.likedCount > 0') + .orderBy('page.likedCount', 'DESC'); - return await Pages.packMany(pages, me); -}); + const pages = await query.take(10).getMany(); + + return await this.pageEntityService.packMany(pages, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index 269b539f74..f3c55fed8b 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -1,6 +1,8 @@ -import { Pages, PageLikes } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PagesRepository, PageLikesRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -40,33 +42,46 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOneBy({ id: ps.pageId }); - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, - if (page.userId === user.id) { - throw new ApiError(meta.errors.yourPage); - } + @Inject(DI.pageLikesRepository) + private pageLikesRepository: PageLikesRepository, - // if already liked - const exist = await PageLikes.findOneBy({ - pageId: page.id, - userId: user.id, - }); + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); + if (page == null) { + throw new ApiError(meta.errors.noSuchPage); + } - if (exist != null) { - throw new ApiError(meta.errors.alreadyLiked); - } + if (page.userId === me.id) { + throw new ApiError(meta.errors.yourPage); + } - // Create like - await PageLikes.insert({ - id: genId(), - createdAt: new Date(), - pageId: page.id, - userId: user.id, - }); + // if already liked + const exist = await this.pageLikesRepository.findOneBy({ + pageId: page.id, + userId: me.id, + }); - Pages.increment({ id: page.id }, 'likedCount', 1); -}); + if (exist != null) { + throw new ApiError(meta.errors.alreadyLiked); + } + + // Create like + await this.pageLikesRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + pageId: page.id, + userId: me.id, + }); + + this.pagesRepository.increment({ id: page.id }, 'likedCount', 1); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts index 5d37e86b91..6d73889d39 100644 --- a/packages/backend/src/server/api/endpoints/pages/show.ts +++ b/packages/backend/src/server/api/endpoints/pages/show.ts @@ -1,7 +1,10 @@ import { IsNull } from 'typeorm'; -import { Pages, Users } from '@/models/index.js'; -import { Page } from '@/models/entities/page.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, PagesRepository } from '@/models/index.js'; +import type { Page } from '@/models/entities/Page.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { PageEntityService } from '@/core/entities/PageEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -44,27 +47,40 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - let page: Page | null = null; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (ps.pageId) { - page = await Pages.findOneBy({ id: ps.pageId }); - } else if (ps.name && ps.username) { - const author = await Users.findOneBy({ - host: IsNull(), - usernameLower: ps.username.toLowerCase(), - }); - if (author) { - page = await Pages.findOneBy({ - name: ps.name, - userId: author.id, - }); - } - } + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } + private pageEntityService: PageEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + let page: Page | null = null; + + if (ps.pageId) { + page = await this.pagesRepository.findOneBy({ id: ps.pageId }); + } else if (ps.name && ps.username) { + const author = await this.usersRepository.findOneBy({ + host: IsNull(), + usernameLower: ps.username.toLowerCase(), + }); + if (author) { + page = await this.pagesRepository.findOneBy({ + name: ps.name, + userId: author.id, + }); + } + } - return await Pages.pack(page, user); -}); + if (page == null) { + throw new ApiError(meta.errors.noSuchPage); + } + + return await this.pageEntityService.pack(page, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts index 6b3a2bec10..88386739be 100644 --- a/packages/backend/src/server/api/endpoints/pages/unlike.ts +++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts @@ -1,5 +1,7 @@ -import { Pages, PageLikes } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PagesRepository, PageLikesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -33,23 +35,34 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOneBy({ id: ps.pageId }); - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, - const exist = await PageLikes.findOneBy({ - pageId: page.id, - userId: user.id, - }); + @Inject(DI.pageLikesRepository) + private pageLikesRepository: PageLikesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); + if (page == null) { + throw new ApiError(meta.errors.noSuchPage); + } - if (exist == null) { - throw new ApiError(meta.errors.notLiked); - } + const exist = await this.pageLikesRepository.findOneBy({ + pageId: page.id, + userId: me.id, + }); - // Delete like - await PageLikes.delete(exist.id); + if (exist == null) { + throw new ApiError(meta.errors.notLiked); + } - Pages.decrement({ id: page.id }, 'likedCount', 1); -}); + // Delete like + await this.pageLikesRepository.delete(exist.id); + + this.pagesRepository.decrement({ id: page.id }, 'likedCount', 1); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index d241f585aa..8980ac4906 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -1,7 +1,9 @@ import ms from 'ms'; import { Not } from 'typeorm'; -import { Pages, DriveFiles } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PagesRepository, DriveFilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -65,52 +67,63 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOneBy({ id: ps.pageId }); - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } - if (page.userId !== user.id) { - throw new ApiError(meta.errors.accessDenied); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, - let eyeCatchingImage = null; - if (ps.eyeCatchingImageId != null) { - eyeCatchingImage = await DriveFiles.findOneBy({ - id: ps.eyeCatchingImageId, - userId: user.id, - }); + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); + if (page == null) { + throw new ApiError(meta.errors.noSuchPage); + } + if (page.userId !== me.id) { + throw new ApiError(meta.errors.accessDenied); + } - if (eyeCatchingImage == null) { - throw new ApiError(meta.errors.noSuchFile); - } - } + let eyeCatchingImage = null; + if (ps.eyeCatchingImageId != null) { + eyeCatchingImage = await this.driveFilesRepository.findOneBy({ + id: ps.eyeCatchingImageId, + userId: me.id, + }); - await Pages.findBy({ - id: Not(ps.pageId), - userId: user.id, - name: ps.name, - }).then(result => { - if (result.length > 0) { - throw new ApiError(meta.errors.nameAlreadyExists); - } - }); + if (eyeCatchingImage == null) { + throw new ApiError(meta.errors.noSuchFile); + } + } - await Pages.update(page.id, { - updatedAt: new Date(), - title: ps.title, - name: ps.name === undefined ? page.name : ps.name, - summary: ps.name === undefined ? page.summary : ps.summary, - content: ps.content, - variables: ps.variables, - script: ps.script, - alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter, - hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned, - font: ps.font === undefined ? page.font : ps.font, - eyeCatchingImageId: ps.eyeCatchingImageId === null - ? null - : ps.eyeCatchingImageId === undefined - ? page.eyeCatchingImageId - : eyeCatchingImage!.id, - }); -}); + await this.pagesRepository.findBy({ + id: Not(ps.pageId), + userId: me.id, + name: ps.name, + }).then(result => { + if (result.length > 0) { + throw new ApiError(meta.errors.nameAlreadyExists); + } + }); + + await this.pagesRepository.update(page.id, { + updatedAt: new Date(), + title: ps.title, + name: ps.name === undefined ? page.name : ps.name, + summary: ps.name === undefined ? page.summary : ps.summary, + content: ps.content, + variables: ps.variables, + script: ps.script, + alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter, + hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned, + font: ps.font === undefined ? page.font : ps.font, + eyeCatchingImageId: ps.eyeCatchingImageId === null + ? null + : ps.eyeCatchingImageId === undefined + ? page.eyeCatchingImageId + : eyeCatchingImage!.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/ping.ts b/packages/backend/src/server/api/endpoints/ping.ts index 2891a0860a..4bb62b298e 100644 --- a/packages/backend/src/server/api/endpoints/ping.ts +++ b/packages/backend/src/server/api/endpoints/ping.ts @@ -1,4 +1,5 @@ -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; export const meta = { requireCredential: false, @@ -24,8 +25,14 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - return { - pong: Date.now(), - }; -}); +@Injectable() +export default class extends Endpoint { + constructor( + ) { + super(meta, paramDef, async () => { + return { + pong: Date.now(), + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 41595b47d9..573331e0d8 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -1,9 +1,12 @@ import { IsNull } from 'typeorm'; -import { Users } from '@/models/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; import * as Acct from '@/misc/acct.js'; -import { User } from '@/models/entities/user.js'; -import define from '../define.js'; +import type { User } from '@/models/entities/User.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MetaService } from '@/core/MetaService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users'], @@ -28,13 +31,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const meta = await fetchMeta(); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - const users = await Promise.all(meta.pinnedUsers.map(acct => Acct.parse(acct)).map(acct => Users.findOneBy({ - usernameLower: acct.username.toLowerCase(), - host: acct.host ?? IsNull(), - }))); + private metaService: MetaService, + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const meta = await this.metaService.fetch(); - return await Users.packMany(users.filter(x => x !== undefined) as User[], me, { detail: true }); -}); + const users = await Promise.all(meta.pinnedthis.usersRepository.map(acct => Acct.parse(acct)).map(acct => this.usersRepository.findOneBy({ + usernameLower: acct.username.toLowerCase(), + host: acct.host ?? IsNull(), + }))); + + return await this.userEntityService.packMany(users.filter(x => x !== undefined) as User[], me, { detail: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index c6a940c65e..7c8188ce3c 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -1,8 +1,10 @@ -import { PromoReads } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PromoReadsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getNote } from '../../common/getters.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['notes'], @@ -27,25 +29,36 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - const exist = await PromoReads.findOneBy({ - noteId: note.id, - userId: user.id, - }); - - if (exist != null) { - return; - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.promoReadsRepository) + private promoReadsRepository: PromoReadsRepository, + + private idService: IdService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + const exist = await this.promoReadsRepository.findOneBy({ + noteId: note.id, + userId: me.id, + }); - await PromoReads.insert({ - id: genId(), - createdAt: new Date(), - noteId: note.id, - userId: user.id, - }); -}); + if (exist != null) { + return; + } + + await this.promoReadsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + noteId: note.id, + userId: me.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 511a6bbb53..4766239533 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -1,13 +1,14 @@ import rndstr from 'rndstr'; import ms from 'ms'; import { IsNull } from 'typeorm'; -import { publishMainStream } from '@/services/stream.js'; -import config from '@/config/index.js'; -import { Users, UserProfiles, PasswordResetRequests } from '@/models/index.js'; -import { sendEmail } from '@/services/send-email.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PasswordResetRequestsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import { EmailService } from '@/core/EmailService.js'; import { ApiError } from '../error.js'; -import define from '../define.js'; export const meta = { tags: ['reset password'], @@ -36,41 +37,61 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const user = await Users.findOneBy({ - usernameLower: ps.username.toLowerCase(), - host: IsNull(), - }); - - // 合致するユーザーが登録されていなかったら無視 - if (user == null) { - return; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.passwordResetRequestsRepository) + private passwordResetRequestsRepository: PasswordResetRequestsRepository, + + private idService: IdService, + private emailService: EmailService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ + usernameLower: ps.username.toLowerCase(), + host: IsNull(), + }); + + // 合致するユーザーが登録されていなかったら無視 + if (user == null) { + return; + } + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + // 合致するメアドが登録されていなかったら無視 + if (profile.email !== ps.email) { + return; + } + + // メアドが認証されていなかったら無視 + if (!profile.emailVerified) { + return; + } + + const token = rndstr('a-z0-9', 64); + + await this.passwordResetRequestsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: profile.userId, + token, + }); + + const link = `${this.config.url}/reset-password/${token}`; + + this.emailService.sendEmail(ps.email, 'Password reset requested', + `To reset password, please click this link:
${link}`, + `To reset password, please click this link: ${link}`); + }); } - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - // 合致するメアドが登録されていなかったら無視 - if (profile.email !== ps.email) { - return; - } - - // メアドが認証されていなかったら無視 - if (!profile.emailVerified) { - return; - } - - const token = rndstr('a-z0-9', 64); - - await PasswordResetRequests.insert({ - id: genId(), - createdAt: new Date(), - userId: profile.userId, - token, - }); - - const link = `${config.url}/reset-password/${token}`; - - sendEmail(ps.email, 'Password reset requested', - `To reset password, please click this link:
${link}`, - `To reset password, please click this link: ${link}`); -}); +} diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts index 140f96d579..526efbc2f6 100644 --- a/packages/backend/src/server/api/endpoints/reset-db.ts +++ b/packages/backend/src/server/api/endpoints/reset-db.ts @@ -1,5 +1,9 @@ -import { resetDb } from '@/db/postgre.js'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import Redis from 'ioredis'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; +import { resetDb } from '@/misc/reset-db.js'; import { ApiError } from '../error.js'; export const meta = { @@ -21,10 +25,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test'; - - await resetDb(); - - await new Promise(resolve => setTimeout(resolve, 1000)); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.redis) + private redisClient: Redis.Redis, + ) { + super(meta, paramDef, async (ps, me) => { + if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test'; + + await redisClient.flushdb(); + await resetDb(this.db); + + await new Promise(resolve => setTimeout(resolve, 1000)); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 797169c2c3..48edde5196 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -1,7 +1,9 @@ import bcrypt from 'bcryptjs'; -import { publishMainStream } from '@/services/stream.js'; -import { Users, UserProfiles, PasswordResetRequests } from '@/models/index.js'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../error.js'; export const meta = { @@ -26,23 +28,34 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const req = await PasswordResetRequests.findOneByOrFail({ - token: ps.token, - }); - - // 発行してから30分以上経過していたら無効 - if (Date.now() - req.createdAt.getTime() > 1000 * 60 * 30) { - throw new Error(); // TODO +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.passwordResetRequestsRepository) + private passwordResetRequestsRepository: PasswordResetRequestsRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const req = await this.passwordResetRequestsRepository.findOneByOrFail({ + token: ps.token, + }); + + // 発行してから30分以上経過していたら無効 + if (Date.now() - req.createdAt.getTime() > 1000 * 60 * 30) { + throw new Error(); // TODO + } + + // Generate hash of password + const salt = await bcrypt.genSalt(8); + const hash = await bcrypt.hash(ps.password, salt); + + await this.userProfilesRepository.update(req.userId, { + password: hash, + }); + + this.passwordResetRequestsRepository.delete(req.id); + }); } - - // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(ps.password, salt); - - await UserProfiles.update(req.userId, { - password: hash, - }); - - PasswordResetRequests.delete(req.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts index 99f3730e97..8989a3073d 100644 --- a/packages/backend/src/server/api/endpoints/server-info.ts +++ b/packages/backend/src/server/api/endpoints/server-info.ts @@ -1,6 +1,7 @@ import * as os from 'node:os'; import si from 'systeminformation'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; export const meta = { requireCredential: false, @@ -15,22 +16,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const memStats = await si.mem(); - const fsStats = await si.fsSize(); +@Injectable() +export default class extends Endpoint { + constructor( + ) { + super(meta, paramDef, async () => { + const memStats = await si.mem(); + const fsStats = await si.fsSize(); - return { - machine: os.hostname(), - cpu: { - model: os.cpus()[0].model, - cores: os.cpus().length, - }, - mem: { - total: memStats.total, - }, - fs: { - total: fsStats[0].size, - used: fsStats[0].used, - }, - }; -}); + return { + machine: os.hostname(), + cpu: { + model: os.cpus()[0].model, + cores: os.cpus().length, + }, + mem: { + total: memStats.total, + }, + fs: { + total: fsStats[0].size, + used: fsStats[0].used, + }, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index cc94f8bf26..17af75578b 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -1,7 +1,8 @@ -import { Instances, NoteReactions, Notes, Users } from '@/models/index.js'; -import define from '../define.js'; -import { } from '@/services/chart/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; +import { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: false, @@ -51,34 +52,51 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const [ - notesCount, - originalNotesCount, - usersCount, - originalUsersCount, - reactionsCount, - //originalReactionsCount, - instances, - ] = await Promise.all([ - Notes.count({ cache: 3600000 }), // 1 hour - Notes.count({ where: { userHost: IsNull() }, cache: 3600000 }), - Users.count({ cache: 3600000 }), - Users.count({ where: { host: IsNull() }, cache: 3600000 }), - NoteReactions.count({ cache: 3600000 }), // 1 hour - //NoteReactions.count({ where: { userHost: IsNull() }, cache: 3600000 }), - Instances.count({ cache: 3600000 }), - ]); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - return { - notesCount, - originalNotesCount, - usersCount, - originalUsersCount, - reactionsCount, - //originalReactionsCount, - instances, - driveUsageLocal: 0, - driveUsageRemote: 0, - }; -}); + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, + + @Inject(DI.noteReactionsRepository) + private noteReactionsRepository: NoteReactionsRepository, + ) { + super(meta, paramDef, async () => { + const [ + notesCount, + originalNotesCount, + usersCount, + originalUsersCount, + reactionsCount, + //originalReactionsCount, + instances, + ] = await Promise.all([ + this.notesRepository.count({ cache: 3600000 }), // 1 hour + this.notesRepository.count({ where: { userHost: IsNull() }, cache: 3600000 }), + this.usersRepository.count({ cache: 3600000 }), + this.usersRepository.count({ where: { host: IsNull() }, cache: 3600000 }), + this.noteReactionsRepository.count({ cache: 3600000 }), // 1 hour + //this.noteReactionsRepository.count({ where: { userHost: IsNull() }, cache: 3600000 }), + this.instancesRepository.count({ cache: 3600000 }), + ]); + + return { + notesCount, + originalNotesCount, + usersCount, + originalUsersCount, + reactionsCount, + //originalReactionsCount, + instances, + driveUsageLocal: 0, + driveUsageRemote: 0, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index 437f8874ff..73a084c2ad 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -1,7 +1,9 @@ -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { genId } from '@/misc/gen-id.js'; -import { SwSubscriptions } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { IdService } from '@/core/IdService.js'; +import { SwSubscriptionsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], @@ -38,35 +40,46 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // if already subscribed - const exist = await SwSubscriptions.findOneBy({ - userId: user.id, - endpoint: ps.endpoint, - auth: ps.auth, - publickey: ps.publickey, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.swSubscriptionsRepository) + private swSubscriptionsRepository: SwSubscriptionsRepository, - const instance = await fetchMeta(true); + private idService: IdService, + private metaService: MetaService, + ) { + super(meta, paramDef, async (ps, me) => { + // if already subscribed + const exist = await this.swSubscriptionsRepository.findOneBy({ + userId: me.id, + endpoint: ps.endpoint, + auth: ps.auth, + publickey: ps.publickey, + }); - if (exist != null) { - return { - state: 'already-subscribed' as const, - key: instance.swPublicKey, - }; - } + const instance = await this.metaService.fetch(true); + + if (exist != null) { + return { + state: 'already-subscribed' as const, + key: instance.swPublicKey, + }; + } - await SwSubscriptions.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - endpoint: ps.endpoint, - auth: ps.auth, - publickey: ps.publickey, - }); + await this.swSubscriptionsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + endpoint: ps.endpoint, + auth: ps.auth, + publickey: ps.publickey, + }); - return { - state: 'subscribed' as const, - key: instance.swPublicKey, - }; -}); + return { + state: 'subscribed' as const, + key: instance.swPublicKey, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index c19e06b879..feb6730154 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -1,5 +1,7 @@ -import { SwSubscriptions } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { SwSubscriptionsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], @@ -18,9 +20,17 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - await SwSubscriptions.delete({ - userId: user.id, - endpoint: ps.endpoint, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.swSubscriptionsRepository) + private swSubscriptionsRepository: SwSubscriptionsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + await this.swSubscriptionsRepository.delete({ + userId: me.id, + endpoint: ps.endpoint, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/test.ts b/packages/backend/src/server/api/endpoints/test.ts index 9949237a7e..39ea1f2171 100644 --- a/packages/backend/src/server/api/endpoints/test.ts +++ b/packages/backend/src/server/api/endpoints/test.ts @@ -1,4 +1,5 @@ -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; export const meta = { tags: ['non-productive'], @@ -21,6 +22,12 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - return ps; -}); +@Injectable() +export default class extends Endpoint { + constructor( + ) { + super(meta, paramDef, async (ps, me) => { + return ps; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index 3e41aeaed8..56474d6988 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -1,6 +1,9 @@ import { IsNull } from 'typeorm'; -import { Users, UsedUsernames } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsedUsernamesRepository, UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { localUsernameSchema } from '@/models/entities/User.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users'], @@ -22,22 +25,33 @@ export const meta = { export const paramDef = { type: 'object', properties: { - username: Users.localUsernameSchema, + username: localUsernameSchema, }, required: ['username'], } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - // Get exist - const exist = await Users.countBy({ - host: IsNull(), - usernameLower: ps.username.toLowerCase(), - }); - - const exist2 = await UsedUsernames.countBy({ username: ps.username.toLowerCase() }); - - return { - available: exist === 0 && exist2 === 0, - }; -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.usedUsernamesRepository) + private usedUsernamesRepository: UsedUsernamesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + // Get exist + const exist = await this.usersRepository.countBy({ + host: IsNull(), + usernameLower: ps.username.toLowerCase(), + }); + + const exist2 = await this.usedUsernamesRepository.countBy({ username: ps.username.toLowerCase() }); + + return { + available: exist === 0 && exist2 === 0, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 3a8211374b..3d05ec2e1d 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -1,7 +1,9 @@ -import { Users } from '@/models/index.js'; -import define from '../define.js'; -import { generateMutedUserQueryForUsers } from '../common/generate-muted-user-query.js'; -import { generateBlockQueryForUsers } from '../common/generate-block-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users'], @@ -38,43 +40,54 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Users.createQueryBuilder('user'); - query.where('user.isExplorable = TRUE'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - switch (ps.state) { - case 'admin': query.andWhere('user.isAdmin = TRUE'); break; - case 'moderator': query.andWhere('user.isModerator = TRUE'); break; - case 'adminOrModerator': query.andWhere('user.isAdmin = TRUE OR user.isModerator = TRUE'); break; - case 'alive': query.andWhere('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; - } + private userEntityService: UserEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.usersRepository.createQueryBuilder('user'); + query.where('user.isExplorable = 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.state) { + case 'admin': query.andWhere('user.isAdmin = TRUE'); break; + case 'moderator': query.andWhere('user.isModerator = TRUE'); break; + case 'adminOrModerator': query.andWhere('user.isAdmin = TRUE OR user.isModerator = TRUE'); break; + case 'alive': query.andWhere('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; + } - if (ps.hostname) { - query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() }); - } + 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.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'DESC'); break; - case '-updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'ASC'); break; - default: query.orderBy('user.id', 'ASC'); break; - } + if (ps.hostname) { + query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() }); + } + + 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.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'DESC'); break; + case '-updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'ASC'); break; + default: query.orderBy('user.id', 'ASC'); break; + } - if (me) generateMutedUserQueryForUsers(query, me); - if (me) generateBlockQueryForUsers(query, me); + if (me) this.queryService.generateMutedUserQueryForUsers(query, me); + if (me) this.queryService.generateBlockQueryForUsers(query, me); - query.take(ps.limit); - query.skip(ps.offset); + query.take(ps.limit); + query.skip(ps.offset); - const users = await query.getMany(); + const users = await query.getMany(); - return await Users.packMany(users, me, { detail: true }); -}); + return await this.userEntityService.packMany(users, me, { detail: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index 09fdf27c23..2d5545cbab 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -1,6 +1,9 @@ -import { Clips } from '@/models/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { ClipsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users', 'clips'], @@ -30,14 +33,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(Clips.createQueryBuilder('clip'), ps.sinceId, ps.untilId) - .andWhere('clip.userId = :userId', { userId: ps.userId }) - .andWhere('clip.isPublic = true'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, - const clips = await query - .take(ps.limit) - .getMany(); + private clipEntityService: ClipEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.clipsRepository.createQueryBuilder('clip'), ps.sinceId, ps.untilId) + .andWhere('clip.userId = :userId', { userId: ps.userId }) + .andWhere('clip.isPublic = true'); - return await Clips.packMany(clips); -}); + const clips = await query + .take(ps.limit) + .getMany(); + + return await this.clipEntityService.packMany(clips); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 7f9f980764..08bcdd9f88 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -1,9 +1,12 @@ import { IsNull } from 'typeorm'; -import { Users, Followings, UserProfiles } from '@/models/index.js'; -import { toPunyNullable } from '@/misc/convert-host.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; export const meta = { tags: ['users'], @@ -66,42 +69,60 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy(ps.userId != null - ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) ?? IsNull() }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user == null) { - throw new ApiError(meta.errors.noSuchUser); - } + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - if (profile.ffVisibility === 'private') { - if (me == null || (me.id !== user.id)) { - throw new ApiError(meta.errors.forbidden); - } - } else if (profile.ffVisibility === 'followers') { - if (me == null) { - throw new ApiError(meta.errors.forbidden); - } else if (me.id !== user.id) { - const following = await Followings.findOneBy({ - followeeId: user.id, - followerId: me.id, - }); - if (following == null) { - throw new ApiError(meta.errors.forbidden); + private utilityService: UtilityService, + private followingEntityService: FollowingEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy(ps.userId != null + ? { id: ps.userId } + : { usernameLower: ps.username!.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() }); + + if (user == null) { + throw new ApiError(meta.errors.noSuchUser); } - } - } - const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) - .andWhere('following.followeeId = :userId', { userId: user.id }) - .innerJoinAndSelect('following.follower', 'follower'); + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); - const followings = await query - .take(ps.limit) - .getMany(); + if (profile.ffVisibility === 'private') { + if (me == null || (me.id !== user.id)) { + throw new ApiError(meta.errors.forbidden); + } + } else if (profile.ffVisibility === 'followers') { + if (me == null) { + throw new ApiError(meta.errors.forbidden); + } else if (me.id !== user.id) { + const following = await this.followingsRepository.findOneBy({ + followeeId: user.id, + followerId: me.id, + }); + if (following == null) { + throw new ApiError(meta.errors.forbidden); + } + } + } + + const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId) + .andWhere('following.followeeId = :userId', { userId: user.id }) + .innerJoinAndSelect('following.follower', 'follower'); - return await Followings.packMany(followings, me, { populateFollower: true }); -}); + const followings = await query + .take(ps.limit) + .getMany(); + + return await this.followingEntityService.packMany(followings, me, { populateFollower: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 0aaa810f76..225ab5210a 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -1,9 +1,12 @@ import { IsNull } from 'typeorm'; -import { Users, Followings, UserProfiles } from '@/models/index.js'; -import { toPunyNullable } from '@/misc/convert-host.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; export const meta = { tags: ['users'], @@ -66,42 +69,60 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy(ps.userId != null - ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) ?? IsNull() }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user == null) { - throw new ApiError(meta.errors.noSuchUser); - } + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - if (profile.ffVisibility === 'private') { - if (me == null || (me.id !== user.id)) { - throw new ApiError(meta.errors.forbidden); - } - } else if (profile.ffVisibility === 'followers') { - if (me == null) { - throw new ApiError(meta.errors.forbidden); - } else if (me.id !== user.id) { - const following = await Followings.findOneBy({ - followeeId: user.id, - followerId: me.id, - }); - if (following == null) { - throw new ApiError(meta.errors.forbidden); + private utilityService: UtilityService, + private followingEntityService: FollowingEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy(ps.userId != null + ? { id: ps.userId } + : { usernameLower: ps.username!.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() }); + + if (user == null) { + throw new ApiError(meta.errors.noSuchUser); } - } - } - const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) - .andWhere('following.followerId = :userId', { userId: user.id }) - .innerJoinAndSelect('following.followee', 'followee'); + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); - const followings = await query - .take(ps.limit) - .getMany(); + if (profile.ffVisibility === 'private') { + if (me == null || (me.id !== user.id)) { + throw new ApiError(meta.errors.forbidden); + } + } else if (profile.ffVisibility === 'followers') { + if (me == null) { + throw new ApiError(meta.errors.forbidden); + } else if (me.id !== user.id) { + const following = await this.followingsRepository.findOneBy({ + followeeId: user.id, + followerId: me.id, + }); + if (following == null) { + throw new ApiError(meta.errors.forbidden); + } + } + } + + const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId) + .andWhere('following.followerId = :userId', { userId: user.id }) + .innerJoinAndSelect('following.followee', 'followee'); - return await Followings.packMany(followings, me, { populateFollowee: true }); -}); + const followings = await query + .take(ps.limit) + .getMany(); + + return await this.followingEntityService.packMany(followings, me, { populateFollowee: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts index 35bf2df598..2d28d6ca07 100644 --- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; -import { GalleryPosts } from '@/models/index.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users', 'gallery'], @@ -30,13 +33,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId) - .andWhere(`post.userId = :userId`, { userId: ps.userId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - const posts = await query - .take(ps.limit) - .getMany(); + private galleryPostEntityService: GalleryPostEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId) + .andWhere('post.userId = :userId', { userId: ps.userId }); - return await GalleryPosts.packMany(posts, user); -}); + const posts = await query + .take(ps.limit) + .getMany(); + + return await this.galleryPostEntityService.packMany(posts, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index 56965d3066..3eeca7562f 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -1,9 +1,12 @@ import { Not, In, IsNull } from 'typeorm'; -import { maximum } from '@/prelude/array.js'; -import { Notes, Users } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { maximum } from '@/misc/prelude/array.js'; +import { NotesRepository, UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['users'], @@ -51,64 +54,78 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Lookup user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Fetch recent notes - const recentNotes = await Notes.find({ - where: { - userId: user.id, - replyId: Not(IsNull()), - }, - order: { - id: -1, - }, - take: 1000, - select: ['replyId'], - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + // Lookup user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Fetch recent notes + const recentNotes = await this.notesRepository.find({ + where: { + userId: user.id, + replyId: Not(IsNull()), + }, + order: { + id: -1, + }, + take: 1000, + select: ['replyId'], + }); + + // 投稿が少なかったら中断 + if (recentNotes.length === 0) { + return []; + } + + // TODO ミュートを考慮 + const replyTargetNotes = await this.notesRepository.find({ + where: { + id: In(recentNotes.map(p => p.replyId)), + }, + select: ['userId'], + }); - // 投稿が少なかったら中断 - if (recentNotes.length === 0) { - return []; - } + const repliedUsers: any = {}; - // TODO ミュートを考慮 - const replyTargetNotes = await Notes.find({ - where: { - id: In(recentNotes.map(p => p.replyId)), - }, - select: ['userId'], - }); - - const repliedUsers: any = {}; - - // Extract replies from recent notes - for (const userId of replyTargetNotes.map(x => x.userId.toString())) { - if (repliedUsers[userId]) { - repliedUsers[userId]++; - } else { - repliedUsers[userId] = 1; - } - } + // Extract replies from recent notes + for (const userId of replyTargetNotes.map(x => x.userId.toString())) { + if (repliedUsers[userId]) { + repliedUsers[userId]++; + } else { + repliedUsers[userId] = 1; + } + } - // Calc peak - const peak = maximum(Object.values(repliedUsers)); + // Calc peak + const peak = maximum(Object.values(repliedUsers)); - // Sort replies by frequency - const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]); + // Sort replies by frequency + const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]); - // Extract top replied users - const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit); + // Extract top replied users + const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit); - // Make replies object (includes weights) - const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ - user: await Users.pack(user, me, { detail: true }), - weight: repliedUsers[user] / peak, - }))); + // Make replies object (includes weights) + const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ + user: await this.userEntityService.pack(user, me, { detail: true }), + weight: repliedUsers[user] / peak, + }))); - return repliesObj; -}); + return repliesObj; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts index 4a6362a3c6..5d7ad84ae0 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -1,8 +1,11 @@ -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { UserGroupJoining } from '@/models/entities/user-group-joining.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import type { UserGroup } from '@/models/entities/UserGroup.js'; +import type { UserGroupJoining } from '@/models/entities/UserGroupJoining.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['groups'], @@ -29,21 +32,35 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const userGroup = await UserGroups.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - name: ps.name, - } as UserGroup).then(x => UserGroups.findOneByOrFail(x.identifiers[0])); - - // Push the owner - await UserGroupJoinings.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - userGroupId: userGroup.id, - } as UserGroupJoining); - - return await UserGroups.pack(userGroup); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private userGroupEntityService: UserGroupEntityService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const userGroup = await this.userGroupsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + name: ps.name, + } as UserGroup).then(x => this.userGroupsRepository.findOneByOrFail(x.identifiers[0])); + + // Push the owner + await this.userGroupJoiningsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + userGroupId: userGroup.id, + } as UserGroupJoining); + + return await this.userGroupEntityService.pack(userGroup); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts index 2ff1f9aec1..50156b049e 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts @@ -1,5 +1,7 @@ -import { UserGroups } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -29,15 +31,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const userGroup = await UserGroups.findOneBy({ - id: ps.groupId, - userId: user.id, - }); - - if (userGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const userGroup = await this.userGroupsRepository.findOneBy({ + id: ps.groupId, + userId: me.id, + }); + + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } + + await this.userGroupsRepository.delete(userGroup.id); + }); } - - await UserGroups.delete(userGroup.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts index 220fff5f3e..0490fd41a0 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts @@ -1,8 +1,10 @@ -import { UserGroupJoinings, UserGroupInvitations } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { UserGroupJoining } from '@/models/entities/user-group-joining.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupInvitationsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import type { UserGroupJoining } from '@/models/entities/UserGroupJoining.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../../error.js'; -import define from '../../../../define.js'; export const meta = { tags: ['groups', 'users'], @@ -31,27 +33,40 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch the invitation - const invitation = await UserGroupInvitations.findOneBy({ - id: ps.invitationId, - }); - - if (invitation == null) { - throw new ApiError(meta.errors.noSuchInvitation); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupInvitationsRepository) + private userGroupInvitationsRepository: UserGroupInvitationsRepository, - if (invitation.userId !== user.id) { - throw new ApiError(meta.errors.noSuchInvitation); - } + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the invitation + const invitation = await this.userGroupInvitationsRepository.findOneBy({ + id: ps.invitationId, + }); - // Push the user - await UserGroupJoinings.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - userGroupId: invitation.userGroupId, - } as UserGroupJoining); + if (invitation == null) { + throw new ApiError(meta.errors.noSuchInvitation); + } - UserGroupInvitations.delete(invitation.id); -}); + if (invitation.userId !== me.id) { + throw new ApiError(meta.errors.noSuchInvitation); + } + + // Push the user + await this.userGroupJoiningsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + userGroupId: invitation.userGroupId, + } as UserGroupJoining); + + this.userGroupInvitationsRepository.delete(invitation.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts index 8d1d3db734..26efc1ecf3 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts @@ -1,5 +1,7 @@ -import { UserGroupInvitations } from '@/models/index.js'; -import define from '../../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupInvitationsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../../error.js'; export const meta = { @@ -29,19 +31,27 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch the invitation - const invitation = await UserGroupInvitations.findOneBy({ - id: ps.invitationId, - }); - - if (invitation == null) { - throw new ApiError(meta.errors.noSuchInvitation); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupInvitationsRepository) + private userGroupInvitationsRepository: UserGroupInvitationsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the invitation + const invitation = await this.userGroupInvitationsRepository.findOneBy({ + id: ps.invitationId, + }); + + if (invitation == null) { + throw new ApiError(meta.errors.noSuchInvitation); + } + + if (invitation.userId !== me.id) { + throw new ApiError(meta.errors.noSuchInvitation); + } + + await this.userGroupInvitationsRepository.delete(invitation.id); + }); } - - if (invitation.userId !== user.id) { - throw new ApiError(meta.errors.noSuchInvitation); - } - - await UserGroupInvitations.delete(invitation.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index 1a8d320f3a..4ae32a6bda 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -1,10 +1,12 @@ -import { UserGroups, UserGroupJoinings, UserGroupInvitations } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { UserGroupInvitation } from '@/models/entities/user-group-invitation.js'; -import { createNotification } from '@/services/create-notification.js'; -import { getUser } from '../../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository, UserGroupJoiningsRepository, UserGroupInvitationsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import type { UserGroupInvitation } from '@/models/entities/UserGroupInvitation.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { CreateNotificationService } from '@/core/CreateNotificationService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import define from '../../../define.js'; export const meta = { tags: ['groups', 'users'], @@ -52,51 +54,69 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the group - const userGroup = await UserGroups.findOneBy({ - id: ps.groupId, - userId: me.id, - }); - - if (userGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + + @Inject(DI.userGroupInvitationsRepository) + private userGroupInvitationsRepository: UserGroupInvitationsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private idService: IdService, + private getterService: GetterService, + private createNotificationService: CreateNotificationService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the group + const userGroup = await this.userGroupsRepository.findOneBy({ + id: ps.groupId, + userId: me.id, + }); + + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } + + // Fetch the user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + const joining = await this.userGroupJoiningsRepository.findOneBy({ + userGroupId: userGroup.id, + userId: user.id, + }); + + if (joining) { + throw new ApiError(meta.errors.alreadyAdded); + } + + const existInvitation = await this.userGroupInvitationsRepository.findOneBy({ + userGroupId: userGroup.id, + userId: user.id, + }); + + if (existInvitation) { + throw new ApiError(meta.errors.alreadyInvited); + } + + const invitation = await this.userGroupInvitationsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: user.id, + userGroupId: userGroup.id, + } as UserGroupInvitation).then(x => this.userGroupInvitationsRepository.findOneByOrFail(x.identifiers[0])); + + // 通知を作成 + this.createNotificationService.createNotification(user.id, 'groupInvited', { + notifierId: me.id, + userGroupInvitationId: invitation.id, + }); + }); } - - // Fetch the user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - const joining = await UserGroupJoinings.findOneBy({ - userGroupId: userGroup.id, - userId: user.id, - }); - - if (joining) { - throw new ApiError(meta.errors.alreadyAdded); - } - - const existInvitation = await UserGroupInvitations.findOneBy({ - userGroupId: userGroup.id, - userId: user.id, - }); - - if (existInvitation) { - throw new ApiError(meta.errors.alreadyInvited); - } - - const invitation = await UserGroupInvitations.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - userGroupId: userGroup.id, - } as UserGroupInvitation).then(x => UserGroupInvitations.findOneByOrFail(x.identifiers[0])); - - // 通知を作成 - createNotification(user.id, 'groupInvited', { - notifierId: me.id, - userGroupInvitationId: invitation.id, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts index 16c6e544e5..e7e69f257d 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -1,6 +1,9 @@ import { Not, In } from 'typeorm'; -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['groups', 'account'], @@ -29,17 +32,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const ownedGroups = await UserGroups.findBy({ - userId: me.id, - }); - - const joinings = await UserGroupJoinings.findBy({ - userId: me.id, - ...(ownedGroups.length > 0 ? { - userGroupId: Not(In(ownedGroups.map(x => x.id))), - } : {}), - }); - - return await Promise.all(joinings.map(x => UserGroups.pack(x.userGroupId))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private userGroupEntityService: UserGroupEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const ownedGroups = await this.userGroupsRepository.findBy({ + userId: me.id, + }); + + const joinings = await this.userGroupJoiningsRepository.findBy({ + userId: me.id, + ...(ownedGroups.length > 0 ? { + userGroupId: Not(In(ownedGroups.map(x => x.id))), + } : {}), + }); + + return await Promise.all(joinings.map(x => this.userGroupEntityService.pack(x.userGroupId))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts index 83dc757db1..0a63dbb7f1 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts @@ -1,5 +1,7 @@ -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -35,19 +37,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the group - const userGroup = await UserGroups.findOneBy({ - id: ps.groupId, - }); - - if (userGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, - if (me.id === userGroup.userId) { - throw new ApiError(meta.errors.youAreOwner); - } + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the group + const userGroup = await this.userGroupsRepository.findOneBy({ + id: ps.groupId, + }); + + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } - await UserGroupJoinings.delete({ userGroupId: userGroup.id, userId: me.id }); -}); + if (me.id === userGroup.userId) { + throw new ApiError(meta.errors.youAreOwner); + } + + await this.userGroupJoiningsRepository.delete({ userGroupId: userGroup.id, userId: me.id }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts index d77cf1a52e..c9ae39561f 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -1,5 +1,8 @@ -import { UserGroups } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['groups', 'account'], @@ -28,10 +31,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const userGroups = await UserGroups.findBy({ - userId: me.id, - }); - - return await Promise.all(userGroups.map(x => UserGroups.pack(x))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + + private userGroupEntityService: UserGroupEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const userGroups = await this.userGroupsRepository.findBy({ + userId: me.id, + }); + + return await Promise.all(userGroups.map(x => this.userGroupEntityService.pack(x))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index ba67a1e5c9..e6f60eef0a 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -1,7 +1,9 @@ -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; export const meta = { tags: ['groups', 'users'], @@ -43,27 +45,40 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the group - const userGroup = await UserGroups.findOneBy({ - id: ps.groupId, - userId: me.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, - if (userGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); - } + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, - // Fetch the user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the group + const userGroup = await this.userGroupsRepository.findOneBy({ + id: ps.groupId, + userId: me.id, + }); - if (user.id === userGroup.userId) { - throw new ApiError(meta.errors.isOwner); - } + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } + + // Fetch the user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); - // Pull the user - await UserGroupJoinings.delete({ userGroupId: userGroup.id, userId: user.id }); -}); + if (user.id === userGroup.userId) { + throw new ApiError(meta.errors.isOwner); + } + + // Pull the user + await this.userGroupJoiningsRepository.delete({ userGroupId: userGroup.id, userId: user.id }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts index 21e3d9da26..1cebfcd204 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/show.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts @@ -1,5 +1,8 @@ -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -35,24 +38,37 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the group - const userGroup = await UserGroups.findOneBy({ - id: ps.groupId, - }); - - if (userGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, - const joining = await UserGroupJoinings.findOneBy({ - userId: me.id, - userGroupId: userGroup.id, - }); + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, - if (joining == null && userGroup.userId !== me.id) { - throw new ApiError(meta.errors.noSuchGroup); - } + private userGroupEntityService: UserGroupEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the group + const userGroup = await this.userGroupsRepository.findOneBy({ + id: ps.groupId, + }); + + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } - return await UserGroups.pack(userGroup); -}); + const joining = await this.userGroupJoiningsRepository.findOneBy({ + userId: me.id, + userGroupId: userGroup.id, + }); + + if (joining == null && userGroup.userId !== me.id) { + throw new ApiError(meta.errors.noSuchGroup); + } + + return await this.userGroupEntityService.pack(userGroup); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index 6456e70dd5..a8b2533b73 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -1,7 +1,10 @@ -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; export const meta = { tags: ['groups', 'users'], @@ -49,35 +52,49 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the group - const userGroup = await UserGroups.findOneBy({ - id: ps.groupId, - userId: me.id, - }); - - if (userGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private userGroupEntityService: UserGroupEntityService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the group + const userGroup = await this.userGroupsRepository.findOneBy({ + id: ps.groupId, + userId: me.id, + }); + + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } + + // Fetch the user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + const joining = await this.userGroupJoiningsRepository.findOneBy({ + userGroupId: userGroup.id, + userId: user.id, + }); + + if (joining == null) { + throw new ApiError(meta.errors.noSuchGroupMember); + } + + await this.userGroupsRepository.update(userGroup.id, { + userId: ps.userId, + }); + + return await this.userGroupEntityService.pack(userGroup.id); + }); } - - // Fetch the user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - const joining = await UserGroupJoinings.findOneBy({ - userGroupId: userGroup.id, - userId: user.id, - }); - - if (joining == null) { - throw new ApiError(meta.errors.noSuchGroupMember); - } - - await UserGroups.update(userGroup.id, { - userId: ps.userId, - }); - - return await UserGroups.pack(userGroup.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts index 0a96165fc4..b679625c85 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/update.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts @@ -1,5 +1,8 @@ -import { UserGroups } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -36,20 +39,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the group - const userGroup = await UserGroups.findOneBy({ - id: ps.groupId, - userId: me.id, - }); - - if (userGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + + private userGroupEntityService: UserGroupEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the group + const userGroup = await this.userGroupsRepository.findOneBy({ + id: ps.groupId, + userId: me.id, + }); - await UserGroups.update(userGroup.id, { - name: ps.name, - }); + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } - return await UserGroups.pack(userGroup.id); -}); + await this.userGroupsRepository.update(userGroup.id, { + name: ps.name, + }); + + return await this.userGroupEntityService.pack(userGroup.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index 783e63f5de..aa64ca1229 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -1,7 +1,10 @@ -import { UserLists } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { UserList } from '@/models/entities/user-list.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import type { UserList } from '@/models/entities/UserList.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['lists'], @@ -28,13 +31,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const userList = await UserLists.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - name: ps.name, - } as UserList).then(x => UserLists.findOneByOrFail(x.identifiers[0])); - - return await UserLists.pack(userList); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + private userListEntityService: UserListEntityService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const userList = await this.userListsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + name: ps.name, + } as UserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); + + return await this.userListEntityService.pack(userList); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts index 5a7613c98a..0f4125a39f 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts @@ -1,5 +1,7 @@ -import { UserLists } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -29,15 +31,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const userList = await UserLists.findOneBy({ - id: ps.listId, - userId: user.id, - }); - - if (userList == null) { - throw new ApiError(meta.errors.noSuchList); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const userList = await this.userListsRepository.findOneBy({ + id: ps.listId, + userId: me.id, + }); + + if (userList == null) { + throw new ApiError(meta.errors.noSuchList); + } + + await this.userListsRepository.delete(userList.id); + }); } - - await UserLists.delete(userList.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts index 889052fa30..919de22377 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -1,5 +1,8 @@ -import { UserLists } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['lists', 'account'], @@ -28,10 +31,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const userLists = await UserLists.findBy({ - userId: me.id, - }); - - return await Promise.all(userLists.map(x => UserLists.pack(x))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + private userListEntityService: UserListEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const userLists = await this.userListsRepository.findBy({ + userId: me.id, + }); + + return await Promise.all(userLists.map(x => this.userListEntityService.pack(x))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index d3d1d6555c..89d97be93e 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -1,8 +1,11 @@ -import { publishUserListStream } from '@/services/stream.js'; -import { UserLists, UserListJoinings, Users } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; export const meta = { tags: ['lists', 'users'], @@ -38,25 +41,40 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the list - const userList = await UserLists.findOneBy({ - id: ps.listId, - userId: me.id, - }); - - if (userList == null) { - throw new ApiError(meta.errors.noSuchList); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the list + const userList = await this.userListsRepository.findOneBy({ + id: ps.listId, + userId: me.id, + }); - // Fetch the user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); + if (userList == null) { + throw new ApiError(meta.errors.noSuchList); + } - // Pull the user - await UserListJoinings.delete({ userListId: userList.id, userId: user.id }); + // Fetch the user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); - publishUserListStream(userList.id, 'userRemoved', await Users.pack(user)); -}); + // Pull the user + await this.userListJoiningsRepository.delete({ userListId: userList.id, userId: user.id }); + + this.globalEventService.publishUserListStream(userList.id, 'userRemoved', await this.userEntityService.pack(user)); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 12b7b86342..77ad772b13 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -1,8 +1,10 @@ -import { pushUserToUserList } from '@/services/user-list/push.js'; -import { UserLists, UserListJoinings, Blockings } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { UserListService } from '@/core/UserListService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; export const meta = { tags: ['lists', 'users'], @@ -50,43 +52,60 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the list - const userList = await UserLists.findOneBy({ - id: ps.listId, - userId: me.id, - }); - - if (userList == null) { - throw new ApiError(meta.errors.noSuchList); - } - - // Fetch the user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Check blocking - if (user.id !== me.id) { - const block = await Blockings.findOneBy({ - blockerId: user.id, - blockeeId: me.id, +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + private getterService: GetterService, + private userListService: UserListService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the list + const userList = await this.userListsRepository.findOneBy({ + id: ps.listId, + userId: me.id, + }); + + if (userList == null) { + throw new ApiError(meta.errors.noSuchList); + } + + // Fetch the user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check blocking + if (user.id !== me.id) { + const block = await this.blockingsRepository.findOneBy({ + blockerId: user.id, + blockeeId: me.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } + + const exist = await this.userListJoiningsRepository.findOneBy({ + userListId: userList.id, + userId: user.id, + }); + + if (exist) { + throw new ApiError(meta.errors.alreadyAdded); + } + + // Push the user + await this.userListService.push(user, userList); }); - if (block) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } - - const exist = await UserListJoinings.findOneBy({ - userListId: userList.id, - userId: user.id, - }); - - if (exist) { - throw new ApiError(meta.errors.alreadyAdded); } - - // Push the user - await pushUserToUserList(user, userList); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index fd0612f735..62e730b2f7 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -1,5 +1,8 @@ -import { UserLists } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -35,16 +38,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the list - const userList = await UserLists.findOneBy({ - id: ps.listId, - userId: me.id, - }); - - if (userList == null) { - throw new ApiError(meta.errors.noSuchList); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + private userListEntityService: UserListEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the list + const userList = await this.userListsRepository.findOneBy({ + id: ps.listId, + userId: me.id, + }); + + if (userList == null) { + throw new ApiError(meta.errors.noSuchList); + } + + return await this.userListEntityService.pack(userList); + }); } - - return await UserLists.pack(userList); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts index 65e708b959..c6669d24d1 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -1,5 +1,8 @@ -import { UserLists } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -36,20 +39,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch the list - const userList = await UserLists.findOneBy({ - id: ps.listId, - userId: user.id, - }); - - if (userList == null) { - throw new ApiError(meta.errors.noSuchList); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + private userListEntityService: UserListEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the list + const userList = await this.userListsRepository.findOneBy({ + id: ps.listId, + userId: me.id, + }); - await UserLists.update(userList.id, { - name: ps.name, - }); + if (userList == null) { + throw new ApiError(meta.errors.noSuchList); + } - return await UserLists.pack(userList.id); -}); + await this.userListsRepository.update(userList.id, { + name: ps.name, + }); + + return await this.userListEntityService.pack(userList.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 9fa56fe83a..bb8104584c 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -1,12 +1,12 @@ import { Brackets } from 'typeorm'; -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['users', 'notes'], @@ -53,70 +53,82 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Lookup user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - //#region Construct query - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere('note.userId = :userId', { userId: user.id }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - - generateVisibilityQuery(query, me); - if (me) { - generateMutedUserQuery(query, me, user); - generateBlockedUserQuery(query, me); - } - - if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + // Lookup user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + //#region Construct query + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('note.userId = :userId', { userId: user.id }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + + this.queryService.generateVisibilityQuery(query, me); + if (me) { + this.queryService.generateMutedUserQuery(query, me, user); + this.queryService.generateBlockedUserQuery(query, me); + } - 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); - qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } - })); - if (ps.excludeNsfw) { - query.andWhere('note.cw IS NULL'); - query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)'); - } - } + 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); + qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); + } + })); + + if (ps.excludeNsfw) { + query.andWhere('note.cw IS NULL'); + query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)'); + } + } - if (!ps.includeReplies) { - query.andWhere('note.replyId IS NULL'); - } + if (!ps.includeReplies) { + query.andWhere('note.replyId IS NULL'); + } - if (ps.includeMyRenotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.userId != :userId', { userId: user.id }); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } + if (ps.includeMyRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.userId != :userId', { userId: user.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } - //#endregion + //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit).getMany(); - return await Notes.packMany(timeline, me); -}); + return await this.noteEntityService.packMany(timeline, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index b1d28af845..96c7ef1e70 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -1,6 +1,9 @@ -import { Pages } from '@/models/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { PageEntityService } from '@/core/entities/PageEntityService.js'; +import { PagesRepository } from '@/models'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users', 'pages'], @@ -30,14 +33,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId) - .andWhere('page.userId = :userId', { userId: ps.userId }) - .andWhere('page.visibility = \'public\''); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, - const pages = await query - .take(ps.limit) - .getMany(); + private pageEntityService: PageEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.pagesRepository.createQueryBuilder('page'), ps.sinceId, ps.untilId) + .andWhere('page.userId = :userId', { userId: ps.userId }) + .andWhere('page.visibility = \'public\''); - return await Pages.packMany(pages); -}); + const pages = await query + .take(ps.limit) + .getMany(); + + return await this.pageEntityService.packMany(pages); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index 9668bd21b8..6b4d882b7c 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -1,7 +1,9 @@ -import { NoteReactions, UserProfiles } from '@/models/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserProfilesRepository, NoteReactionsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -44,23 +46,37 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const profile = await UserProfiles.findOneByOrFail({ userId: ps.userId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, - if (me == null || (me.id !== ps.userId && !profile.publicReactions)) { - throw new ApiError(meta.errors.reactionsNotPublic); - } + @Inject(DI.noteReactionsRepository) + private noteReactionsRepository: NoteReactionsRepository, + + private noteReactionEntityService: NoteReactionEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId }); - const query = makePaginationQuery(NoteReactions.createQueryBuilder('reaction'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere('reaction.userId = :userId', { userId: ps.userId }) - .leftJoinAndSelect('reaction.note', 'note'); + if (me == null || (me.id !== ps.userId && !profile.publicReactions)) { + throw new ApiError(meta.errors.reactionsNotPublic); + } - generateVisibilityQuery(query, me); + const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('reaction.userId = :userId', { userId: ps.userId }) + .leftJoinAndSelect('reaction.note', 'note'); - const reactions = await query - .take(ps.limit) - .getMany(); + this.queryService.generateVisibilityQuery(query, me); - return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, me, { withNote: true }))); -}); + const reactions = await query + .take(ps.limit) + .getMany(); + + return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me, { withNote: true }))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index e7654e1714..e50a5706d9 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -1,8 +1,10 @@ import ms from 'ms'; -import { Users, Followings } from '@/models/index.js'; -import define from '../../define.js'; -import { generateMutedUserQueryForUsers } from '../../common/generate-muted-user-query.js'; -import { generateBlockedUserQuery, generateBlockQueryForUsers } from '../../common/generate-block-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users'], @@ -34,29 +36,43 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Users.createQueryBuilder('user') - .where('user.isLocked = FALSE') - .andWhere('user.isExplorable = TRUE') - .andWhere('user.host IS NULL') - .andWhere('user.updatedAt >= :date', { date: new Date(Date.now() - ms('7days')) }) - .andWhere('user.id != :meId', { meId: me.id }) - .orderBy('user.followersCount', 'DESC'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - generateMutedUserQueryForUsers(query, me); - generateBlockQueryForUsers(query, me); - generateBlockedUserQuery(query, me); + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.usersRepository.createQueryBuilder('user') + .where('user.isLocked = FALSE') + .andWhere('user.isExplorable = TRUE') + .andWhere('user.host IS NULL') + .andWhere('user.updatedAt >= :date', { date: new Date(Date.now() - ms('7days')) }) + .andWhere('user.id != :meId', { meId: me.id }) + .orderBy('user.followersCount', 'DESC'); - const followingQuery = Followings.createQueryBuilder('following') - .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: me.id }); + this.queryService.generateMutedUserQueryForUsers(query, me); + this.queryService.generateBlockQueryForUsers(query, me); + this.queryService.generateBlockedUserQuery(query, me); - query - .andWhere(`user.id NOT IN (${ followingQuery.getQuery() })`); + const followingQuery = this.followingsRepository.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); - query.setParameters(followingQuery.getParameters()); + query + .andWhere(`user.id NOT IN (${ followingQuery.getQuery() })`); - const users = await query.take(ps.limit).skip(ps.offset).getMany(); + query.setParameters(followingQuery.getParameters()); - return await Users.packMany(users, me, { detail: true }); -}); + const users = await query.take(ps.limit).skip(ps.offset).getMany(); + + return await this.userEntityService.packMany(users, me, { detail: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index 233a6a90b4..aea75ae799 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -1,5 +1,8 @@ -import { Users } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users'], @@ -112,10 +115,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const ids = Array.isArray(ps.userId) ? ps.userId : [ps.userId]; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - const relations = await Promise.all(ids.map(id => Users.getRelation(me.id, id))); + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const ids = Array.isArray(ps.userId) ? ps.userId : [ps.userId]; - return Array.isArray(ps.userId) ? relations : relations[0]; -}); + const relations = await Promise.all(ids.map(id => this.userEntityService.getRelation(me.id, id))); + + return Array.isArray(ps.userId) ? relations : relations[0]; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index a9987eafa9..5c211a9017 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -1,12 +1,14 @@ import * as sanitizeHtml from 'sanitize-html'; -import { publishAdminStream } from '@/services/stream.js'; -import { AbuseUserReports, Users } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { sendEmail } from '@/services/send-email.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { getUser } from '../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { EmailService } from '@/core/EmailService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import define from '../../define.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['users'], @@ -46,55 +48,72 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Lookup user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - if (user.id === me.id) { - throw new ApiError(meta.errors.cannotReportYourself); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user.isAdmin) { - throw new ApiError(meta.errors.cannotReportAdmin); - } + @Inject(DI.abuseUserReportsRepository) + private abuseUserReportsRepository: AbuseUserReportsRepository, - const report = await AbuseUserReports.insert({ - id: genId(), - createdAt: new Date(), - targetUserId: user.id, - targetUserHost: user.host, - reporterId: me.id, - reporterHost: null, - comment: ps.comment, - }).then(x => AbuseUserReports.findOneByOrFail(x.identifiers[0])); - - // Publish event to moderators - setImmediate(async () => { - const moderators = await Users.find({ - where: [{ - isAdmin: true, - }, { - isModerator: true, - }], - }); + private idService: IdService, + private metaService: MetaService, + private emailService: EmailService, + private getterService: GetterService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Lookup user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + if (user.id === me.id) { + throw new ApiError(meta.errors.cannotReportYourself); + } + + if (user.isAdmin) { + throw new ApiError(meta.errors.cannotReportAdmin); + } - for (const moderator of moderators) { - publishAdminStream(moderator.id, 'newAbuseUserReport', { - id: report.id, - targetUserId: report.targetUserId, - reporterId: report.reporterId, - comment: report.comment, + const report = await this.abuseUserReportsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + targetUserId: user.id, + targetUserHost: user.host, + reporterId: me.id, + reporterHost: null, + comment: ps.comment, + }).then(x => this.abuseUserReportsRepository.findOneByOrFail(x.identifiers[0])); + + // Publish event to moderators + setImmediate(async () => { + const moderators = await this.usersRepository.find({ + where: [{ + isAdmin: true, + }, { + isModerator: true, + }], + }); + + for (const moderator of moderators) { + this.globalEventService.publishAdminStream(moderator.id, 'newAbuseUserReport', { + id: report.id, + targetUserId: report.targetUserId, + reporterId: report.reporterId, + comment: report.comment, + }); + } + + const meta = await this.metaService.fetch(); + if (meta.email) { + this.emailService.sendEmail(meta.email, 'New abuse report', + sanitizeHtml(ps.comment), + sanitizeHtml(ps.comment)); + } }); - } - - const meta = await fetchMeta(); - if (meta.email) { - sendEmail(meta.email, 'New abuse report', - sanitizeHtml(ps.comment), - sanitizeHtml(ps.comment)); - } - }); -}); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index 6e5bc46bb5..1747dc93f6 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -1,8 +1,11 @@ import { Brackets } from 'typeorm'; -import { Followings, Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { USER_ACTIVE_THRESHOLD } from '@/const.js'; -import { User } from '@/models/entities/user.js'; -import define from '../../define.js'; +import type { User } from '@/models/entities/User.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users'], @@ -39,78 +42,91 @@ export const paramDef = { // TODO: avatar,bannerをJOINしたいけどエラーになる // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 - - if (ps.host) { - const q = Users.createQueryBuilder('user') - .where('user.isSuspended = FALSE') - .andWhere('user.host LIKE :host', { host: ps.host.toLowerCase() + '%' }); - - if (ps.username) { - q.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }); - } - - q.andWhere('user.updatedAt IS NOT NULL'); - q.orderBy('user.updatedAt', 'DESC'); - - const users = await q.take(ps.limit).getMany(); - - return await Users.packMany(users, me, { detail: ps.detail }); - } else if (ps.username) { - let users: User[] = []; - - if (me) { - const followingQuery = Followings.createQueryBuilder('following') - .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: me.id }); - - const query = Users.createQueryBuilder('user') - .where(`user.id IN (${ followingQuery.getQuery() })`) - .andWhere('user.id != :meId', { meId: me.id }) - .andWhere('user.isSuspended = FALSE') - .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) - .andWhere(new Brackets(qb => { qb - .where('user.updatedAt IS NULL') - .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); - })); - - query.setParameters(followingQuery.getParameters()); - - users = await query - .orderBy('user.usernameLower', 'ASC') - .take(ps.limit) - .getMany(); - - if (users.length < ps.limit) { - const otherQuery = await Users.createQueryBuilder('user') - .where(`user.id NOT IN (${ followingQuery.getQuery() })`) - .andWhere('user.id != :meId', { meId: me.id }) - .andWhere('user.isSuspended = FALSE') - .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) - .andWhere('user.updatedAt IS NOT NULL'); - - otherQuery.setParameters(followingQuery.getParameters()); - - const otherUsers = await otherQuery - .orderBy('user.updatedAt', 'DESC') - .take(ps.limit - users.length) - .getMany(); - - users = users.concat(otherUsers); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 + + if (ps.host) { + const q = this.usersRepository.createQueryBuilder('user') + .where('user.isSuspended = FALSE') + .andWhere('user.host LIKE :host', { host: ps.host.toLowerCase() + '%' }); + + if (ps.username) { + q.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }); + } + + q.andWhere('user.updatedAt IS NOT NULL'); + q.orderBy('user.updatedAt', 'DESC'); + + const users = await q.take(ps.limit).getMany(); + + return await this.userEntityService.packMany(users, me, { detail: ps.detail }); + } else if (ps.username) { + let users: User[] = []; + + if (me) { + const followingQuery = this.followingsRepository.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); + + const query = this.usersRepository.createQueryBuilder('user') + .where(`user.id IN (${ followingQuery.getQuery() })`) + .andWhere('user.id != :meId', { meId: me.id }) + .andWhere('user.isSuspended = FALSE') + .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) + .andWhere(new Brackets(qb => { qb + .where('user.updatedAt IS NULL') + .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); + })); + + query.setParameters(followingQuery.getParameters()); + + users = await query + .orderBy('user.usernameLower', 'ASC') + .take(ps.limit) + .getMany(); + + if (users.length < ps.limit) { + const otherQuery = await this.usersRepository.createQueryBuilder('user') + .where(`user.id NOT IN (${ followingQuery.getQuery() })`) + .andWhere('user.id != :meId', { meId: me.id }) + .andWhere('user.isSuspended = FALSE') + .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) + .andWhere('user.updatedAt IS NOT NULL'); + + otherQuery.setParameters(followingQuery.getParameters()); + + const otherUsers = await otherQuery + .orderBy('user.updatedAt', 'DESC') + .take(ps.limit - users.length) + .getMany(); + + users = users.concat(otherUsers); + } + } else { + users = await this.usersRepository.createQueryBuilder('user') + .where('user.isSuspended = FALSE') + .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) + .andWhere('user.updatedAt IS NOT NULL') + .orderBy('user.updatedAt', 'DESC') + .take(ps.limit - users.length) + .getMany(); + } + + return await this.userEntityService.packMany(users, me, { detail: !!ps.detail }); } - } else { - users = await Users.createQueryBuilder('user') - .where('user.isSuspended = FALSE') - .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) - .andWhere('user.updatedAt IS NOT NULL') - .orderBy('user.updatedAt', 'DESC') - .take(ps.limit - users.length) - .getMany(); - } - - return await Users.packMany(users, me, { detail: !!ps.detail }); - } - return []; -}); + return []; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 01729de667..9879b1b68b 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -1,7 +1,10 @@ import { Brackets } from 'typeorm'; -import { UserProfiles, Users } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { User } from '@/models/entities/User.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users'], @@ -34,89 +37,102 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 - - const isUsername = ps.query.startsWith('@'); - - let users: User[] = []; - - if (isUsername) { - const usernameQuery = Users.createQueryBuilder('user') - .where('user.usernameLower LIKE :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) - .andWhere(new Brackets(qb => { qb - .where('user.updatedAt IS NULL') - .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); - })) - .andWhere('user.isSuspended = FALSE'); - - if (ps.origin === 'local') { - usernameQuery.andWhere('user.host IS NULL'); - } else if (ps.origin === 'remote') { - usernameQuery.andWhere('user.host IS NOT NULL'); - } - - users = await usernameQuery - .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') - .take(ps.limit) - .skip(ps.offset) - .getMany(); - } else { - const nameQuery = Users.createQueryBuilder('user') - .where(new Brackets(qb => { - qb.where('user.name ILIKE :query', { query: '%' + ps.query + '%' }); - - // Also search username if it qualifies as username - if (Users.validateLocalUsername(ps.query)) { - qb.orWhere('user.usernameLower LIKE :username', { username: '%' + ps.query.toLowerCase() + '%' }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 + + const isUsername = ps.query.startsWith('@'); + + let users: User[] = []; + + if (isUsername) { + const usernameQuery = this.usersRepository.createQueryBuilder('user') + .where('user.usernameLower LIKE :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) + .andWhere(new Brackets(qb => { qb + .where('user.updatedAt IS NULL') + .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); + })) + .andWhere('user.isSuspended = FALSE'); + + if (ps.origin === 'local') { + usernameQuery.andWhere('user.host IS NULL'); + } else if (ps.origin === 'remote') { + usernameQuery.andWhere('user.host IS NOT NULL'); + } + + users = await usernameQuery + .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') + .take(ps.limit) + .skip(ps.offset) + .getMany(); + } else { + const nameQuery = this.usersRepository.createQueryBuilder('user') + .where(new Brackets(qb => { + qb.where('user.name ILIKE :query', { query: '%' + ps.query + '%' }); + + // Also search username if it qualifies as username + if (this.userEntityService.validateLocalUsername(ps.query)) { + qb.orWhere('user.usernameLower LIKE :username', { username: '%' + ps.query.toLowerCase() + '%' }); + } + })) + .andWhere(new Brackets(qb => { qb + .where('user.updatedAt IS NULL') + .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); + })) + .andWhere('user.isSuspended = FALSE'); + + if (ps.origin === 'local') { + nameQuery.andWhere('user.host IS NULL'); + } else if (ps.origin === 'remote') { + nameQuery.andWhere('user.host IS NOT NULL'); + } + + users = await nameQuery + .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') + .take(ps.limit) + .skip(ps.offset) + .getMany(); + + if (users.length < ps.limit) { + const profQuery = this.userProfilesRepository.createQueryBuilder('prof') + .select('prof.userId') + .where('prof.description ILIKE :query', { query: '%' + ps.query + '%' }); + + if (ps.origin === 'local') { + profQuery.andWhere('prof.userHost IS NULL'); + } else if (ps.origin === 'remote') { + profQuery.andWhere('prof.userHost IS NOT NULL'); + } + + const query = this.usersRepository.createQueryBuilder('user') + .where(`user.id IN (${ profQuery.getQuery() })`) + .andWhere(new Brackets(qb => { qb + .where('user.updatedAt IS NULL') + .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); + })) + .andWhere('user.isSuspended = FALSE') + .setParameters(profQuery.getParameters()); + + users = users.concat(await query + .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') + .take(ps.limit) + .skip(ps.offset) + .getMany(), + ); } - })) - .andWhere(new Brackets(qb => { qb - .where('user.updatedAt IS NULL') - .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); - })) - .andWhere('user.isSuspended = FALSE'); - - if (ps.origin === 'local') { - nameQuery.andWhere('user.host IS NULL'); - } else if (ps.origin === 'remote') { - nameQuery.andWhere('user.host IS NOT NULL'); - } - - users = await nameQuery - .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') - .take(ps.limit) - .skip(ps.offset) - .getMany(); - - if (users.length < ps.limit) { - const profQuery = UserProfiles.createQueryBuilder('prof') - .select('prof.userId') - .where('prof.description ILIKE :query', { query: '%' + ps.query + '%' }); - - if (ps.origin === 'local') { - profQuery.andWhere('prof.userHost IS NULL'); - } else if (ps.origin === 'remote') { - profQuery.andWhere('prof.userHost IS NOT NULL'); } - const query = Users.createQueryBuilder('user') - .where(`user.id IN (${ profQuery.getQuery() })`) - .andWhere(new Brackets(qb => { qb - .where('user.updatedAt IS NULL') - .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); - })) - .andWhere('user.isSuspended = FALSE') - .setParameters(profQuery.getParameters()); - - users = users.concat(await query - .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') - .take(ps.limit) - .skip(ps.offset) - .getMany(), - ); - } + return await this.userEntityService.packMany(users, me, { detail: ps.detail }); + }); } - - return await Users.packMany(users, me, { detail: ps.detail }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 846d83b49f..98f5f03063 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -1,10 +1,14 @@ -import { FindOptionsWhere, In, IsNull } from 'typeorm'; -import { resolveUser } from '@/remote/resolve-user.js'; -import { Users } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import define from '../../define.js'; -import { apiLogger } from '../../logger.js'; +import { In, IsNull } from 'typeorm'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; +import type { User } from '@/models/entities/User.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import { ApiLoggerService } from '../../ApiLoggerService.js'; +import type { FindOptionsWhere } from 'typeorm'; export const meta = { tags: ['users'], @@ -78,53 +82,65 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - let user; - - const isAdminOrModerator = me && (me.isAdmin || me.isModerator); - - if (ps.userIds) { - if (ps.userIds.length === 0) { - return []; - } - - const users = await Users.findBy(isAdminOrModerator ? { - id: In(ps.userIds), - } : { - id: In(ps.userIds), - isSuspended: false, - }); - - // リクエストされた通りに並べ替え - const _users: User[] = []; - for (const id of ps.userIds) { - _users.push(users.find(x => x.id === id)!); - } - - return await Promise.all(_users.map(u => Users.pack(u, me, { - detail: true, - }))); - } else { - // Lookup user - 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); - }); - } else { - const q: FindOptionsWhere = ps.userId != null - ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: IsNull() }; - - user = await Users.findOneBy(q); - } - - if (user == null || (!isAdminOrModerator && user.isSuspended)) { - throw new ApiError(meta.errors.noSuchUser); - } - - return await Users.pack(user, me, { - detail: true, +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private userEntityService: UserEntityService, + private resolveUserService: ResolveUserService, + private apiLoggerService: ApiLoggerService, + ) { + super(meta, paramDef, async (ps, me) => { + let user; + + const isAdminOrModerator = me && (me.isAdmin || me.isModerator); + + if (ps.userIds) { + if (ps.userIds.length === 0) { + return []; + } + + const users = await this.usersRepository.findBy(isAdminOrModerator ? { + id: In(ps.userIds), + } : { + id: In(ps.userIds), + isSuspended: false, + }); + + // リクエストされた通りに並べ替え + const _users: User[] = []; + for (const id of ps.userIds) { + _users.push(users.find(x => x.id === id)!); + } + + return await Promise.all(_users.map(u => this.userEntityService.pack(u, me, { + detail: true, + }))); + } else { + // Lookup user + if (typeof ps.host === 'string' && typeof ps.username === 'string') { + user = await this.resolveUserService.resolveUser(ps.username, ps.host).catch(err => { + this.apiLoggerService.logger.warn(`failed to resolve remote user: ${err}`); + throw new ApiError(meta.errors.failedToResolveRemoteUser); + }); + } else { + const q: FindOptionsWhere = ps.userId != null + ? { id: ps.userId } + : { usernameLower: ps.username!.toLowerCase(), host: IsNull() }; + + user = await this.usersRepository.findOneBy(q); + } + + if (user == null || (!isAdminOrModerator && user.isSuspended)) { + throw new ApiError(meta.errors.noSuchUser); + } + + return await this.userEntityService.pack(user, me, { + detail: true, + }); + } }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index 47f322ee9b..71f4ca0cfa 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -1,6 +1,8 @@ -import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index.js'; -import { awaitAll } from '@/prelude/await-all.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { awaitAll } from '@/misc/prelude/await-all.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -116,78 +118,109 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); - if (user == null) { - throw new ApiError(meta.errors.noSuchUser); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.noteReactionsRepository) + private noteReactionsRepository: NoteReactionsRepository, + + @Inject(DI.pageLikesRepository) + private pageLikesRepository: PageLikesRepository, - const result = await awaitAll({ - notesCount: Notes.createQueryBuilder('note') - .where('note.userId = :userId', { userId: user.id }) - .getCount(), - repliesCount: Notes.createQueryBuilder('note') - .where('note.userId = :userId', { userId: user.id }) - .andWhere('note.replyId IS NOT NULL') - .getCount(), - renotesCount: Notes.createQueryBuilder('note') - .where('note.userId = :userId', { userId: user.id }) - .andWhere('note.renoteId IS NOT NULL') - .getCount(), - repliedCount: Notes.createQueryBuilder('note') - .where('note.replyUserId = :userId', { userId: user.id }) - .getCount(), - renotedCount: Notes.createQueryBuilder('note') - .where('note.renoteUserId = :userId', { userId: user.id }) - .getCount(), - pollVotesCount: PollVotes.createQueryBuilder('vote') - .where('vote.userId = :userId', { userId: user.id }) - .getCount(), - pollVotedCount: PollVotes.createQueryBuilder('vote') - .innerJoin('vote.note', 'note') - .where('note.userId = :userId', { userId: user.id }) - .getCount(), - localFollowingCount: Followings.createQueryBuilder('following') - .where('following.followerId = :userId', { userId: user.id }) - .andWhere('following.followeeHost IS NULL') - .getCount(), - remoteFollowingCount: Followings.createQueryBuilder('following') - .where('following.followerId = :userId', { userId: user.id }) - .andWhere('following.followeeHost IS NOT NULL') - .getCount(), - localFollowersCount: Followings.createQueryBuilder('following') - .where('following.followeeId = :userId', { userId: user.id }) - .andWhere('following.followerHost IS NULL') - .getCount(), - remoteFollowersCount: Followings.createQueryBuilder('following') - .where('following.followeeId = :userId', { userId: user.id }) - .andWhere('following.followerHost IS NOT NULL') - .getCount(), - sentReactionsCount: NoteReactions.createQueryBuilder('reaction') - .where('reaction.userId = :userId', { userId: user.id }) - .getCount(), - receivedReactionsCount: NoteReactions.createQueryBuilder('reaction') - .innerJoin('reaction.note', 'note') - .where('note.userId = :userId', { userId: user.id }) - .getCount(), - noteFavoritesCount: NoteFavorites.createQueryBuilder('favorite') - .where('favorite.userId = :userId', { userId: user.id }) - .getCount(), - pageLikesCount: PageLikes.createQueryBuilder('like') - .where('like.userId = :userId', { userId: user.id }) - .getCount(), - pageLikedCount: PageLikes.createQueryBuilder('like') - .innerJoin('like.page', 'page') - .where('page.userId = :userId', { userId: user.id }) - .getCount(), - driveFilesCount: DriveFiles.createQueryBuilder('file') - .where('file.userId = :userId', { userId: user.id }) - .getCount(), - driveUsage: DriveFiles.calcDriveUsageOf(user), - }); - - result.followingCount = result.localFollowingCount + result.remoteFollowingCount; - result.followersCount = result.localFollowersCount + result.remoteFollowersCount; - - return result; -}); + @Inject(DI.noteFavoritesRepository) + private noteFavoritesRepository: NoteFavoritesRepository, + + @Inject(DI.pollVotesRepository) + private pollVotesRepository: PollVotesRepository, + + private driveFileEntityService: DriveFileEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + if (user == null) { + throw new ApiError(meta.errors.noSuchUser); + } + + const result = await awaitAll({ + notesCount: this.notesRepository.createQueryBuilder('note') + .where('note.userId = :userId', { userId: user.id }) + .getCount(), + repliesCount: this.notesRepository.createQueryBuilder('note') + .where('note.userId = :userId', { userId: user.id }) + .andWhere('note.replyId IS NOT NULL') + .getCount(), + renotesCount: this.notesRepository.createQueryBuilder('note') + .where('note.userId = :userId', { userId: user.id }) + .andWhere('note.renoteId IS NOT NULL') + .getCount(), + repliedCount: this.notesRepository.createQueryBuilder('note') + .where('note.replyUserId = :userId', { userId: user.id }) + .getCount(), + renotedCount: this.notesRepository.createQueryBuilder('note') + .where('note.renoteUserId = :userId', { userId: user.id }) + .getCount(), + pollVotesCount: this.pollVotesRepository.createQueryBuilder('vote') + .where('vote.userId = :userId', { userId: user.id }) + .getCount(), + pollVotedCount: this.pollVotesRepository.createQueryBuilder('vote') + .innerJoin('vote.note', 'note') + .where('note.userId = :userId', { userId: user.id }) + .getCount(), + localFollowingCount: this.followingsRepository.createQueryBuilder('following') + .where('following.followerId = :userId', { userId: user.id }) + .andWhere('following.followeeHost IS NULL') + .getCount(), + remoteFollowingCount: this.followingsRepository.createQueryBuilder('following') + .where('following.followerId = :userId', { userId: user.id }) + .andWhere('following.followeeHost IS NOT NULL') + .getCount(), + localFollowersCount: this.followingsRepository.createQueryBuilder('following') + .where('following.followeeId = :userId', { userId: user.id }) + .andWhere('following.followerHost IS NULL') + .getCount(), + remoteFollowersCount: this.followingsRepository.createQueryBuilder('following') + .where('following.followeeId = :userId', { userId: user.id }) + .andWhere('following.followerHost IS NOT NULL') + .getCount(), + sentReactionsCount: this.noteReactionsRepository.createQueryBuilder('reaction') + .where('reaction.userId = :userId', { userId: user.id }) + .getCount(), + receivedReactionsCount: this.noteReactionsRepository.createQueryBuilder('reaction') + .innerJoin('reaction.note', 'note') + .where('note.userId = :userId', { userId: user.id }) + .getCount(), + noteFavoritesCount: this.noteFavoritesRepository.createQueryBuilder('favorite') + .where('favorite.userId = :userId', { userId: user.id }) + .getCount(), + pageLikesCount: this.pageLikesRepository.createQueryBuilder('like') + .where('like.userId = :userId', { userId: user.id }) + .getCount(), + pageLikedCount: this.pageLikesRepository.createQueryBuilder('like') + .innerJoin('like.page', 'page') + .where('page.userId = :userId', { userId: user.id }) + .getCount(), + driveFilesCount: this.driveFilesRepository.createQueryBuilder('file') + .where('file.userId = :userId', { userId: user.id }) + .getCount(), + driveUsage: this.driveFileEntityService.calcDriveUsageOf(user), + }); + + result.followingCount = result.localFollowingCount + result.remoteFollowingCount; + result.followersCount = result.localFollowersCount + result.remoteFollowersCount; + + return result; + }); + } +} diff --git a/packages/backend/src/server/api/error.ts b/packages/backend/src/server/api/error.ts index 3f0861fdb1..347d5650ad 100644 --- a/packages/backend/src/server/api/error.ts +++ b/packages/backend/src/server/api/error.ts @@ -8,8 +8,8 @@ export class ApiError extends Error { public httpStatusCode?: number; public info?: any; - constructor(e?: E | null | undefined, info?: any | null | undefined) { - if (e == null) e = { + constructor(err?: E | null | undefined, info?: any | null | undefined) { + if (err == null) err = { message: 'Internal error occurred. Please contact us if the error persists.', code: 'INTERNAL_ERROR', id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac', @@ -17,12 +17,12 @@ export class ApiError extends Error { httpStatusCode: 500, }; - super(e.message); - this.message = e.message; - this.code = e.code; - this.id = e.id; - this.kind = e.kind || 'client'; - this.httpStatusCode = e.httpStatusCode; + super(err.message); + this.message = err.message; + this.code = err.code; + this.id = err.id; + this.kind = err.kind ?? 'client'; + this.httpStatusCode = err.httpStatusCode; this.info = info; } } diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts deleted file mode 100644 index 83ece51f51..0000000000 --- a/packages/backend/src/server/api/index.ts +++ /dev/null @@ -1,126 +0,0 @@ -/** - * API Server - */ - -import Koa from 'koa'; -import Router from '@koa/router'; -import multer from '@koa/multer'; -import bodyParser from 'koa-bodyparser'; -import cors from '@koa/cors'; - -import { Instances, AccessTokens, Users } from '@/models/index.js'; -import config from '@/config/index.js'; -import endpoints from './endpoints.js'; -import handler from './api-handler.js'; -import signup from './private/signup.js'; -import signin from './private/signin.js'; -import signupPending from './private/signup-pending.js'; -import discord from './service/discord.js'; -import github from './service/github.js'; -import twitter from './service/twitter.js'; - -// Init app -const app = new Koa(); - -app.use(cors({ - origin: '*', -})); - -// No caching -app.use(async (ctx, next) => { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); - await next(); -}); - -app.use(bodyParser({ - // リクエストが multipart/form-data でない限りはJSONだと見なす - detectJSON: ctx => !ctx.is('multipart/form-data'), -})); - -// Init multer instance -const upload = multer({ - storage: multer.diskStorage({}), - limits: { - fileSize: config.maxFileSize || 262144000, - files: 1, - }, -}); - -// Init router -const router = new Router(); - -/** - * Register endpoint handlers - */ -for (const endpoint of endpoints) { - if (endpoint.meta.requireFile) { - router.post(`/${endpoint.name}`, upload.single('file'), handler.bind(null, endpoint)); - } else { - // 後方互換性のため - if (endpoint.name.includes('-')) { - router.post(`/${endpoint.name.replace(/-/g, '_')}`, handler.bind(null, endpoint)); - - if (endpoint.meta.allowGet) { - router.get(`/${endpoint.name.replace(/-/g, '_')}`, handler.bind(null, endpoint)); - } else { - router.get(`/${endpoint.name.replace(/-/g, '_')}`, async ctx => { ctx.status = 405; }); - } - } - - router.post(`/${endpoint.name}`, handler.bind(null, endpoint)); - - if (endpoint.meta.allowGet) { - router.get(`/${endpoint.name}`, handler.bind(null, endpoint)); - } else { - router.get(`/${endpoint.name}`, async ctx => { ctx.status = 405; }); - } - } -} - -router.post('/signup', signup); -router.post('/signin', signin); -router.post('/signup-pending', signupPending); - -router.use(discord.routes()); -router.use(github.routes()); -router.use(twitter.routes()); - -router.get('/v1/instance/peers', async ctx => { - const instances = await Instances.find({ - select: ['host'], - }); - - ctx.body = instances.map(instance => instance.host); -}); - -router.post('/miauth/:session/check', async ctx => { - const token = await AccessTokens.findOneBy({ - session: ctx.params.session, - }); - - if (token && token.session != null && !token.fetched) { - AccessTokens.update(token.id, { - fetched: true, - }); - - ctx.body = { - ok: true, - token: token.token, - user: await Users.pack(token.userId, null, { detail: true }), - }; - } else { - ctx.body = { - ok: false, - }; - } -}); - -// Return 404 for unknown API -router.all('(.*)', async ctx => { - ctx.status = 404; -}); - -// Register router -app.use(router.routes()); - -export default app; diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts new file mode 100644 index 0000000000..604013c454 --- /dev/null +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -0,0 +1,315 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Redis } from 'ioredis'; +import Router from '@koa/router'; +import { OAuth2 } from 'oauth'; +import { v4 as uuid } from 'uuid'; +import { IsNull } from 'typeorm'; +import { Config } from '@/config.js'; +import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; +import type { ILocalUser } from '@/models/entities/User.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { SigninService } from '../SigninService.js'; +import type Koa from 'koa'; + +@Injectable() +export class DiscordServerService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.redis) + private redisClient: Redis, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private userEntityService: UserEntityService, + private httpRequestService: HttpRequestService, + private globalEventService: GlobalEventService, + private metaService: MetaService, + private signinService: SigninService, + ) { + } + + public create() { + const router = new Router(); + + router.get('/disconnect/discord', async ctx => { + if (!this.#compareOrigin(ctx)) { + ctx.throw(400, 'invalid origin'); + return; + } + + const userToken = this.#getUserToken(ctx); + if (!userToken) { + ctx.throw(400, 'signin required'); + return; + } + + const user = await this.usersRepository.findOneByOrFail({ + host: IsNull(), + token: userToken, + }); + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + delete profile.integrations.discord; + + await this.userProfilesRepository.update(user.id, { + integrations: profile.integrations, + }); + + ctx.body = 'Discordの連携を解除しました :v:'; + + // Publish i updated event + this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, { + detail: true, + includeSecrets: true, + })); + }); + + const getOAuth2 = async () => { + const meta = await this.metaService.fetch(true); + + if (meta.enableDiscordIntegration) { + return new OAuth2( + meta.discordClientId!, + meta.discordClientSecret!, + 'https://discord.com/', + 'api/oauth2/authorize', + 'api/oauth2/token'); + } else { + return null; + } + }; + + router.get('/connect/discord', async ctx => { + if (!this.#compareOrigin(ctx)) { + ctx.throw(400, 'invalid origin'); + return; + } + + const userToken = this.#getUserToken(ctx); + if (!userToken) { + ctx.throw(400, 'signin required'); + return; + } + + const params = { + redirect_uri: `${this.config.url}/api/dc/cb`, + scope: ['identify'], + state: uuid(), + response_type: 'code', + }; + + this.redisClient.set(userToken, JSON.stringify(params)); + + const oauth2 = await getOAuth2(); + ctx.redirect(oauth2!.getAuthorizeUrl(params)); + }); + + router.get('/signin/discord', async ctx => { + const sessid = uuid(); + + const params = { + redirect_uri: `${this.config.url}/api/dc/cb`, + scope: ['identify'], + state: uuid(), + response_type: 'code', + }; + + ctx.cookies.set('signin_with_discord_sid', sessid, { + path: '/', + secure: this.config.url.startsWith('https'), + httpOnly: true, + }); + + this.redisClient.set(sessid, JSON.stringify(params)); + + const oauth2 = await getOAuth2(); + ctx.redirect(oauth2!.getAuthorizeUrl(params)); + }); + + router.get('/dc/cb', async ctx => { + const userToken = this.#getUserToken(ctx); + + const oauth2 = await getOAuth2(); + + if (!userToken) { + const sessid = ctx.cookies.get('signin_with_discord_sid'); + + if (!sessid) { + ctx.throw(400, 'invalid session'); + return; + } + + const code = ctx.query.code; + + if (!code || typeof code !== 'string') { + ctx.throw(400, 'invalid session'); + return; + } + + const { redirect_uri, state } = await new Promise((res, rej) => { + this.redisClient.get(sessid, async (_, state) => { + res(JSON.parse(state)); + }); + }); + + if (ctx.query.state !== state) { + ctx.throw(400, 'invalid session'); + return; + } + + 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, + }); + } + })); + + const { id, username, discriminator } = (await this.httpRequestService.getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { + 'Authorization': `Bearer ${accessToken}`, + })) as Record; + + if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') { + ctx.throw(400, 'invalid session'); + return; + } + + const profile = await this.userProfilesRepository.createQueryBuilder() + .where('"integrations"->\'discord\'->>\'id\' = :id', { id: id }) + .andWhere('"userHost" IS NULL') + .getOne(); + + if (profile == null) { + ctx.throw(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`); + return; + } + + await this.userProfilesRepository.update(profile.userId, { + integrations: { + ...profile.integrations, + discord: { + id: id, + accessToken: accessToken, + refreshToken: refreshToken, + expiresDate: expiresDate, + username: username, + discriminator: discriminator, + }, + }, + }); + + this.signinService.signin(ctx, await this.usersRepository.findOneBy({ id: profile.userId }) as ILocalUser, true); + } else { + const code = ctx.query.code; + + if (!code || typeof code !== 'string') { + ctx.throw(400, 'invalid session'); + return; + } + + const { redirect_uri, state } = await new Promise((res, rej) => { + this.redisClient.get(userToken, async (_, state) => { + res(JSON.parse(state)); + }); + }); + + if (ctx.query.state !== state) { + ctx.throw(400, 'invalid session'); + return; + } + + 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, + }); + } + })); + + const { id, username, discriminator } = (await this.httpRequestService.getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { + 'Authorization': `Bearer ${accessToken}`, + })) as Record; + if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') { + ctx.throw(400, 'invalid session'); + return; + } + + const user = await this.usersRepository.findOneByOrFail({ + host: IsNull(), + token: userToken, + }); + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + await this.userProfilesRepository.update(user.id, { + integrations: { + ...profile.integrations, + discord: { + accessToken: accessToken, + refreshToken: refreshToken, + expiresDate: expiresDate, + id: id, + username: username, + discriminator: discriminator, + }, + }, + }); + + ctx.body = `Discord: @${username}#${discriminator} を、Misskey: @${user.username} に接続しました!`; + + // Publish i updated event + this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, { + detail: true, + includeSecrets: true, + })); + } + }); + + return router; + } + + #getUserToken(ctx: Koa.BaseContext): string | null { + return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; + } + + #compareOrigin(ctx: Koa.BaseContext): boolean { + function normalizeUrl(url?: string): string { + return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; + } + + const referer = ctx.headers['referer']; + + return (normalizeUrl(referer) === normalizeUrl(this.config.url)); + } +} diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts new file mode 100644 index 0000000000..6864f21523 --- /dev/null +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -0,0 +1,287 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Redis } from 'ioredis'; +import Router from '@koa/router'; +import { OAuth2 } from 'oauth'; +import { v4 as uuid } from 'uuid'; +import { IsNull } from 'typeorm'; +import { Config } from '@/config.js'; +import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; +import type { ILocalUser } from '@/models/entities/User.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { SigninService } from '../SigninService.js'; +import type Koa from 'koa'; + +@Injectable() +export class GithubServerService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.redis) + private redisClient: Redis, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private userEntityService: UserEntityService, + private httpRequestService: HttpRequestService, + private globalEventService: GlobalEventService, + private metaService: MetaService, + private signinService: SigninService, + ) { + } + + public create() { + const router = new Router(); + + router.get('/disconnect/github', async ctx => { + if (!this.#compareOrigin(ctx)) { + ctx.throw(400, 'invalid origin'); + return; + } + + const userToken = this.#getUserToken(ctx); + if (!userToken) { + ctx.throw(400, 'signin required'); + return; + } + + const user = await this.usersRepository.findOneByOrFail({ + host: IsNull(), + token: userToken, + }); + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + delete profile.integrations.github; + + await this.userProfilesRepository.update(user.id, { + integrations: profile.integrations, + }); + + ctx.body = 'GitHubの連携を解除しました :v:'; + + // Publish i updated event + this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, { + detail: true, + includeSecrets: true, + })); + }); + + const getOath2 = async () => { + const meta = await this.metaService.fetch(true); + + if (meta.enableGithubIntegration && meta.githubClientId && meta.githubClientSecret) { + return new OAuth2( + meta.githubClientId, + meta.githubClientSecret, + 'https://github.com/', + 'login/oauth/authorize', + 'login/oauth/access_token'); + } else { + return null; + } + }; + + router.get('/connect/github', async ctx => { + if (!this.#compareOrigin(ctx)) { + ctx.throw(400, 'invalid origin'); + return; + } + + const userToken = this.#getUserToken(ctx); + if (!userToken) { + ctx.throw(400, 'signin required'); + return; + } + + const params = { + redirect_uri: `${this.config.url}/api/gh/cb`, + scope: ['read:user'], + state: uuid(), + }; + + this.redisClient.set(userToken, JSON.stringify(params)); + + const oauth2 = await getOath2(); + ctx.redirect(oauth2!.getAuthorizeUrl(params)); + }); + + router.get('/signin/github', async ctx => { + const sessid = uuid(); + + const params = { + redirect_uri: `${this.config.url}/api/gh/cb`, + scope: ['read:user'], + state: uuid(), + }; + + ctx.cookies.set('signin_with_github_sid', sessid, { + path: '/', + secure: this.config.url.startsWith('https'), + httpOnly: true, + }); + + this.redisClient.set(sessid, JSON.stringify(params)); + + const oauth2 = await getOath2(); + ctx.redirect(oauth2!.getAuthorizeUrl(params)); + }); + + router.get('/gh/cb', async ctx => { + const userToken = this.#getUserToken(ctx); + + const oauth2 = await getOath2(); + + if (!userToken) { + const sessid = ctx.cookies.get('signin_with_github_sid'); + + if (!sessid) { + ctx.throw(400, 'invalid session'); + return; + } + + const code = ctx.query.code; + + if (!code || typeof code !== 'string') { + ctx.throw(400, 'invalid session'); + return; + } + + const { redirect_uri, state } = await new Promise((res, rej) => { + this.redisClient.get(sessid, async (_, state) => { + res(JSON.parse(state)); + }); + }); + + if (ctx.query.state !== state) { + ctx.throw(400, 'invalid session'); + return; + } + + 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 }); + } + })); + + const { login, id } = (await this.httpRequestService.getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { + 'Authorization': `bearer ${accessToken}`, + })) as Record; + if (typeof login !== 'string' || typeof id !== 'string') { + ctx.throw(400, 'invalid session'); + return; + } + + const link = await this.userProfilesRepository.createQueryBuilder() + .where('"integrations"->\'github\'->>\'id\' = :id', { id: id }) + .andWhere('"userHost" IS NULL') + .getOne(); + + if (link == null) { + ctx.throw(404, `@${login}と連携しているMisskeyアカウントはありませんでした...`); + return; + } + + this.signinService.signin(ctx, await this.usersRepository.findOneBy({ id: link.userId }) as ILocalUser, true); + } else { + const code = ctx.query.code; + + if (!code || typeof code !== 'string') { + ctx.throw(400, 'invalid session'); + return; + } + + const { redirect_uri, state } = await new Promise((res, rej) => { + this.redisClient.get(userToken, async (_, state) => { + res(JSON.parse(state)); + }); + }); + + if (ctx.query.state !== state) { + ctx.throw(400, 'invalid session'); + return; + } + + 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 }); + } + })); + + const { login, id } = (await this.httpRequestService.getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { + 'Authorization': `bearer ${accessToken}`, + })) as Record; + + if (typeof login !== 'string' || typeof id !== 'string') { + ctx.throw(400, 'invalid session'); + return; + } + + const user = await this.usersRepository.findOneByOrFail({ + host: IsNull(), + token: userToken, + }); + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + await this.userProfilesRepository.update(user.id, { + integrations: { + ...profile.integrations, + github: { + accessToken: accessToken, + id: id, + login: login, + }, + }, + }); + + ctx.body = `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`; + + // Publish i updated event + this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, { + detail: true, + includeSecrets: true, + })); + } + }); + + return router; + } + + #getUserToken(ctx: Koa.BaseContext): string | null { + return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; + } + + #compareOrigin(ctx: Koa.BaseContext): boolean { + function normalizeUrl(url?: string): string { + return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; + } + + const referer = ctx.headers['referer']; + + return (normalizeUrl(referer) === normalizeUrl(this.config.url)); + } +} diff --git a/packages/backend/src/server/api/integration/TwitterServerService.ts b/packages/backend/src/server/api/integration/TwitterServerService.ts new file mode 100644 index 0000000000..14b5f40ea5 --- /dev/null +++ b/packages/backend/src/server/api/integration/TwitterServerService.ts @@ -0,0 +1,230 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Redis } from 'ioredis'; +import Router from '@koa/router'; +import { v4 as uuid } from 'uuid'; +import { IsNull } from 'typeorm'; +import autwh from 'autwh'; +import { Config } from '@/config.js'; +import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; +import type { ILocalUser } from '@/models/entities/User.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { SigninService } from '../SigninService.js'; +import type Koa from 'koa'; + +@Injectable() +export class TwitterServerService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.redis) + private redisClient: Redis, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private userEntityService: UserEntityService, + private httpRequestService: HttpRequestService, + private globalEventService: GlobalEventService, + private metaService: MetaService, + private signinService: SigninService, + ) { + } + + public create() { + const router = new Router(); + + router.get('/disconnect/twitter', async ctx => { + if (!this.#compareOrigin(ctx)) { + ctx.throw(400, 'invalid origin'); + return; + } + + const userToken = this.#getUserToken(ctx); + if (userToken == null) { + ctx.throw(400, 'signin required'); + return; + } + + const user = await this.usersRepository.findOneByOrFail({ + host: IsNull(), + token: userToken, + }); + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + delete profile.integrations.twitter; + + await this.userProfilesRepository.update(user.id, { + integrations: profile.integrations, + }); + + ctx.body = 'Twitterの連携を解除しました :v:'; + + // Publish i updated event + this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, { + detail: true, + includeSecrets: true, + })); + }); + + const getTwAuth = async () => { + const meta = await this.metaService.fetch(true); + + if (meta.enableTwitterIntegration && meta.twitterConsumerKey && meta.twitterConsumerSecret) { + return autwh({ + consumerKey: meta.twitterConsumerKey, + consumerSecret: meta.twitterConsumerSecret, + callbackUrl: `${this.config.url}/api/tw/cb`, + }); + } else { + return null; + } + }; + + router.get('/connect/twitter', async ctx => { + if (!this.#compareOrigin(ctx)) { + ctx.throw(400, 'invalid origin'); + return; + } + + const userToken = this.#getUserToken(ctx); + if (userToken == null) { + ctx.throw(400, 'signin required'); + return; + } + + const twAuth = await getTwAuth(); + const twCtx = await twAuth!.begin(); + this.redisClient.set(userToken, JSON.stringify(twCtx)); + ctx.redirect(twCtx.url); + }); + + router.get('/signin/twitter', async ctx => { + const twAuth = await getTwAuth(); + const twCtx = await twAuth!.begin(); + + const sessid = uuid(); + + this.redisClient.set(sessid, JSON.stringify(twCtx)); + + ctx.cookies.set('signin_with_twitter_sid', sessid, { + path: '/', + secure: this.config.url.startsWith('https'), + httpOnly: true, + }); + + ctx.redirect(twCtx.url); + }); + + router.get('/tw/cb', async ctx => { + const userToken = this.#getUserToken(ctx); + + const twAuth = await getTwAuth(); + + if (userToken == null) { + const sessid = ctx.cookies.get('signin_with_twitter_sid'); + + if (sessid == null) { + ctx.throw(400, 'invalid session'); + return; + } + + const get = new Promise((res, rej) => { + this.redisClient.get(sessid, async (_, twCtx) => { + res(twCtx); + }); + }); + + const twCtx = await get; + + const verifier = ctx.query.oauth_verifier; + if (!verifier || typeof verifier !== 'string') { + ctx.throw(400, 'invalid session'); + return; + } + + const result = await twAuth!.done(JSON.parse(twCtx), verifier); + + const link = await this.userProfilesRepository.createQueryBuilder() + .where('"integrations"->\'twitter\'->>\'userId\' = :id', { id: result.userId }) + .andWhere('"userHost" IS NULL') + .getOne(); + + if (link == null) { + ctx.throw(404, `@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`); + return; + } + + this.signinService.signin(ctx, await this.usersRepository.findOneBy({ id: link.userId }) as ILocalUser, true); + } else { + const verifier = ctx.query.oauth_verifier; + + if (!verifier || typeof verifier !== 'string') { + ctx.throw(400, 'invalid session'); + return; + } + + const get = new Promise((res, rej) => { + this.redisClient.get(userToken, async (_, twCtx) => { + res(twCtx); + }); + }); + + const twCtx = await get; + + const result = await twAuth!.done(JSON.parse(twCtx), verifier); + + const user = await this.usersRepository.findOneByOrFail({ + host: IsNull(), + token: userToken, + }); + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + await this.userProfilesRepository.update(user.id, { + integrations: { + ...profile.integrations, + twitter: { + accessToken: result.accessToken, + accessTokenSecret: result.accessTokenSecret, + userId: result.userId, + screenName: result.screenName, + }, + }, + }); + + ctx.body = `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`; + + // Publish i updated event + this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, { + detail: true, + includeSecrets: true, + })); + } + }); + + return router; + } + + #getUserToken(ctx: Koa.BaseContext): string | null { + return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; + } + + #compareOrigin(ctx: Koa.BaseContext): boolean { + function normalizeUrl(url?: string): string { + return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; + } + + const referer = ctx.headers['referer']; + + return (normalizeUrl(referer) === normalizeUrl(this.config.url)); + } +} diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts deleted file mode 100644 index 9a7751716e..0000000000 --- a/packages/backend/src/server/api/limiter.ts +++ /dev/null @@ -1,77 +0,0 @@ -import Limiter from 'ratelimiter'; -import { CacheableLocalUser, User } from '@/models/entities/user.js'; -import Logger from '@/services/logger.js'; -import { redisClient } from '../../db/redis.js'; -import { IEndpointMeta } from './endpoints.js'; - -const logger = new Logger('limiter'); - -export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable }, actor: string) => new Promise((ok, reject) => { - if (process.env.NODE_ENV === 'test') ok(); - - const hasShortTermLimit = typeof limitation.minInterval === 'number'; - - const hasLongTermLimit = - typeof limitation.duration === 'number' && - typeof limitation.max === 'number'; - - if (hasShortTermLimit) { - min(); - } else if (hasLongTermLimit) { - max(); - } else { - ok(); - } - - // Short-term limit - function min(): void { - const minIntervalLimiter = new Limiter({ - id: `${actor}:${limitation.key}:min`, - duration: limitation.minInterval, - max: 1, - db: redisClient, - }); - - minIntervalLimiter.get((err, info) => { - if (err) { - return reject('ERR'); - } - - logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); - - if (info.remaining === 0) { - reject('BRIEF_REQUEST_INTERVAL'); - } else { - if (hasLongTermLimit) { - max(); - } else { - ok(); - } - } - }); - } - - // Long term limit - function max(): void { - const limiter = new Limiter({ - id: `${actor}:${limitation.key}`, - duration: limitation.duration, - max: limitation.max, - db: redisClient, - }); - - limiter.get((err, info) => { - if (err) { - return reject('ERR'); - } - - logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); - - if (info.remaining === 0) { - reject('RATE_LIMIT_EXCEEDED'); - } else { - ok(); - } - }); - } -}); diff --git a/packages/backend/src/server/api/logger.ts b/packages/backend/src/server/api/logger.ts deleted file mode 100644 index ec22d6c3e2..0000000000 --- a/packages/backend/src/server/api/logger.ts +++ /dev/null @@ -1,3 +0,0 @@ -import Logger from '@/services/logger.js'; - -export const apiLogger = new Logger('api'); diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts deleted file mode 100644 index 68fa814041..0000000000 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ /dev/null @@ -1,190 +0,0 @@ -import endpoints from '../endpoints.js'; -import config from '@/config/index.js'; -import { errors as basicErrors } from './errors.js'; -import { schemas, convertSchemaToOpenApiSchema } from './schemas.js'; - -export function genOpenapiSpec() { - const spec = { - openapi: '3.0.0', - - info: { - version: 'v1', - title: 'Misskey API', - 'x-logo': { url: '/static-assets/api-doc.png' }, - }, - - externalDocs: { - description: 'Repository', - url: 'https://github.com/misskey-dev/misskey', - }, - - servers: [{ - url: config.apiUrl, - }], - - paths: {} as any, - - components: { - schemas: schemas, - - securitySchemes: { - ApiKeyAuth: { - type: 'apiKey', - in: 'body', - name: 'i', - }, - }, - }, - }; - - for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) { - const errors = {} as any; - - if (endpoint.meta.errors) { - for (const e of Object.values(endpoint.meta.errors)) { - errors[e.code] = { - value: { - error: e, - }, - }; - } - } - - const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {}; - - let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n'; - desc += `**Credential required**: *${endpoint.meta.requireCredential ? 'Yes' : 'No'}*`; - if (endpoint.meta.kind) { - const kind = endpoint.meta.kind; - desc += ` / **Permission**: *${kind}*`; - } - - const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json'; - const schema = endpoint.params; - - if (endpoint.meta.requireFile) { - schema.properties.file = { - type: 'string', - format: 'binary', - description: 'The file contents.', - }; - schema.required.push('file'); - } - - const info = { - operationId: endpoint.name, - summary: endpoint.name, - description: desc, - externalDocs: { - description: 'Source code', - url: `https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`, - }, - ...(endpoint.meta.tags ? { - tags: [endpoint.meta.tags[0]], - } : {}), - ...(endpoint.meta.requireCredential ? { - security: [{ - ApiKeyAuth: [], - }], - } : {}), - requestBody: { - required: true, - content: { - [requestType]: { - schema, - }, - }, - }, - responses: { - ...(endpoint.meta.res ? { - '200': { - description: 'OK (with results)', - content: { - 'application/json': { - schema: resSchema, - }, - }, - }, - } : { - '204': { - description: 'OK (without any results)', - }, - }), - '400': { - description: 'Client error', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/Error', - }, - examples: { ...errors, ...basicErrors['400'] }, - }, - }, - }, - '401': { - description: 'Authentication error', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/Error', - }, - examples: basicErrors['401'], - }, - }, - }, - '403': { - description: 'Forbidden error', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/Error', - }, - examples: basicErrors['403'], - }, - }, - }, - '418': { - description: 'I\'m Ai', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/Error', - }, - examples: basicErrors['418'], - }, - }, - }, - ...(endpoint.meta.limit ? { - '429': { - description: 'To many requests', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/Error', - }, - examples: basicErrors['429'], - }, - }, - }, - } : {}), - '500': { - description: 'Internal server error', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/Error', - }, - examples: basicErrors['500'], - }, - }, - }, - }, - }; - - spec.paths['/' + endpoint.name] = { - post: info, - }; - } - - return spec; -} diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts deleted file mode 100644 index 79b31764fd..0000000000 --- a/packages/backend/src/server/api/private/signin.ts +++ /dev/null @@ -1,250 +0,0 @@ -import Koa from 'koa'; -import bcrypt from 'bcryptjs'; -import * as speakeasy from 'speakeasy'; -import signin from '../common/signin.js'; -import config from '@/config/index.js'; -import { Users, Signins, UserProfiles, UserSecurityKeys, AttestationChallenges } from '@/models/index.js'; -import { ILocalUser } from '@/models/entities/user.js'; -import { genId } from '@/misc/gen-id.js'; -import { verifyLogin, hash } from '../2fa.js'; -import { randomBytes } from 'node:crypto'; -import { IsNull } from 'typeorm'; -import { limiter } from '../limiter.js'; -import { getIpHash } from '@/misc/get-ip-hash.js'; - -export default async (ctx: Koa.Context) => { - ctx.set('Access-Control-Allow-Origin', config.url); - ctx.set('Access-Control-Allow-Credentials', 'true'); - - const body = ctx.request.body as any; - const username = body['username']; - const password = body['password']; - const token = body['token']; - - function error(status: number, error: { id: string }) { - ctx.status = status; - ctx.body = { error }; - } - - try { - // not more than 1 attempt per second and not more than 10 attempts per hour - await limiter({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(ctx.ip)); - } catch (err) { - ctx.status = 429; - ctx.body = { - error: { - message: 'Too many failed attempts to sign in. Try again later.', - code: 'TOO_MANY_AUTHENTICATION_FAILURES', - id: '22d05606-fbcf-421a-a2db-b32610dcfd1b', - }, - }; - return; - } - - if (typeof username !== 'string') { - ctx.status = 400; - return; - } - - if (typeof password !== 'string') { - ctx.status = 400; - return; - } - - if (token != null && typeof token !== 'string') { - ctx.status = 400; - return; - } - - // Fetch user - const user = await Users.findOneBy({ - usernameLower: username.toLowerCase(), - host: IsNull(), - }) as ILocalUser; - - if (user == null) { - error(404, { - id: '6cc579cc-885d-43d8-95c2-b8c7fc963280', - }); - return; - } - - if (user.isSuspended) { - error(403, { - id: 'e03a5f46-d309-4865-9b69-56282d94e1eb', - }); - return; - } - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - // Compare password - const same = await bcrypt.compare(password, profile.password!); - - async function fail(status?: number, failure?: { id: string }) { - // Append signin history - await Signins.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - ip: ctx.ip, - headers: ctx.headers, - success: false, - }); - - error(status || 500, failure || { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' }); - } - - if (!profile.twoFactorEnabled) { - if (same) { - signin(ctx, user); - return; - } else { - await fail(403, { - id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', - }); - return; - } - } - - if (token) { - if (!same) { - await fail(403, { - id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', - }); - return; - } - - const verified = (speakeasy as any).totp.verify({ - secret: profile.twoFactorSecret, - encoding: 'base32', - token: token, - window: 2, - }); - - if (verified) { - signin(ctx, user); - return; - } else { - await fail(403, { - id: 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f', - }); - return; - } - } else if (body.credentialId) { - if (!same && !profile.usePasswordLessLogin) { - await fail(403, { - id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', - }); - return; - } - - const clientDataJSON = Buffer.from(body.clientDataJSON, 'hex'); - const clientData = JSON.parse(clientDataJSON.toString('utf-8')); - const challenge = await AttestationChallenges.findOneBy({ - userId: user.id, - id: body.challengeId, - registrationChallenge: false, - challenge: hash(clientData.challenge).toString('hex'), - }); - - if (!challenge) { - await fail(403, { - id: '2715a88a-2125-4013-932f-aa6fe72792da', - }); - return; - } - - await AttestationChallenges.delete({ - userId: user.id, - id: body.challengeId, - }); - - if (new Date().getTime() - challenge.createdAt.getTime() >= 5 * 60 * 1000) { - await fail(403, { - id: '2715a88a-2125-4013-932f-aa6fe72792da', - }); - return; - } - - const securityKey = await UserSecurityKeys.findOneBy({ - id: Buffer.from( - body.credentialId - .replace(/-/g, '+') - .replace(/_/g, '/'), - 'base64' - ).toString('hex'), - }); - - if (!securityKey) { - await fail(403, { - id: '66269679-aeaf-4474-862b-eb761197e046', - }); - return; - } - - const isValid = verifyLogin({ - publicKey: Buffer.from(securityKey.publicKey, 'hex'), - authenticatorData: Buffer.from(body.authenticatorData, 'hex'), - clientDataJSON, - clientData, - signature: Buffer.from(body.signature, 'hex'), - challenge: challenge.challenge, - }); - - if (isValid) { - signin(ctx, user); - return; - } else { - await fail(403, { - id: '93b86c4b-72f9-40eb-9815-798928603d1e', - }); - return; - } - } else { - if (!same && !profile.usePasswordLessLogin) { - await fail(403, { - id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', - }); - return; - } - - const keys = await UserSecurityKeys.findBy({ - userId: user.id, - }); - - if (keys.length === 0) { - await fail(403, { - id: 'f27fd449-9af4-4841-9249-1f989b9fa4a4', - }); - return; - } - - // 32 byte challenge - const challenge = randomBytes(32).toString('base64') - .replace(/=/g, '') - .replace(/\+/g, '-') - .replace(/\//g, '_'); - - const challengeId = genId(); - - await AttestationChallenges.insert({ - userId: user.id, - id: challengeId, - challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'), - createdAt: new Date(), - registrationChallenge: false, - }); - - ctx.body = { - challenge, - challengeId, - securityKeys: keys.map(key => ({ - id: key.id, - })), - }; - ctx.status = 200; - return; - } - // never get here -}; diff --git a/packages/backend/src/server/api/private/signup-pending.ts b/packages/backend/src/server/api/private/signup-pending.ts deleted file mode 100644 index e5e39ba00d..0000000000 --- a/packages/backend/src/server/api/private/signup-pending.ts +++ /dev/null @@ -1,35 +0,0 @@ -import Koa from 'koa'; -import { Users, UserPendings, UserProfiles } from '@/models/index.js'; -import { signup } from '../common/signup.js'; -import signin from '../common/signin.js'; - -export default async (ctx: Koa.Context) => { - const body = ctx.request.body; - - const code = body['code']; - - try { - const pendingUser = await UserPendings.findOneByOrFail({ code }); - - const { account, secret } = await signup({ - username: pendingUser.username, - passwordHash: pendingUser.password, - }); - - UserPendings.delete({ - id: pendingUser.id, - }); - - const profile = await UserProfiles.findOneByOrFail({ userId: account.id }); - - await UserProfiles.update({ userId: profile.userId }, { - email: pendingUser.email, - emailVerified: true, - emailVerifyCode: null, - }); - - signin(ctx, account); - } catch (e) { - ctx.throw(400, e); - } -}; diff --git a/packages/backend/src/server/api/private/signup.ts b/packages/backend/src/server/api/private/signup.ts deleted file mode 100644 index 26f172637c..0000000000 --- a/packages/backend/src/server/api/private/signup.ts +++ /dev/null @@ -1,112 +0,0 @@ -import Koa from 'koa'; -import rndstr from 'rndstr'; -import bcrypt from 'bcryptjs'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { verifyHcaptcha, verifyRecaptcha } from '@/misc/captcha.js'; -import { Users, RegistrationTickets, UserPendings } from '@/models/index.js'; -import { signup } from '../common/signup.js'; -import config from '@/config/index.js'; -import { sendEmail } from '@/services/send-email.js'; -import { genId } from '@/misc/gen-id.js'; -import { validateEmailForAccount } from '@/services/validate-email-for-account.js'; - -export default async (ctx: Koa.Context) => { - const body = ctx.request.body; - - const instance = await fetchMeta(true); - - // Verify *Captcha - // ただしテスト時はこの機構は障害となるため無効にする - if (process.env.NODE_ENV !== 'test') { - if (instance.enableHcaptcha && instance.hcaptchaSecretKey) { - await verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(e => { - ctx.throw(400, e); - }); - } - - if (instance.enableRecaptcha && instance.recaptchaSecretKey) { - await verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(e => { - ctx.throw(400, e); - }); - } - } - - const username = body['username']; - const password = body['password']; - const host: string | null = process.env.NODE_ENV === 'test' ? (body['host'] || null) : null; - const invitationCode = body['invitationCode']; - const emailAddress = body['emailAddress']; - - if (instance.emailRequiredForSignup) { - if (emailAddress == null || typeof emailAddress !== 'string') { - ctx.status = 400; - return; - } - - const available = await validateEmailForAccount(emailAddress); - if (!available) { - ctx.status = 400; - return; - } - } - - if (instance.disableRegistration) { - if (invitationCode == null || typeof invitationCode !== 'string') { - ctx.status = 400; - return; - } - - const ticket = await RegistrationTickets.findOneBy({ - code: invitationCode, - }); - - if (ticket == null) { - ctx.status = 400; - return; - } - - RegistrationTickets.delete(ticket.id); - } - - if (instance.emailRequiredForSignup) { - const code = rndstr('a-z0-9', 16); - - // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(password, salt); - - await UserPendings.insert({ - id: genId(), - createdAt: new Date(), - code, - email: emailAddress, - username: username, - password: hash, - }); - - const link = `${config.url}/signup-complete/${code}`; - - sendEmail(emailAddress, 'Signup', - `To complete signup, please click this link:
${link}`, - `To complete signup, please click this link: ${link}`); - - ctx.status = 204; - } else { - try { - const { account, secret } = await signup({ - username, password, host, - }); - - const res = await Users.pack(account, account, { - detail: true, - includeSecrets: true, - }); - - (res as any).token = secret; - - ctx.body = res; - } catch (e) { - ctx.throw(400, e); - } - } -}; diff --git a/packages/backend/src/server/api/service/discord.ts b/packages/backend/src/server/api/service/discord.ts deleted file mode 100644 index 97cbcbecdb..0000000000 --- a/packages/backend/src/server/api/service/discord.ts +++ /dev/null @@ -1,287 +0,0 @@ -import Koa from 'koa'; -import Router from '@koa/router'; -import { OAuth2 } from 'oauth'; -import { v4 as uuid } from 'uuid'; -import { IsNull } from 'typeorm'; -import { getJson } from '@/misc/fetch.js'; -import config from '@/config/index.js'; -import { publishMainStream } from '@/services/stream.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Users, UserProfiles } from '@/models/index.js'; -import { ILocalUser } from '@/models/entities/user.js'; -import { redisClient } from '../../../db/redis.js'; -import signin from '../common/signin.js'; - -function getUserToken(ctx: Koa.BaseContext): string | null { - return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; -} - -function compareOrigin(ctx: Koa.BaseContext): boolean { - function normalizeUrl(url?: string): string { - return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; - } - - const referer = ctx.headers['referer']; - - return (normalizeUrl(referer) === normalizeUrl(config.url)); -} - -// Init router -const router = new Router(); - -router.get('/disconnect/discord', async ctx => { - if (!compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); - return; - } - - const userToken = getUserToken(ctx); - if (!userToken) { - ctx.throw(400, 'signin required'); - return; - } - - const user = await Users.findOneByOrFail({ - host: IsNull(), - token: userToken, - }); - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - delete profile.integrations.discord; - - await UserProfiles.update(user.id, { - integrations: profile.integrations, - }); - - ctx.body = 'Discordの連携を解除しました :v:'; - - // Publish i updated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { - detail: true, - includeSecrets: true, - })); -}); - -async function getOAuth2() { - const meta = await fetchMeta(true); - - if (meta.enableDiscordIntegration) { - return new OAuth2( - meta.discordClientId!, - meta.discordClientSecret!, - 'https://discord.com/', - 'api/oauth2/authorize', - 'api/oauth2/token'); - } else { - return null; - } -} - -router.get('/connect/discord', async ctx => { - if (!compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); - return; - } - - const userToken = getUserToken(ctx); - if (!userToken) { - ctx.throw(400, 'signin required'); - return; - } - - const params = { - redirect_uri: `${config.url}/api/dc/cb`, - scope: ['identify'], - state: uuid(), - response_type: 'code', - }; - - redisClient.set(userToken, JSON.stringify(params)); - - const oauth2 = await getOAuth2(); - ctx.redirect(oauth2!.getAuthorizeUrl(params)); -}); - -router.get('/signin/discord', async ctx => { - const sessid = uuid(); - - const params = { - redirect_uri: `${config.url}/api/dc/cb`, - scope: ['identify'], - state: uuid(), - response_type: 'code', - }; - - ctx.cookies.set('signin_with_discord_sid', sessid, { - path: '/', - secure: config.url.startsWith('https'), - httpOnly: true, - }); - - redisClient.set(sessid, JSON.stringify(params)); - - const oauth2 = await getOAuth2(); - ctx.redirect(oauth2!.getAuthorizeUrl(params)); -}); - -router.get('/dc/cb', async ctx => { - const userToken = getUserToken(ctx); - - const oauth2 = await getOAuth2(); - - if (!userToken) { - const sessid = ctx.cookies.get('signin_with_discord_sid'); - - if (!sessid) { - ctx.throw(400, 'invalid session'); - return; - } - - const code = ctx.query.code; - - if (!code || typeof code !== 'string') { - ctx.throw(400, 'invalid session'); - return; - } - - const { redirect_uri, state } = await new Promise((res, rej) => { - redisClient.get(sessid, async (_, state) => { - res(JSON.parse(state)); - }); - }); - - if (ctx.query.state !== state) { - ctx.throw(400, 'invalid session'); - return; - } - - 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, - }); - } - })); - - const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { - 'Authorization': `Bearer ${accessToken}`, - })) as Record; - - if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') { - ctx.throw(400, 'invalid session'); - return; - } - - const profile = await UserProfiles.createQueryBuilder() - .where('"integrations"->\'discord\'->>\'id\' = :id', { id: id }) - .andWhere('"userHost" IS NULL') - .getOne(); - - if (profile == null) { - ctx.throw(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`); - return; - } - - await UserProfiles.update(profile.userId, { - integrations: { - ...profile.integrations, - discord: { - id: id, - accessToken: accessToken, - refreshToken: refreshToken, - expiresDate: expiresDate, - username: username, - discriminator: discriminator, - }, - }, - }); - - signin(ctx, await Users.findOneBy({ id: profile.userId }) as ILocalUser, true); - } else { - const code = ctx.query.code; - - if (!code || typeof code !== 'string') { - ctx.throw(400, 'invalid session'); - return; - } - - const { redirect_uri, state } = await new Promise((res, rej) => { - redisClient.get(userToken, async (_, state) => { - res(JSON.parse(state)); - }); - }); - - if (ctx.query.state !== state) { - ctx.throw(400, 'invalid session'); - return; - } - - 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, - }); - } - })); - - const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { - 'Authorization': `Bearer ${accessToken}`, - })) as Record; - if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') { - ctx.throw(400, 'invalid session'); - return; - } - - const user = await Users.findOneByOrFail({ - host: IsNull(), - token: userToken, - }); - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - await UserProfiles.update(user.id, { - integrations: { - ...profile.integrations, - discord: { - accessToken: accessToken, - refreshToken: refreshToken, - expiresDate: expiresDate, - id: id, - username: username, - discriminator: discriminator, - }, - }, - }); - - ctx.body = `Discord: @${username}#${discriminator} を、Misskey: @${user.username} に接続しました!`; - - // Publish i updated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { - detail: true, - includeSecrets: true, - })); - } -}); - -export default router; diff --git a/packages/backend/src/server/api/service/github.ts b/packages/backend/src/server/api/service/github.ts deleted file mode 100644 index 04dbd1f7ab..0000000000 --- a/packages/backend/src/server/api/service/github.ts +++ /dev/null @@ -1,259 +0,0 @@ -import Koa from 'koa'; -import Router from '@koa/router'; -import { OAuth2 } from 'oauth'; -import { v4 as uuid } from 'uuid'; -import { IsNull } from 'typeorm'; -import { getJson } from '@/misc/fetch.js'; -import config from '@/config/index.js'; -import { publishMainStream } from '@/services/stream.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Users, UserProfiles } from '@/models/index.js'; -import { ILocalUser } from '@/models/entities/user.js'; -import { redisClient } from '../../../db/redis.js'; -import signin from '../common/signin.js'; - -function getUserToken(ctx: Koa.BaseContext): string | null { - return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; -} - -function compareOrigin(ctx: Koa.BaseContext): boolean { - function normalizeUrl(url?: string): string { - return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; - } - - const referer = ctx.headers['referer']; - - return (normalizeUrl(referer) === normalizeUrl(config.url)); -} - -// Init router -const router = new Router(); - -router.get('/disconnect/github', async ctx => { - if (!compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); - return; - } - - const userToken = getUserToken(ctx); - if (!userToken) { - ctx.throw(400, 'signin required'); - return; - } - - const user = await Users.findOneByOrFail({ - host: IsNull(), - token: userToken, - }); - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - delete profile.integrations.github; - - await UserProfiles.update(user.id, { - integrations: profile.integrations, - }); - - ctx.body = 'GitHubの連携を解除しました :v:'; - - // Publish i updated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { - detail: true, - includeSecrets: true, - })); -}); - -async function getOath2() { - const meta = await fetchMeta(true); - - if (meta.enableGithubIntegration && meta.githubClientId && meta.githubClientSecret) { - return new OAuth2( - meta.githubClientId, - meta.githubClientSecret, - 'https://github.com/', - 'login/oauth/authorize', - 'login/oauth/access_token'); - } else { - return null; - } -} - -router.get('/connect/github', async ctx => { - if (!compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); - return; - } - - const userToken = getUserToken(ctx); - if (!userToken) { - ctx.throw(400, 'signin required'); - return; - } - - const params = { - redirect_uri: `${config.url}/api/gh/cb`, - scope: ['read:user'], - state: uuid(), - }; - - redisClient.set(userToken, JSON.stringify(params)); - - const oauth2 = await getOath2(); - ctx.redirect(oauth2!.getAuthorizeUrl(params)); -}); - -router.get('/signin/github', async ctx => { - const sessid = uuid(); - - const params = { - redirect_uri: `${config.url}/api/gh/cb`, - scope: ['read:user'], - state: uuid(), - }; - - ctx.cookies.set('signin_with_github_sid', sessid, { - path: '/', - secure: config.url.startsWith('https'), - httpOnly: true, - }); - - redisClient.set(sessid, JSON.stringify(params)); - - const oauth2 = await getOath2(); - ctx.redirect(oauth2!.getAuthorizeUrl(params)); -}); - -router.get('/gh/cb', async ctx => { - const userToken = getUserToken(ctx); - - const oauth2 = await getOath2(); - - if (!userToken) { - const sessid = ctx.cookies.get('signin_with_github_sid'); - - if (!sessid) { - ctx.throw(400, 'invalid session'); - return; - } - - const code = ctx.query.code; - - if (!code || typeof code !== 'string') { - ctx.throw(400, 'invalid session'); - return; - } - - const { redirect_uri, state } = await new Promise((res, rej) => { - redisClient.get(sessid, async (_, state) => { - res(JSON.parse(state)); - }); - }); - - if (ctx.query.state !== state) { - ctx.throw(400, 'invalid session'); - return; - } - - 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 }); - } - })); - - const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { - 'Authorization': `bearer ${accessToken}`, - })) as Record; - if (typeof login !== 'string' || typeof id !== 'string') { - ctx.throw(400, 'invalid session'); - return; - } - - const link = await UserProfiles.createQueryBuilder() - .where('"integrations"->\'github\'->>\'id\' = :id', { id: id }) - .andWhere('"userHost" IS NULL') - .getOne(); - - if (link == null) { - ctx.throw(404, `@${login}と連携しているMisskeyアカウントはありませんでした...`); - return; - } - - signin(ctx, await Users.findOneBy({ id: link.userId }) as ILocalUser, true); - } else { - const code = ctx.query.code; - - if (!code || typeof code !== 'string') { - ctx.throw(400, 'invalid session'); - return; - } - - const { redirect_uri, state } = await new Promise((res, rej) => { - redisClient.get(userToken, async (_, state) => { - res(JSON.parse(state)); - }); - }); - - if (ctx.query.state !== state) { - ctx.throw(400, 'invalid session'); - return; - } - - 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 }); - } - })); - - const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { - 'Authorization': `bearer ${accessToken}`, - })) as Record; - - if (typeof login !== 'string' || typeof id !== 'string') { - ctx.throw(400, 'invalid session'); - return; - } - - const user = await Users.findOneByOrFail({ - host: IsNull(), - token: userToken, - }); - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - await UserProfiles.update(user.id, { - integrations: { - ...profile.integrations, - github: { - accessToken: accessToken, - id: id, - login: login, - }, - }, - }); - - ctx.body = `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`; - - // Publish i updated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { - detail: true, - includeSecrets: true, - })); - } -}); - -export default router; diff --git a/packages/backend/src/server/api/service/twitter.ts b/packages/backend/src/server/api/service/twitter.ts deleted file mode 100644 index 2b4f9f6daa..0000000000 --- a/packages/backend/src/server/api/service/twitter.ts +++ /dev/null @@ -1,201 +0,0 @@ -import Koa from 'koa'; -import Router from '@koa/router'; -import { v4 as uuid } from 'uuid'; -import autwh from 'autwh'; -import { IsNull } from 'typeorm'; -import { publishMainStream } from '@/services/stream.js'; -import config from '@/config/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Users, UserProfiles } from '@/models/index.js'; -import { ILocalUser } from '@/models/entities/user.js'; -import signin from '../common/signin.js'; -import { redisClient } from '../../../db/redis.js'; - -function getUserToken(ctx: Koa.BaseContext): string | null { - return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; -} - -function compareOrigin(ctx: Koa.BaseContext): boolean { - function normalizeUrl(url?: string): string { - return url == null ? '' : url.endsWith('/') ? url.substr(0, url.length - 1) : url; - } - - const referer = ctx.headers['referer']; - - return (normalizeUrl(referer) === normalizeUrl(config.url)); -} - -// Init router -const router = new Router(); - -router.get('/disconnect/twitter', async ctx => { - if (!compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); - return; - } - - const userToken = getUserToken(ctx); - if (userToken == null) { - ctx.throw(400, 'signin required'); - return; - } - - const user = await Users.findOneByOrFail({ - host: IsNull(), - token: userToken, - }); - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - delete profile.integrations.twitter; - - await UserProfiles.update(user.id, { - integrations: profile.integrations, - }); - - ctx.body = 'Twitterの連携を解除しました :v:'; - - // Publish i updated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { - detail: true, - includeSecrets: true, - })); -}); - -async function getTwAuth() { - const meta = await fetchMeta(true); - - if (meta.enableTwitterIntegration && meta.twitterConsumerKey && meta.twitterConsumerSecret) { - return autwh({ - consumerKey: meta.twitterConsumerKey, - consumerSecret: meta.twitterConsumerSecret, - callbackUrl: `${config.url}/api/tw/cb`, - }); - } else { - return null; - } -} - -router.get('/connect/twitter', async ctx => { - if (!compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); - return; - } - - const userToken = getUserToken(ctx); - if (userToken == null) { - ctx.throw(400, 'signin required'); - return; - } - - const twAuth = await getTwAuth(); - const twCtx = await twAuth!.begin(); - redisClient.set(userToken, JSON.stringify(twCtx)); - ctx.redirect(twCtx.url); -}); - -router.get('/signin/twitter', async ctx => { - const twAuth = await getTwAuth(); - const twCtx = await twAuth!.begin(); - - const sessid = uuid(); - - redisClient.set(sessid, JSON.stringify(twCtx)); - - ctx.cookies.set('signin_with_twitter_sid', sessid, { - path: '/', - secure: config.url.startsWith('https'), - httpOnly: true, - }); - - ctx.redirect(twCtx.url); -}); - -router.get('/tw/cb', async ctx => { - const userToken = getUserToken(ctx); - - const twAuth = await getTwAuth(); - - if (userToken == null) { - const sessid = ctx.cookies.get('signin_with_twitter_sid'); - - if (sessid == null) { - ctx.throw(400, 'invalid session'); - return; - } - - const get = new Promise((res, rej) => { - redisClient.get(sessid, async (_, twCtx) => { - res(twCtx); - }); - }); - - const twCtx = await get; - - const verifier = ctx.query.oauth_verifier; - if (!verifier || typeof verifier !== 'string') { - ctx.throw(400, 'invalid session'); - return; - } - - const result = await twAuth!.done(JSON.parse(twCtx), verifier); - - const link = await UserProfiles.createQueryBuilder() - .where('"integrations"->\'twitter\'->>\'userId\' = :id', { id: result.userId }) - .andWhere('"userHost" IS NULL') - .getOne(); - - if (link == null) { - ctx.throw(404, `@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`); - return; - } - - signin(ctx, await Users.findOneBy({ id: link.userId }) as ILocalUser, true); - } else { - const verifier = ctx.query.oauth_verifier; - - if (!verifier || typeof verifier !== 'string') { - ctx.throw(400, 'invalid session'); - return; - } - - const get = new Promise((res, rej) => { - redisClient.get(userToken, async (_, twCtx) => { - res(twCtx); - }); - }); - - const twCtx = await get; - - const result = await twAuth!.done(JSON.parse(twCtx), verifier); - - const user = await Users.findOneByOrFail({ - host: IsNull(), - token: userToken, - }); - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - await UserProfiles.update(user.id, { - integrations: { - ...profile.integrations, - twitter: { - accessToken: result.accessToken, - accessTokenSecret: result.accessTokenSecret, - userId: result.userId, - screenName: result.screenName, - }, - }, - }); - - ctx.body = `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`; - - // Publish i updated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { - detail: true, - includeSecrets: true, - })); - } -}); - -export default router; diff --git a/packages/backend/src/server/api/stream/ChannelsService.ts b/packages/backend/src/server/api/stream/ChannelsService.ts new file mode 100644 index 0000000000..d6005b1ee8 --- /dev/null +++ b/packages/backend/src/server/api/stream/ChannelsService.ts @@ -0,0 +1,62 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import { HybridTimelineChannelService } from './channels/hybrid-timeline.js'; +import { LocalTimelineChannelService } from './channels/local-timeline.js'; +import { HomeTimelineChannelService } from './channels/home-timeline.js'; +import { GlobalTimelineChannelService } from './channels/global-timeline.js'; +import { MainChannelService } from './channels/main.js'; +import { ChannelChannelService } from './channels/channel.js'; +import { AdminChannelService } from './channels/admin.js'; +import { ServerStatsChannelService } from './channels/server-stats.js'; +import { QueueStatsChannelService } from './channels/queue-stats.js'; +import { UserListChannelService } from './channels/user-list.js'; +import { AntennaChannelService } from './channels/antenna.js'; +import { MessagingChannelService } from './channels/messaging.js'; +import { MessagingIndexChannelService } from './channels/messaging-index.js'; +import { DriveChannelService } from './channels/drive.js'; +import { HashtagChannelService } from './channels/hashtag.js'; + +@Injectable() +export class ChannelsService { + constructor( + private mainChannelService: MainChannelService, + private homeTimelineChannelService: HomeTimelineChannelService, + private localTimelineChannelService: LocalTimelineChannelService, + private hybridTimelineChannelService: HybridTimelineChannelService, + private globalTimelineChannelService: GlobalTimelineChannelService, + private userListChannelService: UserListChannelService, + private hashtagChannelService: HashtagChannelService, + private antennaChannelService: AntennaChannelService, + private channelChannelService: ChannelChannelService, + private messagingChannelService: MessagingChannelService, + private messagingIndexChannelService: MessagingIndexChannelService, + private driveChannelService: DriveChannelService, + private serverStatsChannelService: ServerStatsChannelService, + private queueStatsChannelService: QueueStatsChannelService, + private adminChannelService: AdminChannelService, + ) { + } + + public getChannelService(name: string) { + switch (name) { + case 'main': return this.mainChannelService; + case 'homeTimeline': return this.homeTimelineChannelService; + case 'localTimeline': return this.localTimelineChannelService; + case 'hybridTimeline': return this.hybridTimelineChannelService; + case 'globalTimeline': return this.globalTimelineChannelService; + case 'userList': return this.userListChannelService; + case 'hashtag': return this.hashtagChannelService; + case 'antenna': return this.antennaChannelService; + case 'channel': return this.channelChannelService; + case 'messaging': return this.messagingChannelService; + case 'messagingIndex': return this.messagingIndexChannelService; + case 'drive': return this.driveChannelService; + case 'serverStats': return this.serverStatsChannelService; + case 'queueStats': return this.queueStatsChannelService; + case 'admin': return this.adminChannelService; + + default: + throw new Error(`no such channel: ${name}`); + } + } +} diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index d2cc5122d5..5480c12c09 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -1,4 +1,4 @@ -import Connection from '.'; +import type Connection from '.'; /** * Stream channel diff --git a/packages/backend/src/server/api/stream/channels/admin.ts b/packages/backend/src/server/api/stream/channels/admin.ts index 945182ea10..8c3c0d2adf 100644 --- a/packages/backend/src/server/api/stream/channels/admin.ts +++ b/packages/backend/src/server/api/stream/channels/admin.ts @@ -1,6 +1,7 @@ +import { Inject, Injectable } from '@nestjs/common'; import Channel from '../channel.js'; -export default class extends Channel { +class AdminChannel extends Channel { public readonly chName = 'admin'; public static shouldShare = true; public static requireCredential = true; @@ -12,3 +13,20 @@ export default class extends Channel { }); } } + +@Injectable() +export class AdminChannelService { + public readonly shouldShare = AdminChannel.shouldShare; + public readonly requireCredential = AdminChannel.requireCredential; + + constructor( + ) { + } + + public create(id: string, connection: Channel['connection']): AdminChannel { + return new AdminChannel( + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts index d28320d928..7c34aef495 100644 --- a/packages/backend/src/server/api/stream/channels/antenna.ts +++ b/packages/backend/src/server/api/stream/channels/antenna.ts @@ -1,15 +1,22 @@ -import Channel from '../channel.js'; -import { Notes } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { NotesRepository } from '@/models/index.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { StreamMessages } from '../types.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import Channel from '../channel.js'; +import type { StreamMessages } from '../types.js'; -export default class extends Channel { +class AntennaChannel extends Channel { public readonly chName = 'antenna'; public static shouldShare = false; public static requireCredential = false; private antennaId: string; - constructor(id: string, connection: Channel['connection']) { + constructor( + private noteEntityService: NoteEntityService, + + id: string, + connection: Channel['connection'], + ) { super(id, connection); this.onEvent = this.onEvent.bind(this); } @@ -23,7 +30,7 @@ export default class extends Channel { private async onEvent(data: StreamMessages['antenna']['payload']) { if (data.type === 'note') { - const note = await Notes.pack(data.body.id, this.user, { detail: true }); + const note = await this.noteEntityService.pack(data.body.id, this.user, { detail: true }); // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.muting)) return; @@ -43,3 +50,22 @@ export default class extends Channel { this.subscriber.off(`antennaStream:${this.antennaId}`, this.onEvent); } } + +@Injectable() +export class AntennaChannelService { + public readonly shouldShare = AntennaChannel.shouldShare; + public readonly requireCredential = AntennaChannel.requireCredential; + + constructor( + private noteEntityService: NoteEntityService, + ) { + } + + public create(id: string, connection: Channel['connection']): AntennaChannel { + return new AntennaChannel( + this.noteEntityService, + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 3cdd89a8b3..2ef70e62e9 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -1,11 +1,14 @@ -import Channel from '../channel.js'; -import { Notes, Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { NotesRepository, UsersRepository } from '@/models/index.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { User } from '@/models/entities/user.js'; -import { StreamMessages } from '../types.js'; -import { Packed } from '@/misc/schema.js'; +import type { User } from '@/models/entities/User.js'; +import type { Packed } from '@/misc/schema.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import Channel from '../channel.js'; +import type { StreamMessages } from '../types.js'; -export default class extends Channel { +class ChannelChannel extends Channel { public readonly chName = 'channel'; public static shouldShare = false; public static requireCredential = false; @@ -13,7 +16,13 @@ export default class extends Channel { private typers: Record = {}; private emitTypersIntervalId: ReturnType; - constructor(id: string, connection: Channel['connection']) { + constructor( + private noteEntityService: NoteEntityService, + private userEntityService: UserEntityService, + + id: string, + connection: Channel['connection'], + ) { super(id, connection); this.onNote = this.onNote.bind(this); this.emitTypers = this.emitTypers.bind(this); @@ -33,13 +42,13 @@ export default class extends Channel { // リプライなら再pack if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user, { + note.reply = await this.noteEntityService.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 this.noteEntityService.pack(note.renoteId, this.user, { detail: true, }); } @@ -73,7 +82,7 @@ export default class extends Channel { if (now.getTime() - date.getTime() > 5000) delete this.typers[userId]; } - const users = await Users.packMany(Object.keys(this.typers), null, { detail: false }); + const users = await this.userEntityService.packMany(Object.keys(this.typers), null, { detail: false }); this.send({ type: 'typers', @@ -89,3 +98,24 @@ export default class extends Channel { clearInterval(this.emitTypersIntervalId); } } + +@Injectable() +export class ChannelChannelService { + public readonly shouldShare = ChannelChannel.shouldShare; + public readonly requireCredential = ChannelChannel.requireCredential; + + constructor( + private noteEntityService: NoteEntityService, + private userEntityService: UserEntityService, + ) { + } + + public create(id: string, connection: Channel['connection']): ChannelChannel { + return new ChannelChannel( + this.noteEntityService, + this.userEntityService, + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/channels/drive.ts b/packages/backend/src/server/api/stream/channels/drive.ts index 140255acd1..80d83cd690 100644 --- a/packages/backend/src/server/api/stream/channels/drive.ts +++ b/packages/backend/src/server/api/stream/channels/drive.ts @@ -1,6 +1,7 @@ +import { Inject, Injectable } from '@nestjs/common'; import Channel from '../channel.js'; -export default class extends Channel { +class DriveChannel extends Channel { public readonly chName = 'drive'; public static shouldShare = true; public static requireCredential = true; @@ -12,3 +13,20 @@ export default class extends Channel { }); } } + +@Injectable() +export class DriveChannelService { + public readonly shouldShare = DriveChannel.shouldShare; + public readonly requireCredential = DriveChannel.requireCredential; + + constructor( + ) { + } + + public create(id: string, connection: Channel['connection']): DriveChannel { + return new DriveChannel( + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index 5b4ae850ec..a8617582dc 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -1,23 +1,31 @@ -import Channel from '../channel.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Notes } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { NotesRepository } from '@/models/index.js'; import { checkWordMute } from '@/misc/check-word-mute.js'; import { isInstanceMuted } from '@/misc/is-instance-muted.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { Packed } from '@/misc/schema.js'; +import type { Packed } from '@/misc/schema.js'; +import { MetaService } from '@/core/MetaService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import Channel from '../channel.js'; -export default class extends Channel { +class GlobalTimelineChannel extends Channel { public readonly chName = 'globalTimeline'; public static shouldShare = true; public static requireCredential = false; - constructor(id: string, connection: Channel['connection']) { + constructor( + private metaService: MetaService, + private noteEntityService: NoteEntityService, + + id: string, + connection: Channel['connection'], + ) { super(id, connection); this.onNote = this.onNote.bind(this); } public async init(params: any) { - const meta = await fetchMeta(); + const meta = await this.metaService.fetch(); if (meta.disableGlobalTimeline) { if (this.user == null || (!this.user.isAdmin && !this.user.isModerator)) return; } @@ -32,13 +40,13 @@ export default class extends Channel { // リプライなら再pack if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user, { + note.reply = await this.noteEntityService.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 this.noteEntityService.pack(note.renoteId, this.user, { detail: true, }); } @@ -75,3 +83,24 @@ export default class extends Channel { this.subscriber.off('notesStream', this.onNote); } } + +@Injectable() +export class GlobalTimelineChannelService { + public readonly shouldShare = GlobalTimelineChannel.shouldShare; + public readonly requireCredential = GlobalTimelineChannel.requireCredential; + + constructor( + private metaService: MetaService, + private noteEntityService: NoteEntityService, + ) { + } + + public create(id: string, connection: Channel['connection']): GlobalTimelineChannel { + return new GlobalTimelineChannel( + this.metaService, + this.noteEntityService, + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index 741db447e6..0f6c081c12 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -1,16 +1,23 @@ -import Channel from '../channel.js'; -import { Notes } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { NotesRepository } from '@/models/index.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { Packed } from '@/misc/schema.js'; +import type { Packed } from '@/misc/schema.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import Channel from '../channel.js'; -export default class extends Channel { +class HashtagChannel extends Channel { public readonly chName = 'hashtag'; public static shouldShare = false; public static requireCredential = false; private q: string[][]; - constructor(id: string, connection: Channel['connection']) { + constructor( + private noteEntityService: NoteEntityService, + + id: string, + connection: Channel['connection'], + ) { super(id, connection); this.onNote = this.onNote.bind(this); } @@ -31,7 +38,7 @@ export default class extends Channel { // Renoteなら再pack if (note.renoteId != null) { - note.renote = await Notes.pack(note.renoteId, this.user, { + note.renote = await this.noteEntityService.pack(note.renoteId, this.user, { detail: true, }); } @@ -51,3 +58,22 @@ export default class extends Channel { this.subscriber.off('notesStream', this.onNote); } } + +@Injectable() +export class HashtagChannelService { + public readonly shouldShare = HashtagChannel.shouldShare; + public readonly requireCredential = HashtagChannel.requireCredential; + + constructor( + private noteEntityService: NoteEntityService, + ) { + } + + public create(id: string, connection: Channel['connection']): HashtagChannel { + return new HashtagChannel( + this.noteEntityService, + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 075a242ef0..16e0cebc72 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -1,16 +1,23 @@ -import Channel from '../channel.js'; -import { Notes } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { NotesRepository } from '@/models/index.js'; import { checkWordMute } from '@/misc/check-word-mute.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { isInstanceMuted } from '@/misc/is-instance-muted.js'; -import { Packed } from '@/misc/schema.js'; +import type { Packed } from '@/misc/schema.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import Channel from '../channel.js'; -export default class extends Channel { +class HomeTimelineChannel extends Channel { public readonly chName = 'homeTimeline'; public static shouldShare = true; public static requireCredential = true; - constructor(id: string, connection: Channel['connection']) { + constructor( + private noteEntityService: NoteEntityService, + + id: string, + connection: Channel['connection'], + ) { super(id, connection); this.onNote = this.onNote.bind(this); } @@ -32,7 +39,7 @@ export default class extends Channel { if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return; if (['followers', 'specified'].includes(note.visibility)) { - note = await Notes.pack(note.id, this.user!, { + note = await this.noteEntityService.pack(note.id, this.user!, { detail: true, }); @@ -42,13 +49,13 @@ export default class extends Channel { } else { // リプライなら再pack if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user!, { + note.reply = await this.noteEntityService.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 this.noteEntityService.pack(note.renoteId, this.user!, { detail: true, }); } @@ -83,3 +90,22 @@ export default class extends Channel { this.subscriber.off('notesStream', this.onNote); } } + +@Injectable() +export class HomeTimelineChannelService { + public readonly shouldShare = HomeTimelineChannel.shouldShare; + public readonly requireCredential = HomeTimelineChannel.requireCredential; + + constructor( + private noteEntityService: NoteEntityService, + ) { + } + + public create(id: string, connection: Channel['connection']): HomeTimelineChannel { + return new HomeTimelineChannel( + this.noteEntityService, + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index f5dedf77ce..f1ce822583 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -1,23 +1,32 @@ -import Channel from '../channel.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Notes } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { NotesRepository } from '@/models/index.js'; import { checkWordMute } from '@/misc/check-word-mute.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { isInstanceMuted } from '@/misc/is-instance-muted.js'; -import { Packed } from '@/misc/schema.js'; +import type { Packed } from '@/misc/schema.js'; +import { DI } from '@/di-symbols.js'; +import { MetaService } from '@/core/MetaService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import Channel from '../channel.js'; -export default class extends Channel { +class HybridTimelineChannel extends Channel { public readonly chName = 'hybridTimeline'; public static shouldShare = true; public static requireCredential = true; - constructor(id: string, connection: Channel['connection']) { + constructor( + private metaService: MetaService, + private noteEntityService: NoteEntityService, + + id: string, + connection: Channel['connection'], + ) { super(id, connection); this.onNote = this.onNote.bind(this); } - public async init(params: any) { - const meta = await fetchMeta(); + public async init(params: any): Promise { + const meta = await this.metaService.fetch(); if (meta.disableLocalTimeline && !this.user!.isAdmin && !this.user!.isModerator) return; // Subscribe events @@ -37,7 +46,7 @@ export default class extends Channel { )) return; if (['followers', 'specified'].includes(note.visibility)) { - note = await Notes.pack(note.id, this.user!, { + note = await this.noteEntityService.pack(note.id, this.user!, { detail: true, }); @@ -47,13 +56,13 @@ export default class extends Channel { } else { // リプライなら再pack if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user!, { + note.reply = await this.noteEntityService.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 this.noteEntityService.pack(note.renoteId, this.user!, { detail: true, }); } @@ -86,8 +95,29 @@ export default class extends Channel { this.send('note', note); } - public dispose() { + public dispose(): void { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); } } + +@Injectable() +export class HybridTimelineChannelService { + public readonly shouldShare = HybridTimelineChannel.shouldShare; + public readonly requireCredential = HybridTimelineChannel.requireCredential; + + constructor( + private metaService: MetaService, + private noteEntityService: NoteEntityService, + ) { + } + + public create(id: string, connection: Channel['connection']): HybridTimelineChannel { + return new HybridTimelineChannel( + this.metaService, + this.noteEntityService, + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/channels/index.ts b/packages/backend/src/server/api/stream/channels/index.ts deleted file mode 100644 index d422edde87..0000000000 --- a/packages/backend/src/server/api/stream/channels/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -import main from './main.js'; -import homeTimeline from './home-timeline.js'; -import localTimeline from './local-timeline.js'; -import hybridTimeline from './hybrid-timeline.js'; -import globalTimeline from './global-timeline.js'; -import serverStats from './server-stats.js'; -import queueStats from './queue-stats.js'; -import userList from './user-list.js'; -import antenna from './antenna.js'; -import messaging from './messaging.js'; -import messagingIndex from './messaging-index.js'; -import drive from './drive.js'; -import hashtag from './hashtag.js'; -import channel from './channel.js'; -import admin from './admin.js'; - -export default { - main, - homeTimeline, - localTimeline, - hybridTimeline, - globalTimeline, - serverStats, - queueStats, - userList, - antenna, - messaging, - messagingIndex, - drive, - hashtag, - channel, - admin, -}; diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index f01f477238..5a5a43f845 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -1,22 +1,30 @@ -import Channel from '../channel.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Notes } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { NotesRepository } from '@/models/index.js'; import { checkWordMute } from '@/misc/check-word-mute.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { Packed } from '@/misc/schema.js'; +import type { Packed } from '@/misc/schema.js'; +import { MetaService } from '@/core/MetaService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import Channel from '../channel.js'; -export default class extends Channel { +class LocalTimelineChannel extends Channel { public readonly chName = 'localTimeline'; public static shouldShare = true; public static requireCredential = false; - constructor(id: string, connection: Channel['connection']) { + constructor( + private metaService: MetaService, + private noteEntityService: NoteEntityService, + + id: string, + connection: Channel['connection'], + ) { super(id, connection); this.onNote = this.onNote.bind(this); } public async init(params: any) { - const meta = await fetchMeta(); + const meta = await this.metaService.fetch(); if (meta.disableLocalTimeline) { if (this.user == null || (!this.user.isAdmin && !this.user.isModerator)) return; } @@ -32,13 +40,13 @@ export default class extends Channel { // リプライなら再pack if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user, { + note.reply = await this.noteEntityService.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 this.noteEntityService.pack(note.renoteId, this.user, { detail: true, }); } @@ -72,3 +80,24 @@ export default class extends Channel { this.subscriber.off('notesStream', this.onNote); } } + +@Injectable() +export class LocalTimelineChannelService { + public readonly shouldShare = LocalTimelineChannel.shouldShare; + public readonly requireCredential = LocalTimelineChannel.requireCredential; + + constructor( + private metaService: MetaService, + private noteEntityService: NoteEntityService, + ) { + } + + public create(id: string, connection: Channel['connection']): LocalTimelineChannel { + return new LocalTimelineChannel( + this.metaService, + this.noteEntityService, + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts index 9cfea0bfc4..12908e07b4 100644 --- a/packages/backend/src/server/api/stream/channels/main.ts +++ b/packages/backend/src/server/api/stream/channels/main.ts @@ -1,12 +1,23 @@ -import Channel from '../channel.js'; -import { Notes } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { NotesRepository } from '@/models/index.js'; import { isInstanceMuted, isUserFromMutedInstance } from '@/misc/is-instance-muted.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import Channel from '../channel.js'; -export default class extends Channel { +class MainChannel extends Channel { public readonly chName = 'main'; public static shouldShare = true; public static requireCredential = true; + constructor( + private noteEntityService: NoteEntityService, + + id: string, + connection: Channel['connection'], + ) { + super(id, connection); + } + public async init(params: any) { // Subscribe main stream channel this.subscriber.on(`mainStream:${this.user!.id}`, async data => { @@ -17,7 +28,7 @@ export default class extends Channel { if (data.body.userId && this.muting.has(data.body.userId)) return; if (data.body.note && data.body.note.isHidden) { - const note = await Notes.pack(data.body.note.id, this.user, { + const note = await this.noteEntityService.pack(data.body.note.id, this.user, { detail: true, }); this.connection.cacheNote(note); @@ -30,7 +41,7 @@ export default class extends Channel { if (this.muting.has(data.body.userId)) return; if (data.body.isHidden) { - const note = await Notes.pack(data.body.id, this.user, { + const note = await this.noteEntityService.pack(data.body.id, this.user, { detail: true, }); this.connection.cacheNote(note); @@ -44,3 +55,22 @@ export default class extends Channel { }); } } + +@Injectable() +export class MainChannelService { + public readonly shouldShare = MainChannel.shouldShare; + public readonly requireCredential = MainChannel.requireCredential; + + constructor( + private noteEntityService: NoteEntityService, + ) { + } + + public create(id: string, connection: Channel['connection']): MainChannel { + return new MainChannel( + this.noteEntityService, + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/channels/messaging-index.ts b/packages/backend/src/server/api/stream/channels/messaging-index.ts index b930785d20..bebc07f4ad 100644 --- a/packages/backend/src/server/api/stream/channels/messaging-index.ts +++ b/packages/backend/src/server/api/stream/channels/messaging-index.ts @@ -1,6 +1,7 @@ +import { Inject, Injectable } from '@nestjs/common'; import Channel from '../channel.js'; -export default class extends Channel { +class MessagingIndexChannel extends Channel { public readonly chName = 'messagingIndex'; public static shouldShare = true; public static requireCredential = true; @@ -12,3 +13,20 @@ export default class extends Channel { }); } } + +@Injectable() +export class MessagingIndexChannelService { + public readonly shouldShare = MessagingIndexChannel.shouldShare; + public readonly requireCredential = MessagingIndexChannel.requireCredential; + + constructor( + ) { + } + + public create(id: string, connection: Channel['connection']): MessagingIndexChannel { + return new MessagingIndexChannel( + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/channels/messaging.ts b/packages/backend/src/server/api/stream/channels/messaging.ts index 877d44c38e..5bf20c4101 100644 --- a/packages/backend/src/server/api/stream/channels/messaging.ts +++ b/packages/backend/src/server/api/stream/channels/messaging.ts @@ -1,11 +1,14 @@ -import { readUserMessagingMessage, readGroupMessagingMessage, deliverReadActivity } from '../../common/read-messaging-message.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupJoiningsRepository, UsersRepository, MessagingMessagesRepository } from '@/models/index.js'; +import type { User, ILocalUser, IRemoteUser } from '@/models/entities/User.js'; +import type { UserGroup } from '@/models/entities/UserGroup.js'; +import { MessagingService } from '@/core/MessagingService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; import Channel from '../channel.js'; -import { UserGroupJoinings, Users, MessagingMessages } from '@/models/index.js'; -import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { StreamMessages } from '../types.js'; +import type { StreamMessages } from '../types.js'; -export default class extends Channel { +class MessagingChannel extends Channel { public readonly chName = 'messaging'; public static shouldShare = false; public static requireCredential = true; @@ -17,7 +20,16 @@ export default class extends Channel { private typers: Record = {}; private emitTypersIntervalId: ReturnType; - constructor(id: string, connection: Channel['connection']) { + constructor( + private usersRepository: UsersRepository, + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + private messagingMessagesRepository: MessagingMessagesRepository, + private userEntityService: UserEntityService, + private messagingService: MessagingService, + + id: string, + connection: Channel['connection'], + ) { super(id, connection); this.onEvent = this.onEvent.bind(this); this.onMessage = this.onMessage.bind(this); @@ -26,12 +38,12 @@ export default class extends Channel { public async init(params: any) { this.otherpartyId = params.otherparty; - this.otherparty = this.otherpartyId ? await Users.findOneByOrFail({ id: this.otherpartyId }) : null; + this.otherparty = this.otherpartyId ? await this.usersRepository.findOneByOrFail({ id: this.otherpartyId }) : null; this.groupId = params.group; // Check joining if (this.groupId) { - const joining = await UserGroupJoinings.findOneBy({ + const joining = await this.userGroupJoiningsRepository.findOneBy({ userId: this.user!.id, userGroupId: this.groupId, }); @@ -68,16 +80,16 @@ export default class extends Channel { switch (type) { case 'read': if (this.otherpartyId) { - readUserMessagingMessage(this.user!.id, this.otherpartyId, [body.id]); + this.messagingService.readUserMessagingMessage(this.user!.id, this.otherpartyId, [body.id]); // リモートユーザーからのメッセージだったら既読配信 - if (Users.isLocalUser(this.user!) && Users.isRemoteUser(this.otherparty!)) { - MessagingMessages.findOneBy({ id: body.id }).then(message => { - if (message) deliverReadActivity(this.user as ILocalUser, this.otherparty as IRemoteUser, message); + if (this.userEntityService.isLocalUser(this.user!) && this.userEntityService.isRemoteUser(this.otherparty!)) { + this.messagingMessagesRepository.findOneBy({ id: body.id }).then(message => { + if (message) this.messagingService.deliverReadActivity(this.user as ILocalUser, this.otherparty as IRemoteUser, message); }); } } else if (this.groupId) { - readGroupMessagingMessage(this.user!.id, this.groupId, [body.id]); + this.messagingService.readGroupMessagingMessage(this.user!.id, this.groupId, [body.id]); } break; } @@ -91,7 +103,7 @@ export default class extends Channel { if (now.getTime() - date.getTime() > 5000) delete this.typers[userId]; } - const users = await Users.packMany(Object.keys(this.typers), null, { detail: false }); + const users = await this.userEntityService.packMany(Object.keys(this.typers), null, { detail: false }); this.send({ type: 'typers', @@ -105,3 +117,36 @@ export default class extends Channel { clearInterval(this.emitTypersIntervalId); } } + +@Injectable() +export class MessagingChannelService { + public readonly shouldShare = MessagingChannel.shouldShare; + public readonly requireCredential = MessagingChannel.requireCredential; + + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + private userEntityService: UserEntityService, + private messagingService: MessagingService, + ) { + } + + public create(id: string, connection: Channel['connection']): MessagingChannel { + return new MessagingChannel( + this.usersRepository, + this.userGroupJoiningsRepository, + this.messagingMessagesRepository, + this.userEntityService, + this.messagingService, + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/channels/queue-stats.ts b/packages/backend/src/server/api/stream/channels/queue-stats.ts index b67600474b..1802c6723b 100644 --- a/packages/backend/src/server/api/stream/channels/queue-stats.ts +++ b/packages/backend/src/server/api/stream/channels/queue-stats.ts @@ -1,9 +1,10 @@ import Xev from 'xev'; +import { Inject, Injectable } from '@nestjs/common'; import Channel from '../channel.js'; const ev = new Xev(); -export default class extends Channel { +class QueueStatsChannel extends Channel { public readonly chName = 'queueStats'; public static shouldShare = true; public static requireCredential = false; @@ -40,3 +41,20 @@ export default class extends Channel { ev.removeListener('queueStats', this.onStats); } } + +@Injectable() +export class QueueStatsChannelService { + public readonly shouldShare = QueueStatsChannel.shouldShare; + public readonly requireCredential = QueueStatsChannel.requireCredential; + + constructor( + ) { + } + + public create(id: string, connection: Channel['connection']): QueueStatsChannel { + return new QueueStatsChannel( + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/channels/server-stats.ts b/packages/backend/src/server/api/stream/channels/server-stats.ts index db75a6fa38..e2b00de25f 100644 --- a/packages/backend/src/server/api/stream/channels/server-stats.ts +++ b/packages/backend/src/server/api/stream/channels/server-stats.ts @@ -1,9 +1,10 @@ import Xev from 'xev'; +import { Inject, Injectable } from '@nestjs/common'; import Channel from '../channel.js'; const ev = new Xev(); -export default class extends Channel { +class ServerStatsChannel extends Channel { public readonly chName = 'serverStats'; public static shouldShare = true; public static requireCredential = false; @@ -40,3 +41,20 @@ export default class extends Channel { ev.removeListener('serverStats', this.onStats); } } + +@Injectable() +export class ServerStatsChannelService { + public readonly shouldShare = ServerStatsChannel.shouldShare; + public readonly requireCredential = ServerStatsChannel.requireCredential; + + constructor( + ) { + } + + public create(id: string, connection: Channel['connection']): ServerStatsChannel { + return new ServerStatsChannel( + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 97ad2983c5..a45c7d9468 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -1,10 +1,14 @@ -import Channel from '../channel.js'; -import { Notes, UserListJoinings, UserLists } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; +import type { User } from '@/models/entities/User.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { Packed } from '@/misc/schema.js'; +import type { Packed } from '@/misc/schema.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; +import Channel from '../channel.js'; -export default class extends Channel { +class UserListChannel extends Channel { public readonly chName = 'userList'; public static shouldShare = false; public static requireCredential = false; @@ -12,7 +16,14 @@ export default class extends Channel { public listUsers: User['id'][] = []; private listUsersClock: NodeJS.Timer; - constructor(id: string, connection: Channel['connection']) { + constructor( + private userListsRepository: UserListsRepository, + private userListJoiningsRepository: UserListJoiningsRepository, + private noteEntityService: NoteEntityService, + + id: string, + connection: Channel['connection'], + ) { super(id, connection); this.updateListUsers = this.updateListUsers.bind(this); this.onNote = this.onNote.bind(this); @@ -22,7 +33,7 @@ export default class extends Channel { this.listId = params.listId as string; // Check existence and owner - const list = await UserLists.findOneBy({ + const list = await this.userListsRepository.findOneBy({ id: this.listId, userId: this.user!.id, }); @@ -38,7 +49,7 @@ export default class extends Channel { } private async updateListUsers() { - const users = await UserListJoinings.find({ + const users = await this.userListJoiningsRepository.find({ where: { userListId: this.listId, }, @@ -52,7 +63,7 @@ export default class extends Channel { if (!this.listUsers.includes(note.userId)) return; if (['followers', 'specified'].includes(note.visibility)) { - note = await Notes.pack(note.id, this.user, { + note = await this.noteEntityService.pack(note.id, this.user, { detail: true, }); @@ -62,13 +73,13 @@ export default class extends Channel { } else { // リプライなら再pack if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user, { + note.reply = await this.noteEntityService.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 this.noteEntityService.pack(note.renoteId, this.user, { detail: true, }); } @@ -90,3 +101,30 @@ export default class extends Channel { clearInterval(this.listUsersClock); } } + +@Injectable() +export class UserListChannelService { + public readonly shouldShare = UserListChannel.shouldShare; + public readonly requireCredential = UserListChannel.requireCredential; + + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, + + private noteEntityService: NoteEntityService, + ) { + } + + public create(id: string, connection: Channel['connection']): UserListChannel { + return new UserListChannel( + this.userListsRepository, + this.userListJoiningsRepository, + this.noteEntityService, + id, + connection, + ); + } +} diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index 2d23145f14..0c5066b736 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -1,18 +1,18 @@ -import { EventEmitter } from 'events'; -import * as websocket from 'websocket'; -import readNote from '@/services/note/read.js'; -import { User } from '@/models/entities/user.js'; -import { Channel as ChannelModel } from '@/models/entities/channel.js'; -import { Users, Followings, Mutings, UserProfiles, ChannelFollowings, Blockings } from '@/models/index.js'; -import { AccessToken } from '@/models/entities/access-token.js'; -import { UserProfile } from '@/models/entities/user-profile.js'; -import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '@/services/stream.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { Packed } from '@/misc/schema.js'; -import { readNotification } from '../common/read-notification.js'; -import channels from './channels/index.js'; -import Channel from './channel.js'; -import { StreamEventEmitter, StreamMessages } from './types.js'; +import type { User } from '@/models/entities/User.js'; +import type { Channel as ChannelModel } from '@/models/entities/Channel.js'; +import type { FollowingsRepository, MutingsRepository, UserProfilesRepository, ChannelFollowingsRepository, BlockingsRepository } from '@/models/index.js'; +import type { AccessToken } from '@/models/entities/AccessToken.js'; +import type { UserProfile } from '@/models/entities/UserProfile.js'; +import type { UserGroup } from '@/models/entities/UserGroup.js'; +import type { Packed } from '@/misc/schema.js'; +import type { GlobalEventService } from '@/core/GlobalEventService.js'; +import type { NoteReadService } from '@/core/NoteReadService.js'; +import type { NotificationService } from '@/core/NotificationService.js'; +import type { ChannelsService } from './ChannelsService.js'; +import type * as websocket from 'websocket'; +import type { EventEmitter } from 'events'; +import type Channel from './channel.js'; +import type { StreamEventEmitter, StreamMessages } from './types.js'; /** * Main stream connection @@ -32,6 +32,16 @@ export default class Connection { private cachedNotes: Packed<'Note'>[] = []; constructor( + private followingsRepository: FollowingsRepository, + private mutingsRepository: MutingsRepository, + private blockingsRepository: BlockingsRepository, + private channelFollowingsRepository: ChannelFollowingsRepository, + private userProfilesRepository: UserProfilesRepository, + private channelsService: ChannelsService, + private globalEventService: GlobalEventService, + private noteReadService: NoteReadService, + private notificationService: NotificationService, + wsConnection: websocket.connection, subscriber: EventEmitter, user: User | null | undefined, @@ -173,7 +183,7 @@ export default class Connection { if (note == null) return; if (this.user && (note.userId !== this.user.id)) { - readNote(this.user.id, [note], { + this.noteReadService.read(this.user.id, [note], { following: this.following, followingChannels: this.followingChannels, }); @@ -182,7 +192,7 @@ export default class Connection { private onReadNotification(payload: any) { if (!payload.id) return; - readNotification(this.user!.id, [payload.id]); + this.notificationService.readNotification(this.user!.id, [payload.id]); } /** @@ -253,16 +263,18 @@ export default class Connection { * チャンネルに接続 */ public connectChannel(id: string, params: any, channel: string, pong = false) { - if ((channels as any)[channel].requireCredential && this.user == null) { + const channelService = this.channelsService.getChannelService(channel); + + if (channelService.requireCredential && this.user == null) { return; } // 共有可能チャンネルに接続しようとしていて、かつそのチャンネルに既に接続していたら無意味なので無視 - if ((channels as any)[channel].shouldShare && this.channels.some(c => c.chName === channel)) { + if (channelService.shouldShare && this.channels.some(c => c.chName === channel)) { return; } - const ch: Channel = new (channels as any)[channel](id, this); + const ch: Channel = channelService.create(id, this); this.channels.push(ch); ch.init(params); @@ -299,22 +311,22 @@ export default class Connection { private typingOnChannel(channel: ChannelModel['id']) { if (this.user) { - publishChannelStream(channel, 'typing', this.user.id); + this.globalEventService.publishChannelStream(channel, 'typing', this.user.id); } } private typingOnMessaging(param: { partner?: User['id']; group?: UserGroup['id']; }) { if (this.user) { if (param.partner) { - publishMessagingStream(param.partner, this.user.id, 'typing', this.user.id); + this.globalEventService.publishMessagingStream(param.partner, this.user.id, 'typing', this.user.id); } else if (param.group) { - publishGroupMessagingStream(param.group, 'typing', this.user.id); + this.globalEventService.publishGroupMessagingStream(param.group, 'typing', this.user.id); } } } private async updateFollowing() { - const followings = await Followings.find({ + const followings = await this.followingsRepository.find({ where: { followerId: this.user!.id, }, @@ -325,7 +337,7 @@ export default class Connection { } private async updateMuting() { - const mutings = await Mutings.find({ + const mutings = await this.mutingsRepository.find({ where: { muterId: this.user!.id, }, @@ -336,7 +348,7 @@ export default class Connection { } private async updateBlocking() { // ここでいうBlockingは被Blockingの意 - const blockings = await Blockings.find({ + const blockings = await this.blockingsRepository.find({ where: { blockeeId: this.user!.id, }, @@ -347,7 +359,7 @@ export default class Connection { } private async updateFollowingChannels() { - const followings = await ChannelFollowings.find({ + const followings = await this.channelFollowingsRepository.find({ where: { followerId: this.user!.id, }, @@ -358,7 +370,7 @@ export default class Connection { } private async updateUserProfile() { - this.userProfile = await UserProfiles.findOneBy({ + this.userProfile = await this.userProfilesRepository.findOneBy({ userId: this.user!.id, }); } diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 3b0a75d793..000f9a25dd 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -1,21 +1,20 @@ -import { EventEmitter } from 'events'; -import Emitter from 'strict-event-emitter-types'; -import { Channel } from '@/models/entities/channel.js'; -import { User } from '@/models/entities/user.js'; -import { UserProfile } from '@/models/entities/user-profile.js'; -import { Note } from '@/models/entities/note.js'; -import { Antenna } from '@/models/entities/antenna.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFolder } from '@/models/entities/drive-folder.js'; -import { Emoji } from '@/models/entities/emoji.js'; -import { UserList } from '@/models/entities/user-list.js'; -import { MessagingMessage } from '@/models/entities/messaging-message.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { AbuseUserReport } from '@/models/entities/abuse-user-report.js'; -import { Signin } from '@/models/entities/signin.js'; -import { Page } from '@/models/entities/page.js'; -import { Packed } from '@/misc/schema.js'; -import { Webhook } from '@/models/entities/webhook'; +import type { Channel } from '@/models/entities/Channel.js'; +import type { User } from '@/models/entities/User.js'; +import type { UserProfile } from '@/models/entities/UserProfile.js'; +import type { Note } from '@/models/entities/Note.js'; +import type { Antenna } from '@/models/entities/Antenna.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { DriveFolder } from '@/models/entities/DriveFolder.js'; +import type { UserList } from '@/models/entities/UserList.js'; +import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; +import type { UserGroup } from '@/models/entities/UserGroup.js'; +import type { AbuseUserReport } from '@/models/entities/AbuseUserReport.js'; +import type { Signin } from '@/models/entities/Signin.js'; +import type { Page } from '@/models/entities/Page.js'; +import type { Packed } from '@/misc/schema.js'; +import type { Webhook } from '@/models/entities/Webhook.js'; +import type Emitter from 'strict-event-emitter-types'; +import type { EventEmitter } from 'events'; //#region Stream type-body definitions export interface InternalStreamTypes { diff --git a/packages/backend/src/server/api/streaming.ts b/packages/backend/src/server/api/streaming.ts deleted file mode 100644 index f8e42d27fe..0000000000 --- a/packages/backend/src/server/api/streaming.ts +++ /dev/null @@ -1,67 +0,0 @@ -import * as http from 'node:http'; -import * as websocket from 'websocket'; - -import MainStreamConnection from './stream/index.js'; -import { ParsedUrlQuery } from 'querystring'; -import authenticate from './authenticate.js'; -import { EventEmitter } from 'events'; -import { subsdcriber as redisClient } from '../../db/redis.js'; -import { Users } from '@/models/index.js'; - -export const initializeStreamingServer = (server: http.Server) => { - // Init websocket server - const ws = new websocket.server({ - httpServer: server, - }); - - ws.on('request', async (request) => { - const q = request.resourceURL.query as ParsedUrlQuery; - - // TODO: トークンが間違ってるなどしてauthenticateに失敗したら - // コネクション切断するなりエラーメッセージ返すなりする - // (現状はエラーがキャッチされておらずサーバーのログに流れて邪魔なので) - const [user, app] = await authenticate(q.i as string); - - if (user?.isSuspended) { - request.reject(400); - return; - } - - const connection = request.accept(); - - const ev = new EventEmitter(); - - async function onRedisMessage(_: string, data: string) { - const parsed = JSON.parse(data); - ev.emit(parsed.channel, parsed.message); - } - - redisClient.on('message', onRedisMessage); - - const main = new MainStreamConnection(connection, ev, user, app); - - const intervalId = user ? setInterval(() => { - Users.update(user.id, { - lastActiveDate: new Date(), - }); - }, 1000 * 60 * 5) : null; - if (user) { - Users.update(user.id, { - lastActiveDate: new Date(), - }); - } - - connection.once('close', () => { - ev.removeAllListeners(); - main.dispose(); - redisClient.off('message', onRedisMessage); - if (intervalId) clearInterval(intervalId); - }); - - connection.on('message', async (data) => { - if (data.type === 'utf8' && data.utf8Data === 'ping') { - connection.send('pong'); - } - }); - }); -}; diff --git a/packages/backend/src/server/assets/bad-egg.png b/packages/backend/src/server/assets/bad-egg.png new file mode 100644 index 0000000000..e96ba0dcc1 Binary files /dev/null and b/packages/backend/src/server/assets/bad-egg.png differ diff --git a/packages/backend/src/server/assets/cache-expired.png b/packages/backend/src/server/assets/cache-expired.png new file mode 100644 index 0000000000..5d988c502b Binary files /dev/null and b/packages/backend/src/server/assets/cache-expired.png differ diff --git a/packages/backend/src/server/assets/dummy.png b/packages/backend/src/server/assets/dummy.png new file mode 100644 index 0000000000..39332b0c1b Binary files /dev/null and b/packages/backend/src/server/assets/dummy.png differ diff --git a/packages/backend/src/server/assets/not-an-image.png b/packages/backend/src/server/assets/not-an-image.png new file mode 100644 index 0000000000..39e4aa0892 Binary files /dev/null and b/packages/backend/src/server/assets/not-an-image.png differ diff --git a/packages/backend/src/server/assets/thumbnail-not-available.png b/packages/backend/src/server/assets/thumbnail-not-available.png new file mode 100644 index 0000000000..07cad9919c Binary files /dev/null and b/packages/backend/src/server/assets/thumbnail-not-available.png differ diff --git a/packages/backend/src/server/assets/tombstone.png b/packages/backend/src/server/assets/tombstone.png new file mode 100644 index 0000000000..83159d6b3c Binary files /dev/null and b/packages/backend/src/server/assets/tombstone.png differ diff --git a/packages/backend/src/server/file/assets/bad-egg.png b/packages/backend/src/server/file/assets/bad-egg.png deleted file mode 100644 index e96ba0dcc1..0000000000 Binary files a/packages/backend/src/server/file/assets/bad-egg.png and /dev/null differ diff --git a/packages/backend/src/server/file/assets/cache-expired.png b/packages/backend/src/server/file/assets/cache-expired.png deleted file mode 100644 index 5d988c502b..0000000000 Binary files a/packages/backend/src/server/file/assets/cache-expired.png and /dev/null differ diff --git a/packages/backend/src/server/file/assets/dummy.png b/packages/backend/src/server/file/assets/dummy.png deleted file mode 100644 index 39332b0c1b..0000000000 Binary files a/packages/backend/src/server/file/assets/dummy.png and /dev/null differ diff --git a/packages/backend/src/server/file/assets/not-an-image.png b/packages/backend/src/server/file/assets/not-an-image.png deleted file mode 100644 index 39e4aa0892..0000000000 Binary files a/packages/backend/src/server/file/assets/not-an-image.png and /dev/null differ diff --git a/packages/backend/src/server/file/assets/thumbnail-not-available.png b/packages/backend/src/server/file/assets/thumbnail-not-available.png deleted file mode 100644 index 07cad9919c..0000000000 Binary files a/packages/backend/src/server/file/assets/thumbnail-not-available.png and /dev/null differ diff --git a/packages/backend/src/server/file/assets/tombstone.png b/packages/backend/src/server/file/assets/tombstone.png deleted file mode 100644 index 83159d6b3c..0000000000 Binary files a/packages/backend/src/server/file/assets/tombstone.png and /dev/null differ diff --git a/packages/backend/src/server/file/index.ts b/packages/backend/src/server/file/index.ts deleted file mode 100644 index 07a493700a..0000000000 --- a/packages/backend/src/server/file/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * File Server - */ - -import * as fs from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; -import Koa from 'koa'; -import cors from '@koa/cors'; -import Router from '@koa/router'; -import sendDriveFile from './send-drive-file.js'; - -const _filename = fileURLToPath(import.meta.url); -const _dirname = dirname(_filename); - -// Init app -const app = new Koa(); -app.use(cors()); -app.use(async (ctx, next) => { - ctx.set('Content-Security-Policy', `default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'`); - await next(); -}); - -// Init router -const router = new Router(); - -router.get('/app-default.jpg', ctx => { - const file = fs.createReadStream(`${_dirname}/assets/dummy.png`); - ctx.body = file; - ctx.set('Content-Type', 'image/jpeg'); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); -}); - -router.get('/:key', sendDriveFile); -router.get('/:key/(.*)', sendDriveFile); - -// Register router -app.use(router.routes()); - -export default app; diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts deleted file mode 100644 index c34e043145..0000000000 --- a/packages/backend/src/server/file/send-drive-file.ts +++ /dev/null @@ -1,126 +0,0 @@ -import * as fs from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; -import Koa from 'koa'; -import send from 'koa-send'; -import rename from 'rename'; -import { serverLogger } from '../index.js'; -import { contentDisposition } from '@/misc/content-disposition.js'; -import { DriveFiles } from '@/models/index.js'; -import { InternalStorage } from '@/services/drive/internal-storage.js'; -import { createTemp } from '@/misc/create-temp.js'; -import { downloadUrl } from '@/misc/download-url.js'; -import { detectType } from '@/misc/get-file-info.js'; -import { convertToWebp, convertToJpeg, convertToPng } from '@/services/drive/image-processor.js'; -import { GenerateVideoThumbnail } from '@/services/drive/generate-video-thumbnail.js'; -import { StatusError } from '@/misc/fetch.js'; -import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; - -const _filename = fileURLToPath(import.meta.url); -const _dirname = dirname(_filename); - -const assets = `${_dirname}/../../server/file/assets/`; - -const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void => { - serverLogger.error(e); - ctx.status = 500; - ctx.set('Cache-Control', 'max-age=300'); -}; - -// eslint-disable-next-line import/no-default-export -export default async function(ctx: Koa.Context) { - const key = ctx.params.key; - - // Fetch drive file - const file = await DriveFiles.createQueryBuilder('file') - .where('file.accessKey = :accessKey', { accessKey: key }) - .orWhere('file.thumbnailAccessKey = :thumbnailAccessKey', { thumbnailAccessKey: key }) - .orWhere('file.webpublicAccessKey = :webpublicAccessKey', { webpublicAccessKey: key }) - .getOne(); - - if (file == null) { - ctx.status = 404; - ctx.set('Cache-Control', 'max-age=86400'); - await send(ctx as any, '/dummy.png', { root: assets }); - return; - } - - const isThumbnail = file.thumbnailAccessKey === key; - const isWebpublic = file.webpublicAccessKey === key; - - if (!file.storedInternal) { - if (file.isLink && file.uri) { // 期限切れリモートファイル - const [path, cleanup] = await createTemp(); - - try { - await downloadUrl(file.uri, path); - - const { mime, ext } = await detectType(path); - - const convertFile = async () => { - if (isThumbnail) { - if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(mime)) { - return await convertToWebp(path, 498, 280); - } else if (mime.startsWith('video/')) { - return await GenerateVideoThumbnail(path); - } - } - - if (isWebpublic) { - if (['image/svg+xml'].includes(mime)) { - return await convertToPng(path, 2048, 2048); - } - } - - return { - data: fs.readFileSync(path), - ext, - type: mime, - }; - }; - - const image = await convertFile(); - ctx.body = image.data; - ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream'); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); - } catch (e) { - serverLogger.error(`${e}`); - - if (e instanceof StatusError && e.isClientError) { - ctx.status = e.statusCode; - ctx.set('Cache-Control', 'max-age=86400'); - } else { - ctx.status = 500; - ctx.set('Cache-Control', 'max-age=300'); - } - } finally { - cleanup(); - } - return; - } - - ctx.status = 204; - ctx.set('Cache-Control', 'max-age=86400'); - return; - } - - if (isThumbnail || isWebpublic) { - const { mime, ext } = await detectType(InternalStorage.resolvePath(key)); - const filename = rename(file.name, { - suffix: isThumbnail ? '-thumb' : '-web', - extname: ext ? `.${ext}` : undefined, - }).toString(); - - ctx.body = InternalStorage.read(key); - ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : 'application/octet-stream'); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); - ctx.set('Content-Disposition', contentDisposition('inline', filename)); - } else { - const readable = InternalStorage.read(file.accessKey!); - readable.on('error', commonReadableHandlerGenerator(ctx)); - ctx.body = readable; - ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.type) ? file.type : 'application/octet-stream'); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); - ctx.set('Content-Disposition', contentDisposition('inline', file.name)); - } -} diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts deleted file mode 100644 index f31de2b7f4..0000000000 --- a/packages/backend/src/server/index.ts +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Core Server - */ - -import cluster from 'node:cluster'; -import * as fs from 'node:fs'; -import * as http from 'node:http'; -import Koa from 'koa'; -import Router from '@koa/router'; -import mount from 'koa-mount'; -import koaLogger from 'koa-logger'; -import * as slow from 'koa-slow'; - -import { IsNull } from 'typeorm'; -import config from '@/config/index.js'; -import Logger from '@/services/logger.js'; -import { UserProfiles, Users } from '@/models/index.js'; -import { genIdenticon } from '@/misc/gen-identicon.js'; -import { createTemp } from '@/misc/create-temp.js'; -import { publishMainStream } from '@/services/stream.js'; -import * as Acct from '@/misc/acct.js'; -import { envOption } from '../env.js'; -import activityPub from './activitypub.js'; -import nodeinfo from './nodeinfo.js'; -import wellKnown from './well-known.js'; -import apiServer from './api/index.js'; -import fileServer from './file/index.js'; -import proxyServer from './proxy/index.js'; -import webServer from './web/index.js'; -import { initializeStreamingServer } from './api/streaming.js'; - -export const serverLogger = new Logger('server', 'gray', false); - -// Init app -const app = new Koa(); -app.proxy = true; - -if (!['production', 'test'].includes(process.env.NODE_ENV || '')) { - // Logger - app.use(koaLogger(str => { - serverLogger.info(str); - })); - - // Delay - if (envOption.slow) { - app.use(slow({ - delay: 3000, - })); - } -} - -// HSTS -// 6months (15552000sec) -if (config.url.startsWith('https') && !config.disableHsts) { - app.use(async (ctx, next) => { - ctx.set('strict-transport-security', 'max-age=15552000; preload'); - await next(); - }); -} - -app.use(mount('/api', apiServer)); -app.use(mount('/files', fileServer)); -app.use(mount('/proxy', proxyServer)); - -// Init router -const router = new Router(); - -// Routing -router.use(activityPub.routes()); -router.use(nodeinfo.routes()); -router.use(wellKnown.routes()); - -router.get('/avatar/@:acct', async ctx => { - const { username, host } = Acct.parse(ctx.params.acct); - const user = await Users.findOne({ - where: { - usernameLower: username.toLowerCase(), - host: (host == null) || (host === config.host) ? IsNull() : host, - isSuspended: false, - }, - relations: ['avatar'], - }); - - if (user) { - ctx.redirect(Users.getAvatarUrlSync(user)); - } else { - ctx.redirect('/static-assets/user-unknown.png'); - } -}); - -router.get('/identicon/:x', async ctx => { - const [temp, cleanup] = await createTemp(); - await genIdenticon(ctx.params.x, fs.createWriteStream(temp)); - ctx.set('Content-Type', 'image/png'); - ctx.body = fs.createReadStream(temp).on('close', () => cleanup()); -}); - -router.get('/verify-email/:code', async ctx => { - const profile = await UserProfiles.findOneBy({ - emailVerifyCode: ctx.params.code, - }); - - if (profile != null) { - ctx.body = 'Verify succeeded!'; - ctx.status = 200; - - await UserProfiles.update({ userId: profile.userId }, { - emailVerified: true, - emailVerifyCode: null, - }); - - publishMainStream(profile.userId, 'meUpdated', await Users.pack(profile.userId, { id: profile.userId }, { - detail: true, - includeSecrets: true, - })); - } else { - ctx.status = 404; - } -}); - -// Register router -app.use(router.routes()); - -app.use(mount(webServer)); - -function createServer() { - return http.createServer(app.callback()); -} - -// For testing -export const startServer = () => { - const server = createServer(); - - initializeStreamingServer(server); - - server.listen(config.port); - - return server; -}; - -export default () => new Promise(resolve => { - const server = createServer(); - - initializeStreamingServer(server); - - server.on('error', e => { - switch ((e as any).code) { - case 'EACCES': - serverLogger.error(`You do not have permission to listen on port ${config.port}.`); - break; - case 'EADDRINUSE': - serverLogger.error(`Port ${config.port} is already in use by another process.`); - break; - default: - serverLogger.error(e); - break; - } - - if (cluster.isWorker) { - process.send!('listenFailed'); - } else { - // disableClustering - process.exit(1); - } - }); - - server.listen(config.port, resolve); -}); diff --git a/packages/backend/src/server/nodeinfo.ts b/packages/backend/src/server/nodeinfo.ts deleted file mode 100644 index f139d203d2..0000000000 --- a/packages/backend/src/server/nodeinfo.ts +++ /dev/null @@ -1,104 +0,0 @@ -import Router from '@koa/router'; -import config from '@/config/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Users, Notes } from '@/models/index.js'; -import { IsNull, MoreThan } from 'typeorm'; -import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import { Cache } from '@/misc/cache.js'; - -const router = new Router(); - -const nodeinfo2_1path = '/nodeinfo/2.1'; -const nodeinfo2_0path = '/nodeinfo/2.0'; - -export const links = [/* (awaiting release) { - rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1', - href: config.url + nodeinfo2_1path -}, */{ - rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', - href: config.url + nodeinfo2_0path, -}]; - -const nodeinfo2 = async () => { - const now = Date.now(); - const [ - meta, - total, - activeHalfyear, - activeMonth, - localPosts, - ] = await Promise.all([ - fetchMeta(true), - Users.count({ where: { host: IsNull() } }), - Users.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 15552000000)) } }), - Users.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 2592000000)) } }), - Notes.count({ where: { userHost: IsNull() } }), - ]); - - const proxyAccount = meta.proxyAccountId ? await Users.pack(meta.proxyAccountId).catch(() => null) : null; - - return { - software: { - name: 'misskey', - version: config.version, - repository: meta.repositoryUrl, - }, - protocols: ['activitypub'], - services: { - inbound: [] as string[], - outbound: ['atom1.0', 'rss2.0'], - }, - openRegistrations: !meta.disableRegistration, - usage: { - users: { total, activeHalfyear, activeMonth }, - localPosts, - localComments: 0, - }, - metadata: { - nodeName: meta.name, - nodeDescription: meta.description, - maintainer: { - name: meta.maintainerName, - email: meta.maintainerEmail, - }, - langs: meta.langs, - tosUrl: meta.ToSUrl, - repositoryUrl: meta.repositoryUrl, - feedbackUrl: meta.feedbackUrl, - disableRegistration: meta.disableRegistration, - disableLocalTimeline: meta.disableLocalTimeline, - disableGlobalTimeline: meta.disableGlobalTimeline, - emailRequiredForSignup: meta.emailRequiredForSignup, - enableHcaptcha: meta.enableHcaptcha, - enableRecaptcha: meta.enableRecaptcha, - maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, - enableTwitterIntegration: meta.enableTwitterIntegration, - enableGithubIntegration: meta.enableGithubIntegration, - enableDiscordIntegration: meta.enableDiscordIntegration, - enableEmail: meta.enableEmail, - enableServiceWorker: meta.enableServiceWorker, - proxyAccountName: proxyAccount ? proxyAccount.username : null, - themeColor: meta.themeColor || '#86b300', - }, - }; -}; - -const cache = new Cache>>(1000 * 60 * 10); - -router.get(nodeinfo2_1path, async ctx => { - const base = await cache.fetch(null, () => nodeinfo2()); - - ctx.body = { version: '2.1', ...base }; - ctx.set('Cache-Control', 'public, max-age=600'); -}); - -router.get(nodeinfo2_0path, async ctx => { - const base = await cache.fetch(null, () => nodeinfo2()); - - delete base.software.repository; - - ctx.body = { version: '2.0', ...base }; - ctx.set('Cache-Control', 'public, max-age=600'); -}); - -export default router; diff --git a/packages/backend/src/server/proxy/index.ts b/packages/backend/src/server/proxy/index.ts deleted file mode 100644 index 506ba10ef1..0000000000 --- a/packages/backend/src/server/proxy/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Media Proxy - */ - -import Koa from 'koa'; -import cors from '@koa/cors'; -import Router from '@koa/router'; -import { proxyMedia } from './proxy-media.js'; - -// Init app -const app = new Koa(); -app.use(cors()); -app.use(async (ctx, next) => { - ctx.set('Content-Security-Policy', `default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'`); - await next(); -}); - -// Init router -const router = new Router(); - -router.get('/:url*', proxyMedia); - -// Register router -app.use(router.routes()); - -export default app; diff --git a/packages/backend/src/server/proxy/proxy-media.ts b/packages/backend/src/server/proxy/proxy-media.ts deleted file mode 100644 index ca036e8fdf..0000000000 --- a/packages/backend/src/server/proxy/proxy-media.ts +++ /dev/null @@ -1,98 +0,0 @@ -import * as fs from 'node:fs'; -import Koa from 'koa'; -import sharp from 'sharp'; -import { IImage, convertToWebp } from '@/services/drive/image-processor.js'; -import { createTemp } from '@/misc/create-temp.js'; -import { downloadUrl } from '@/misc/download-url.js'; -import { detectType } from '@/misc/get-file-info.js'; -import { StatusError } from '@/misc/fetch.js'; -import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; -import { serverLogger } from '../index.js'; -import { isMimeImage } from '@/misc/is-mime-image.js'; - -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export async function proxyMedia(ctx: Koa.Context) { - const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url; - - if (typeof url !== 'string') { - ctx.status = 400; - return; - } - - // Create temp file - const [path, cleanup] = await createTemp(); - - try { - await downloadUrl(url, path); - - const { mime, ext } = await detectType(path); - const isConvertibleImage = isMimeImage(mime, 'sharp-convertible-image'); - - let image: IImage; - - if ('static' in ctx.query && isConvertibleImage) { - image = await convertToWebp(path, 498, 280); - } else if ('preview' in ctx.query && isConvertibleImage) { - image = await convertToWebp(path, 200, 200); - } else if ('badge' in ctx.query) { - if (!isConvertibleImage) { - // 画像でないなら404でお茶を濁す - throw new StatusError('Unexpected mime', 404); - } - - const mask = sharp(path) - .resize(96, 96, { - fit: 'inside', - withoutEnlargement: false, - }) - .greyscale() - .normalise() - .linear(1.75, -(128 * 1.75) + 128) // 1.75x contrast - .flatten({ background: '#000' }) - .toColorspace('b-w'); - - const stats = await mask.clone().stats(); - - if (stats.entropy < 0.1) { - // エントロピーがあまりない場合は404にする - throw new StatusError('Skip to provide badge', 404); - } - - const data = sharp({ - create: { width: 96, height: 96, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } }, - }) - .pipelineColorspace('b-w') - .boolean(await mask.png().toBuffer(), 'eor'); - - image = { - data: await data.png().toBuffer(), - ext: 'png', - type: 'image/png', - }; - } else if (mime === 'image/svg+xml') { - image = await convertToWebp(path, 2048, 2048, 1); - } else if (!mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(mime)) { - throw new StatusError('Rejected type', 403, 'Rejected type'); - } else { - image = { - data: fs.readFileSync(path), - ext, - type: mime, - }; - } - - ctx.set('Content-Type', image.type); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); - ctx.body = image.data; - } catch (e) { - serverLogger.error(`${e}`); - - if (e instanceof StatusError && (e.statusCode === 302 || e.isClientError)) { - ctx.status = e.statusCode; - } else { - ctx.status = 500; - } - } finally { - cleanup(); - } -} diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts new file mode 100644 index 0000000000..67a7efaa25 --- /dev/null +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -0,0 +1,594 @@ +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { PathOrFileDescriptor, readFileSync } from 'node:fs'; +import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; +import Koa from 'koa'; +import Router from '@koa/router'; +import send from 'koa-send'; +import favicon from 'koa-favicon'; +import views from 'koa-views'; +import sharp from 'sharp'; +import { createBullBoard } from '@bull-board/api'; +import { BullAdapter } from '@bull-board/api/bullAdapter.js'; +import { KoaAdapter } from '@bull-board/koa'; +import { In, IsNull } from 'typeorm'; +import { Config } from '@/config.js'; +import { getNoteSummary } from '@/misc/get-note-summary.js'; +import { DI } from '@/di-symbols.js'; +import * as Acct from '@/misc/acct.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/queue/QueueModule.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { PageEntityService } from '@/core/entities/PageEntityService.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import manifest from './manifest.json' assert { type: 'json' }; +import { FeedService } from './FeedService.js'; +import { UrlPreviewService } from './UrlPreviewService.js'; + +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); + +const staticAssets = `${_dirname}/../../../assets/`; +const clientAssets = `${_dirname}/../../../../client/assets/`; +const assets = `${_dirname}/../../../../../built/_client_dist_/`; +const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`; + +@Injectable() +export class ClientServerService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, + + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + + private userEntityService: UserEntityService, + private noteEntityService: NoteEntityService, + private pageEntityService: PageEntityService, + private galleryPostEntityService: GalleryPostEntityService, + private clipEntityService: ClipEntityService, + private channelEntityService: ChannelEntityService, + private metaService: MetaService, + private urlPreviewService: UrlPreviewService, + private feedService: FeedService, + + @Inject('queue:system') public systemQueue: SystemQueue, + @Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue, + @Inject('queue:deliver') public deliverQueue: DeliverQueue, + @Inject('queue:inbox') public inboxQueue: InboxQueue, + @Inject('queue:db') public dbQueue: DbQueue, + @Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue, + @Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue, + ) { + } + + async #manifestHandler(ctx: Koa.Context) { + // TODO + //const res = structuredClone(manifest); + const res = JSON.parse(JSON.stringify(manifest)); + + const instance = await this.metaService.fetch(true); + + res.short_name = instance.name ?? 'Misskey'; + res.name = instance.name ?? 'Misskey'; + if (instance.themeColor) res.theme_color = instance.themeColor; + + ctx.set('Cache-Control', 'max-age=300'); + ctx.body = res; + } + + public createApp() { + const app = new Koa(); + + //#region Bull Dashboard + const bullBoardPath = '/queue'; + + // Authenticate + app.use(async (ctx, next) => { + if (ctx.path === bullBoardPath || ctx.path.startsWith(bullBoardPath + '/')) { + const token = ctx.cookies.get('token'); + if (token == null) { + ctx.status = 401; + return; + } + const user = await this.usersRepository.findOneBy({ token }); + if (user == null || !(user.isAdmin || user.isModerator)) { + ctx.status = 403; + return; + } + } + await next(); + }); + + const serverAdapter = new KoaAdapter(); + + createBullBoard({ + queues: [ + this.systemQueue, + this.endedPollNotificationQueue, + this.deliverQueue, + this.inboxQueue, + this.dbQueue, + this.objectStorageQueue, + this.webhookDeliverQueue, + ].map(q => new BullAdapter(q)), + serverAdapter, + }); + + serverAdapter.setBasePath(bullBoardPath); + app.use(serverAdapter.registerPlugin()); + //#endregion + + // Init renderer + app.use(views(_dirname + '/views', { + extension: 'pug', + options: { + version: this.config.version, + getClientEntry: () => process.env.NODE_ENV === 'production' ? + this.config.clientEntry : + JSON.parse(readFileSync(`${_dirname}/../../../../../built/_client_dist_/manifest.json`, 'utf-8'))['src/init.ts'], + config: this.config, + }, + })); + + // Serve favicon + app.use(favicon(`${_dirname}/../../../assets/favicon.ico`)); + + // Common request handler + app.use(async (ctx, next) => { + // IFrameの中に入れられないようにする + ctx.set('X-Frame-Options', 'DENY'); + await next(); + }); + + // Init router + const router = new Router(); + + //#region static assets + + router.get('/static-assets/(.*)', async ctx => { + await send(ctx as any, ctx.path.replace('/static-assets/', ''), { + root: staticAssets, + maxage: ms('7 days'), + }); + }); + + router.get('/client-assets/(.*)', async ctx => { + await send(ctx as any, ctx.path.replace('/client-assets/', ''), { + root: clientAssets, + maxage: ms('7 days'), + }); + }); + + router.get('/assets/(.*)', async ctx => { + await send(ctx as any, ctx.path.replace('/assets/', ''), { + root: assets, + maxage: ms('7 days'), + }); + }); + + // Apple touch icon + router.get('/apple-touch-icon.png', async ctx => { + await send(ctx as any, '/apple-touch-icon.png', { + root: staticAssets, + }); + }); + + router.get('/twemoji/(.*)', async ctx => { + const path = ctx.path.replace('/twemoji/', ''); + + if (!path.match(/^[0-9a-f-]+\.svg$/)) { + ctx.status = 404; + return; + } + + ctx.set('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); + + await send(ctx as any, path, { + root: `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/`, + maxage: ms('30 days'), + }); + }); + + router.get('/twemoji-badge/(.*)', async ctx => { + const path = ctx.path.replace('/twemoji-badge/', ''); + + if (!path.match(/^[0-9a-f-]+\.png$/)) { + ctx.status = 404; + return; + } + + const mask = await sharp( + `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/${path.replace('.png', '')}.svg`, + { density: 1000 }, + ) + .resize(488, 488) + .greyscale() + .normalise() + .linear(1.75, -(128 * 1.75) + 128) // 1.75x contrast + .flatten({ background: '#000' }) + .extend({ + top: 12, + bottom: 12, + left: 12, + right: 12, + background: '#000', + }) + .toColorspace('b-w') + .png() + .toBuffer(); + + const buffer = await sharp({ + create: { width: 512, height: 512, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } }, + }) + .pipelineColorspace('b-w') + .boolean(mask, 'eor') + .resize(96, 96) + .png() + .toBuffer(); + + ctx.set('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); + ctx.set('Cache-Control', 'max-age=2592000'); + ctx.set('Content-Type', 'image/png'); + ctx.body = buffer; + }); + + // ServiceWorker + router.get('/sw.js', async ctx => { + await send(ctx as any, '/sw.js', { + root: swAssets, + maxage: ms('10 minutes'), + }); + }); + + // Manifest + router.get('/manifest.json', ctx => this.#manifestHandler(ctx)); + + router.get('/robots.txt', async ctx => { + await send(ctx as any, '/robots.txt', { + root: staticAssets, + }); + }); + + //#endregion + + // Docs + router.get('/api-doc', async ctx => { + await send(ctx as any, '/redoc.html', { + root: staticAssets, + }); + }); + + // URL preview endpoint + router.get('/url', ctx => this.urlPreviewService.handle(ctx)); + + router.get('/api.json', async ctx => { + ctx.body = genOpenapiSpec(); + }); + + const getFeed = async (acct: string) => { + const { username, host } = Acct.parse(acct); + const user = await this.usersRepository.findOneBy({ + usernameLower: username.toLowerCase(), + host: host ?? IsNull(), + isSuspended: false, + }); + + return user && await this.feedService.packFeed(user); + }; + + // Atom + router.get('/@:user.atom', async ctx => { + const feed = await getFeed(ctx.params.user); + + if (feed) { + ctx.set('Content-Type', 'application/atom+xml; charset=utf-8'); + ctx.body = feed.atom1(); + } else { + ctx.status = 404; + } + }); + + // RSS + router.get('/@:user.rss', async ctx => { + const feed = await getFeed(ctx.params.user); + + if (feed) { + ctx.set('Content-Type', 'application/rss+xml; charset=utf-8'); + ctx.body = feed.rss2(); + } else { + ctx.status = 404; + } + }); + + // JSON + router.get('/@:user.json', async ctx => { + const feed = await getFeed(ctx.params.user); + + if (feed) { + ctx.set('Content-Type', 'application/json; charset=utf-8'); + ctx.body = feed.json1(); + } else { + ctx.status = 404; + } + }); + + //#region SSR (for crawlers) + // User + router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => { + const { username, host } = Acct.parse(ctx.params.user); + const user = await this.usersRepository.findOneBy({ + usernameLower: username.toLowerCase(), + host: host ?? IsNull(), + isSuspended: false, + }); + + if (user != null) { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + const meta = await this.metaService.fetch(); + const me = profile.fields + ? profile.fields + .filter(filed => filed.value != null && filed.value.match(/^https?:/)) + .map(field => field.value) + : []; + + await ctx.render('user', { + user, profile, me, + avatarUrl: await this.userEntityService.getAvatarUrl(user), + sub: ctx.params.sub, + instanceName: meta.name ?? 'Misskey', + icon: meta.iconUrl, + themeColor: meta.themeColor, + }); + ctx.set('Cache-Control', 'public, max-age=15'); + } else { + // リモートユーザーなので + // モデレータがAPI経由で参照可能にするために404にはしない + await next(); + } + }); + + router.get('/users/:user', async ctx => { + const user = await this.usersRepository.findOneBy({ + id: ctx.params.user, + host: IsNull(), + isSuspended: false, + }); + + if (user == null) { + ctx.status = 404; + return; + } + + ctx.redirect(`/@${user.username}${ user.host == null ? '' : '@' + user.host}`); + }); + + // Note + router.get('/notes/:note', async (ctx, next) => { + const note = await this.notesRepository.findOneBy({ + id: ctx.params.note, + visibility: In(['public', 'home']), + }); + + if (note) { + const _note = await this.noteEntityService.pack(note); + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId }); + const meta = await this.metaService.fetch(); + await ctx.render('note', { + note: _note, + profile, + avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: note.userId })), + // TODO: Let locale changeable by instance setting + summary: getNoteSummary(_note), + instanceName: meta.name ?? 'Misskey', + icon: meta.iconUrl, + themeColor: meta.themeColor, + }); + + ctx.set('Cache-Control', 'public, max-age=15'); + + return; + } + + await next(); + }); + + // Page + router.get('/@:user/pages/:page', async (ctx, next) => { + const { username, host } = Acct.parse(ctx.params.user); + const user = await this.usersRepository.findOneBy({ + usernameLower: username.toLowerCase(), + host: host ?? IsNull(), + }); + + if (user == null) return; + + const page = await this.pagesRepository.findOneBy({ + name: ctx.params.page, + userId: user.id, + }); + + if (page) { + const _page = await this.pageEntityService.pack(page); + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: page.userId }); + const meta = await this.metaService.fetch(); + await ctx.render('page', { + page: _page, + profile, + avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: page.userId })), + instanceName: meta.name ?? 'Misskey', + icon: meta.iconUrl, + themeColor: meta.themeColor, + }); + + if (['public'].includes(page.visibility)) { + ctx.set('Cache-Control', 'public, max-age=15'); + } else { + ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + } + + return; + } + + await next(); + }); + + // Clip + // TODO: 非publicなclipのハンドリング + router.get('/clips/:clip', async (ctx, next) => { + const clip = await this.clipsRepository.findOneBy({ + id: ctx.params.clip, + }); + + if (clip) { + const _clip = await this.clipEntityService.pack(clip); + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId }); + const meta = await this.metaService.fetch(); + await ctx.render('clip', { + clip: _clip, + profile, + avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: clip.userId })), + instanceName: meta.name ?? 'Misskey', + icon: meta.iconUrl, + themeColor: meta.themeColor, + }); + + ctx.set('Cache-Control', 'public, max-age=15'); + + return; + } + + await next(); + }); + + // Gallery post + router.get('/gallery/:post', async (ctx, next) => { + const post = await this.galleryPostsRepository.findOneBy({ id: ctx.params.post }); + + if (post) { + const _post = await this.galleryPostEntityService.pack(post); + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId }); + const meta = await this.metaService.fetch(); + await ctx.render('gallery-post', { + post: _post, + profile, + avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: post.userId })), + instanceName: meta.name ?? 'Misskey', + icon: meta.iconUrl, + themeColor: meta.themeColor, + }); + + ctx.set('Cache-Control', 'public, max-age=15'); + + return; + } + + await next(); + }); + + // Channel + router.get('/channels/:channel', async (ctx, next) => { + const channel = await this.channelsRepository.findOneBy({ + id: ctx.params.channel, + }); + + if (channel) { + const _channel = await this.channelEntityService.pack(channel); + const meta = await this.metaService.fetch(); + await ctx.render('channel', { + channel: _channel, + instanceName: meta.name ?? 'Misskey', + icon: meta.iconUrl, + themeColor: meta.themeColor, + }); + + ctx.set('Cache-Control', 'public, max-age=15'); + + return; + } + + await next(); + }); + //#endregion + + router.get('/_info_card_', async ctx => { + const meta = await this.metaService.fetch(true); + + ctx.remove('X-Frame-Options'); + + await ctx.render('info-card', { + version: this.config.version, + host: this.config.host, + meta: meta, + originalUsersCount: await this.usersRepository.countBy({ host: IsNull() }), + originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }), + }); + }); + + router.get('/bios', async ctx => { + await ctx.render('bios', { + version: this.config.version, + }); + }); + + router.get('/cli', async ctx => { + await ctx.render('cli', { + version: this.config.version, + }); + }); + + const override = (source: string, target: string, depth = 0) => + [, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); + + router.get('/flush', async ctx => { + await ctx.render('flush'); + }); + + // streamingに非WebSocketリクエストが来た場合にbase htmlをキャシュ付きで返すと、Proxy等でそのパスがキャッシュされておかしくなる + router.get('/streaming', async ctx => { + ctx.status = 503; + ctx.set('Cache-Control', 'private, max-age=0'); + }); + + // Render base html for all requests + router.get('(.*)', async ctx => { + const meta = await this.metaService.fetch(); + await ctx.render('base', { + img: meta.bannerUrl, + title: meta.name ?? 'Misskey', + instanceName: meta.name ?? 'Misskey', + desc: meta.description, + icon: meta.iconUrl, + themeColor: meta.themeColor, + }); + ctx.set('Cache-Control', 'public, max-age=15'); + }); + + // Register router + app.use(router.routes()); + + return app; + } +} diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts new file mode 100644 index 0000000000..8b676aebe5 --- /dev/null +++ b/packages/backend/src/server/web/FeedService.ts @@ -0,0 +1,86 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { In, IsNull } from 'typeorm'; +import { Feed } from 'feed'; +import { DI } from '@/di-symbols.js'; +import { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import { Config } from '@/config.js'; +import type { User } from '@/models/entities/User.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; + +@Injectable() +export class FeedService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private userEntityService: UserEntityService, + private driveFileEntityService: DriveFileEntityService, + ) { + } + + public async packFeed(user: User) { + const author = { + link: `${this.config.url}/@${user.username}`, + name: user.name ?? user.username, + }; + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + const notes = await this.notesRepository.find({ + where: { + userId: user.id, + renoteId: IsNull(), + visibility: In(['public', 'home']), + }, + order: { createdAt: -1 }, + take: 20, + }); + + const feed = new Feed({ + id: author.link, + title: `${author.name} (@${user.username}@${this.config.host})`, + updated: notes[0].createdAt, + generator: 'Misskey', + description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`, + link: author.link, + image: await this.userEntityService.getAvatarUrl(user), + feedLinks: { + json: `${author.link}.json`, + atom: `${author.link}.atom`, + }, + author, + copyright: user.name ?? user.username, + }); + + for (const note of notes) { + const files = note.fileIds.length > 0 ? await this.driveFilesRepository.findBy({ + id: In(note.fileIds), + }) : []; + const file = files.find(file => file.type.startsWith('image/')); + + feed.addItem({ + title: `New note by ${author.name}`, + link: `${this.config.url}/notes/${note.id}`, + date: note.createdAt, + description: note.cw ?? undefined, + content: note.text ?? undefined, + image: file ? this.driveFileEntityService.getPublicUrl(file) ?? undefined : undefined, + }); + } + + return feed; + } +} diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts new file mode 100644 index 0000000000..4e3b456144 --- /dev/null +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -0,0 +1,84 @@ +import { Inject, Injectable } from '@nestjs/common'; +import summaly from 'summaly'; +import { DI } from '@/di-symbols.js'; +import { UsersRepository } from '@/models/index.js'; +import { Config } from '@/config.js'; +import { MetaService } from '@/core/MetaService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; +import Logger from '@/logger.js'; +import { query } from '@/misc/prelude/url.js'; +import type Koa from 'koa'; + +@Injectable() +export class UrlPreviewService { + #logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private metaService: MetaService, + private httpRequestService: HttpRequestService, + ) { + this.#logger = new Logger('url-preview'); + } + + #wrap(url?: string): string | null { + return url != null + ? url.match(/^https?:\/\//) + ? `${this.config.url}/proxy/preview.webp?${query({ + url, + preview: '1', + })}` + : url + : null; + } + + public async handle(ctx: Koa.Context) { + const url = ctx.query.url; + if (typeof url !== 'string') { + ctx.status = 400; + return; + } + + const lang = ctx.query.lang; + if (Array.isArray(lang)) { + ctx.status = 400; + return; + } + + const meta = await this.metaService.fetch(); + + this.#logger.info(meta.summalyProxy + ? `(Proxy) Getting preview of ${url}@${lang} ...` + : `Getting preview of ${url}@${lang} ...`); + + try { + const summary = meta.summalyProxy ? await this.httpRequestService.getJson(`${meta.summalyProxy}?${query({ + url: url, + lang: lang ?? 'ja-JP', + })}`) : await summaly.default(url, { + followRedirects: false, + lang: lang ?? 'ja-JP', + }); + + this.#logger.succ(`Got preview of ${url}: ${summary.title}`); + + summary.icon = this.#wrap(summary.icon); + summary.thumbnail = this.#wrap(summary.thumbnail); + + // Cache 7days + ctx.set('Cache-Control', 'max-age=604800, immutable'); + + ctx.body = summary; + } catch (err) { + this.#logger.warn(`Failed to get preview of ${url}: ${err}`); + ctx.status = 200; + ctx.set('Cache-Control', 'max-age=86400, immutable'); + ctx.body = '{}'; + } + } +} diff --git a/packages/backend/src/server/web/feed.ts b/packages/backend/src/server/web/feed.ts deleted file mode 100644 index 4abe2885cf..0000000000 --- a/packages/backend/src/server/web/feed.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Feed } from 'feed'; -import { In, IsNull } from 'typeorm'; -import config from '@/config/index.js'; -import { User } from '@/models/entities/user.js'; -import { Notes, DriveFiles, UserProfiles, Users } from '@/models/index.js'; - -export default async function(user: User) { - const author = { - link: `${config.url}/@${user.username}`, - name: user.name || user.username, - }; - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - const notes = await Notes.find({ - where: { - userId: user.id, - renoteId: IsNull(), - visibility: In(['public', 'home']), - }, - order: { createdAt: -1 }, - take: 20, - }); - - const feed = new Feed({ - id: author.link, - title: `${author.name} (@${user.username}@${config.host})`, - updated: notes[0].createdAt, - generator: 'Misskey', - description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`, - link: author.link, - image: await Users.getAvatarUrl(user), - feedLinks: { - json: `${author.link}.json`, - atom: `${author.link}.atom`, - }, - author, - copyright: user.name || user.username, - }); - - for (const note of notes) { - const files = note.fileIds.length > 0 ? await DriveFiles.findBy({ - id: In(note.fileIds), - }) : []; - const file = files.find(file => file.type.startsWith('image/')); - - feed.addItem({ - title: `New note by ${author.name}`, - link: `${config.url}/notes/${note.id}`, - date: note.createdAt, - description: note.cw || undefined, - content: note.text || undefined, - image: file ? DriveFiles.getPublicUrl(file) || undefined : undefined, - }); - } - - return feed; -} diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts deleted file mode 100644 index be95becb68..0000000000 --- a/packages/backend/src/server/web/index.ts +++ /dev/null @@ -1,521 +0,0 @@ -/** - * Web Client Server - */ - -import { dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { PathOrFileDescriptor, readFileSync } from 'node:fs'; -import ms from 'ms'; -import Koa from 'koa'; -import Router from '@koa/router'; -import send from 'koa-send'; -import favicon from 'koa-favicon'; -import views from 'koa-views'; -import sharp from 'sharp'; -import { createBullBoard } from '@bull-board/api'; -import { BullAdapter } from '@bull-board/api/bullAdapter.js'; -import { KoaAdapter } from '@bull-board/koa'; - -import { In, IsNull } from 'typeorm'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import config from '@/config/index.js'; -import { Users, Notes, UserProfiles, Pages, Channels, Clips, GalleryPosts } from '@/models/index.js'; -import * as Acct from '@/misc/acct.js'; -import { getNoteSummary } from '@/misc/get-note-summary.js'; -import { queues } from '@/queue/queues.js'; -import { genOpenapiSpec } from '../api/openapi/gen-spec.js'; -import { urlPreviewHandler } from './url-preview.js'; -import { manifestHandler } from './manifest.js'; -import packFeed from './feed.js'; - -const _filename = fileURLToPath(import.meta.url); -const _dirname = dirname(_filename); - -const staticAssets = `${_dirname}/../../../assets/`; -const clientAssets = `${_dirname}/../../../../client/assets/`; -const assets = `${_dirname}/../../../../../built/_client_dist_/`; -const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`; - -// Init app -const app = new Koa(); - -//#region Bull Dashboard -const bullBoardPath = '/queue'; - -// Authenticate -app.use(async (ctx, next) => { - if (ctx.path === bullBoardPath || ctx.path.startsWith(bullBoardPath + '/')) { - const token = ctx.cookies.get('token'); - if (token == null) { - ctx.status = 401; - return; - } - const user = await Users.findOneBy({ token }); - if (user == null || !(user.isAdmin || user.isModerator)) { - ctx.status = 403; - return; - } - } - await next(); -}); - -const serverAdapter = new KoaAdapter(); - -createBullBoard({ - queues: queues.map(q => new BullAdapter(q)), - serverAdapter, -}); - -serverAdapter.setBasePath(bullBoardPath); -app.use(serverAdapter.registerPlugin()); -//#endregion - -// Init renderer -app.use(views(_dirname + '/views', { - extension: 'pug', - options: { - version: config.version, - getClientEntry: () => process.env.NODE_ENV === 'production' ? - config.clientEntry : - JSON.parse(readFileSync(`${_dirname}/../../../../../built/_client_dist_/manifest.json`, 'utf-8'))['src/init.ts'], - config, - }, -})); - -// Serve favicon -app.use(favicon(`${_dirname}/../../../assets/favicon.ico`)); - -// Common request handler -app.use(async (ctx, next) => { - // IFrameの中に入れられないようにする - ctx.set('X-Frame-Options', 'DENY'); - await next(); -}); - -// Init router -const router = new Router(); - -//#region static assets - -router.get('/static-assets/(.*)', async ctx => { - await send(ctx as any, ctx.path.replace('/static-assets/', ''), { - root: staticAssets, - maxage: ms('7 days'), - }); -}); - -router.get('/client-assets/(.*)', async ctx => { - await send(ctx as any, ctx.path.replace('/client-assets/', ''), { - root: clientAssets, - maxage: ms('7 days'), - }); -}); - -router.get('/assets/(.*)', async ctx => { - await send(ctx as any, ctx.path.replace('/assets/', ''), { - root: assets, - maxage: ms('7 days'), - }); -}); - -// Apple touch icon -router.get('/apple-touch-icon.png', async ctx => { - await send(ctx as any, '/apple-touch-icon.png', { - root: staticAssets, - }); -}); - -router.get('/twemoji/(.*)', async ctx => { - const path = ctx.path.replace('/twemoji/', ''); - - if (!path.match(/^[0-9a-f-]+\.svg$/)) { - ctx.status = 404; - return; - } - - ctx.set('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); - - await send(ctx as any, path, { - root: `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/`, - maxage: ms('30 days'), - }); -}); - -router.get('/twemoji-badge/(.*)', async ctx => { - const path = ctx.path.replace('/twemoji-badge/', ''); - - if (!path.match(/^[0-9a-f-]+\.png$/)) { - ctx.status = 404; - return; - } - - const mask = await sharp( - `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/${path.replace('.png', '')}.svg`, - { density: 1000 }, - ) - .resize(488, 488) - .greyscale() - .normalise() - .linear(1.75, -(128 * 1.75) + 128) // 1.75x contrast - .flatten({ background: '#000' }) - .extend({ - top: 12, - bottom: 12, - left: 12, - right: 12, - background: '#000', - }) - .toColorspace('b-w') - .png() - .toBuffer(); - - const buffer = await sharp({ - create: { width: 512, height: 512, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } }, - }) - .pipelineColorspace('b-w') - .boolean(mask, 'eor') - .resize(96, 96) - .png() - .toBuffer(); - - ctx.set('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); - ctx.set('Cache-Control', 'max-age=2592000'); - ctx.set('Content-Type', 'image/png'); - ctx.body = buffer; -}); - -// ServiceWorker -router.get(`/sw.js`, async ctx => { - await send(ctx as any, `/sw.js`, { - root: swAssets, - maxage: ms('10 minutes'), - }); -}); - -// Manifest -router.get('/manifest.json', manifestHandler); - -router.get('/robots.txt', async ctx => { - await send(ctx as any, '/robots.txt', { - root: staticAssets, - }); -}); - -//#endregion - -// Docs -router.get('/api-doc', async ctx => { - await send(ctx as any, '/redoc.html', { - root: staticAssets, - }); -}); - -// URL preview endpoint -router.get('/url', urlPreviewHandler); - -router.get('/api.json', async ctx => { - ctx.body = genOpenapiSpec(); -}); - -const getFeed = async (acct: string) => { - const { username, host } = Acct.parse(acct); - const user = await Users.findOneBy({ - usernameLower: username.toLowerCase(), - host: host ?? IsNull(), - isSuspended: false, - }); - - return user && await packFeed(user); -}; - -// Atom -router.get('/@:user.atom', async ctx => { - const feed = await getFeed(ctx.params.user); - - if (feed) { - ctx.set('Content-Type', 'application/atom+xml; charset=utf-8'); - ctx.body = feed.atom1(); - } else { - ctx.status = 404; - } -}); - -// RSS -router.get('/@:user.rss', async ctx => { - const feed = await getFeed(ctx.params.user); - - if (feed) { - ctx.set('Content-Type', 'application/rss+xml; charset=utf-8'); - ctx.body = feed.rss2(); - } else { - ctx.status = 404; - } -}); - -// JSON -router.get('/@:user.json', async ctx => { - const feed = await getFeed(ctx.params.user); - - if (feed) { - ctx.set('Content-Type', 'application/json; charset=utf-8'); - ctx.body = feed.json1(); - } else { - ctx.status = 404; - } -}); - -//#region SSR (for crawlers) -// User -router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => { - const { username, host } = Acct.parse(ctx.params.user); - const user = await Users.findOneBy({ - usernameLower: username.toLowerCase(), - host: host ?? IsNull(), - isSuspended: false, - }); - - if (user != null) { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - const meta = await fetchMeta(); - const me = profile.fields - ? profile.fields - .filter(filed => filed.value != null && filed.value.match(/^https?:/)) - .map(field => field.value) - : []; - - await ctx.render('user', { - user, profile, me, - avatarUrl: await Users.getAvatarUrl(user), - sub: ctx.params.sub, - instanceName: meta.name || 'Misskey', - icon: meta.iconUrl, - themeColor: meta.themeColor, - }); - ctx.set('Cache-Control', 'public, max-age=15'); - } else { - // リモートユーザーなので - // モデレータがAPI経由で参照可能にするために404にはしない - await next(); - } -}); - -router.get('/users/:user', async ctx => { - const user = await Users.findOneBy({ - id: ctx.params.user, - host: IsNull(), - isSuspended: false, - }); - - if (user == null) { - ctx.status = 404; - return; - } - - ctx.redirect(`/@${user.username}${ user.host == null ? '' : '@' + user.host}`); -}); - -// Note -router.get('/notes/:note', async (ctx, next) => { - const note = await Notes.findOneBy({ - id: ctx.params.note, - visibility: In(['public', 'home']), - }); - - if (note) { - const _note = await Notes.pack(note); - const profile = await UserProfiles.findOneByOrFail({ userId: note.userId }); - const meta = await fetchMeta(); - await ctx.render('note', { - note: _note, - profile, - avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: note.userId })), - // TODO: Let locale changeable by instance setting - summary: getNoteSummary(_note), - instanceName: meta.name || 'Misskey', - icon: meta.iconUrl, - themeColor: meta.themeColor, - }); - - ctx.set('Cache-Control', 'public, max-age=15'); - - return; - } - - await next(); -}); - -// Page -router.get('/@:user/pages/:page', async (ctx, next) => { - const { username, host } = Acct.parse(ctx.params.user); - const user = await Users.findOneBy({ - usernameLower: username.toLowerCase(), - host: host ?? IsNull(), - }); - - if (user == null) return; - - const page = await Pages.findOneBy({ - name: ctx.params.page, - userId: user.id, - }); - - if (page) { - const _page = await Pages.pack(page); - const profile = await UserProfiles.findOneByOrFail({ userId: page.userId }); - const meta = await fetchMeta(); - await ctx.render('page', { - page: _page, - profile, - avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: page.userId })), - instanceName: meta.name || 'Misskey', - icon: meta.iconUrl, - themeColor: meta.themeColor, - }); - - if (['public'].includes(page.visibility)) { - ctx.set('Cache-Control', 'public, max-age=15'); - } else { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); - } - - return; - } - - await next(); -}); - -// Clip -// TODO: 非publicなclipのハンドリング -router.get('/clips/:clip', async (ctx, next) => { - const clip = await Clips.findOneBy({ - id: ctx.params.clip, - }); - - if (clip) { - const _clip = await Clips.pack(clip); - const profile = await UserProfiles.findOneByOrFail({ userId: clip.userId }); - const meta = await fetchMeta(); - await ctx.render('clip', { - clip: _clip, - profile, - avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: clip.userId })), - instanceName: meta.name || 'Misskey', - icon: meta.iconUrl, - themeColor: meta.themeColor, - }); - - ctx.set('Cache-Control', 'public, max-age=15'); - - return; - } - - await next(); -}); - -// Gallery post -router.get('/gallery/:post', async (ctx, next) => { - const post = await GalleryPosts.findOneBy({ id: ctx.params.post }); - - if (post) { - const _post = await GalleryPosts.pack(post); - const profile = await UserProfiles.findOneByOrFail({ userId: post.userId }); - const meta = await fetchMeta(); - await ctx.render('gallery-post', { - post: _post, - profile, - avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: post.userId })), - instanceName: meta.name || 'Misskey', - icon: meta.iconUrl, - themeColor: meta.themeColor, - }); - - ctx.set('Cache-Control', 'public, max-age=15'); - - return; - } - - await next(); -}); - -// Channel -router.get('/channels/:channel', async (ctx, next) => { - const channel = await Channels.findOneBy({ - id: ctx.params.channel, - }); - - if (channel) { - const _channel = await Channels.pack(channel); - const meta = await fetchMeta(); - await ctx.render('channel', { - channel: _channel, - instanceName: meta.name || 'Misskey', - icon: meta.iconUrl, - themeColor: meta.themeColor, - }); - - ctx.set('Cache-Control', 'public, max-age=15'); - - return; - } - - await next(); -}); -//#endregion - -router.get('/_info_card_', async ctx => { - const meta = await fetchMeta(true); - - ctx.remove('X-Frame-Options'); - - await ctx.render('info-card', { - version: config.version, - host: config.host, - meta: meta, - originalUsersCount: await Users.countBy({ host: IsNull() }), - originalNotesCount: await Notes.countBy({ userHost: IsNull() }), - }); -}); - -router.get('/bios', async ctx => { - await ctx.render('bios', { - version: config.version, - }); -}); - -router.get('/cli', async ctx => { - await ctx.render('cli', { - version: config.version, - }); -}); - -const override = (source: string, target: string, depth = 0) => - [, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); - -router.get('/flush', async ctx => { - await ctx.render('flush'); -}); - -// streamingに非WebSocketリクエストが来た場合にbase htmlをキャシュ付きで返すと、Proxy等でそのパスがキャッシュされておかしくなる -router.get('/streaming', async ctx => { - ctx.status = 503; - ctx.set('Cache-Control', 'private, max-age=0'); -}); - -// Render base html for all requests -router.get('(.*)', async ctx => { - const meta = await fetchMeta(); - await ctx.render('base', { - img: meta.bannerUrl, - title: meta.name || 'Misskey', - instanceName: meta.name || 'Misskey', - desc: meta.description, - icon: meta.iconUrl, - themeColor: meta.themeColor, - }); - ctx.set('Cache-Control', 'public, max-age=15'); -}); - -// Register router -app.use(router.routes()); - -export default app; diff --git a/packages/backend/src/server/web/manifest.ts b/packages/backend/src/server/web/manifest.ts deleted file mode 100644 index ee568b8077..0000000000 --- a/packages/backend/src/server/web/manifest.ts +++ /dev/null @@ -1,18 +0,0 @@ -import Koa from 'koa'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import manifest from './manifest.json' assert { type: 'json' }; - -export const manifestHandler = async (ctx: Koa.Context) => { - // TODO - //const res = structuredClone(manifest); - const res = JSON.parse(JSON.stringify(manifest)); - - const instance = await fetchMeta(true); - - res.short_name = instance.name || 'Misskey'; - res.name = instance.name || 'Misskey'; - if (instance.themeColor) res.theme_color = instance.themeColor; - - ctx.set('Cache-Control', 'max-age=300'); - ctx.body = res; -}; diff --git a/packages/backend/src/server/web/url-preview.ts b/packages/backend/src/server/web/url-preview.ts deleted file mode 100644 index 1e259649f9..0000000000 --- a/packages/backend/src/server/web/url-preview.ts +++ /dev/null @@ -1,65 +0,0 @@ -import Koa from 'koa'; -import summaly from 'summaly'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import Logger from '@/services/logger.js'; -import config from '@/config/index.js'; -import { query } from '@/prelude/url.js'; -import { getJson } from '@/misc/fetch.js'; - -const logger = new Logger('url-preview'); - -export const urlPreviewHandler = async (ctx: Koa.Context) => { - const url = ctx.query.url; - if (typeof url !== 'string') { - ctx.status = 400; - return; - } - - const lang = ctx.query.lang; - if (Array.isArray(lang)) { - ctx.status = 400; - return; - } - - const meta = await fetchMeta(); - - logger.info(meta.summalyProxy - ? `(Proxy) Getting preview of ${url}@${lang} ...` - : `Getting preview of ${url}@${lang} ...`); - - try { - const summary = meta.summalyProxy ? await getJson(`${meta.summalyProxy}?${query({ - url: url, - lang: lang ?? 'ja-JP', - })}`) : await summaly.default(url, { - followRedirects: false, - lang: lang ?? 'ja-JP', - }); - - logger.succ(`Got preview of ${url}: ${summary.title}`); - - summary.icon = wrap(summary.icon); - summary.thumbnail = wrap(summary.thumbnail); - - // Cache 7days - ctx.set('Cache-Control', 'max-age=604800, immutable'); - - ctx.body = summary; - } catch (err) { - logger.warn(`Failed to get preview of ${url}: ${err}`); - ctx.status = 200; - ctx.set('Cache-Control', 'max-age=86400, immutable'); - ctx.body = '{}'; - } -}; - -function wrap(url?: string): string | null { - return url != null - ? url.match(/^https?:\/\//) - ? `${config.url}/proxy/preview.webp?${query({ - url, - preview: '1', - })}` - : url - : null; -} diff --git a/packages/backend/src/server/well-known.ts b/packages/backend/src/server/well-known.ts deleted file mode 100644 index 1d094f2edd..0000000000 --- a/packages/backend/src/server/well-known.ts +++ /dev/null @@ -1,151 +0,0 @@ -import Router from '@koa/router'; - -import config from '@/config/index.js'; -import * as Acct from '@/misc/acct.js'; -import { links } from './nodeinfo.js'; -import { escapeAttribute, escapeValue } from '@/prelude/xml.js'; -import { Users } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import { FindOptionsWhere, IsNull } from 'typeorm'; - -// Init router -const router = new Router(); - -const XRD = (...x: { element: string, value?: string, attributes?: Record }[]) => - `${x.map(({ element, value, attributes }) => - `<${ - Object.entries(typeof attributes === 'object' && attributes || {}).reduce((a, [k, v]) => `${a} ${k}="${escapeAttribute(v)}"`, element) - }${ - typeof value === 'string' ? `>${escapeValue(value)}`).reduce((a, c) => a + c, '')}`; - -const allPath = '/.well-known/(.*)'; -const webFingerPath = '/.well-known/webfinger'; -const jrd = 'application/jrd+json'; -const xrd = 'application/xrd+xml'; - -router.use(allPath, async (ctx, next) => { - ctx.set({ - 'Access-Control-Allow-Headers': 'Accept', - 'Access-Control-Allow-Methods': 'GET, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Expose-Headers': 'Vary', - }); - await next(); -}); - -router.options(allPath, async ctx => { - ctx.status = 204; -}); - -router.get('/.well-known/host-meta', async ctx => { - ctx.set('Content-Type', xrd); - ctx.body = XRD({ element: 'Link', attributes: { - rel: 'lrdd', - type: xrd, - template: `${config.url}${webFingerPath}?resource={uri}`, - } }); -}); - -router.get('/.well-known/host-meta.json', async ctx => { - ctx.set('Content-Type', jrd); - ctx.body = { - links: [{ - rel: 'lrdd', - type: jrd, - template: `${config.url}${webFingerPath}?resource={uri}`, - }], - }; -}); - -router.get('/.well-known/nodeinfo', async ctx => { - ctx.body = { links }; -}); - -/* TODO -router.get('/.well-known/change-password', async ctx => { -}); -*/ - -router.get(webFingerPath, async ctx => { - const fromId = (id: User['id']): FindOptionsWhere => ({ - id, - host: IsNull(), - isSuspended: false, - }); - - const generateQuery = (resource: string): FindOptionsWhere | number => - resource.startsWith(`${config.url.toLowerCase()}/users/`) ? - fromId(resource.split('/').pop()!) : - fromAcct(Acct.parse( - resource.startsWith(`${config.url.toLowerCase()}/@`) ? resource.split('/').pop()! : - resource.startsWith('acct:') ? resource.slice('acct:'.length) : - resource)); - - const fromAcct = (acct: Acct.Acct): FindOptionsWhere | number => - !acct.host || acct.host === config.host.toLowerCase() ? { - usernameLower: acct.username, - host: IsNull(), - isSuspended: false, - } : 422; - - if (typeof ctx.query.resource !== 'string') { - ctx.status = 400; - return; - } - - const query = generateQuery(ctx.query.resource.toLowerCase()); - - if (typeof query === 'number') { - ctx.status = query; - return; - } - - const user = await Users.findOneBy(query); - - if (user == null) { - ctx.status = 404; - return; - } - - const subject = `acct:${user.username}@${config.host}`; - const self = { - rel: 'self', - type: 'application/activity+json', - href: `${config.url}/users/${user.id}`, - }; - const profilePage = { - rel: 'http://webfinger.net/rel/profile-page', - type: 'text/html', - href: `${config.url}/@${user.username}`, - }; - const subscribe = { - rel: 'http://ostatus.org/schema/1.0/subscribe', - template: `${config.url}/authorize-follow?acct={uri}`, - }; - - if (ctx.accepts(jrd, xrd) === xrd) { - ctx.body = XRD( - { element: 'Subject', value: subject }, - { element: 'Link', attributes: self }, - { element: 'Link', attributes: profilePage }, - { element: 'Link', attributes: subscribe }); - ctx.type = xrd; - } else { - ctx.body = { - subject, - links: [self, profilePage, subscribe], - }; - ctx.type = jrd; - } - - ctx.vary('Accept'); - ctx.set('Cache-Control', 'public, max-age=180'); -}); - -// Return 404 for other .well-known -router.all(allPath, async ctx => { - ctx.status = 404; -}); - -export default router; -- cgit v1.2.3-freya From dd35f2cce6789ce8a0cdf15f0db86506efb07a8f Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 18 Sep 2022 23:07:41 +0900 Subject: refactor(backend): refactor logger --- packages/backend/src/core/CoreModule.ts | 10 +++++++ packages/backend/src/core/DownloadService.ts | 6 ++-- packages/backend/src/core/EmailService.ts | 6 ++-- .../src/core/FetchInstanceMetadataService.ts | 25 ++++++++-------- packages/backend/src/core/LoggerService.ts | 33 ++++++++++++++++++++++ .../backend/src/core/chart/ChartLoggerService.ts | 14 +++++++++ .../backend/src/core/chart/charts/active-users.ts | 4 ++- .../backend/src/core/chart/charts/ap-request.ts | 4 ++- packages/backend/src/core/chart/charts/drive.ts | 4 ++- .../backend/src/core/chart/charts/federation.ts | 4 ++- packages/backend/src/core/chart/charts/hashtag.ts | 4 ++- packages/backend/src/core/chart/charts/instance.ts | 4 ++- packages/backend/src/core/chart/charts/notes.ts | 4 ++- .../src/core/chart/charts/per-user-drive.ts | 4 ++- .../src/core/chart/charts/per-user-following.ts | 4 ++- .../src/core/chart/charts/per-user-notes.ts | 4 ++- .../src/core/chart/charts/per-user-reactions.ts | 4 ++- .../backend/src/core/chart/charts/test-grouped.ts | 4 ++- .../src/core/chart/charts/test-intersection.ts | 4 ++- .../backend/src/core/chart/charts/test-unique.ts | 4 ++- packages/backend/src/core/chart/charts/test.ts | 4 ++- packages/backend/src/core/chart/charts/users.ts | 4 ++- packages/backend/src/core/chart/core.ts | 24 ++++++++++------ .../backend/src/core/remote/RemoteLoggerService.ts | 6 ++-- packages/backend/src/queue/QueueLoggerService.ts | 6 ++-- packages/backend/src/server/FileServerService.ts | 25 +++++++++------- .../backend/src/server/MediaProxyServerService.ts | 11 +++++--- packages/backend/src/server/ServerService.ts | 21 ++++++++------ .../backend/src/server/api/ApiLoggerService.ts | 6 ++-- .../backend/src/server/api/RateLimiterService.ts | 14 +++++---- .../backend/src/server/web/UrlPreviewService.ts | 6 ++-- 31 files changed, 202 insertions(+), 75 deletions(-) create mode 100644 packages/backend/src/core/LoggerService.ts create mode 100644 packages/backend/src/core/chart/ChartLoggerService.ts (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 523362818f..da07728d22 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -50,6 +50,7 @@ import { WebhookService } from './WebhookService.js'; import { ProxyAccountService } from './ProxyAccountService.js'; import { UtilityService } from './UtilityService.js'; import { FileInfoService } from './FileInfoService.js'; +import { ChartLoggerService } from './chart/ChartLoggerService.js'; import FederationChart from './chart/charts/federation.js'; import NotesChart from './chart/charts/notes.js'; import UsersChart from './chart/charts/users.js'; @@ -113,9 +114,11 @@ import { ApPersonService } from './remote/activitypub/models/ApPersonService.js' import { ApQuestionService } from './remote/activitypub/models/ApQuestionService.js'; import { QueueModule } from './queue/QueueModule.js'; import { QueueService } from './QueueService.js'; +import { LoggerService } from './LoggerService.js'; import type { Provider } from '@nestjs/common'; //#region 文字列ベースでのinjection用(循環参照対応のため) +const $LoggerService: Provider = { provide: 'LoggerService', useExisting: LoggerService }; const $AccountUpdateService: Provider = { provide: 'AccountUpdateService', useExisting: AccountUpdateService }; const $AiService: Provider = { provide: 'AiService', useExisting: AiService }; const $AntennaService: Provider = { provide: 'AntennaService', useExisting: AntennaService }; @@ -166,6 +169,7 @@ const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', u const $WebhookService: Provider = { provide: 'WebhookService', useExisting: WebhookService }; const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService }; const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService }; +const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService }; const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart }; const $NotesChart: Provider = { provide: 'NotesChart', useExisting: NotesChart }; const $UsersChart: Provider = { provide: 'UsersChart', useExisting: UsersChart }; @@ -236,6 +240,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting QueueModule, ], providers: [ + LoggerService, AccountUpdateService, AiService, AntennaService, @@ -286,6 +291,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting WebhookService, UtilityService, FileInfoService, + ChartLoggerService, FederationChart, NotesChart, UsersChart, @@ -350,6 +356,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting QueueService, //#region 文字列ベースでのinjection用(循環参照対応のため) + $LoggerService, $AccountUpdateService, $AiService, $AntennaService, @@ -400,6 +407,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $WebhookService, $UtilityService, $FileInfoService, + $ChartLoggerService, $FederationChart, $NotesChart, $UsersChart, @@ -465,6 +473,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ], exports: [ QueueModule, + LoggerService, AccountUpdateService, AiService, AntennaService, @@ -579,6 +588,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting QueueService, //#region 文字列ベースでのinjection用(循環参照対応のため) + $LoggerService, $AccountUpdateService, $AiService, $AntennaService, diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts index 84d5ca2e8b..9c5c45ed2c 100644 --- a/packages/backend/src/core/DownloadService.ts +++ b/packages/backend/src/core/DownloadService.ts @@ -8,10 +8,11 @@ import got, * as Got from 'got'; import chalk from 'chalk'; import { DI } from '@/di-symbols.js'; import { Config } from '@/config.js'; -import Logger from '@/logger.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { createTemp } from '@/misc/create-temp.js'; import { StatusError } from '@/misc/status-error.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import type Logger from '@/logger.js'; const pipeline = util.promisify(stream.pipeline); @@ -24,8 +25,9 @@ export class DownloadService { private config: Config, private httpRequestService: HttpRequestService, + private loggerService: LoggerService, ) { - this.#logger = new Logger('download'); + this.#logger = this.loggerService.getLogger('download'); } public async downloadUrl(url: string, path: string): Promise { diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 7d6960b73b..a593922acc 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -4,8 +4,9 @@ import { validate as validateEmail } from 'deep-email-validator'; import { MetaService } from '@/core/MetaService.js'; import { DI } from '@/di-symbols.js'; import { Config } from '@/config.js'; -import Logger from '@/logger.js'; +import type Logger from '@/logger.js'; import { UserProfilesRepository } from '@/models/index.js'; +import { LoggerService } from '@/core/LoggerService.js'; @Injectable() export class EmailService { @@ -19,8 +20,9 @@ export class EmailService { private userProfilesRepository: UserProfilesRepository, private metaService: MetaService, + private loggerService: LoggerService, ) { - this.#logger = new Logger('email'); + this.#logger = this.loggerService.getLogger('email'); } public async sendEmail(to: string, subject: string, html: string, text: string) { diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 6353784c13..4414d83942 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -6,13 +6,12 @@ import tinycolor from 'tinycolor2'; import type { Instance } from '@/models/entities/Instance.js'; import { InstancesRepository } from '@/models/index.js'; import { AppLockService } from '@/core/AppLockService.js'; -import Logger from '@/logger.js'; +import type Logger from '@/logger.js'; import { DI } from '@/di-symbols.js'; +import { LoggerService } from '@/core/LoggerService.js'; import { HttpRequestService } from './HttpRequestService.js'; import type { DOMWindow } from 'jsdom'; -const logger = new Logger('metadata', 'cyan'); - type NodeInfo = { openRegistrations?: any; software?: { @@ -33,13 +32,17 @@ type NodeInfo = { @Injectable() export class FetchInstanceMetadataService { + #logger: Logger; + constructor( @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, private appLockService: AppLockService, private httpRequestService: HttpRequestService, + private loggerService: LoggerService, ) { + this.#logger = this.loggerService.getLogger('metadata', 'cyan'); } public async fetchInstanceMetadata(instance: Instance, force = false): Promise { @@ -54,7 +57,7 @@ export class FetchInstanceMetadataService { } } - logger.info(`Fetching metadata of ${instance.host} ...`); + this.#logger.info(`Fetching metadata of ${instance.host} ...`); try { const [info, dom, manifest] = await Promise.all([ @@ -71,7 +74,7 @@ export class FetchInstanceMetadataService { this.#getDescription(info, dom, manifest).catch(() => null), ]); - logger.succ(`Successfuly fetched metadata of ${instance.host}`); + this.#logger.succ(`Successfuly fetched metadata of ${instance.host}`); const updates = { infoUpdatedAt: new Date(), @@ -93,16 +96,16 @@ export class FetchInstanceMetadataService { await this.instancesRepository.update(instance.id, updates); - logger.succ(`Successfuly updated metadata of ${instance.host}`); + this.#logger.succ(`Successfuly updated metadata of ${instance.host}`); } catch (e) { - logger.error(`Failed to update metadata of ${instance.host}: ${e}`); + this.#logger.error(`Failed to update metadata of ${instance.host}: ${e}`); } finally { unlock(); } } async #fetchNodeinfo(instance: Instance): Promise { - logger.info(`Fetching nodeinfo of ${instance.host} ...`); + this.#logger.info(`Fetching nodeinfo of ${instance.host} ...`); try { const wellknown = await this.httpRequestService.getJson('https://' + instance.host + '/.well-known/nodeinfo') @@ -134,18 +137,18 @@ export class FetchInstanceMetadataService { throw err.statusCode ?? err.message; }); - logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`); + this.#logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`); return info as NodeInfo; } catch (err) { - logger.error(`Failed to fetch nodeinfo of ${instance.host}: ${err}`); + this.#logger.error(`Failed to fetch nodeinfo of ${instance.host}: ${err}`); throw err; } } async #fetchDom(instance: Instance): Promise { - logger.info(`Fetching HTML of ${instance.host} ...`); + this.#logger.info(`Fetching HTML of ${instance.host} ...`); const url = 'https://' + instance.host; diff --git a/packages/backend/src/core/LoggerService.ts b/packages/backend/src/core/LoggerService.ts new file mode 100644 index 0000000000..d844b38841 --- /dev/null +++ b/packages/backend/src/core/LoggerService.ts @@ -0,0 +1,33 @@ +import { Inject, Injectable } from '@nestjs/common'; +import * as SyslogPro from 'syslog-pro'; +import { DI } from '@/di-symbols.js'; +import { Config } from '@/config.js'; +import Logger from '@/logger.js'; + +@Injectable() +export class LoggerService { + #syslogClient; + + constructor( + @Inject(DI.config) + private config: Config, + ) { + if (this.config.syslog) { + this.#syslogClient = new SyslogPro.RFC5424({ + applacationName: 'Misskey', + timestamp: true, + encludeStructuredData: true, + color: true, + extendedColor: true, + server: { + target: config.syslog.host, + port: config.syslog.port, + }, + }); + } + } + + public getLogger(domain: string, color?: string | undefined, store?: boolean) { + return new Logger(domain, color, store, this.#syslogClient); + } +} diff --git a/packages/backend/src/core/chart/ChartLoggerService.ts b/packages/backend/src/core/chart/ChartLoggerService.ts new file mode 100644 index 0000000000..544a006ac9 --- /dev/null +++ b/packages/backend/src/core/chart/ChartLoggerService.ts @@ -0,0 +1,14 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type Logger from '@/logger.js'; +import { LoggerService } from '@/core/LoggerService.js'; + +@Injectable() +export class ChartLoggerService { + public logger: Logger; + + constructor( + private loggerService: LoggerService, + ) { + this.logger = this.loggerService.getLogger('chart', 'white', process.env.NODE_ENV !== 'test'); + } +} diff --git a/packages/backend/src/core/chart/charts/active-users.ts b/packages/backend/src/core/chart/charts/active-users.ts index a5d9f166ed..40c60910ea 100644 --- a/packages/backend/src/core/chart/charts/active-users.ts +++ b/packages/backend/src/core/chart/charts/active-users.ts @@ -4,6 +4,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import type { User } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/active-users.js'; import type { KVs } from '../core.js'; @@ -22,8 +23,9 @@ export default class ActiveUsersChart extends Chart { private db: DataSource, private appLockService: AppLockService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/ap-request.ts b/packages/backend/src/core/chart/charts/ap-request.ts index c857cea98c..4b91fbbf18 100644 --- a/packages/backend/src/core/chart/charts/ap-request.ts +++ b/packages/backend/src/core/chart/charts/ap-request.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/ap-request.js'; import type { KVs } from '../core.js'; @@ -17,8 +18,9 @@ export default class ApRequestChart extends Chart { private db: DataSource, private appLockService: AppLockService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/drive.ts b/packages/backend/src/core/chart/charts/drive.ts index dd6d002030..494dfbbe57 100644 --- a/packages/backend/src/core/chart/charts/drive.ts +++ b/packages/backend/src/core/chart/charts/drive.ts @@ -4,6 +4,7 @@ import type { DriveFile } from '@/models/entities/DriveFile.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/drive.js'; import type { KVs } from '../core.js'; @@ -18,8 +19,9 @@ export default class DriveChart extends Chart { private db: DataSource, private appLockService: AppLockService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts index 372e0f1fae..4366d4cce1 100644 --- a/packages/backend/src/core/chart/charts/federation.ts +++ b/packages/backend/src/core/chart/charts/federation.ts @@ -5,6 +5,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/federation.js'; import type { KVs } from '../core.js'; @@ -26,8 +27,9 @@ export default class FederationChart extends Chart { private metaService: MetaService, private appLockService: AppLockService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/hashtag.ts b/packages/backend/src/core/chart/charts/hashtag.ts index 66ac0b882b..8b8c795cfd 100644 --- a/packages/backend/src/core/chart/charts/hashtag.ts +++ b/packages/backend/src/core/chart/charts/hashtag.ts @@ -5,6 +5,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/hashtag.js'; import type { KVs } from '../core.js'; @@ -20,8 +21,9 @@ export default class HashtagChart extends Chart { private appLockService: AppLockService, private userEntityService: UserEntityService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema, true); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema, true); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/instance.ts b/packages/backend/src/core/chart/charts/instance.ts index c43ebeddc1..be70bc79c0 100644 --- a/packages/backend/src/core/chart/charts/instance.ts +++ b/packages/backend/src/core/chart/charts/instance.ts @@ -7,6 +7,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UtilityService } from '@/core/UtilityService.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/instance.js'; import type { KVs } from '../core.js'; @@ -34,8 +35,9 @@ export default class InstanceChart extends Chart { private utilityService: UtilityService, private appLockService: AppLockService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema, true); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema, true); } protected async tickMajor(group: string): Promise>> { diff --git a/packages/backend/src/core/chart/charts/notes.ts b/packages/backend/src/core/chart/charts/notes.ts index 1597b5727e..e1bfeabf99 100644 --- a/packages/backend/src/core/chart/charts/notes.ts +++ b/packages/backend/src/core/chart/charts/notes.ts @@ -5,6 +5,7 @@ import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/notes.js'; import type { KVs } from '../core.js'; @@ -22,8 +23,9 @@ export default class NotesChart extends Chart { private notesRepository: NotesRepository, private appLockService: AppLockService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/per-user-drive.ts b/packages/backend/src/core/chart/charts/per-user-drive.ts index 181b9a38bb..752203daaf 100644 --- a/packages/backend/src/core/chart/charts/per-user-drive.ts +++ b/packages/backend/src/core/chart/charts/per-user-drive.ts @@ -6,6 +6,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-drive.js'; import type { KVs } from '../core.js'; @@ -24,8 +25,9 @@ export default class PerUserDriveChart extends Chart { private appLockService: AppLockService, private driveFileEntityService: DriveFileEntityService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema, true); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema, true); } protected async tickMajor(group: string): Promise>> { diff --git a/packages/backend/src/core/chart/charts/per-user-following.ts b/packages/backend/src/core/chart/charts/per-user-following.ts index 5195723a25..48bf3d7c62 100644 --- a/packages/backend/src/core/chart/charts/per-user-following.ts +++ b/packages/backend/src/core/chart/charts/per-user-following.ts @@ -6,6 +6,7 @@ import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { FollowingsRepository } from '@/models/index.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-following.js'; import type { KVs } from '../core.js'; @@ -24,8 +25,9 @@ export default class PerUserFollowingChart extends Chart { private appLockService: AppLockService, private userEntityService: UserEntityService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema, true); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema, true); } protected async tickMajor(group: string): Promise>> { diff --git a/packages/backend/src/core/chart/charts/per-user-notes.ts b/packages/backend/src/core/chart/charts/per-user-notes.ts index 6dbe309b7c..ffe52dcd56 100644 --- a/packages/backend/src/core/chart/charts/per-user-notes.ts +++ b/packages/backend/src/core/chart/charts/per-user-notes.ts @@ -6,6 +6,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { NotesRepository } from '@/models/index.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-notes.js'; import type { KVs } from '../core.js'; @@ -23,8 +24,9 @@ export default class PerUserNotesChart extends Chart { private notesRepository: NotesRepository, private appLockService: AppLockService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema, true); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema, true); } protected async tickMajor(group: string): Promise>> { diff --git a/packages/backend/src/core/chart/charts/per-user-reactions.ts b/packages/backend/src/core/chart/charts/per-user-reactions.ts index 73a58656f8..4160219720 100644 --- a/packages/backend/src/core/chart/charts/per-user-reactions.ts +++ b/packages/backend/src/core/chart/charts/per-user-reactions.ts @@ -6,6 +6,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-reactions.js'; import type { KVs } from '../core.js'; @@ -21,8 +22,9 @@ export default class PerUserReactionsChart extends Chart { private appLockService: AppLockService, private userEntityService: UserEntityService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema, true); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema, true); } protected async tickMajor(group: string): Promise>> { diff --git a/packages/backend/src/core/chart/charts/test-grouped.ts b/packages/backend/src/core/chart/charts/test-grouped.ts index e6cbe89790..103cf2a27e 100644 --- a/packages/backend/src/core/chart/charts/test-grouped.ts +++ b/packages/backend/src/core/chart/charts/test-grouped.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/test-grouped.js'; import type { KVs } from '../core.js'; @@ -19,8 +20,9 @@ export default class TestGroupedChart extends Chart { private db: DataSource, private appLockService: AppLockService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema, true); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema, true); } protected async tickMajor(group: string): Promise>> { diff --git a/packages/backend/src/core/chart/charts/test-intersection.ts b/packages/backend/src/core/chart/charts/test-intersection.ts index f2f17c8de6..0e38769165 100644 --- a/packages/backend/src/core/chart/charts/test-intersection.ts +++ b/packages/backend/src/core/chart/charts/test-intersection.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/test-intersection.js'; import type { KVs } from '../core.js'; @@ -17,8 +18,9 @@ export default class TestIntersectionChart extends Chart { private db: DataSource, private appLockService: AppLockService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/test-unique.ts b/packages/backend/src/core/chart/charts/test-unique.ts index ce01594520..44e1b8365d 100644 --- a/packages/backend/src/core/chart/charts/test-unique.ts +++ b/packages/backend/src/core/chart/charts/test-unique.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/test-unique.js'; import type { KVs } from '../core.js'; @@ -17,8 +18,9 @@ export default class TestUniqueChart extends Chart { private db: DataSource, private appLockService: AppLockService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/test.ts b/packages/backend/src/core/chart/charts/test.ts index bd59b7aa63..6269674023 100644 --- a/packages/backend/src/core/chart/charts/test.ts +++ b/packages/backend/src/core/chart/charts/test.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/test.js'; import type { KVs } from '../core.js'; @@ -19,8 +20,9 @@ export default class TestChart extends Chart { private db: DataSource, private appLockService: AppLockService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/charts/users.ts b/packages/backend/src/core/chart/charts/users.ts index 4fdddcc0a3..b3187997cf 100644 --- a/packages/backend/src/core/chart/charts/users.ts +++ b/packages/backend/src/core/chart/charts/users.ts @@ -6,6 +6,7 @@ import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UsersRepository } from '@/models/index.js'; import Chart from '../core.js'; +import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/users.js'; import type { KVs } from '../core.js'; @@ -24,8 +25,9 @@ export default class UsersChart extends Chart { private appLockService: AppLockService, private userEntityService: UserEntityService, + private chartLoggerService: ChartLoggerService, ) { - super(db, (k) => appLockService.getChartInsertLock(k), name, schema); + super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); } protected async tickMajor(): Promise>> { diff --git a/packages/backend/src/core/chart/core.ts b/packages/backend/src/core/chart/core.ts index 1933e80c7b..93a6c3ce05 100644 --- a/packages/backend/src/core/chart/core.ts +++ b/packages/backend/src/core/chart/core.ts @@ -7,11 +7,9 @@ import * as nestedProperty from 'nested-property'; import { EntitySchema, LessThan, Between } from 'typeorm'; import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '@/misc/prelude/time.js'; -import Logger from '@/logger.js'; +import type Logger from '@/logger.js'; import type { Repository, DataSource } from 'typeorm'; -const logger = new Logger('chart', 'white', process.env.NODE_ENV !== 'test'); - const columnPrefix = '___' as const; const uniqueTempColumnPrefix = 'unique_temp___' as const; const columnDot = '_' as const; @@ -111,6 +109,8 @@ export function getJsonSchema(schema: S): ToJsonSchema { + #logger: Logger; + public schema: T; private name: string; @@ -231,10 +231,18 @@ export default abstract class Chart { private lock: (key: string) => Promise<() => void>; - constructor(db: DataSource, lock: (key: string) => Promise<() => void>, name: string, schema: T, grouped = false) { + constructor( + db: DataSource, + lock: (key: string) => Promise<() => void>, + logger: Logger, + name: string, + schema: T, + grouped = false, + ) { this.name = name; this.schema = schema; this.lock = lock; + this.#logger = logger; const { hour, day } = Chart.schemaToEntity(name, schema, grouped); this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour); @@ -325,7 +333,7 @@ export default abstract class Chart { // 初期ログデータを作成 data = this.getNewLog(null); - logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): Initial commit created`); + this.#logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): Initial commit created`); } const date = Chart.dateToTimestamp(current); @@ -355,7 +363,7 @@ export default abstract class Chart { ...columns, }).then(x => repository.findOneByOrFail(x.identifiers[0])) as RawRecord; - logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`); + this.#logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`); return log; } finally { @@ -374,7 +382,7 @@ export default abstract class Chart { public async save(): Promise { if (this.buffer.length === 0) { - logger.info(`${this.name}: Write skipped`); + this.#logger.info(`${this.name}: Write skipped`); return; } @@ -473,7 +481,7 @@ export default abstract class Chart { .execute(), ]); - logger.info(`${this.name + (logHour.group ? `:${logHour.group}` : '')}: Updated`); + this.#logger.info(`${this.name + (logHour.group ? `:${logHour.group}` : '')}: Updated`); // TODO: この一連の処理が始まった後に新たにbufferに入ったものは消さないようにする this.buffer = this.buffer.filter(q => q.group != null && (q.group !== logHour.group)); diff --git a/packages/backend/src/core/remote/RemoteLoggerService.ts b/packages/backend/src/core/remote/RemoteLoggerService.ts index 7ce8fe6cfc..68246466c8 100644 --- a/packages/backend/src/core/remote/RemoteLoggerService.ts +++ b/packages/backend/src/core/remote/RemoteLoggerService.ts @@ -1,12 +1,14 @@ import { Inject, Injectable } from '@nestjs/common'; -import Logger from '@/logger.js'; +import type Logger from '@/logger.js'; +import { LoggerService } from '@/core/LoggerService.js'; @Injectable() export class RemoteLoggerService { public logger: Logger; constructor( + private loggerService: LoggerService, ) { - this.logger = new Logger('remote', 'cyan'); + this.logger = this.loggerService.getLogger('remote', 'cyan'); } } diff --git a/packages/backend/src/queue/QueueLoggerService.ts b/packages/backend/src/queue/QueueLoggerService.ts index 4cdd4edfbb..a311470cc9 100644 --- a/packages/backend/src/queue/QueueLoggerService.ts +++ b/packages/backend/src/queue/QueueLoggerService.ts @@ -1,12 +1,14 @@ import { Inject, Injectable } from '@nestjs/common'; -import Logger from '@/logger.js'; +import type Logger from '@/logger.js'; +import { LoggerService } from '@/core/LoggerService.js'; @Injectable() export class QueueLoggerService { public logger: Logger; constructor( + private loggerService: LoggerService, ) { - this.logger = new Logger('queue', 'orange'); + this.logger = this.loggerService.getLogger('queue', 'orange'); } } diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 0f4246bfd1..4db9205e36 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -13,29 +13,24 @@ import { DI } from '@/di-symbols.js'; import { createTemp } from '@/misc/create-temp.js'; import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; import { StatusError } from '@/misc/status-error.js'; -import Logger from '@/logger.js'; +import type Logger from '@/logger.js'; import { DownloadService } from '@/core/DownloadService.js'; import { ImageProcessingService } from '@/core/ImageProcessingService.js'; import { VideoProcessingService } from '@/core/VideoProcessingService.js'; import { InternalStorageService } from '@/core/InternalStorageService.js'; import { contentDisposition } from '@/misc/content-disposition.js'; import { FileInfoService } from '@/core/FileInfoService.js'; - -const serverLogger = new Logger('server', 'gray', false); +import { LoggerService } from '@/core/LoggerService.js'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); const assets = `${_dirname}/../../server/file/assets/`; -const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void => { - serverLogger.error(e); - ctx.status = 500; - ctx.set('Cache-Control', 'max-age=300'); -}; - @Injectable() export class FileServerService { + #logger: Logger; + constructor( @Inject(DI.config) private config: Config, @@ -48,9 +43,19 @@ export class FileServerService { private imageProcessingService: ImageProcessingService, private videoProcessingService: VideoProcessingService, private internalStorageService: InternalStorageService, + private loggerService: LoggerService, ) { + this.#logger = this.loggerService.getLogger('server', 'gray', false); } + public commonReadableHandlerGenerator(ctx: Koa.Context) { + return (e: Error): void => { + this.#logger.error(e); + ctx.status = 500; + ctx.set('Cache-Control', 'max-age=300'); + }; + } + public createServer() { const app = new Koa(); app.use(cors()); @@ -134,7 +139,7 @@ export class FileServerService { ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream'); ctx.set('Cache-Control', 'max-age=31536000, immutable'); } catch (err) { - serverLogger.error(`${err}`); + this.#logger.error(`${err}`); if (err instanceof StatusError && err.isClientError) { ctx.status = err.statusCode; diff --git a/packages/backend/src/server/MediaProxyServerService.ts b/packages/backend/src/server/MediaProxyServerService.ts index b9cee38f0a..5344d3a9f4 100644 --- a/packages/backend/src/server/MediaProxyServerService.ts +++ b/packages/backend/src/server/MediaProxyServerService.ts @@ -13,13 +13,14 @@ import { ImageProcessingService } from '@/core/ImageProcessingService.js'; import type { IImage } from '@/core/ImageProcessingService.js'; import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; import { StatusError } from '@/misc/status-error.js'; -import Logger from '@/logger.js'; +import type Logger from '@/logger.js'; import { FileInfoService } from '@/core/FileInfoService.js'; - -const serverLogger = new Logger('server', 'gray', false); +import { LoggerService } from '@/core/LoggerService.js'; @Injectable() export class MediaProxyServerService { + #logger: Logger; + constructor( @Inject(DI.config) private config: Config, @@ -27,7 +28,9 @@ export class MediaProxyServerService { private fileInfoService: FileInfoService, private downloadService: DownloadService, private imageProcessingService: ImageProcessingService, + private loggerService: LoggerService, ) { + this.#logger = this.loggerService.getLogger('server', 'gray', false); } public createServer() { @@ -123,7 +126,7 @@ export class MediaProxyServerService { ctx.set('Cache-Control', 'max-age=31536000, immutable'); ctx.body = image.data; } catch (err) { - serverLogger.error(`${err}`); + this.#logger.error(`${err}`); if (err instanceof StatusError && (err.statusCode === 302 || err.isClientError)) { ctx.status = err.statusCode; diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 2ee7135c2a..8349e1e9f5 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -12,12 +12,13 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { Config } from '@/config.js'; import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; -import Logger from '@/logger.js'; +import type Logger from '@/logger.js'; import { envOption } from '@/env.js'; import * as Acct from '@/misc/acct.js'; import { genIdenticon } from '@/misc/gen-identicon.js'; import { createTemp } from '@/misc/create-temp.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { LoggerService } from '@/core/LoggerService.js'; import { ActivityPubServerService } from './ActivityPubServerService.js'; import { NodeinfoServerService } from './NodeinfoServerService.js'; import { ApiServerService } from './api/ApiServerService.js'; @@ -27,10 +28,10 @@ import { MediaProxyServerService } from './MediaProxyServerService.js'; import { FileServerService } from './FileServerService.js'; import { ClientServerService } from './web/ClientServerService.js'; -const serverLogger = new Logger('server', 'gray', false); - @Injectable() export class ServerService { + #logger: Logger; + constructor( @Inject(DI.config) private config: Config, @@ -51,7 +52,9 @@ export class ServerService { private mediaProxyServerService: MediaProxyServerService, private clientServerService: ClientServerService, private globalEventService: GlobalEventService, + private loggerService: LoggerService, ) { + this.#logger = this.loggerService.getLogger('server', 'gray', false); } public launch() { @@ -62,7 +65,7 @@ export class ServerService { if (!['production', 'test'].includes(process.env.NODE_ENV ?? '')) { // Logger koa.use(koaLogger(str => { - serverLogger.info(str); + this.#logger.info(str); })); // Delay @@ -151,16 +154,16 @@ export class ServerService { this.streamingApiServerService.attachStreamingApi(server); - server.on('error', e => { - switch ((e as any).code) { + server.on('error', err => { + switch ((err as any).code) { case 'EACCES': - serverLogger.error(`You do not have permission to listen on port ${this.config.port}.`); + this.#logger.error(`You do not have permission to listen on port ${this.config.port}.`); break; case 'EADDRINUSE': - serverLogger.error(`Port ${this.config.port} is already in use by another process.`); + this.#logger.error(`Port ${this.config.port} is already in use by another process.`); break; default: - serverLogger.error(e); + this.#logger.error(err); break; } diff --git a/packages/backend/src/server/api/ApiLoggerService.ts b/packages/backend/src/server/api/ApiLoggerService.ts index c8c5fec85c..c4fb25036e 100644 --- a/packages/backend/src/server/api/ApiLoggerService.ts +++ b/packages/backend/src/server/api/ApiLoggerService.ts @@ -1,12 +1,14 @@ import { Inject, Injectable } from '@nestjs/common'; -import Logger from '@/logger.js'; +import type Logger from '@/logger.js'; +import { LoggerService } from '@/core/LoggerService.js'; @Injectable() export class ApiLoggerService { public logger: Logger; constructor( + private loggerService: LoggerService, ) { - this.logger = new Logger('api'); + this.logger = this.loggerService.getLogger('api'); } } diff --git a/packages/backend/src/server/api/RateLimiterService.ts b/packages/backend/src/server/api/RateLimiterService.ts index d390a47b8f..61b7b1ff47 100644 --- a/packages/backend/src/server/api/RateLimiterService.ts +++ b/packages/backend/src/server/api/RateLimiterService.ts @@ -2,17 +2,21 @@ import { Inject, Injectable } from '@nestjs/common'; import Limiter from 'ratelimiter'; import Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; -import Logger from '@/logger.js'; +import type Logger from '@/logger.js'; +import { LoggerService } from '@/core/LoggerService.js'; import type { IEndpointMeta } from './endpoints.js'; -const logger = new Logger('limiter'); - @Injectable() export class RateLimiterService { + #logger: Logger; + constructor( @Inject(DI.redis) private redisClient: Redis.Redis, + + private loggerService: LoggerService, ) { + this.#logger = this.loggerService.getLogger('limiter'); } public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable }, actor: string) { @@ -33,7 +37,7 @@ export class RateLimiterService { return reject('ERR'); } - logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); + this.#logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); if (info.remaining === 0) { reject('BRIEF_REQUEST_INTERVAL'); @@ -61,7 +65,7 @@ export class RateLimiterService { return reject('ERR'); } - logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); + this.#logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); if (info.remaining === 0) { reject('RATE_LIMIT_EXCEEDED'); diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 4e3b456144..726b158340 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -5,8 +5,9 @@ import { UsersRepository } from '@/models/index.js'; import { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; -import Logger from '@/logger.js'; +import type Logger from '@/logger.js'; import { query } from '@/misc/prelude/url.js'; +import { LoggerService } from '@/core/LoggerService.js'; import type Koa from 'koa'; @Injectable() @@ -22,8 +23,9 @@ export class UrlPreviewService { private metaService: MetaService, private httpRequestService: HttpRequestService, + private loggerService: LoggerService, ) { - this.#logger = new Logger('url-preview'); + this.#logger = this.loggerService.getLogger('url-preview'); } #wrap(url?: string): string | null { -- cgit v1.2.3-freya From 2fbd09a07e3cdabee5104c7fcb27f27efd6f79a4 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 18 Sep 2022 23:36:00 +0900 Subject: fix --- packages/backend/src/server/FileServerService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 4db9205e36..e2b0ea5afe 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -172,7 +172,7 @@ export class FileServerService { ctx.set('Content-Disposition', contentDisposition('inline', filename)); } else { const readable = this.internalStorageService.read(file.accessKey!); - readable.on('error', commonReadableHandlerGenerator(ctx)); + readable.on('error', this.commonReadableHandlerGenerator(ctx)); ctx.body = readable; ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.type) ? file.type : 'application/octet-stream'); ctx.set('Cache-Control', 'max-age=31536000, immutable'); -- cgit v1.2.3-freya From a2eac9fff67f811ed4ac1a80a88fd1f0eafae6c8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 19 Sep 2022 03:11:50 +0900 Subject: test --- packages/backend/src/core/AiService.ts | 10 +- packages/backend/src/core/AntennaService.ts | 28 ++-- packages/backend/src/core/AppLockService.ts | 10 +- packages/backend/src/core/CaptchaService.ts | 6 +- .../backend/src/core/CreateNotificationService.ts | 8 +- packages/backend/src/core/CustomEmojiService.ts | 26 ++-- packages/backend/src/core/DownloadService.ts | 20 +-- packages/backend/src/core/DriveService.ts | 82 ++++++------ packages/backend/src/core/EmailService.ts | 8 +- .../backend/src/core/FederatedInstanceService.ts | 10 +- .../src/core/FetchInstanceMetadataService.ts | 52 ++++---- packages/backend/src/core/FileInfoService.ts | 26 ++-- packages/backend/src/core/HttpRequestService.ts | 14 +- packages/backend/src/core/IdService.ts | 6 +- packages/backend/src/core/InstanceActorService.ts | 10 +- packages/backend/src/core/LoggerService.ts | 6 +- packages/backend/src/core/MessagingService.ts | 4 +- packages/backend/src/core/MetaService.ts | 16 +-- packages/backend/src/core/NoteCreateService.ts | 36 +++--- packages/backend/src/core/NoteDeleteService.ts | 14 +- packages/backend/src/core/NotificationService.ts | 8 +- packages/backend/src/core/RelayService.ts | 12 +- packages/backend/src/core/UserBlockingService.ts | 16 +-- packages/backend/src/core/UserFollowingService.ts | 38 +++--- .../backend/src/core/UserKeypairStoreService.ts | 6 +- packages/backend/src/core/WebhookService.ts | 24 ++-- packages/backend/src/core/chart/core.ts | 12 +- .../backend/src/core/entities/NoteEntityService.ts | 12 +- .../backend/src/core/entities/UserEntityService.ts | 6 +- .../backend/src/core/remote/ResolveUserService.ts | 34 ++--- .../backend/src/core/remote/WebfingerService.ts | 4 +- .../core/remote/activitypub/ApAudienceService.ts | 14 +- .../core/remote/activitypub/ApDbResolverService.ts | 12 +- .../src/core/remote/activitypub/ApInboxService.ts | 144 ++++++++++----------- .../core/remote/activitypub/ApRendererService.ts | 6 +- .../core/remote/activitypub/ApRequestService.ts | 32 ++--- .../remote/activitypub/models/ApImageService.ts | 6 +- .../remote/activitypub/models/ApNoteService.ts | 22 ++-- .../remote/activitypub/models/ApPersonService.ts | 26 ++-- .../remote/activitypub/models/ApQuestionService.ts | 6 +- packages/backend/src/daemons/JanitorService.ts | 6 +- packages/backend/src/daemons/QueueStatsService.ts | 6 +- packages/backend/src/daemons/ServerStatsService.ts | 6 +- .../backend/src/queue/QueueProcessorService.ts | 16 +-- .../CheckExpiredMutingsProcessorService.ts | 8 +- .../processors/CleanChartsProcessorService.ts | 8 +- .../src/queue/processors/CleanProcessorService.ts | 8 +- .../processors/CleanRemoteFilesProcessorService.ts | 8 +- .../processors/DeleteAccountProcessorService.ts | 10 +- .../processors/DeleteDriveFilesProcessorService.ts | 8 +- .../queue/processors/DeleteFileProcessorService.ts | 4 +- .../queue/processors/DeliverProcessorService.ts | 20 +-- .../EndedPollNotificationProcessorService.ts | 4 +- .../processors/ExportBlockingProcessorService.ts | 14 +- .../ExportCustomEmojisProcessorService.ts | 16 +-- .../processors/ExportFollowingProcessorService.ts | 14 +- .../processors/ExportMutingProcessorService.ts | 14 +- .../processors/ExportNotesProcessorService.ts | 14 +- .../processors/ExportUserListsProcessorService.ts | 14 +- .../processors/ImportBlockingProcessorService.ts | 12 +- .../ImportCustomEmojisProcessorService.ts | 14 +- .../processors/ImportFollowingProcessorService.ts | 12 +- .../processors/ImportMutingProcessorService.ts | 12 +- .../processors/ImportUserListsProcessorService.ts | 10 +- .../src/queue/processors/InboxProcessorService.ts | 6 +- .../processors/ResyncChartsProcessorService.ts | 8 +- .../queue/processors/TickChartsProcessorService.ts | 8 +- .../processors/WebhookDeliverProcessorService.ts | 6 +- .../backend/src/server/ActivityPubServerService.ts | 64 ++++----- packages/backend/src/server/FileServerService.ts | 14 +- .../backend/src/server/MediaProxyServerService.ts | 10 +- packages/backend/src/server/ServerService.ts | 12 +- packages/backend/src/server/api/ApiCallService.ts | 28 ++-- .../backend/src/server/api/AuthenticateService.ts | 6 +- .../backend/src/server/api/RateLimiterService.ts | 8 +- .../src/server/api/endpoints/admin/suspend-user.ts | 8 +- .../backend/src/server/api/endpoints/ap/show.ts | 12 +- .../server/api/integration/DiscordServerService.ts | 14 +- .../server/api/integration/GithubServerService.ts | 14 +- .../server/api/integration/TwitterServerService.ts | 14 +- .../backend/src/server/web/ClientServerService.ts | 4 +- .../backend/src/server/web/UrlPreviewService.ts | 16 +-- 82 files changed, 671 insertions(+), 671 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index 1cfc3382a9..e6102a1b91 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -15,7 +15,7 @@ let isSupportedCpu: undefined | boolean = undefined; @Injectable() export class AiService { - #model: nsfw.NSFWJS; + private model: nsfw.NSFWJS; constructor( @Inject(DI.config) @@ -26,7 +26,7 @@ export class AiService { public async detectSensitive(path: string): Promise { try { if (isSupportedCpu === undefined) { - const cpuFlags = await this.#getCpuFlags(); + const cpuFlags = await this.getCpuFlags(); isSupportedCpu = REQUIRED_CPU_FLAGS.every(required => cpuFlags.includes(required)); } @@ -37,12 +37,12 @@ export class AiService { const tf = await import('@tensorflow/tfjs-node'); - if (this.#model == null) this.#model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 }); + if (this.model == null) this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 }); const buffer = await fs.promises.readFile(path); const image = await tf.node.decodeImage(buffer, 3) as any; try { - const predictions = await this.#model.classify(image); + const predictions = await this.model.classify(image); return predictions; } finally { image.dispose(); @@ -53,7 +53,7 @@ export class AiService { } } - async #getCpuFlags(): Promise { + private async getCpuFlags(): Promise { const str = await si.cpuFlags(); return str.split(/\s+/); } diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 8993880a06..e0af033952 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -16,9 +16,9 @@ import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class AntennaService implements OnApplicationShutdown { - #antennasFetched: boolean; - #antennas: Antenna[]; - #blockingCache: Cache; + private antennasFetched: boolean; + private antennas: Antenna[]; + private blockingCache: Cache; constructor( @Inject(DI.redisSubscriber) @@ -49,9 +49,9 @@ export class AntennaService implements OnApplicationShutdown { private idService: IdService, private globalEventServie: GlobalEventService, ) { - this.#antennasFetched = false; - this.#antennas = []; - this.#blockingCache = new Cache(1000 * 60 * 5); + this.antennasFetched = false; + this.antennas = []; + this.blockingCache = new Cache(1000 * 60 * 5); this.redisSubscriber.on('message', this.onRedisMessage); } @@ -67,13 +67,13 @@ export class AntennaService implements OnApplicationShutdown { const { type, body } = obj.message; switch (type) { case 'antennaCreated': - this.#antennas.push(body); + this.antennas.push(body); break; case 'antennaUpdated': - this.#antennas[this.#antennas.findIndex(a => a.id === body.id)] = body; + this.antennas[this.antennas.findIndex(a => a.id === body.id)] = body; break; case 'antennaDeleted': - this.#antennas = this.#antennas.filter(a => a.id !== body.id); + this.antennas = this.antennas.filter(a => a.id !== body.id); break; default: break; @@ -137,7 +137,7 @@ export class AntennaService implements OnApplicationShutdown { if (note.visibility === 'specified') return false; // アンテナ作成者がノート作成者にブロックされていたらスキップ - const blockings = await this.#blockingCache.fetch(noteUser.id, () => this.blockingsRepository.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId))); + const blockings = await this.blockingCache.fetch(noteUser.id, () => this.blockingsRepository.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId))); if (blockings.some(blocking => blocking === antenna.userId)) return false; if (note.visibility === 'followers') { @@ -218,11 +218,11 @@ export class AntennaService implements OnApplicationShutdown { } public async getAntennas() { - if (!this.#antennasFetched) { - this.#antennas = await this.antennasRepository.find(); - this.#antennasFetched = true; + if (!this.antennasFetched) { + this.antennas = await this.antennasRepository.find(); + this.antennasFetched = true; } - return this.#antennas; + return this.antennas; } } diff --git a/packages/backend/src/core/AppLockService.ts b/packages/backend/src/core/AppLockService.ts index f3c345493b..04b3d8b112 100644 --- a/packages/backend/src/core/AppLockService.ts +++ b/packages/backend/src/core/AppLockService.ts @@ -11,13 +11,13 @@ const retryDelay = 100; @Injectable() export class AppLockService { - #lock: (key: string, timeout?: number) => Promise<() => void>; + private lock: (key: string, timeout?: number) => Promise<() => void>; constructor( @Inject(DI.redis) private redisClient: Redis.Redis, ) { - this.#lock = promisify(redisLock(this.redisClient, retryDelay)); + this.lock = promisify(redisLock(this.redisClient, retryDelay)); } /** @@ -27,14 +27,14 @@ export class AppLockService { * @returns Unlock function */ public getApLock(uri: string, timeout = 30 * 1000): Promise<() => void> { - return this.#lock(`ap-object:${uri}`, timeout); + return this.lock(`ap-object:${uri}`, timeout); } public getFetchInstanceMetadataLock(host: string, timeout = 30 * 1000): Promise<() => void> { - return this.#lock(`instance:${host}`, timeout); + return this.lock(`instance:${host}`, timeout); } public getChartInsertLock(lockKey: string, timeout = 30 * 1000): Promise<() => void> { - return this.#lock(`chart-insert:${lockKey}`, timeout); + return this.lock(`chart-insert:${lockKey}`, timeout); } } diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index 891e5315c2..b1b52fd6a9 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -19,7 +19,7 @@ export class CaptchaService { ) { } - async #getCaptchaResponse(url: string, secret: string, response: string): Promise { + private async getCaptchaResponse(url: string, secret: string, response: string): Promise { const params = new URLSearchParams({ secret, response, @@ -46,7 +46,7 @@ export class CaptchaService { } public async verifyRecaptcha(secret: string, response: string): Promise { - const result = await this.#getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(e => { + const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(e => { throw `recaptcha-request-failed: ${e}`; }); @@ -57,7 +57,7 @@ export class CaptchaService { } public async verifyHcaptcha(secret: string, response: string): Promise { - const result = await this.#getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(e => { + const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(e => { throw `hcaptcha-request-failed: ${e}`; }); diff --git a/packages/backend/src/core/CreateNotificationService.ts b/packages/backend/src/core/CreateNotificationService.ts index 10aa2c5df5..525fac6d92 100644 --- a/packages/backend/src/core/CreateNotificationService.ts +++ b/packages/backend/src/core/CreateNotificationService.ts @@ -78,8 +78,8 @@ export class CreateNotificationService { this.globalEventServie.publishMainStream(notifieeId, 'unreadNotification', packed); this.pushNotificationService.pushNotification(notifieeId, 'notification', packed); - if (type === 'follow') this.#emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! })); - if (type === 'receiveFollowRequest') this.#emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! })); + if (type === 'follow') this.emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! })); + if (type === 'receiveFollowRequest') this.emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! })); }, 2000); return notification; @@ -90,7 +90,7 @@ export class CreateNotificationService { // TODO: locale ファイルをクライアント用とサーバー用で分けたい - async #emailNotificationFollow(userId: User['id'], follower: User) { + private async emailNotificationFollow(userId: User['id'], follower: User) { /* const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); if (!userProfile.email || !userProfile.emailNotificationTypes.includes('follow')) return; @@ -101,7 +101,7 @@ export class CreateNotificationService { */ } - async #emailNotificationReceiveFollowRequest(userId: User['id'], follower: User) { + private async emailNotificationReceiveFollowRequest(userId: User['id'], follower: User) { /* const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); if (!userProfile.email || !userProfile.emailNotificationTypes.includes('receiveFollowRequest')) return; diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 83e28baea4..32dad70d1c 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -23,7 +23,7 @@ type PopulatedEmoji = { @Injectable() export class CustomEmojiService { - #cache: Cache; + private cache: Cache; constructor( @Inject(DI.config) @@ -40,7 +40,7 @@ export class CustomEmojiService { private utilityService: UtilityService, private reactionService: ReactionService, ) { - this.#cache = new Cache(1000 * 60 * 60 * 12); + this.cache = new Cache(1000 * 60 * 60 * 12); } public async add(data: { @@ -67,7 +67,7 @@ export class CustomEmojiService { return emoji; } - #normalizeHost(src: string | undefined, noteUserHost: string | null): string | null { + private normalizeHost(src: string | undefined, noteUserHost: string | null): string | null { // クエリに使うホスト let host = src === '.' ? null // .はローカルホスト (ここがマッチするのはリアクションのみ) : src === undefined ? noteUserHost // ノートなどでホスト省略表記の場合はローカルホスト (ここがリアクションにマッチすることはない) @@ -79,14 +79,14 @@ export class CustomEmojiService { return host; } - #parseEmojiStr(emojiName: string, noteUserHost: string | null) { + private parseEmojiStr(emojiName: string, noteUserHost: string | null) { const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/); if (!match) return { name: null, host: null }; const name = match[1]; // ホスト正規化 - const host = this.utilityService.toPunyNullable(this.#normalizeHost(match[2], noteUserHost)); + const host = this.utilityService.toPunyNullable(this.normalizeHost(match[2], noteUserHost)); return { name, host }; } @@ -98,7 +98,7 @@ export class CustomEmojiService { * @returns 絵文字情報, nullは未マッチを意味する */ public async populateEmoji(emojiName: string, noteUserHost: string | null): Promise { - const { name, host } = this.#parseEmojiStr(emojiName, noteUserHost); + const { name, host } = this.parseEmojiStr(emojiName, noteUserHost); if (name == null) return null; const queryOrNull = async () => (await this.emojisRepository.findOneBy({ @@ -106,7 +106,7 @@ export class CustomEmojiService { host: host ?? IsNull(), })) ?? null; - const emoji = await this.#cache.fetch(`${name} ${host}`, queryOrNull); + const emoji = await this.cache.fetch(`${name} ${host}`, queryOrNull); if (emoji == null) return null; @@ -132,20 +132,20 @@ export class CustomEmojiService { let emojis: { name: string | null; host: string | null; }[] = []; for (const note of notes) { emojis = emojis.concat(note.emojis - .map(e => this.#parseEmojiStr(e, note.userHost))); + .map(e => this.parseEmojiStr(e, note.userHost))); if (note.renote) { emojis = emojis.concat(note.renote.emojis - .map(e => this.#parseEmojiStr(e, note.renote!.userHost))); + .map(e => this.parseEmojiStr(e, note.renote!.userHost))); if (note.renote.user) { emojis = emojis.concat(note.renote.user.emojis - .map(e => this.#parseEmojiStr(e, note.renote!.userHost))); + .map(e => this.parseEmojiStr(e, note.renote!.userHost))); } } const customReactions = Object.keys(note.reactions).map(x => this.reactionService.decodeReaction(x)).filter(x => x.name != null) as typeof emojis; emojis = emojis.concat(customReactions); if (note.user) { emojis = emojis.concat(note.user.emojis - .map(e => this.#parseEmojiStr(e, note.userHost))); + .map(e => this.parseEmojiStr(e, note.userHost))); } } return emojis.filter(x => x.name != null) as { name: string; host: string | null; }[]; @@ -155,7 +155,7 @@ export class CustomEmojiService { * 与えられた絵文字のリストをデータベースから取得し、キャッシュに追加します */ public async prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise { - const notCachedEmojis = emojis.filter(emoji => this.#cache.get(`${emoji.name} ${emoji.host}`) == null); + const notCachedEmojis = emojis.filter(emoji => this.cache.get(`${emoji.name} ${emoji.host}`) == null); const emojisQuery: any[] = []; const hosts = new Set(notCachedEmojis.map(e => e.host)); for (const host of hosts) { @@ -169,7 +169,7 @@ export class CustomEmojiService { select: ['name', 'host', 'originalUrl', 'publicUrl'], }) : []; for (const emoji of _emojis) { - this.#cache.set(`${emoji.name} ${emoji.host}`, emoji); + this.cache.set(`${emoji.name} ${emoji.host}`, emoji); } } } diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts index 9c5c45ed2c..81939d5f51 100644 --- a/packages/backend/src/core/DownloadService.ts +++ b/packages/backend/src/core/DownloadService.ts @@ -18,7 +18,7 @@ const pipeline = util.promisify(stream.pipeline); @Injectable() export class DownloadService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -27,11 +27,11 @@ export class DownloadService { private httpRequestService: HttpRequestService, private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('download'); + this.logger = this.loggerService.getLogger('download'); } public async downloadUrl(url: string, path: string): Promise { - this.#logger.info(`Downloading ${chalk.cyan(url)} ...`); + this.logger.info(`Downloading ${chalk.cyan(url)} ...`); const timeout = 30 * 1000; const operationTimeout = 60 * 1000; @@ -60,8 +60,8 @@ export class DownloadService { }, }).on('response', (res: Got.Response) => { if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !this.config.proxy && res.ip) { - if (this.#isPrivateIp(res.ip)) { - this.#logger.warn(`Blocked address: ${res.ip}`); + if (this.isPrivateIp(res.ip)) { + this.logger.warn(`Blocked address: ${res.ip}`); req.destroy(); } } @@ -70,13 +70,13 @@ export class DownloadService { if (contentLength != null) { const size = Number(contentLength); if (size > maxSize) { - this.#logger.warn(`maxSize exceeded (${size} > ${maxSize}) on response`); + this.logger.warn(`maxSize exceeded (${size} > ${maxSize}) on response`); req.destroy(); } } }).on('downloadProgress', (progress: Got.Progress) => { if (progress.transferred > maxSize) { - this.#logger.warn(`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`); + this.logger.warn(`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`); req.destroy(); } }); @@ -91,14 +91,14 @@ export class DownloadService { } } - this.#logger.succ(`Download finished: ${chalk.cyan(url)}`); + this.logger.succ(`Download finished: ${chalk.cyan(url)}`); } public async downloadTextFile(url: string): Promise { // Create temp file const [path, cleanup] = await createTemp(); - this.#logger.info(`text file: Temp file is ${path}`); + this.logger.info(`text file: Temp file is ${path}`); try { // write content at URL to temp file @@ -112,7 +112,7 @@ export class DownloadService { } } - #isPrivateIp(ip: string): boolean { + private isPrivateIp(ip: string): boolean { for (const net of this.config.allowedPrivateNetworks ?? []) { const cidr = new IPCIDR(net); if (cidr.contains(ip)) { diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index e601122fca..467f3c1cde 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -74,8 +74,8 @@ type UploadFromUrlArgs = { @Injectable() export class DriveService { - #registerLogger: Logger; - #downloaderLogger: Logger; + private registerLogger: Logger; + private downloaderLogger: Logger; constructor( @Inject(DI.config) @@ -110,8 +110,8 @@ export class DriveService { private instanceChart: InstanceChart, ) { const logger = new Logger('drive', 'blue'); - this.#registerLogger = logger.createSubLogger('register', 'yellow'); - this.#downloaderLogger = logger.createSubLogger('downloader'); + this.registerLogger = logger.createSubLogger('register', 'yellow'); + this.downloaderLogger = logger.createSubLogger('downloader'); } /*** @@ -122,7 +122,7 @@ export class DriveService { * @param hash Hash for original * @param size Size for original */ - async #save(file: DriveFile, path: string, name: string, type: string, hash: string, size: number): Promise { + private async save(file: DriveFile, path: string, name: string, type: string, hash: string, size: number): Promise { // thunbnail, webpublic を必要なら生成 const alts = await this.generateAlts(path, type, !file.uri); @@ -161,25 +161,25 @@ export class DriveService { //#endregion //#region Uploads - this.#registerLogger.info(`uploading original: ${key}`); + this.registerLogger.info(`uploading original: ${key}`); const uploads = [ - this.#upload(key, fs.createReadStream(path), type, name), + this.upload(key, fs.createReadStream(path), type, name), ]; if (alts.webpublic) { webpublicKey = `${meta.objectStoragePrefix}/webpublic-${uuid()}.${alts.webpublic.ext}`; webpublicUrl = `${ baseUrl }/${ webpublicKey }`; - this.#registerLogger.info(`uploading webpublic: ${webpublicKey}`); - uploads.push(this.#upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, name)); + this.registerLogger.info(`uploading webpublic: ${webpublicKey}`); + uploads.push(this.upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, name)); } if (alts.thumbnail) { thumbnailKey = `${meta.objectStoragePrefix}/thumbnail-${uuid()}.${alts.thumbnail.ext}`; thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`; - this.#registerLogger.info(`uploading thumbnail: ${thumbnailKey}`); - uploads.push(this.#upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type)); + this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`); + uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type)); } await Promise.all(uploads); @@ -211,12 +211,12 @@ export class DriveService { if (alts.thumbnail) { thumbnailUrl = this.internalStorageService.saveFromBuffer(thumbnailAccessKey, alts.thumbnail.data); - this.#registerLogger.info(`thumbnail stored: ${thumbnailAccessKey}`); + this.registerLogger.info(`thumbnail stored: ${thumbnailAccessKey}`); } if (alts.webpublic) { webpublicUrl = this.internalStorageService.saveFromBuffer(webpublicAccessKey, alts.webpublic.data); - this.#registerLogger.info(`web stored: ${webpublicAccessKey}`); + this.registerLogger.info(`web stored: ${webpublicAccessKey}`); } file.storedInternal = true; @@ -251,7 +251,7 @@ export class DriveService { thumbnail, }; } catch (err) { - this.#registerLogger.warn(`GenerateVideoThumbnail failed: ${err}`); + this.registerLogger.warn(`GenerateVideoThumbnail failed: ${err}`); return { webpublic: null, thumbnail: null, @@ -260,7 +260,7 @@ export class DriveService { } if (!['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml'].includes(type)) { - this.#registerLogger.debug('web image and thumbnail not created (not an required file)'); + this.registerLogger.debug('web image and thumbnail not created (not an required file)'); return { webpublic: null, thumbnail: null, @@ -290,7 +290,7 @@ export class DriveService { metadata.height && metadata.height <= 2048 ); } catch (err) { - this.#registerLogger.warn(`sharp failed: ${err}`); + this.registerLogger.warn(`sharp failed: ${err}`); return { webpublic: null, thumbnail: null, @@ -301,7 +301,7 @@ export class DriveService { let webpublic: IImage | null = null; if (generateWeb && !satisfyWebpublic) { - this.#registerLogger.info('creating web image'); + this.registerLogger.info('creating web image'); try { if (['image/jpeg', 'image/webp'].includes(type)) { @@ -311,14 +311,14 @@ export class DriveService { } else if (['image/svg+xml'].includes(type)) { webpublic = await this.imageProcessingService.convertSharpToPng(img, 2048, 2048); } else { - this.#registerLogger.debug('web image not created (not an required image)'); + this.registerLogger.debug('web image not created (not an required image)'); } } catch (err) { - this.#registerLogger.warn('web image not created (an error occured)', err as Error); + this.registerLogger.warn('web image not created (an error occured)', err as Error); } } else { - if (satisfyWebpublic) this.#registerLogger.info('web image not created (original satisfies webpublic)'); - else this.#registerLogger.info('web image not created (from remote)'); + if (satisfyWebpublic) this.registerLogger.info('web image not created (original satisfies webpublic)'); + else this.registerLogger.info('web image not created (from remote)'); } // #endregion webpublic @@ -329,10 +329,10 @@ export class DriveService { if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(type)) { thumbnail = await this.imageProcessingService.convertSharpToWebp(img, 498, 280); } else { - this.#registerLogger.debug('thumbnail not created (not an required file)'); + this.registerLogger.debug('thumbnail not created (not an required file)'); } } catch (err) { - this.#registerLogger.warn('thumbnail not created (an error occured)', err as Error); + this.registerLogger.warn('thumbnail not created (an error occured)', err as Error); } // #endregion thumbnail @@ -345,7 +345,7 @@ export class DriveService { /** * Upload to ObjectStorage */ - async #upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { + private async upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { if (type === 'image/apng') type = 'image/png'; if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream'; @@ -369,10 +369,10 @@ export class DriveService { }); const result = await upload.promise(); - if (result) this.#registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`); + if (result) this.registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`); } - async #deleteOldFile(user: IRemoteUser) { + private async deleteOldFile(user: IRemoteUser) { const q = this.driveFilesRepository.createQueryBuilder('file') .where('file.userId = :userId', { userId: user.id }) .andWhere('file.isLink = FALSE'); @@ -430,7 +430,7 @@ export class DriveService { sensitiveThresholdForPorn: 0.75, enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos, }); - this.#registerLogger.info(`${JSON.stringify(info)}`); + this.registerLogger.info(`${JSON.stringify(info)}`); // 現状 false positive が多すぎて実用に耐えない //if (info.porn && instance.disallowUploadWhenPredictedAsPorn) { @@ -448,7 +448,7 @@ export class DriveService { }); if (much) { - this.#registerLogger.info(`file with same hash is found: ${much.id}`); + this.registerLogger.info(`file with same hash is found: ${much.id}`); return much; } } @@ -463,11 +463,11 @@ export class DriveService { if (this.userEntityService.isLocalUser(user) && u?.driveCapacityOverrideMb != null) { driveCapacity = 1024 * 1024 * u.driveCapacityOverrideMb; - this.#registerLogger.debug('drive capacity override applied'); - this.#registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`); + this.registerLogger.debug('drive capacity override applied'); + this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`); } - this.#registerLogger.debug(`drive usage is ${usage} (max: ${driveCapacity})`); + this.registerLogger.debug(`drive usage is ${usage} (max: ${driveCapacity})`); // If usage limit exceeded if (usage + info.size > driveCapacity) { @@ -475,7 +475,7 @@ export class DriveService { throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.'); } else { // (アバターまたはバナーを含まず)最も古いファイルを削除する - this.#deleteOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as IRemoteUser); + this.deleteOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as IRemoteUser); } } } @@ -566,22 +566,22 @@ export class DriveService { } catch (err) { // duplicate key error (when already registered) if (isDuplicateKeyValueError(err)) { - this.#registerLogger.info(`already registered ${file.uri}`); + this.registerLogger.info(`already registered ${file.uri}`); file = await this.driveFilesRepository.findOneBy({ uri: file.uri!, userId: user ? user.id : IsNull(), }) as DriveFile; } else { - this.#registerLogger.error(err as Error); + this.registerLogger.error(err as Error); throw err; } } } else { - file = await (this.#save(file, path, detectedName, info.type.mime, info.md5, info.size)); + file = await (this.save(file, path, detectedName, info.type.mime, info.md5, info.size)); } - this.#registerLogger.succ(`drive file has been created ${file.id}`); + this.registerLogger.succ(`drive file has been created ${file.id}`); if (user) { this.driveFileEntityService.pack(file, { self: true }).then(packedFile => { @@ -624,7 +624,7 @@ export class DriveService { } } - this.#deletePostProcess(file, isExpired); + this.deletePostProcess(file, isExpired); } public async deleteFileSync(file: DriveFile, isExpired = false) { @@ -654,10 +654,10 @@ export class DriveService { await Promise.all(promises); } - this.#deletePostProcess(file, isExpired); + this.deletePostProcess(file, isExpired); } - async #deletePostProcess(file: DriveFile, isExpired = false) { + private async deletePostProcess(file: DriveFile, isExpired = false) { // リモートファイル期限切れ削除後は直リンクにする if (isExpired && file.userHost !== null && file.uri != null) { this.driveFilesRepository.update(file.id, { @@ -725,10 +725,10 @@ export class DriveService { await this.downloadService.downloadUrl(url, path); const driveFile = await this.addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive, requestIp, requestHeaders }); - this.#downloaderLogger.succ(`Got: ${driveFile.id}`); + this.downloaderLogger.succ(`Got: ${driveFile.id}`); return driveFile!; } catch (err) { - this.#downloaderLogger.error(`Failed to create drive file: ${err}`, { + this.downloaderLogger.error(`Failed to create drive file: ${err}`, { url: url, e: err, }); diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index a593922acc..521ab7fd81 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -10,7 +10,7 @@ import { LoggerService } from '@/core/LoggerService.js'; @Injectable() export class EmailService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -22,7 +22,7 @@ export class EmailService { private metaService: MetaService, private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('email'); + this.logger = this.loggerService.getLogger('email'); } public async sendEmail(to: string, subject: string, html: string, text: string) { @@ -134,9 +134,9 @@ export class EmailService { `, }); - this.#logger.info(`Message sent: ${info.messageId}`); + this.logger.info(`Message sent: ${info.messageId}`); } catch (err) { - this.#logger.error(err as Error); + this.logger.error(err as Error); throw err; } } diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index 24bedd8192..a4894a4376 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -8,7 +8,7 @@ import { UtilityService } from './UtilityService.js'; @Injectable() export class FederatedInstanceService { - #cache: Cache; + private cache: Cache; constructor( @Inject(DI.instancesRepository) @@ -17,13 +17,13 @@ export class FederatedInstanceService { private utilityService: UtilityService, private idService: IdService, ) { - this.#cache = new Cache(1000 * 60 * 60); + this.cache = new Cache(1000 * 60 * 60); } public async registerOrFetchInstanceDoc(host: string): Promise { host = this.utilityService.toPuny(host); - const cached = this.#cache.get(host); + const cached = this.cache.get(host); if (cached) return cached; const index = await this.instancesRepository.findOneBy({ host }); @@ -36,10 +36,10 @@ export class FederatedInstanceService { lastCommunicatedAt: new Date(), }).then(x => this.instancesRepository.findOneByOrFail(x.identifiers[0])); - this.#cache.set(host, i); + this.cache.set(host, i); return i; } else { - this.#cache.set(host, index); + this.cache.set(host, index); return index; } } diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 4414d83942..376617914e 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -32,7 +32,7 @@ type NodeInfo = { @Injectable() export class FetchInstanceMetadataService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.instancesRepository) @@ -42,7 +42,7 @@ export class FetchInstanceMetadataService { private httpRequestService: HttpRequestService, private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('metadata', 'cyan'); + this.logger = this.loggerService.getLogger('metadata', 'cyan'); } public async fetchInstanceMetadata(instance: Instance, force = false): Promise { @@ -57,24 +57,24 @@ export class FetchInstanceMetadataService { } } - this.#logger.info(`Fetching metadata of ${instance.host} ...`); + this.logger.info(`Fetching metadata of ${instance.host} ...`); try { const [info, dom, manifest] = await Promise.all([ - this.#fetchNodeinfo(instance).catch(() => null), - this.#fetchDom(instance).catch(() => null), - this.#fetchManifest(instance).catch(() => null), + this.fetchNodeinfo(instance).catch(() => null), + this.fetchDom(instance).catch(() => null), + this.fetchManifest(instance).catch(() => null), ]); const [favicon, icon, themeColor, name, description] = await Promise.all([ - this.#fetchFaviconUrl(instance, dom).catch(() => null), - this.#fetchIconUrl(instance, dom, manifest).catch(() => null), - this.#getThemeColor(info, dom, manifest).catch(() => null), - this.#getSiteName(info, dom, manifest).catch(() => null), - this.#getDescription(info, dom, manifest).catch(() => null), + this.fetchFaviconUrl(instance, dom).catch(() => null), + this.fetchIconUrl(instance, dom, manifest).catch(() => null), + this.getThemeColor(info, dom, manifest).catch(() => null), + this.getSiteName(info, dom, manifest).catch(() => null), + this.getDescription(info, dom, manifest).catch(() => null), ]); - this.#logger.succ(`Successfuly fetched metadata of ${instance.host}`); + this.logger.succ(`Successfuly fetched metadata of ${instance.host}`); const updates = { infoUpdatedAt: new Date(), @@ -96,16 +96,16 @@ export class FetchInstanceMetadataService { await this.instancesRepository.update(instance.id, updates); - this.#logger.succ(`Successfuly updated metadata of ${instance.host}`); + this.logger.succ(`Successfuly updated metadata of ${instance.host}`); } catch (e) { - this.#logger.error(`Failed to update metadata of ${instance.host}: ${e}`); + this.logger.error(`Failed to update metadata of ${instance.host}: ${e}`); } finally { unlock(); } } - async #fetchNodeinfo(instance: Instance): Promise { - this.#logger.info(`Fetching nodeinfo of ${instance.host} ...`); + private async fetchNodeinfo(instance: Instance): Promise { + this.logger.info(`Fetching nodeinfo of ${instance.host} ...`); try { const wellknown = await this.httpRequestService.getJson('https://' + instance.host + '/.well-known/nodeinfo') @@ -137,18 +137,18 @@ export class FetchInstanceMetadataService { throw err.statusCode ?? err.message; }); - this.#logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`); + this.logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`); return info as NodeInfo; } catch (err) { - this.#logger.error(`Failed to fetch nodeinfo of ${instance.host}: ${err}`); + this.logger.error(`Failed to fetch nodeinfo of ${instance.host}: ${err}`); throw err; } } - async #fetchDom(instance: Instance): Promise { - this.#logger.info(`Fetching HTML of ${instance.host} ...`); + private async fetchDom(instance: Instance): Promise { + this.logger.info(`Fetching HTML of ${instance.host} ...`); const url = 'https://' + instance.host; @@ -160,7 +160,7 @@ export class FetchInstanceMetadataService { return doc; } - async #fetchManifest(instance: Instance): Promise | null> { + private async fetchManifest(instance: Instance): Promise | null> { const url = 'https://' + instance.host; const manifestUrl = url + '/manifest.json'; @@ -170,7 +170,7 @@ export class FetchInstanceMetadataService { return manifest; } - async #fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] | null): Promise { + private async fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] | null): Promise { const url = 'https://' + instance.host; if (doc) { @@ -197,7 +197,7 @@ export class FetchInstanceMetadataService { return null; } - async #fetchIconUrl(instance: Instance, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { + private async fetchIconUrl(instance: Instance, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) { const url = 'https://' + instance.host; return (new URL(manifest.icons[0].src, url)).href; @@ -225,7 +225,7 @@ export class FetchInstanceMetadataService { return null; } - async #getThemeColor(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { + private async getThemeColor(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { const themeColor = info?.metadata?.themeColor ?? doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') ?? manifest?.theme_color; if (themeColor) { @@ -236,7 +236,7 @@ export class FetchInstanceMetadataService { return null; } - async #getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { + private async getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (info && info.metadata) { if (info.metadata.nodeName || info.metadata.name) { return info.metadata.nodeName ?? info.metadata.name; @@ -258,7 +258,7 @@ export class FetchInstanceMetadataService { return null; } - async #getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { + private async getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (info && info.metadata) { if (info.metadata.nodeDescription || info.metadata.description) { return info.metadata.nodeDescription ?? info.metadata.description; diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts index 45c94dafdc..fd8a4fdd3a 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -61,7 +61,7 @@ export class FileInfoService { const warnings = [] as string[]; const size = await this.getFileSize(path); - const md5 = await this.#calcHash(path); + const md5 = await this.calcHash(path); let type = await this.detectType(path); @@ -71,7 +71,7 @@ export class FileInfoService { let orientation: number | undefined; if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/bmp', 'image/tiff', 'image/svg+xml', 'image/vnd.adobe.photoshop'].includes(type.mime)) { - const imageSize = await this.#detectImageSize(path).catch(e => { + const imageSize = await this.detectImageSize(path).catch(e => { warnings.push(`detectImageSize failed: ${e}`); return undefined; }); @@ -98,7 +98,7 @@ export class FileInfoService { let blurhash: string | undefined; if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/svg+xml'].includes(type.mime)) { - blurhash = await this.#getBlurhash(path).catch(e => { + blurhash = await this.getBlurhash(path).catch(e => { warnings.push(`getBlurhash failed: ${e}`); return undefined; }); @@ -108,7 +108,7 @@ export class FileInfoService { let porn = false; if (!opts.skipSensitiveDetection) { - await this.#detectSensitivity( + await this.detectSensitivity( path, type.mime, opts.sensitiveThreshold ?? 0.5, @@ -135,7 +135,7 @@ export class FileInfoService { }; } - async #detectSensitivity(source: string, mime: string, sensitiveThreshold: number, sensitiveThresholdForPorn: number, analyzeVideo: boolean): Promise<[sensitive: boolean, porn: boolean]> { + private async detectSensitivity(source: string, mime: string, sensitiveThreshold: number, sensitiveThresholdForPorn: number, analyzeVideo: boolean): Promise<[sensitive: boolean, porn: boolean]> { let sensitive = false; let porn = false; @@ -204,7 +204,7 @@ export class FileInfoService { let frameIndex = 0; let targetIndex = 0; let nextIndex = 1; - for await (const path of this.#asyncIterateFrames(outDir, command)) { + for await (const path of this.asyncIterateFrames(outDir, command)) { try { const index = frameIndex++; if (index !== targetIndex) { @@ -230,7 +230,7 @@ export class FileInfoService { return [sensitive, porn]; } - async *#asyncIterateFrames(cwd: string, command: FFmpeg.FfmpegCommand): AsyncGenerator { + private async *asyncIterateFrames(cwd: string, command: FFmpeg.FfmpegCommand): AsyncGenerator { const watcher = new FSWatcher({ cwd, disableGlobbing: true, @@ -245,7 +245,7 @@ export class FileInfoService { const current = `${i}.png`; const next = `${i + 1}.png`; const framePath = join(cwd, current); - if (await this.#exists(join(cwd, next))) { + if (await this.exists(join(cwd, next))) { yield framePath; } else if (!finished) { // eslint-disable-line @typescript-eslint/no-unnecessary-condition watcher.add(next); @@ -261,7 +261,7 @@ export class FileInfoService { command.once('error', reject); }); yield framePath; - } else if (await this.#exists(framePath)) { + } else if (await this.exists(framePath)) { yield framePath; } else { return; @@ -269,7 +269,7 @@ export class FileInfoService { } } - #exists(path: string): Promise { + private exists(path: string): Promise { return fs.promises.access(path).then(() => true, () => false); } @@ -333,7 +333,7 @@ export class FileInfoService { /** * Calculate MD5 hash */ - async #calcHash(path: string): Promise { + private async calcHash(path: string): Promise { const hash = crypto.createHash('md5').setEncoding('hex'); await pipeline(fs.createReadStream(path), hash); return hash.read(); @@ -342,7 +342,7 @@ export class FileInfoService { /** * Detect dimensions of image */ - async #detectImageSize(path: string): Promise<{ + private async detectImageSize(path: string): Promise<{ width: number; height: number; wUnits: string; @@ -358,7 +358,7 @@ export class FileInfoService { /** * Calculate average color of image */ - #getBlurhash(path: string): Promise { + private getBlurhash(path: string): Promise { return new Promise((resolve, reject) => { sharp(path) .raw() diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index 21cde12536..f4c00cd259 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -15,12 +15,12 @@ export class HttpRequestService { /** * Get http non-proxy agent */ - #http: http.Agent; + private http: http.Agent; /** * Get https non-proxy agent */ - #https: https.Agent; + private https: https.Agent; /** * Get http proxy or non-proxy agent @@ -42,13 +42,13 @@ export class HttpRequestService { lookup: false, // nativeのdns.lookupにfallbackしない }); - this.#http = new http.Agent({ + this.http = new http.Agent({ keepAlive: true, keepAliveMsecs: 30 * 1000, lookup: cache.lookup, } as http.AgentOptions); - this.#https = new https.Agent({ + this.https = new https.Agent({ keepAlive: true, keepAliveMsecs: 30 * 1000, lookup: cache.lookup, @@ -65,7 +65,7 @@ export class HttpRequestService { scheduling: 'lifo', proxy: config.proxy, }) - : this.#http; + : this.http; this.httpsAgent = config.proxy ? new HttpsProxyAgent({ @@ -76,7 +76,7 @@ export class HttpRequestService { scheduling: 'lifo', proxy: config.proxy, }) - : this.#https; + : this.https; } /** @@ -86,7 +86,7 @@ export class HttpRequestService { */ public getAgentByUrl(url: URL, bypassProxy = false): http.Agent | https.Agent { if (bypassProxy || (this.config.proxyBypassHosts || []).includes(url.hostname)) { - return url.protocol === 'http:' ? this.#http : this.#https; + return url.protocol === 'http:' ? this.http : this.https; } else { return url.protocol === 'http:' ? this.httpAgent : this.httpsAgent; } diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts index b3b0d63627..345b72bac5 100644 --- a/packages/backend/src/core/IdService.ts +++ b/packages/backend/src/core/IdService.ts @@ -9,19 +9,19 @@ import { genObjectId } from '@/misc/id/object-id.js'; @Injectable() export class IdService { - #metohd: string; + private metohd: string; constructor( @Inject(DI.config) private config: Config, ) { - this.#metohd = config.id.toLowerCase(); + this.metohd = config.id.toLowerCase(); } public genId(date?: Date): string { if (!date || (date > new Date())) date = new Date(); - switch (this.#metohd) { + switch (this.metohd) { case 'aid': return genAid(date); case 'meid': return genMeid(date); case 'meidg': return genMeidg(date); diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts index 3a93a49c7b..57d55870b1 100644 --- a/packages/backend/src/core/InstanceActorService.ts +++ b/packages/backend/src/core/InstanceActorService.ts @@ -10,7 +10,7 @@ const ACTOR_USERNAME = 'instance.actor' as const; @Injectable() export class InstanceActorService { - #cache: Cache; + private cache: Cache; constructor( @Inject(DI.usersRepository) @@ -18,11 +18,11 @@ export class InstanceActorService { private createSystemUserService: CreateSystemUserService, ) { - this.#cache = new Cache(Infinity); + this.cache = new Cache(Infinity); } public async getInstanceActor(): Promise { - const cached = this.#cache.get(null); + const cached = this.cache.get(null); if (cached) return cached; const user = await this.usersRepository.findOneBy({ @@ -31,11 +31,11 @@ export class InstanceActorService { }) as ILocalUser | undefined; if (user) { - this.#cache.set(null, user); + this.cache.set(null, user); return user; } else { const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as ILocalUser; - this.#cache.set(null, created); + this.cache.set(null, created); return created; } } diff --git a/packages/backend/src/core/LoggerService.ts b/packages/backend/src/core/LoggerService.ts index d844b38841..558e3016dc 100644 --- a/packages/backend/src/core/LoggerService.ts +++ b/packages/backend/src/core/LoggerService.ts @@ -6,14 +6,14 @@ import Logger from '@/logger.js'; @Injectable() export class LoggerService { - #syslogClient; + private syslogClient; constructor( @Inject(DI.config) private config: Config, ) { if (this.config.syslog) { - this.#syslogClient = new SyslogPro.RFC5424({ + this.syslogClient = new SyslogPro.RFC5424({ applacationName: 'Misskey', timestamp: true, encludeStructuredData: true, @@ -28,6 +28,6 @@ export class LoggerService { } public getLogger(domain: string, color?: string | undefined, store?: boolean) { - return new Logger(domain, color, store, this.#syslogClient); + return new Logger(domain, color, store, this.syslogClient); } } diff --git a/packages/backend/src/core/MessagingService.ts b/packages/backend/src/core/MessagingService.ts index 669089e1e5..1819b32a45 100644 --- a/packages/backend/src/core/MessagingService.ts +++ b/packages/backend/src/core/MessagingService.ts @@ -142,10 +142,10 @@ export class MessagingService { public async deleteMessage(message: MessagingMessage) { await this.messagingMessagesRepository.delete(message.id); - this.#postDeleteMessage(message); + this.postDeleteMessage(message); } - async #postDeleteMessage(message: MessagingMessage) { + private async postDeleteMessage(message: MessagingMessage) { if (message.recipientId) { const user = await this.usersRepository.findOneByOrFail({ id: message.userId }); const recipient = await this.usersRepository.findOneByOrFail({ id: message.recipientId }); diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index b5bd423765..4099e340be 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -7,24 +7,24 @@ import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class MetaService implements OnApplicationShutdown { - #cache: Meta | undefined; - #intervalId: NodeJS.Timer; + private cache: Meta | undefined; + private intervalId: NodeJS.Timer; constructor( @Inject(DI.db) private db: DataSource, ) { if (process.env.NODE_ENV !== 'test') { - this.#intervalId = setInterval(() => { + this.intervalId = setInterval(() => { this.fetch(true).then(meta => { - this.#cache = meta; + this.cache = meta; }); }, 1000 * 10); } } async fetch(noCache = false): Promise { - if (!noCache && this.#cache) return this.#cache; + if (!noCache && this.cache) return this.cache; return await this.db.transaction(async transactionalEntityManager => { // 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する @@ -37,7 +37,7 @@ export class MetaService implements OnApplicationShutdown { const meta = metas[0]; if (meta) { - this.#cache = meta; + this.cache = meta; return meta; } else { // metaが空のときfetchMetaが同時に呼ばれるとここが同時に呼ばれてしまうことがあるのでフェイルセーフなupsertを使う @@ -51,13 +51,13 @@ export class MetaService implements OnApplicationShutdown { ) .then((x) => transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0])); - this.#cache = saved; + this.cache = saved; return saved; } }); } public onApplicationShutdown(signal?: string | undefined) { - clearInterval(this.#intervalId); + clearInterval(this.intervalId); } } diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 83c080893d..5acc07fba6 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -277,7 +277,7 @@ export class NoteCreateService { emojis = data.apEmojis ?? extractCustomEmojisFromMfm(combinedTokens); - mentionedUsers = data.apMentions ?? await this.#extractMentionedUsers(user, combinedTokens); + mentionedUsers = data.apMentions ?? await this.extractMentionedUsers(user, combinedTokens); } tags = tags.filter(tag => Array.from(tag ?? '').length <= 128).splice(0, 32); @@ -300,14 +300,14 @@ export class NoteCreateService { } } - const note = await this.#insertNote(user, data, tags, emojis, mentionedUsers); + const note = await this.insertNote(user, data, tags, emojis, mentionedUsers); - setImmediate(() => this.#postNoteCreated(note, user, data, silent, tags!, mentionedUsers!)); + setImmediate(() => this.postNoteCreated(note, user, data, silent, tags!, mentionedUsers!)); return note; } - async #insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) { + private async insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) { const insert = new Note({ id: this.idService.genId(data.createdAt!), createdAt: data.createdAt!, @@ -403,7 +403,7 @@ export class NoteCreateService { } } - async #postNoteCreated(note: Note, user: { + private async postNoteCreated(note: Note, user: { id: User['id']; username: User['username']; host: User['host']; @@ -428,7 +428,7 @@ export class NoteCreateService { } // Increment notes count (user) - this.#incNotesCountOfUser(user); + this.incNotesCountOfUser(user); // Word mute mutedWordsCache.fetch(null, () => this.userProfilesRepository.find({ @@ -473,12 +473,12 @@ export class NoteCreateService { } if (data.reply) { - this.#saveReply(data.reply, note); + this.saveReply(data.reply, note); } // この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき if (data.renote && (await this.noteEntityService.countSameRenotes(user.id, data.renote.id, note.id) === 0)) { - this.#incRenoteCount(data.renote); + this.incRenoteCount(data.renote); } if (data.poll && data.poll.expiresAt) { @@ -536,7 +536,7 @@ export class NoteCreateService { const nm = new NotificationManager(this.mutingsRepository, this.createNotificationService, user, note); const nmRelatedPromises = []; - await this.#createMentionedEvents(mentionedUsers, note, nm); + await this.createMentionedEvents(mentionedUsers, note, nm); // If has in reply to note if (data.reply) { @@ -590,7 +590,7 @@ export class NoteCreateService { //#region AP deliver if (this.userEntityService.isLocalUser(user)) { (async () => { - const noteActivity = await this.#renderNoteOrRenoteActivity(data, note); + const noteActivity = await this.renderNoteOrRenoteActivity(data, note); const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity); // メンションされたリモートユーザーに配送 @@ -644,10 +644,10 @@ export class NoteCreateService { } // Register to search database - this.#index(note); + this.index(note); } - #incRenoteCount(renote: Note) { + private incRenoteCount(renote: Note) { this.notesRepository.createQueryBuilder().update() .set({ renoteCount: () => '"renoteCount" + 1', @@ -657,7 +657,7 @@ export class NoteCreateService { .execute(); } - async #createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) { + private async createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) { for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) { const threadMuted = await this.noteThreadMutingsRepository.findOneBy({ userId: u.id, @@ -686,11 +686,11 @@ export class NoteCreateService { } } - #saveReply(reply: Note, note: Note) { + private saveReply(reply: Note, note: Note) { this.notesRepository.increment({ id: reply.id }, 'repliesCount', 1); } - async #renderNoteOrRenoteActivity(data: Option, note: Note) { + private async renderNoteOrRenoteActivity(data: Option, note: Note) { if (data.localOnly) return null; const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0) @@ -700,7 +700,7 @@ export class NoteCreateService { return this.apRendererService.renderActivity(content); } - #index(note: Note) { + private index(note: Note) { if (note.text == null || this.config.elasticsearch == null) return; /* es!.index({ @@ -714,7 +714,7 @@ export class NoteCreateService { });*/ } - #incNotesCountOfUser(user: { id: User['id']; }) { + private incNotesCountOfUser(user: { id: User['id']; }) { this.usersRepository.createQueryBuilder().update() .set({ updatedAt: new Date(), @@ -724,7 +724,7 @@ export class NoteCreateService { .execute(); } - async #extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.MfmNode[]): Promise { + private async extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.MfmNode[]): Promise { if (tokens == null) return []; const mentions = extractMentions(tokens); diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index 9153418beb..6365286f84 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -79,16 +79,16 @@ export class NoteDeleteService { ? this.apRendererService.renderUndo(this.apRendererService.renderAnnounce(renote.uri ?? `${this.config.url}/notes/${renote.id}`, note), user) : this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${note.id}`), user)); - this.#deliverToConcerned(user, note, content); + this.deliverToConcerned(user, note, content); } // also deliever delete activity to cascaded notes - const cascadingNotes = (await this.#findCascadingNotes(note)).filter(note => !note.localOnly); // filter out local-only notes + const cascadingNotes = (await this.findCascadingNotes(note)).filter(note => !note.localOnly); // filter out local-only notes for (const cascadingNote of cascadingNotes) { if (!cascadingNote.user) continue; if (!this.userEntityService.isLocalUser(cascadingNote.user)) continue; const content = this.apRendererService.renderActivity(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${cascadingNote.id}`), cascadingNote.user)); - this.#deliverToConcerned(cascadingNote.user, cascadingNote, content); + this.deliverToConcerned(cascadingNote.user, cascadingNote, content); } //#endregion @@ -110,7 +110,7 @@ export class NoteDeleteService { }); } - async #findCascadingNotes(note: Note) { + private async findCascadingNotes(note: Note) { const cascadingNotes: Note[] = []; const recursive = async (noteId: string) => { @@ -132,7 +132,7 @@ export class NoteDeleteService { return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users } - async #getMentionedRemoteUsers(note: Note) { + private async getMentionedRemoteUsers(note: Note) { const where = [] as any[]; // mention / reply / dm @@ -157,10 +157,10 @@ export class NoteDeleteService { }) as IRemoteUser[]; } - async #deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) { + private async deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) { this.apDeliverManagerService.deliverToFollowers(user, content); this.relayService.deliverToRelays(user, content); - const remoteUsers = await this.#getMentionedRemoteUsers(note); + const remoteUsers = await this.getMentionedRemoteUsers(note); for (const remoteUser of remoteUsers) { this.apDeliverManagerService.deliverToUser(user, content, remoteUser); } diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index 44957d62d3..ca9e60889d 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -38,8 +38,8 @@ export class NotificationService { if (result.affected === 0) return; - if (!await this.userEntityService.getHasUnreadNotification(userId)) return this.#postReadAllNotifications(userId); - else return this.#postReadNotifications(userId, notificationIds); + if (!await this.userEntityService.getHasUnreadNotification(userId)) return this.postReadAllNotifications(userId); + else return this.postReadNotifications(userId, notificationIds); } public async readNotificationByQuery( @@ -55,12 +55,12 @@ export class NotificationService { return this.readNotification(userId, notificationIds); } - #postReadAllNotifications(userId: User['id']) { + private postReadAllNotifications(userId: User['id']) { this.globalEventService.publishMainStream(userId, 'readAllNotifications'); return this.pushNotificationService.pushNotification(userId, 'readAllNotifications', undefined); } - #postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) { + private postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) { this.globalEventService.publishMainStream(userId, 'readNotifications', notificationIds); return this.pushNotificationService.pushNotification(userId, 'readNotifications', { notificationIds }); } diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts index c1d85e8b48..688ea03d34 100644 --- a/packages/backend/src/core/RelayService.ts +++ b/packages/backend/src/core/RelayService.ts @@ -14,7 +14,7 @@ const ACTOR_USERNAME = 'relay.actor' as const; @Injectable() export class RelayService { - #relaysCache: Cache; + private relaysCache: Cache; constructor( @Inject(DI.usersRepository) @@ -28,10 +28,10 @@ export class RelayService { private createSystemUserService: CreateSystemUserService, private apRendererService: ApRendererService, ) { - this.#relaysCache = new Cache(1000 * 60 * 10); + this.relaysCache = new Cache(1000 * 60 * 10); } - async #getRelayActor(): Promise { + private async getRelayActor(): Promise { const user = await this.usersRepository.findOneBy({ host: IsNull(), username: ACTOR_USERNAME, @@ -50,7 +50,7 @@ export class RelayService { status: 'requesting', }).then(x => this.relaysRepository.findOneByOrFail(x.identifiers[0])); - const relayActor = await this.#getRelayActor(); + const relayActor = await this.getRelayActor(); const follow = await this.apRendererService.renderFollowRelay(relay, relayActor); const activity = this.apRendererService.renderActivity(follow); this.queueService.deliver(relayActor, activity, relay.inbox); @@ -67,7 +67,7 @@ export class RelayService { throw new Error('relay not found'); } - const relayActor = await this.#getRelayActor(); + const relayActor = await this.getRelayActor(); const follow = this.apRendererService.renderFollowRelay(relay, relayActor); const undo = this.apRendererService.renderUndo(follow, relayActor); const activity = this.apRendererService.renderActivity(undo); @@ -100,7 +100,7 @@ export class RelayService { public async deliverToRelays(user: { id: User['id']; host: null; }, activity: any): Promise { if (activity == null) return; - const relays = await this.#relaysCache.fetch(null, () => this.relaysRepository.findBy({ + const relays = await this.relaysCache.fetch(null, () => this.relaysRepository.findBy({ status: 'accepted', })); if (relays.length === 0) return; diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index a9396fcbba..9efb021f64 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -44,11 +44,11 @@ export class UserBlockingService { public async block(blocker: User, blockee: User) { await Promise.all([ - this.#cancelRequest(blocker, blockee), - this.#cancelRequest(blockee, blocker), - this.#unFollow(blocker, blockee), - this.#unFollow(blockee, blocker), - this.#removeFromList(blockee, blocker), + this.cancelRequest(blocker, blockee), + this.cancelRequest(blockee, blocker), + this.unFollow(blocker, blockee), + this.unFollow(blockee, blocker), + this.removeFromList(blockee, blocker), ]); const blocking = { @@ -68,7 +68,7 @@ export class UserBlockingService { } } - async #cancelRequest(follower: User, followee: User) { + private async cancelRequest(follower: User, followee: User) { const request = await this.followRequestsRepository.findOneBy({ followeeId: followee.id, followerId: follower.id, @@ -118,7 +118,7 @@ export class UserBlockingService { } } - async #unFollow(follower: User, followee: User) { + private async unFollow(follower: User, followee: User) { const following = await this.followingsRepository.findOneBy({ followerId: follower.id, followeeId: followee.id, @@ -159,7 +159,7 @@ export class UserBlockingService { } } - async #removeFromList(listOwner: User, user: User) { + private async removeFromList(listOwner: User, user: User) { const userLists = await this.userListsRepository.findBy({ userId: listOwner.id, }); diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 6aae5a6b99..ff86d4343f 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -131,7 +131,7 @@ export class UserFollowingService { } } - await this.#insertFollowingDoc(followee, follower); + await this.insertFollowingDoc(followee, follower); if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { const content = this.apRendererService.renderActivity(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee)); @@ -139,7 +139,7 @@ export class UserFollowingService { } } - async #insertFollowingDoc( + private async insertFollowingDoc( followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox'] }, @@ -273,7 +273,7 @@ export class UserFollowingService { await this.followingsRepository.delete(following.id); - this.#decrementFollowing(follower, followee); + this.decrementFollowing(follower, followee); // Publish unfollow event if (!silent && this.userEntityService.isLocalUser(follower)) { @@ -304,7 +304,7 @@ export class UserFollowingService { } } - async #decrementFollowing( + private async decrementFollowing( follower: {id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }, ): Promise { @@ -445,7 +445,7 @@ export class UserFollowingService { throw new IdentifiableError('8884c2dd-5795-4ac9-b27e-6a01d38190f9', 'No follow request.'); } - await this.#insertFollowingDoc(followee, follower); + await this.insertFollowingDoc(followee, follower); if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { const content = this.apRendererService.renderActivity(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, request.requestId!), followee)); @@ -477,13 +477,13 @@ export class UserFollowingService { */ public async rejectFollowRequest(user: Local, follower: Both): Promise { if (this.userEntityService.isRemoteUser(follower)) { - this.#deliverReject(user, follower); + this.deliverReject(user, follower); } - await this.#removeFollowRequest(user, follower); + await this.removeFollowRequest(user, follower); if (this.userEntityService.isLocalUser(follower)) { - this.#publishUnfollow(user, follower); + this.publishUnfollow(user, follower); } } @@ -492,13 +492,13 @@ export class UserFollowingService { */ public async rejectFollow(user: Local, follower: Both): Promise { if (this.userEntityService.isRemoteUser(follower)) { - this.#deliverReject(user, follower); + this.deliverReject(user, follower); } - await this.#removeFollow(user, follower); + await this.removeFollow(user, follower); if (this.userEntityService.isLocalUser(follower)) { - this.#publishUnfollow(user, follower); + this.publishUnfollow(user, follower); } } @@ -506,15 +506,15 @@ export class UserFollowingService { * AP Reject/Follow */ public async remoteReject(actor: Remote, follower: Local): Promise { - await this.#removeFollowRequest(actor, follower); - await this.#removeFollow(actor, follower); - this.#publishUnfollow(actor, follower); + await this.removeFollowRequest(actor, follower); + await this.removeFollow(actor, follower); + this.publishUnfollow(actor, follower); } /** * Remove follow request record */ - async #removeFollowRequest(followee: Both, follower: Both): Promise { + private async removeFollowRequest(followee: Both, follower: Both): Promise { const request = await this.followRequestsRepository.findOneBy({ followeeId: followee.id, followerId: follower.id, @@ -528,7 +528,7 @@ export class UserFollowingService { /** * Remove follow record */ - async #removeFollow(followee: Both, follower: Both): Promise { + private async removeFollow(followee: Both, follower: Both): Promise { const following = await this.followingsRepository.findOneBy({ followeeId: followee.id, followerId: follower.id, @@ -537,13 +537,13 @@ export class UserFollowingService { if (!following) return; await this.followingsRepository.delete(following.id); - this.#decrementFollowing(follower, followee); + this.decrementFollowing(follower, followee); } /** * Deliver Reject to remote */ - async #deliverReject(followee: Local, follower: Remote): Promise { + private async deliverReject(followee: Local, follower: Remote): Promise { const request = await this.followRequestsRepository.findOneBy({ followeeId: followee.id, followerId: follower.id, @@ -556,7 +556,7 @@ export class UserFollowingService { /** * Publish unfollow to local */ - async #publishUnfollow(followee: Both, follower: Local): Promise { + private async publishUnfollow(followee: Both, follower: Local): Promise { const packedFollowee = await this.userEntityService.pack(followee.id, follower, { detail: true, }); diff --git a/packages/backend/src/core/UserKeypairStoreService.ts b/packages/backend/src/core/UserKeypairStoreService.ts index f093d2ea91..e53f37b714 100644 --- a/packages/backend/src/core/UserKeypairStoreService.ts +++ b/packages/backend/src/core/UserKeypairStoreService.ts @@ -7,16 +7,16 @@ import { DI } from '@/di-symbols.js'; @Injectable() export class UserKeypairStoreService { - #cache: Cache; + private cache: Cache; constructor( @Inject(DI.userKeypairsRepository) private userKeypairsRepository: UserKeypairsRepository, ) { - this.#cache = new Cache(Infinity); + this.cache = new Cache(Infinity); } public async getUserKeypair(userId: User['id']): Promise { - return await this.#cache.fetch(userId, () => this.userKeypairsRepository.findOneByOrFail({ userId: userId })); + return await this.cache.fetch(userId, () => this.userKeypairsRepository.findOneByOrFail({ userId: userId })); } } diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index 3ed615ca0b..1d74290dd9 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -7,8 +7,8 @@ import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class WebhookService implements OnApplicationShutdown { - #webhooksFetched = false; - #webhooks: Webhook[] = []; + private webhooksFetched = false; + private webhooks: Webhook[] = []; constructor( @Inject(DI.redisSubscriber) @@ -22,14 +22,14 @@ export class WebhookService implements OnApplicationShutdown { } public async getActiveWebhooks() { - if (!this.#webhooksFetched) { - this.#webhooks = await this.webhooksRepository.findBy({ + if (!this.webhooksFetched) { + this.webhooks = await this.webhooksRepository.findBy({ active: true, }); - this.#webhooksFetched = true; + this.webhooksFetched = true; } - return this.#webhooks; + return this.webhooks; } private async onMessage(_, data) { @@ -40,23 +40,23 @@ export class WebhookService implements OnApplicationShutdown { switch (type) { case 'webhookCreated': if (body.active) { - this.#webhooks.push(body); + this.webhooks.push(body); } break; case 'webhookUpdated': if (body.active) { - const i = this.#webhooks.findIndex(a => a.id === body.id); + const i = this.webhooks.findIndex(a => a.id === body.id); if (i > -1) { - this.#webhooks[i] = body; + this.webhooks[i] = body; } else { - this.#webhooks.push(body); + this.webhooks.push(body); } } else { - this.#webhooks = this.#webhooks.filter(a => a.id !== body.id); + this.webhooks = this.webhooks.filter(a => a.id !== body.id); } break; case 'webhookDeleted': - this.#webhooks = this.#webhooks.filter(a => a.id !== body.id); + this.webhooks = this.webhooks.filter(a => a.id !== body.id); break; default: break; diff --git a/packages/backend/src/core/chart/core.ts b/packages/backend/src/core/chart/core.ts index 93a6c3ce05..cf5aa48884 100644 --- a/packages/backend/src/core/chart/core.ts +++ b/packages/backend/src/core/chart/core.ts @@ -109,7 +109,7 @@ export function getJsonSchema(schema: S): ToJsonSchema { - #logger: Logger; + private logger: Logger; public schema: T; @@ -242,7 +242,7 @@ export default abstract class Chart { this.name = name; this.schema = schema; this.lock = lock; - this.#logger = logger; + this.logger = logger; const { hour, day } = Chart.schemaToEntity(name, schema, grouped); this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour); @@ -333,7 +333,7 @@ export default abstract class Chart { // 初期ログデータを作成 data = this.getNewLog(null); - this.#logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): Initial commit created`); + this.logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): Initial commit created`); } const date = Chart.dateToTimestamp(current); @@ -363,7 +363,7 @@ export default abstract class Chart { ...columns, }).then(x => repository.findOneByOrFail(x.identifiers[0])) as RawRecord; - this.#logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`); + this.logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`); return log; } finally { @@ -382,7 +382,7 @@ export default abstract class Chart { public async save(): Promise { if (this.buffer.length === 0) { - this.#logger.info(`${this.name}: Write skipped`); + this.logger.info(`${this.name}: Write skipped`); return; } @@ -481,7 +481,7 @@ export default abstract class Chart { .execute(), ]); - this.#logger.info(`${this.name + (logHour.group ? `:${logHour.group}` : '')}: Updated`); + this.logger.info(`${this.name + (logHour.group ? `:${logHour.group}` : '')}: Updated`); // TODO: この一連の処理が始まった後に新たにbufferに入ったものは消さないようにする this.buffer = this.buffer.filter(q => q.group != null && (q.group !== logHour.group)); diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index eab233bbef..669680758d 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -68,7 +68,7 @@ export class NoteEntityService implements OnModuleInit { this.reactionService = this.moduleRef.get('ReactionService'); } - async #hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) { + private async hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) { // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) let hide = false; @@ -128,7 +128,7 @@ export class NoteEntityService implements OnModuleInit { } } - async #populatePoll(note: Note, meId: User['id'] | null) { + private async populatePoll(note: Note, meId: User['id'] | null) { const poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id }); const choices = poll.choices.map(c => ({ text: c, @@ -166,7 +166,7 @@ export class NoteEntityService implements OnModuleInit { }; } - async #populateMyReaction(note: Note, meId: User['id'], _hint_?: { + private async populateMyReaction(note: Note, meId: User['id'], _hint_?: { myReactions: Map; }) { if (_hint_?.myReactions) { @@ -319,10 +319,10 @@ export class NoteEntityService implements OnModuleInit { _hint_: options?._hint_, }) : undefined, - poll: note.hasPoll ? this.#populatePoll(note, meId) : undefined, + poll: note.hasPoll ? this.populatePoll(note, meId) : undefined, ...(meId ? { - myReaction: this.#populateMyReaction(note, meId, options?._hint_), + myReaction: this.populateMyReaction(note, meId, options?._hint_), } : {}), } : {}), }); @@ -339,7 +339,7 @@ export class NoteEntityService implements OnModuleInit { } if (!opts.skipHide) { - await this.#hideNote(packed, meId); + await this.hideNote(packed, meId); } return packed; diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 48d8af83fb..343e42df07 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -48,7 +48,7 @@ export class UserEntityService implements OnModuleInit { private pageEntityService: PageEntityService; private customEmojiService: CustomEmojiService; private antennaService: AntennaService; - #userInstanceCache: Cache; + private userInstanceCache: Cache; constructor( private moduleRef: ModuleRef, @@ -119,7 +119,7 @@ export class UserEntityService implements OnModuleInit { //private customEmojiService: CustomEmojiService, //private antennaService: AntennaService, ) { - this.#userInstanceCache = new Cache(1000 * 60 * 60 * 3); + this.userInstanceCache = new Cache(1000 * 60 * 60 * 3); } onModuleInit() { @@ -384,7 +384,7 @@ export class UserEntityService implements OnModuleInit { isModerator: user.isModerator ?? falsy, isBot: user.isBot ?? falsy, isCat: user.isCat ?? falsy, - instance: user.host ? this.#userInstanceCache.fetch(user.host, + instance: user.host ? this.userInstanceCache.fetch(user.host, () => this.instancesRepository.findOneBy({ host: user.host! }), v => v != null, ).then(instance => instance ? { diff --git a/packages/backend/src/core/remote/ResolveUserService.ts b/packages/backend/src/core/remote/ResolveUserService.ts index 4ef91bc8e6..b45168fb02 100644 --- a/packages/backend/src/core/remote/ResolveUserService.ts +++ b/packages/backend/src/core/remote/ResolveUserService.ts @@ -14,7 +14,7 @@ import { ApPersonService } from './activitypub/models/ApPersonService.js'; @Injectable() export class ResolveUserService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -28,14 +28,14 @@ export class ResolveUserService { private remoteLoggerService: RemoteLoggerService, private apPersonService: ApPersonService, ) { - this.#logger = this.remoteLoggerService.logger.createSubLogger('resolve-user'); + this.logger = this.remoteLoggerService.logger.createSubLogger('resolve-user'); } public async resolveUser(username: string, host: string | null): Promise { const usernameLower = username.toLowerCase(); if (host == null) { - this.#logger.info(`return local user: ${usernameLower}`); + this.logger.info(`return local user: ${usernameLower}`); return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => { if (u == null) { throw new Error('user not found'); @@ -48,7 +48,7 @@ export class ResolveUserService { host = this.utilityService.toPuny(host); if (this.config.host === host) { - this.#logger.info(`return local user: ${usernameLower}`); + this.logger.info(`return local user: ${usernameLower}`); return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => { if (u == null) { throw new Error('user not found'); @@ -63,9 +63,9 @@ export class ResolveUserService { const acctLower = `${usernameLower}@${host}`; if (user == null) { - const self = await this.#resolveSelf(acctLower); + const self = await this.resolveSelf(acctLower); - this.#logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`); + this.logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`); return await this.apPersonService.createPerson(self.href); } @@ -76,13 +76,13 @@ export class ResolveUserService { lastFetchedAt: new Date(), }); - this.#logger.info(`try resync: ${acctLower}`); - const self = await this.#resolveSelf(acctLower); + this.logger.info(`try resync: ${acctLower}`); + const self = await this.resolveSelf(acctLower); if (user.uri !== self.href) { // if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping. - this.#logger.info(`uri missmatch: ${acctLower}`); - this.#logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`); + this.logger.info(`uri missmatch: ${acctLower}`); + this.logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`); // validate uri const uri = new URL(self.href); @@ -97,12 +97,12 @@ export class ResolveUserService { uri: self.href, }); } else { - this.#logger.info(`uri is fine: ${acctLower}`); + this.logger.info(`uri is fine: ${acctLower}`); } await this.apPersonService.updatePerson(self.href); - this.#logger.info(`return resynced remote user: ${acctLower}`); + this.logger.info(`return resynced remote user: ${acctLower}`); return await this.usersRepository.findOneBy({ uri: self.href }).then(u => { if (u == null) { throw new Error('user not found'); @@ -112,19 +112,19 @@ export class ResolveUserService { }); } - this.#logger.info(`return existing remote user: ${acctLower}`); + this.logger.info(`return existing remote user: ${acctLower}`); return user; } - async #resolveSelf(acctLower: string) { - this.#logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); + private async resolveSelf(acctLower: string) { + this.logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); const finger = await this.webfingerService.webfinger(acctLower).catch(err => { - this.#logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`); + this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`); throw new Error(`Failed to WebFinger for ${acctLower}: ${ err.statusCode ?? err.message }`); }); const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self'); if (!self) { - this.#logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`); + this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`); throw new Error('self link not found'); } return self; diff --git a/packages/backend/src/core/remote/WebfingerService.ts b/packages/backend/src/core/remote/WebfingerService.ts index 24ccaf528b..ab46314792 100644 --- a/packages/backend/src/core/remote/WebfingerService.ts +++ b/packages/backend/src/core/remote/WebfingerService.ts @@ -26,12 +26,12 @@ export class WebfingerService { } public async webfinger(query: string): Promise { - const url = this.#genUrl(query); + const url = this.genUrl(query); return await this.httpRequestService.getJson(url, 'application/jrd+json, application/json') as IWebFinger; } - #genUrl(query: string): string { + private genUrl(query: string): string { if (query.match(/^https?:\/\//)) { const u = new URL(query); return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query }); diff --git a/packages/backend/src/core/remote/activitypub/ApAudienceService.ts b/packages/backend/src/core/remote/activitypub/ApAudienceService.ts index 178631ac55..744017aa3a 100644 --- a/packages/backend/src/core/remote/activitypub/ApAudienceService.ts +++ b/packages/backend/src/core/remote/activitypub/ApAudienceService.ts @@ -25,8 +25,8 @@ export class ApAudienceService { } public async parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { - const toGroups = this.#groupingAudience(getApIds(to), actor); - const ccGroups = this.#groupingAudience(getApIds(cc), actor); + const toGroups = this.groupingAudience(getApIds(to), actor); + const ccGroups = this.groupingAudience(getApIds(cc), actor); const others = unique(concat([toGroups.other, ccGroups.other])); @@ -66,7 +66,7 @@ export class ApAudienceService { }; } - #groupingAudience(ids: string[], actor: CacheableRemoteUser) { + private groupingAudience(ids: string[], actor: CacheableRemoteUser) { const groups = { public: [] as string[], followers: [] as string[], @@ -74,9 +74,9 @@ export class ApAudienceService { }; for (const id of ids) { - if (this.#isPublic(id)) { + if (this.isPublic(id)) { groups.public.push(id); - } else if (this.#isFollowers(id, actor)) { + } else if (this.isFollowers(id, actor)) { groups.followers.push(id); } else { groups.other.push(id); @@ -88,7 +88,7 @@ export class ApAudienceService { return groups; } - #isPublic(id: string) { + private isPublic(id: string) { return [ 'https://www.w3.org/ns/activitystreams#Public', 'as#Public', @@ -96,7 +96,7 @@ export class ApAudienceService { ].includes(id); } - #isFollowers(id: string, actor: CacheableRemoteUser) { + private isFollowers(id: string, actor: CacheableRemoteUser) { return ( id === (actor.followersUri ?? `${actor.uri}/followers`) ); diff --git a/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts b/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts index 37f58c6b0c..6f197985da 100644 --- a/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts @@ -31,8 +31,8 @@ export type UriParseResult = { @Injectable() export class ApDbResolverService { - #publicKeyCache: Cache; - #publicKeyByUserIdCache: Cache; + private publicKeyCache: Cache; + private publicKeyByUserIdCache: Cache; constructor( @Inject(DI.config) @@ -53,8 +53,8 @@ export class ApDbResolverService { private userCacheService: UserCacheService, private apPersonService: ApPersonService, ) { - this.#publicKeyCache = new Cache(Infinity); - this.#publicKeyByUserIdCache = new Cache(Infinity); + this.publicKeyCache = new Cache(Infinity); + this.publicKeyByUserIdCache = new Cache(Infinity); } public parseUri(value: string | IObject): UriParseResult { @@ -140,7 +140,7 @@ export class ApDbResolverService { user: CacheableRemoteUser; key: UserPublickey; } | null> { - const key = await this.#publicKeyCache.fetch(keyId, async () => { + const key = await this.publicKeyCache.fetch(keyId, async () => { const key = await this.userPublickeysRepository.findOneBy({ keyId, }); @@ -169,7 +169,7 @@ export class ApDbResolverService { if (user == null) return null; - const key = await this.#publicKeyByUserIdCache.fetch(user.id, () => this.userPublickeysRepository.findOneBy({ userId: user.id }), v => v != null); + const key = await this.publicKeyByUserIdCache.fetch(user.id, () => this.userPublickeysRepository.findOneBy({ userId: user.id }), v => v != null); return { user, diff --git a/packages/backend/src/core/remote/activitypub/ApInboxService.ts b/packages/backend/src/core/remote/activitypub/ApInboxService.ts index dc65f65ae1..0482e029d2 100644 --- a/packages/backend/src/core/remote/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/remote/activitypub/ApInboxService.ts @@ -34,7 +34,7 @@ import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow @Injectable() export class ApInboxService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -81,7 +81,7 @@ export class ApInboxService { private queueService: QueueService, private messagingService: MessagingService, ) { - this.#logger = this.apLoggerService.logger; + this.logger = this.apLoggerService.logger; } public async performActivity(actor: CacheableRemoteUser, activity: IObject) { @@ -93,7 +93,7 @@ export class ApInboxService { await this.performOneActivity(actor, act); } catch (err) { if (err instanceof Error || typeof err === 'string') { - this.#logger.error(err); + this.logger.error(err); } } } @@ -115,39 +115,39 @@ export class ApInboxService { if (actor.isSuspended) return; if (isCreate(activity)) { - await this.#create(actor, activity); + await this.create(actor, activity); } else if (isDelete(activity)) { - await this.#delete(actor, activity); + await this.delete(actor, activity); } else if (isUpdate(activity)) { - await this.#update(actor, activity); + await this.update(actor, activity); } else if (isRead(activity)) { - await this.#read(actor, activity); + await this.read(actor, activity); } else if (isFollow(activity)) { - await this.#follow(actor, activity); + await this.follow(actor, activity); } else if (isAccept(activity)) { - await this.#accept(actor, activity); + await this.accept(actor, activity); } else if (isReject(activity)) { - await this.#reject(actor, activity); + await this.reject(actor, activity); } else if (isAdd(activity)) { - await this.#add(actor, activity).catch(err => this.#logger.error(err)); + await this.add(actor, activity).catch(err => this.logger.error(err)); } else if (isRemove(activity)) { - await this.#remove(actor, activity).catch(err => this.#logger.error(err)); + await this.remove(actor, activity).catch(err => this.logger.error(err)); } else if (isAnnounce(activity)) { - await this.#announce(actor, activity); + await this.announce(actor, activity); } else if (isLike(activity)) { - await this.#like(actor, activity); + await this.like(actor, activity); } else if (isUndo(activity)) { - await this.#undo(actor, activity); + await this.undo(actor, activity); } else if (isBlock(activity)) { - await this.#block(actor, activity); + await this.block(actor, activity); } else if (isFlag(activity)) { - await this.#flag(actor, activity); + await this.flag(actor, activity); } else { - this.#logger.warn(`unrecognized activity type: ${(activity as any).type}`); + this.logger.warn(`unrecognized activity type: ${(activity as any).type}`); } } - async #follow(actor: CacheableRemoteUser, activity: IFollow): Promise { + private async follow(actor: CacheableRemoteUser, activity: IFollow): Promise { const followee = await this.apDbResolverService.getUserFromApId(activity.object); if (followee == null) { @@ -162,7 +162,7 @@ export class ApInboxService { return 'ok'; } - async #like(actor: CacheableRemoteUser, activity: ILike): Promise { + private async like(actor: CacheableRemoteUser, activity: ILike): Promise { const targetUri = getApId(activity.object); const note = await this.apNoteService.fetchNote(targetUri); @@ -179,7 +179,7 @@ export class ApInboxService { }).then(() => 'ok'); } - async #read(actor: CacheableRemoteUser, activity: IRead): Promise { + private async read(actor: CacheableRemoteUser, activity: IRead): Promise { const id = await getApId(activity.object); if (!this.utilityService.isSelfHost(this.utilityService.extractDbHost(id))) { @@ -201,24 +201,24 @@ export class ApInboxService { return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`; } - async #accept(actor: CacheableRemoteUser, activity: IAccept): Promise { + private async accept(actor: CacheableRemoteUser, activity: IAccept): Promise { const uri = activity.id ?? activity; - this.#logger.info(`Accept: ${uri}`); + this.logger.info(`Accept: ${uri}`); const resolver = this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(err => { - this.#logger.error(`Resolution failed: ${err}`); + this.logger.error(`Resolution failed: ${err}`); throw err; }); - if (isFollow(object)) return await this.#acceptFollow(actor, object); + if (isFollow(object)) return await this.acceptFollow(actor, object); return `skip: Unknown Accept type: ${getApType(object)}`; } - async #acceptFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { + private async acceptFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある const follower = await this.apDbResolverService.getUserFromApId(activity.actor); @@ -241,7 +241,7 @@ export class ApInboxService { return 'ok'; } - async #add(actor: CacheableRemoteUser, activity: IAdd): Promise { + private async add(actor: CacheableRemoteUser, activity: IAdd): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -260,17 +260,17 @@ export class ApInboxService { throw new Error(`unknown target: ${activity.target}`); } - async #announce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { + private async announce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { const uri = getApId(activity); - this.#logger.info(`Announce: ${uri}`); + this.logger.info(`Announce: ${uri}`); const targetUri = getApId(activity.object); - this.#announceNote(actor, activity, targetUri); + this.announceNote(actor, activity, targetUri); } - async #announceNote(actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { + private async announceNote(actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { const uri = getApId(activity); if (actor.isSuspended) { @@ -298,18 +298,18 @@ export class ApInboxService { // 対象が4xxならスキップ if (err instanceof StatusError) { if (err.isClientError) { - this.#logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`); + this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`); return; } - this.#logger.warn(`Error in announce target ${targetUri} - ${err.statusCode ?? err}`); + this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode ?? err}`); } throw err; } if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) return 'skip: invalid actor for this activity'; - this.#logger.info(`Creating the (Re)Note: ${uri}`); + this.logger.info(`Creating the (Re)Note: ${uri}`); const activityAudience = await this.apAudienceService.parseAudience(actor, activity.to, activity.cc); @@ -325,7 +325,7 @@ export class ApInboxService { } } - async #block(actor: CacheableRemoteUser, activity: IBlock): Promise { + private async block(actor: CacheableRemoteUser, activity: IBlock): Promise { // ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず const blockee = await this.apDbResolverService.getUserFromApId(activity.object); @@ -342,10 +342,10 @@ export class ApInboxService { return 'ok'; } - async #create(actor: CacheableRemoteUser, activity: ICreate): Promise { + private async create(actor: CacheableRemoteUser, activity: ICreate): Promise { const uri = getApId(activity); - this.#logger.info(`Create: ${uri}`); + this.logger.info(`Create: ${uri}`); // copy audiences between activity <=> object. if (typeof activity.object === 'object') { @@ -366,18 +366,18 @@ export class ApInboxService { const resolver = this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { - this.#logger.error(`Resolution failed: ${e}`); + this.logger.error(`Resolution failed: ${e}`); throw e; }); if (isPost(object)) { - this.#createNote(resolver, actor, object, false, activity); + this.createNote(resolver, actor, object, false, activity); } else { - this.#logger.warn(`Unknown type: ${getApType(object)}`); + this.logger.warn(`Unknown type: ${getApType(object)}`); } } - async #createNote(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { + private async createNote(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { const uri = getApId(note); if (typeof note === 'object') { @@ -411,7 +411,7 @@ export class ApInboxService { } } - async #delete(actor: CacheableRemoteUser, activity: IDelete): Promise { + private async delete(actor: CacheableRemoteUser, activity: IDelete): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -444,16 +444,16 @@ export class ApInboxService { } if (validPost.includes(formerType)) { - return await this.#deleteNote(actor, uri); + return await this.deleteNote(actor, uri); } else if (validActor.includes(formerType)) { - return await this.#deleteActor(actor, uri); + return await this.deleteActor(actor, uri); } else { return `Unknown type ${formerType}`; } } - async #deleteActor(actor: CacheableRemoteUser, uri: string): Promise { - this.#logger.info(`Deleting the Actor: ${uri}`); + private async deleteActor(actor: CacheableRemoteUser, uri: string): Promise { + this.logger.info(`Deleting the Actor: ${uri}`); if (actor.uri !== uri) { return `skip: delete actor ${actor.uri} !== ${uri}`; @@ -461,7 +461,7 @@ export class ApInboxService { const user = await this.usersRepository.findOneByOrFail({ id: actor.id }); if (user.isDeleted) { - this.#logger.info('skip: already deleted'); + this.logger.info('skip: already deleted'); } const job = await this.queueService.createDeleteAccountJob(actor); @@ -473,8 +473,8 @@ export class ApInboxService { return `ok: queued ${job.name} ${job.id}`; } - async #deleteNote(actor: CacheableRemoteUser, uri: string): Promise { - this.#logger.info(`Deleting the Note: ${uri}`); + private async deleteNote(actor: CacheableRemoteUser, uri: string): Promise { + this.logger.info(`Deleting the Note: ${uri}`); const unlock = await this.appLockService.getApLock(uri); @@ -505,7 +505,7 @@ export class ApInboxService { } } - async #flag(actor: CacheableRemoteUser, activity: IFlag): Promise { + private async flag(actor: CacheableRemoteUser, activity: IFlag): Promise { // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する const uris = getApIds(activity.object); @@ -529,24 +529,24 @@ export class ApInboxService { return 'ok'; } - async #reject(actor: CacheableRemoteUser, activity: IReject): Promise { + private async reject(actor: CacheableRemoteUser, activity: IReject): Promise { const uri = activity.id ?? activity; - this.#logger.info(`Reject: ${uri}`); + this.logger.info(`Reject: ${uri}`); const resolver = this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { - this.#logger.error(`Resolution failed: ${e}`); + this.logger.error(`Resolution failed: ${e}`); throw e; }); - if (isFollow(object)) return await this.#rejectFollow(actor, object); + if (isFollow(object)) return await this.rejectFollow(actor, object); return `skip: Unknown Reject type: ${getApType(object)}`; } - async #rejectFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { + private async rejectFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある const follower = await this.apDbResolverService.getUserFromApId(activity.actor); @@ -569,7 +569,7 @@ export class ApInboxService { return 'ok'; } - async #remove(actor: CacheableRemoteUser, activity: IRemove): Promise { + private async remove(actor: CacheableRemoteUser, activity: IRemove): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -588,32 +588,32 @@ export class ApInboxService { throw new Error(`unknown target: ${activity.target}`); } - async #undo(actor: CacheableRemoteUser, activity: IUndo): Promise { + private async undo(actor: CacheableRemoteUser, activity: IUndo): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } const uri = activity.id ?? activity; - this.#logger.info(`Undo: ${uri}`); + this.logger.info(`Undo: ${uri}`); const resolver = this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { - this.#logger.error(`Resolution failed: ${e}`); + this.logger.error(`Resolution failed: ${e}`); throw e; }); - if (isFollow(object)) return await this.#undoFollow(actor, object); - if (isBlock(object)) return await this.#undoBlock(actor, object); - if (isLike(object)) return await this.#undoLike(actor, object); - if (isAnnounce(object)) return await this.#undoAnnounce(actor, object); - if (isAccept(object)) return await this.#undoAccept(actor, object); + if (isFollow(object)) return await this.undoFollow(actor, object); + if (isBlock(object)) return await this.undoBlock(actor, object); + if (isLike(object)) return await this.undoLike(actor, object); + if (isAnnounce(object)) return await this.undoAnnounce(actor, object); + if (isAccept(object)) return await this.undoAccept(actor, object); return `skip: unknown object type ${getApType(object)}`; } - async #undoAccept(actor: CacheableRemoteUser, activity: IAccept): Promise { + private async undoAccept(actor: CacheableRemoteUser, activity: IAccept): Promise { const follower = await this.apDbResolverService.getUserFromApId(activity.object); if (follower == null) { return 'skip: follower not found'; @@ -632,7 +632,7 @@ export class ApInboxService { return 'skip: フォローされていない'; } - async #undoAnnounce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { + private async undoAnnounce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { const uri = getApId(activity); const note = await this.notesRepository.findOneBy({ @@ -646,7 +646,7 @@ export class ApInboxService { return 'ok: deleted'; } - async #undoBlock(actor: CacheableRemoteUser, activity: IBlock): Promise { + private async undoBlock(actor: CacheableRemoteUser, activity: IBlock): Promise { const blockee = await this.apDbResolverService.getUserFromApId(activity.object); if (blockee == null) { @@ -661,7 +661,7 @@ export class ApInboxService { return 'ok'; } - async #undoFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { + private async undoFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { const followee = await this.apDbResolverService.getUserFromApId(activity.object); if (followee == null) { return 'skip: followee not found'; @@ -694,7 +694,7 @@ export class ApInboxService { return 'skip: リクエストもフォローもされていない'; } - async #undoLike(actor: CacheableRemoteUser, activity: ILike): Promise { + private async undoLike(actor: CacheableRemoteUser, activity: ILike): Promise { const targetUri = getApId(activity.object); const note = await this.apNoteService.fetchNote(targetUri); @@ -708,17 +708,17 @@ export class ApInboxService { return 'ok'; } - async #update(actor: CacheableRemoteUser, activity: IUpdate): Promise { + private async update(actor: CacheableRemoteUser, activity: IUpdate): Promise { if ('actor' in activity && actor.uri !== activity.actor) { return 'skip: invalid actor'; } - this.#logger.debug('Update'); + this.logger.debug('Update'); const resolver = this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { - this.#logger.error(`Resolution failed: ${e}`); + this.logger.error(`Resolution failed: ${e}`); throw e; }); diff --git a/packages/backend/src/core/remote/activitypub/ApRendererService.ts b/packages/backend/src/core/remote/activitypub/ApRendererService.ts index b065c1acdd..5a4cef63e0 100644 --- a/packages/backend/src/core/remote/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/remote/activitypub/ApRendererService.ts @@ -365,7 +365,7 @@ export class ApRendererService { text: apText, })); - const emojis = await this.#getEmojis(note.emojis); + const emojis = await this.getEmojis(note.emojis); const apemojis = emojis.map(emoji => this.renderEmoji(emoji)); const tag = [ @@ -448,7 +448,7 @@ export class ApRendererService { } } - const emojis = await this.#getEmojis(user.emojis); + const emojis = await this.getEmojis(user.emojis); const apemojis = emojis.map(emoji => this.renderEmoji(emoji)); const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag)); @@ -687,7 +687,7 @@ export class ApRendererService { return page; } - async #getEmojis(names: string[]): Promise { + private async getEmojis(names: string[]): Promise { if (names == null || names.length === 0) return []; const emojis = await Promise.all( diff --git a/packages/backend/src/core/remote/activitypub/ApRequestService.ts b/packages/backend/src/core/remote/activitypub/ApRequestService.ts index a449a77dc8..2abaca06af 100644 --- a/packages/backend/src/core/remote/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/remote/activitypub/ApRequestService.ts @@ -36,14 +36,14 @@ export class ApRequestService { ) { } - #createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record }): Signed { + private createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record }): Signed { const u = new URL(args.url); const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`; const request: Request = { url: u.href, method: 'POST', - headers: this.#objectAssignWithLcKey({ + headers: this.objectAssignWithLcKey({ 'Date': new Date().toUTCString(), 'Host': u.hostname, 'Content-Type': 'application/activity+json', @@ -51,7 +51,7 @@ export class ApRequestService { }, args.additionalHeaders), }; - const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']); + const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']); return { request, @@ -61,20 +61,20 @@ export class ApRequestService { }; } - #createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record }): Signed { + private createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record }): Signed { const u = new URL(args.url); const request: Request = { url: u.href, method: 'GET', - headers: this.#objectAssignWithLcKey({ + headers: this.objectAssignWithLcKey({ 'Accept': 'application/activity+json, application/ld+json', 'Date': new Date().toUTCString(), 'Host': new URL(args.url).hostname, }, args.additionalHeaders), }; - const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']); + const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']); return { request, @@ -84,12 +84,12 @@ export class ApRequestService { }; } - #signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed { - const signingString = this.#genSigningString(request, includeHeaders); + private signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed { + const signingString = this.genSigningString(request, includeHeaders); const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64'); const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`; - request.headers = this.#objectAssignWithLcKey(request.headers, { + request.headers = this.objectAssignWithLcKey(request.headers, { Signature: signatureHeader, }); @@ -101,8 +101,8 @@ export class ApRequestService { }; } - #genSigningString(request: Request, includeHeaders: string[]): string { - request.headers = this.#lcObjectKey(request.headers); + private genSigningString(request: Request, includeHeaders: string[]): string { + request.headers = this.lcObjectKey(request.headers); const results: string[] = []; @@ -117,14 +117,14 @@ export class ApRequestService { return results.join('\n'); } - #lcObjectKey(src: Record): Record { + private lcObjectKey(src: Record): Record { const dst: Record = {}; for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key]; return dst; } - #objectAssignWithLcKey(a: Record, b: Record): Record { - return Object.assign(this.#lcObjectKey(a), this.#lcObjectKey(b)); + private objectAssignWithLcKey(a: Record, b: Record): Record { + return Object.assign(this.lcObjectKey(a), this.lcObjectKey(b)); } public async signedPost(user: { id: User['id'] }, url: string, object: any) { @@ -132,7 +132,7 @@ export class ApRequestService { const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); - const req = this.#createSignedPost({ + const req = this.createSignedPost({ key: { privateKeyPem: keypair.privateKey, keyId: `${this.config.url}/users/${user.id}#main-key`, @@ -160,7 +160,7 @@ export class ApRequestService { public async signedGet(url: string, user: { id: User['id'] }) { const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); - const req = this.#createSignedGet({ + const req = this.createSignedGet({ key: { privateKeyPem: keypair.privateKey, keyId: `${this.config.url}/users/${user.id}#main-key`, diff --git a/packages/backend/src/core/remote/activitypub/models/ApImageService.ts b/packages/backend/src/core/remote/activitypub/models/ApImageService.ts index d4e01923a2..da6ed61c56 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApImageService.ts @@ -14,7 +14,7 @@ import { ApLoggerService } from '../ApLoggerService.js'; @Injectable() export class ApImageService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -28,7 +28,7 @@ export class ApImageService { private driveService: DriveService, private apLoggerService: ApLoggerService, ) { - this.#logger = this.apLoggerService.logger; + this.logger = this.apLoggerService.logger; } /** @@ -46,7 +46,7 @@ export class ApImageService { throw new Error('invalid image: url not privided'); } - this.#logger.info(`Creating the Image: ${image.url}`); + this.logger.info(`Creating the Image: ${image.url}`); const instance = await this.metaService.fetch(); diff --git a/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts b/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts index c74949c595..1efe62333b 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts @@ -35,7 +35,7 @@ import type { IObject, IPost } from '../type.js'; @Injectable() export class ApNoteService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -71,7 +71,7 @@ export class ApNoteService { private apDbResolverService: ApDbResolverService, private apLoggerService: ApLoggerService, ) { - this.#logger = this.apLoggerService.logger; + this.logger = this.apLoggerService.logger; } public validateNote(object: any, uri: string) { @@ -116,7 +116,7 @@ export class ApNoteService { const entryUri = getApId(value); const err = this.validateNote(object, entryUri); if (err) { - this.#logger.error(`${err.message}`, { + this.logger.error(`${err.message}`, { resolver: { history: resolver.getHistory(), }, @@ -128,9 +128,9 @@ export class ApNoteService { const note: IPost = object; - this.#logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); + this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); - this.#logger.info(`Creating the Note: ${note.id}`); + this.logger.info(`Creating the Note: ${note.id}`); // 投稿者をフェッチ const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser; @@ -174,7 +174,7 @@ export class ApNoteService { const reply: Note | null = note.inReplyTo ? await this.resolveNote(note.inReplyTo, resolver).then(x => { if (x == null) { - this.#logger.warn('Specified inReplyTo, but nout found'); + this.logger.warn('Specified inReplyTo, but nout found'); throw new Error('inReplyTo not found'); } else { return x; @@ -191,7 +191,7 @@ export class ApNoteService { } } - this.#logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`); + this.logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`); throw err; }) : null; @@ -255,9 +255,9 @@ export class ApNoteService { const tryCreateVote = async (name: string, index: number): Promise => { if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { - this.#logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); + this.logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); } else if (index >= 0) { - this.#logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); + this.logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); await this.pollService.vote(actor, reply, index); // リモートフォロワーにUpdate配信 @@ -272,7 +272,7 @@ export class ApNoteService { } const emojis = await this.extractEmojis(note.tag ?? [], actor.host).catch(e => { - this.#logger.info(`extractEmojis: ${e}`); + this.logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); @@ -386,7 +386,7 @@ export class ApNoteService { return exists; } - this.#logger.info(`register emoji host=${host}, name=${name}`); + this.logger.info(`register emoji host=${host}, name=${name}`); return await this.emojisRepository.insert({ id: this.idService.genId(), diff --git a/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts b/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts index 1ca6463484..d088fa5554 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts @@ -91,7 +91,7 @@ export class ApPersonService implements OnModuleInit { private usersChart: UsersChart; private instanceChart: InstanceChart; private apLoggerService: ApLoggerService; - #logger: Logger; + private logger: Logger; constructor( private moduleRef: ModuleRef, @@ -153,7 +153,7 @@ export class ApPersonService implements OnModuleInit { this.usersChart = this.moduleRef.get('UsersChart'); this.instanceChart = this.moduleRef.get('InstanceChart'); this.apLoggerService = this.moduleRef.get('ApLoggerService'); - this.#logger = this.apLoggerService.logger; + this.logger = this.apLoggerService.logger; } /** @@ -161,7 +161,7 @@ export class ApPersonService implements OnModuleInit { * @param x Fetched object * @param uri Fetch target URI */ - #validateActor(x: IObject, uri: string): IActor { + private validateActor(x: IObject, uri: string): IActor { const expectHost = this.utilityService.toPuny(new URL(uri).hostname); if (x == null) { @@ -264,9 +264,9 @@ export class ApPersonService implements OnModuleInit { const object = await resolver.resolve(uri) as any; - const person = this.#validateActor(object, uri); + const person = this.validateActor(object, uri); - this.#logger.info(`Creating the Person: ${person.id}`); + this.logger.info(`Creating the Person: ${person.id}`); const host = this.utilityService.toPuny(new URL(object.id).hostname); @@ -338,7 +338,7 @@ export class ApPersonService implements OnModuleInit { throw new Error('already registered'); } } else { - this.#logger.error(e instanceof Error ? e : new Error(e as string)); + this.logger.error(e instanceof Error ? e : new Error(e as string)); throw e; } } @@ -379,7 +379,7 @@ export class ApPersonService implements OnModuleInit { //#region カスタム絵文字取得 const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host).catch(err => { - this.#logger.info(`extractEmojis: ${err}`); + this.logger.info(`extractEmojis: ${err}`); return [] as Emoji[]; }); @@ -390,7 +390,7 @@ export class ApPersonService implements OnModuleInit { }); //#endregion - await this.updateFeatured(user!.id).catch(err => this.#logger.error(err)); + await this.updateFeatured(user!.id).catch(err => this.logger.error(err)); return user!; } @@ -422,9 +422,9 @@ export class ApPersonService implements OnModuleInit { const object = hint ?? await resolver.resolve(uri); - const person = this.#validateActor(object, uri); + const person = this.validateActor(object, uri); - this.#logger.info(`Updating the Person: ${person.id}`); + this.logger.info(`Updating the Person: ${person.id}`); // アバターとヘッダー画像をフェッチ const [avatar, banner] = await Promise.all([ @@ -438,7 +438,7 @@ export class ApPersonService implements OnModuleInit { // カスタム絵文字取得 const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => { - this.#logger.info(`extractEmojis: ${e}`); + this.logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); @@ -503,7 +503,7 @@ export class ApPersonService implements OnModuleInit { followerSharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), }); - await this.updateFeatured(exist.id).catch(err => this.#logger.error(err)); + await this.updateFeatured(exist.id).catch(err => this.logger.error(err)); } /** @@ -556,7 +556,7 @@ export class ApPersonService implements OnModuleInit { if (!this.userEntityService.isRemoteUser(user)) return; if (!user.featured) return; - this.#logger.info(`Updating the featured: ${user.uri}`); + this.logger.info(`Updating the featured: ${user.uri}`); const resolver = this.apResolverService.createResolver(); diff --git a/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts index 97f26d1681..2b89cb030b 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts @@ -12,7 +12,7 @@ import type { IObject, IQuestion } from '../type.js'; @Injectable() export class ApQuestionService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -27,7 +27,7 @@ export class ApQuestionService { private apResolverService: ApResolverService, private apLoggerService: ApLoggerService, ) { - this.#logger = this.apLoggerService.logger; + this.logger = this.apLoggerService.logger; } public async extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise { @@ -82,7 +82,7 @@ export class ApQuestionService { // resolve new Question object const resolver = this.apResolverService.createResolver(); const question = await resolver.resolve(value) as IQuestion; - this.#logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); + this.logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); if (question.type !== 'Question') throw new Error('object is not a Question'); diff --git a/packages/backend/src/daemons/JanitorService.ts b/packages/backend/src/daemons/JanitorService.ts index dacbb8cca6..a51f570725 100644 --- a/packages/backend/src/daemons/JanitorService.ts +++ b/packages/backend/src/daemons/JanitorService.ts @@ -8,7 +8,7 @@ const interval = 30 * 60 * 1000; @Injectable() export class JanitorService implements OnApplicationShutdown { - #intervalId: NodeJS.Timer; + private intervalId: NodeJS.Timer; constructor( @Inject(DI.attestationChallengesRepository) @@ -28,10 +28,10 @@ export class JanitorService implements OnApplicationShutdown { tick(); - this.#intervalId = setInterval(tick, interval); + this.intervalId = setInterval(tick, interval); } public onApplicationShutdown(signal?: string | undefined) { - clearInterval(this.#intervalId); + clearInterval(this.intervalId); } } diff --git a/packages/backend/src/daemons/QueueStatsService.ts b/packages/backend/src/daemons/QueueStatsService.ts index f05d3ce33f..931de19067 100644 --- a/packages/backend/src/daemons/QueueStatsService.ts +++ b/packages/backend/src/daemons/QueueStatsService.ts @@ -10,7 +10,7 @@ const interval = 10000; @Injectable() export class QueueStatsService implements OnApplicationShutdown { - #intervalId: NodeJS.Timer; + private intervalId: NodeJS.Timer; constructor( private queueService: QueueService, @@ -68,10 +68,10 @@ export class QueueStatsService implements OnApplicationShutdown { tick(); - this.#intervalId = setInterval(tick, interval); + this.intervalId = setInterval(tick, interval); } public onApplicationShutdown(signal?: string | undefined) { - clearInterval(this.#intervalId); + clearInterval(this.intervalId); } } diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts index 3a6d408431..e40912442d 100644 --- a/packages/backend/src/daemons/ServerStatsService.ts +++ b/packages/backend/src/daemons/ServerStatsService.ts @@ -14,7 +14,7 @@ const round = (num: number) => Math.round(num * 10) / 10; @Injectable() export class ServerStatsService implements OnApplicationShutdown { - #intervalId: NodeJS.Timer; + private intervalId: NodeJS.Timer; constructor( ) { @@ -58,11 +58,11 @@ export class ServerStatsService implements OnApplicationShutdown { tick(); - this.#intervalId = setInterval(tick, interval); + this.intervalId = setInterval(tick, interval); } public onApplicationShutdown(signal?: string | undefined) { - clearInterval(this.#intervalId); + clearInterval(this.intervalId); } } diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 7be0f7b469..753df8cad6 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -16,7 +16,7 @@ import { QueueLoggerService } from './QueueLoggerService.js'; @Injectable() export class QueueProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -32,7 +32,7 @@ export class QueueProcessorService { private deliverProcessorService: DeliverProcessorService, private inboxProcessorService: InboxProcessorService, ) { - this.#logger = this.queueLoggerService.logger; + this.logger = this.queueLoggerService.logger; } public start() { @@ -44,12 +44,12 @@ export class QueueProcessorService { }; } - const systemLogger = this.#logger.createSubLogger('system'); - const deliverLogger = this.#logger.createSubLogger('deliver'); - const webhookLogger = this.#logger.createSubLogger('webhook'); - const inboxLogger = this.#logger.createSubLogger('inbox'); - const dbLogger = this.#logger.createSubLogger('db'); - const objectStorageLogger = this.#logger.createSubLogger('objectStorage'); + const systemLogger = this.logger.createSubLogger('system'); + const deliverLogger = this.logger.createSubLogger('deliver'); + const webhookLogger = this.logger.createSubLogger('webhook'); + const inboxLogger = this.logger.createSubLogger('inbox'); + const dbLogger = this.logger.createSubLogger('db'); + const objectStorageLogger = this.logger.createSubLogger('objectStorage'); this.queueService.systemQueue .on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`)) diff --git a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts index 514dc1dcf3..17337837a3 100644 --- a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts +++ b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts @@ -10,7 +10,7 @@ import type Bull from 'bull'; @Injectable() export class CheckExpiredMutingsProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -22,11 +22,11 @@ export class CheckExpiredMutingsProcessorService { private globalEventService: GlobalEventService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('check-expired-mutings'); + this.logger = this.queueLoggerService.logger.createSubLogger('check-expired-mutings'); } public async process(job: Bull.Job>, done: () => void): Promise { - this.#logger.info('Checking expired mutings...'); + this.logger.info('Checking expired mutings...'); const expired = await this.mutingsRepository.createQueryBuilder('muting') .where('muting.expiresAt IS NOT NULL') @@ -44,7 +44,7 @@ export class CheckExpiredMutingsProcessorService { } } - this.#logger.succ('All expired mutings checked.'); + this.logger.succ('All expired mutings checked.'); done(); } } diff --git a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts index 0eaad9b9ed..6f2fb8dea0 100644 --- a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts @@ -20,7 +20,7 @@ import type Bull from 'bull'; @Injectable() export class CleanChartsProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -41,11 +41,11 @@ export class CleanChartsProcessorService { private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('clean-charts'); + this.logger = this.queueLoggerService.logger.createSubLogger('clean-charts'); } public async process(job: Bull.Job>, done: () => void): Promise { - this.#logger.info('Clean charts...'); + this.logger.info('Clean charts...'); await Promise.all([ this.federationChart.clean(), @@ -62,7 +62,7 @@ export class CleanChartsProcessorService { this.apRequestChart.clean(), ]); - this.#logger.succ('All charts successfully cleaned.'); + this.logger.succ('All charts successfully cleaned.'); done(); } } diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts index 6150120806..830f0c56b6 100644 --- a/packages/backend/src/queue/processors/CleanProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanProcessorService.ts @@ -9,7 +9,7 @@ import type Bull from 'bull'; @Injectable() export class CleanProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -20,17 +20,17 @@ export class CleanProcessorService { private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('clean'); + this.logger = this.queueLoggerService.logger.createSubLogger('clean'); } public async process(job: Bull.Job>, done: () => void): Promise { - this.#logger.info('Cleaning...'); + this.logger.info('Cleaning...'); this.userIpsRepository.delete({ createdAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90))), }); - this.#logger.succ('Cleaned.'); + this.logger.succ('Cleaned.'); done(); } } diff --git a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts index 8c53632563..c3c68be1bc 100644 --- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts @@ -10,7 +10,7 @@ import type Bull from 'bull'; @Injectable() export class CleanRemoteFilesProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -22,11 +22,11 @@ export class CleanRemoteFilesProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('clean-remote-files'); + this.logger = this.queueLoggerService.logger.createSubLogger('clean-remote-files'); } public async process(job: Bull.Job>, done: () => void): Promise { - this.#logger.info('Deleting cached remote files...'); + this.logger.info('Deleting cached remote files...'); let deletedCount = 0; let cursor: any = null; @@ -63,7 +63,7 @@ export class CleanRemoteFilesProcessorService { job.progress(deletedCount / total); } - this.#logger.succ('All cahced remote files has been deleted.'); + this.logger.succ('All cahced remote files has been deleted.'); done(); } } diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index a5255c5c05..ab82f87d5e 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -14,7 +14,7 @@ import type { DbUserDeleteJobData } from '../types.js'; @Injectable() export class DeleteAccountProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -36,11 +36,11 @@ export class DeleteAccountProcessorService { private emailService: EmailService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('delete-account'); + this.logger = this.queueLoggerService.logger.createSubLogger('delete-account'); } public async process(job: Bull.Job): Promise { - this.#logger.info(`Deleting account of ${job.data.user.id} ...`); + this.logger.info(`Deleting account of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -71,7 +71,7 @@ export class DeleteAccountProcessorService { await this.notesRepository.delete(notes.map(note => note.id)); } - this.#logger.succ('All of notes deleted'); + this.logger.succ('All of notes deleted'); } { // Delete files @@ -100,7 +100,7 @@ export class DeleteAccountProcessorService { } } - this.#logger.succ('All of files deleted'); + this.logger.succ('All of files deleted'); } { // Send email notification diff --git a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts index 80814bb5a2..430fbf19e9 100644 --- a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts @@ -11,7 +11,7 @@ import type { DbUserJobData } from '../types.js'; @Injectable() export class DeleteDriveFilesProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -26,11 +26,11 @@ export class DeleteDriveFilesProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('delete-drive-files'); + this.logger = this.queueLoggerService.logger.createSubLogger('delete-drive-files'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Deleting drive files of ${job.data.user.id} ...`); + this.logger.info(`Deleting drive files of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -72,7 +72,7 @@ export class DeleteDriveFilesProcessorService { job.progress(deletedCount / total); } - this.#logger.succ(`All drive files (${deletedCount}) of ${user.id} has been deleted.`); + this.logger.succ(`All drive files (${deletedCount}) of ${user.id} has been deleted.`); done(); } } diff --git a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts index 55424f6444..72923b80a9 100644 --- a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts @@ -9,7 +9,7 @@ import type { ObjectStorageFileJobData } from '../types.js'; @Injectable() export class DeleteFileProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -18,7 +18,7 @@ export class DeleteFileProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('delete-file'); + this.logger = this.queueLoggerService.logger.createSubLogger('delete-file'); } public async process(job: Bull.Job): Promise { diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 3403ec83a9..1bf51c1bc6 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -21,9 +21,9 @@ import type { DeliverJobData } from '../types.js'; @Injectable() export class DeliverProcessorService { - #logger: Logger; - #suspendedHostsCache: Cache; - #latest: string | null; + private logger: Logger; + private suspendedHostsCache: Cache; + private latest: string | null; constructor( @Inject(DI.config) @@ -45,9 +45,9 @@ export class DeliverProcessorService { private federationChart: FederationChart, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('deliver'); - this.#suspendedHostsCache = new Cache(1000 * 60 * 60); - this.#latest = null; + this.logger = this.queueLoggerService.logger.createSubLogger('deliver'); + this.suspendedHostsCache = new Cache(1000 * 60 * 60); + this.latest = null; } public async process(job: Bull.Job): Promise { @@ -60,22 +60,22 @@ export class DeliverProcessorService { } // isSuspendedなら中断 - let suspendedHosts = this.#suspendedHostsCache.get(null); + let suspendedHosts = this.suspendedHostsCache.get(null); if (suspendedHosts == null) { suspendedHosts = await this.instancesRepository.find({ where: { isSuspended: true, }, }); - this.#suspendedHostsCache.set(null, suspendedHosts); + this.suspendedHostsCache.set(null, suspendedHosts); } if (suspendedHosts.map(x => x.host).includes(this.utilityService.toPuny(host))) { return 'skip (suspended)'; } try { - if (this.#latest !== (this.#latest = JSON.stringify(job.data.content, null, 2))) { - this.#logger.debug(`delivering ${this.#latest}`); + if (this.latest !== (this.latest = JSON.stringify(job.data.content, null, 2))) { + this.logger.debug(`delivering ${this.latest}`); } await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content); diff --git a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts index b90c7be629..3e55a351a1 100644 --- a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts +++ b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts @@ -11,7 +11,7 @@ import type { EndedPollNotificationJobData } from '../types.js'; @Injectable() export class EndedPollNotificationProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -26,7 +26,7 @@ export class EndedPollNotificationProcessorService { private createNotificationService: CreateNotificationService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('ended-poll-notification'); + this.logger = this.queueLoggerService.logger.createSubLogger('ended-poll-notification'); } public async process(job: Bull.Job, done: () => void): Promise { diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index 9b520be06e..cbc483698f 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -16,7 +16,7 @@ import type { DbUserJobData } from '../types.js'; @Injectable() export class ExportBlockingProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -32,11 +32,11 @@ export class ExportBlockingProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('export-blocking'); + this.logger = this.queueLoggerService.logger.createSubLogger('export-blocking'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Exporting blocking of ${job.data.user.id} ...`); + this.logger.info(`Exporting blocking of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -47,7 +47,7 @@ export class ExportBlockingProcessorService { // Create temp file const [path, cleanup] = await createTemp(); - this.#logger.info(`Temp file is ${path}`); + this.logger.info(`Temp file is ${path}`); try { const stream = fs.createWriteStream(path, { flags: 'a' }); @@ -84,7 +84,7 @@ export class ExportBlockingProcessorService { await new Promise((res, rej) => { stream.write(content + '\n', err => { if (err) { - this.#logger.error(err); + this.logger.error(err); rej(err); } else { res(); @@ -102,12 +102,12 @@ export class ExportBlockingProcessorService { } stream.end(); - this.#logger.succ(`Exported to: ${path}`); + this.logger.succ(`Exported to: ${path}`); const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); - this.#logger.succ(`Exported to: ${driveFile.id}`); + this.logger.succ(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index 93341c2c63..c49a47561b 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -17,7 +17,7 @@ import type Bull from 'bull'; @Injectable() export class ExportCustomEmojisProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -33,11 +33,11 @@ export class ExportCustomEmojisProcessorService { private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis'); + this.logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info('Exporting custom emojis ...'); + this.logger.info('Exporting custom emojis ...'); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -47,7 +47,7 @@ export class ExportCustomEmojisProcessorService { const [path, cleanup] = await createTempDir(); - this.#logger.info(`Temp dir is ${path}`); + this.logger.info(`Temp dir is ${path}`); const metaPath = path + '/meta.json'; @@ -59,7 +59,7 @@ export class ExportCustomEmojisProcessorService { return new Promise((res, rej) => { metaStream.write(text, err => { if (err) { - this.#logger.error(err); + this.logger.error(err); rej(err); } else { res(); @@ -90,7 +90,7 @@ export class ExportCustomEmojisProcessorService { await this.downloadService.downloadUrl(emoji.originalUrl, emojiPath); downloaded = true; } catch (e) { // TODO: 何度か再試行 - this.#logger.error(e instanceof Error ? e : new Error(e as string)); + this.logger.error(e instanceof Error ? e : new Error(e as string)); } if (!downloaded) { @@ -118,12 +118,12 @@ export class ExportCustomEmojisProcessorService { zlib: { level: 0 }, }); archiveStream.on('close', async () => { - this.#logger.succ(`Exported to: ${archivePath}`); + this.logger.succ(`Exported to: ${archivePath}`); const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.zip'; const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true }); - this.#logger.succ(`Exported to: ${driveFile.id}`); + this.logger.succ(`Exported to: ${driveFile.id}`); cleanup(); archiveCleanup(); done(); diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index 9946015ff7..4c6162432a 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -16,7 +16,7 @@ import type { DbUserJobData } from '../types.js'; @Injectable() export class ExportFollowingProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -35,11 +35,11 @@ export class ExportFollowingProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('export-following'); + this.logger = this.queueLoggerService.logger.createSubLogger('export-following'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Exporting following of ${job.data.user.id} ...`); + this.logger.info(`Exporting following of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -50,7 +50,7 @@ export class ExportFollowingProcessorService { // Create temp file const [path, cleanup] = await createTemp(); - this.#logger.info(`Temp file is ${path}`); + this.logger.info(`Temp file is ${path}`); try { const stream = fs.createWriteStream(path, { flags: 'a' }); @@ -94,7 +94,7 @@ export class ExportFollowingProcessorService { await new Promise((res, rej) => { stream.write(content + '\n', err => { if (err) { - this.#logger.error(err); + this.logger.error(err); rej(err); } else { res(); @@ -105,12 +105,12 @@ export class ExportFollowingProcessorService { } stream.end(); - this.#logger.succ(`Exported to: ${path}`); + this.logger.succ(`Exported to: ${path}`); const fileName = 'following-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); - this.#logger.succ(`Exported to: ${driveFile.id}`); + this.logger.succ(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index a34cea0f41..7781d2787f 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -15,7 +15,7 @@ import type { DbUserJobData } from '../types.js'; @Injectable() export class ExportMutingProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -34,11 +34,11 @@ export class ExportMutingProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('export-muting'); + this.logger = this.queueLoggerService.logger.createSubLogger('export-muting'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Exporting muting of ${job.data.user.id} ...`); + this.logger.info(`Exporting muting of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -49,7 +49,7 @@ export class ExportMutingProcessorService { // Create temp file const [path, cleanup] = await createTemp(); - this.#logger.info(`Temp file is ${path}`); + this.logger.info(`Temp file is ${path}`); try { const stream = fs.createWriteStream(path, { flags: 'a' }); @@ -87,7 +87,7 @@ export class ExportMutingProcessorService { await new Promise((res, rej) => { stream.write(content + '\n', err => { if (err) { - this.#logger.error(err); + this.logger.error(err); rej(err); } else { res(); @@ -105,12 +105,12 @@ export class ExportMutingProcessorService { } stream.end(); - this.#logger.succ(`Exported to: ${path}`); + this.logger.succ(`Exported to: ${path}`); const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); - this.#logger.succ(`Exported to: ${driveFile.id}`); + this.logger.succ(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index 24fcc1a8ad..62b3a53c49 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -16,7 +16,7 @@ import type { DbUserJobData } from '../types.js'; @Injectable() export class ExportNotesProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -34,11 +34,11 @@ export class ExportNotesProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('export-notes'); + this.logger = this.queueLoggerService.logger.createSubLogger('export-notes'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Exporting notes of ${job.data.user.id} ...`); + this.logger.info(`Exporting notes of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -49,7 +49,7 @@ export class ExportNotesProcessorService { // Create temp file const [path, cleanup] = await createTemp(); - this.#logger.info(`Temp file is ${path}`); + this.logger.info(`Temp file is ${path}`); try { const stream = fs.createWriteStream(path, { flags: 'a' }); @@ -58,7 +58,7 @@ export class ExportNotesProcessorService { return new Promise((res, rej) => { stream.write(text, err => { if (err) { - this.#logger.error(err); + this.logger.error(err); rej(err); } else { res(); @@ -112,12 +112,12 @@ export class ExportNotesProcessorService { await write(']'); stream.end(); - this.#logger.succ(`Exported to: ${path}`); + this.logger.succ(`Exported to: ${path}`); const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); - this.#logger.succ(`Exported to: ${driveFile.id}`); + this.logger.succ(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index a02e9bdee4..097835ac81 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -15,7 +15,7 @@ import type { DbUserJobData } from '../types.js'; @Injectable() export class ExportUserListsProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -34,11 +34,11 @@ export class ExportUserListsProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('export-user-lists'); + this.logger = this.queueLoggerService.logger.createSubLogger('export-user-lists'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Exporting user lists of ${job.data.user.id} ...`); + this.logger.info(`Exporting user lists of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -53,7 +53,7 @@ export class ExportUserListsProcessorService { // Create temp file const [path, cleanup] = await createTemp(); - this.#logger.info(`Temp file is ${path}`); + this.logger.info(`Temp file is ${path}`); try { const stream = fs.createWriteStream(path, { flags: 'a' }); @@ -70,7 +70,7 @@ export class ExportUserListsProcessorService { await new Promise((res, rej) => { stream.write(content + '\n', err => { if (err) { - this.#logger.error(err); + this.logger.error(err); rej(err); } else { res(); @@ -81,12 +81,12 @@ export class ExportUserListsProcessorService { } stream.end(); - this.#logger.succ(`Exported to: ${path}`); + this.logger.succ(`Exported to: ${path}`); const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); - this.#logger.succ(`Exported to: ${driveFile.id}`); + this.logger.succ(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index abae196299..44c8800a68 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -15,7 +15,7 @@ import type { DbUserImportJobData } from '../types.js'; @Injectable() export class ImportBlockingProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -36,11 +36,11 @@ export class ImportBlockingProcessorService { private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('import-blocking'); + this.logger = this.queueLoggerService.logger.createSubLogger('import-blocking'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Importing blocking of ${job.data.user.id} ...`); + this.logger.info(`Importing blocking of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -88,15 +88,15 @@ export class ImportBlockingProcessorService { // skip myself if (target.id === job.data.user.id) continue; - this.#logger.info(`Block[${linenum}] ${target.id} ...`); + this.logger.info(`Block[${linenum}] ${target.id} ...`); await this.userBlockingService.block(user, target); } catch (e) { - this.#logger.warn(`Error in line:${linenum} ${e}`); + this.logger.warn(`Error in line:${linenum} ${e}`); } } - this.#logger.succ('Imported'); + this.logger.succ('Imported'); done(); } } diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index 6f86589aec..4919fb2f7b 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -17,7 +17,7 @@ import type { DbUserImportJobData } from '../types.js'; // TODO: 名前衝突時の動作を選べるようにする @Injectable() export class ImportCustomEmojisProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -40,11 +40,11 @@ export class ImportCustomEmojisProcessorService { private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('import-custom-emojis'); + this.logger = this.queueLoggerService.logger.createSubLogger('import-custom-emojis'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info('Importing custom emojis ...'); + this.logger.info('Importing custom emojis ...'); const file = await this.driveFilesRepository.findOneBy({ id: job.data.fileId, @@ -56,7 +56,7 @@ export class ImportCustomEmojisProcessorService { const [path, cleanup] = await createTempDir(); - this.#logger.info(`Temp dir is ${path}`); + this.logger.info(`Temp dir is ${path}`); const destPath = path + '/emojis.zip'; @@ -65,7 +65,7 @@ export class ImportCustomEmojisProcessorService { await this.downloadService.downloadUrl(file.url, destPath); } catch (e) { // TODO: 何度か再試行 if (e instanceof Error || typeof e === 'string') { - this.#logger.error(e); + this.logger.error(e); } throw e; } @@ -101,10 +101,10 @@ export class ImportCustomEmojisProcessorService { cleanup(); - this.#logger.succ('Imported'); + this.logger.succ('Imported'); done(); }); unzipStream.pipe(extractor); - this.#logger.succ(`Unzipping to ${outputPath}`); + this.logger.succ(`Unzipping to ${outputPath}`); } } diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index 087e0baf96..5e49678d05 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -15,7 +15,7 @@ import type { DbUserImportJobData } from '../types.js'; @Injectable() export class ImportFollowingProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -33,11 +33,11 @@ export class ImportFollowingProcessorService { private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('import-following'); + this.logger = this.queueLoggerService.logger.createSubLogger('import-following'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Importing following of ${job.data.user.id} ...`); + this.logger.info(`Importing following of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -85,15 +85,15 @@ export class ImportFollowingProcessorService { // skip myself if (target.id === job.data.user.id) continue; - this.#logger.info(`Follow[${linenum}] ${target.id} ...`); + this.logger.info(`Follow[${linenum}] ${target.id} ...`); this.userFollowingService.follow(user, target); } catch (e) { - this.#logger.warn(`Error in line:${linenum} ${e}`); + this.logger.warn(`Error in line:${linenum} ${e}`); } } - this.#logger.succ('Imported'); + this.logger.succ('Imported'); done(); } } diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index 404091e8ca..c613c7e74e 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -16,7 +16,7 @@ import type { DbUserImportJobData } from '../types.js'; @Injectable() export class ImportMutingProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -34,11 +34,11 @@ export class ImportMutingProcessorService { private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('import-muting'); + this.logger = this.queueLoggerService.logger.createSubLogger('import-muting'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Importing muting of ${job.data.user.id} ...`); + this.logger.info(`Importing muting of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -86,15 +86,15 @@ export class ImportMutingProcessorService { // skip myself if (target.id === job.data.user.id) continue; - this.#logger.info(`Mute[${linenum}] ${target.id} ...`); + this.logger.info(`Mute[${linenum}] ${target.id} ...`); await this.userMutingService.mute(user, target); } catch (e) { - this.#logger.warn(`Error in line:${linenum} ${e}`); + this.logger.warn(`Error in line:${linenum} ${e}`); } } - this.#logger.succ('Imported'); + this.logger.succ('Imported'); done(); } } diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index aed1a4cde5..96c862e5c9 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -16,7 +16,7 @@ import type { DbUserImportJobData } from '../types.js'; @Injectable() export class ImportUserListsProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -41,11 +41,11 @@ export class ImportUserListsProcessorService { private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('import-user-lists'); + this.logger = this.queueLoggerService.logger.createSubLogger('import-user-lists'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Importing user lists of ${job.data.user.id} ...`); + this.logger.info(`Importing user lists of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -102,11 +102,11 @@ export class ImportUserListsProcessorService { this.userListService.push(target, list!); } catch (e) { - this.#logger.warn(`Error in line:${linenum} ${e}`); + this.logger.warn(`Error in line:${linenum} ${e}`); } } - this.#logger.succ('Imported'); + this.logger.succ('Imported'); done(); } } diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 5733f5d0a9..4593b4fb61 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -31,7 +31,7 @@ import type { DeliverJobData, InboxJobData } from '../types.js'; // ユーザーのinboxにアクティビティが届いた時の処理 @Injectable() export class InboxProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -57,7 +57,7 @@ export class InboxProcessorService { private federationChart: FederationChart, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('inbox'); + this.logger = this.queueLoggerService.logger.createSubLogger('inbox'); } public async process(job: Bull.Job): Promise { @@ -67,7 +67,7 @@ export class InboxProcessorService { //#region Log const info = Object.assign({}, activity) as any; delete info['@context']; - this.#logger.debug(JSON.stringify(info, null, 2)); + this.logger.debug(JSON.stringify(info, null, 2)); //#endregion const host = this.utilityService.toPuny(new URL(signature.keyId).hostname); diff --git a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts index f976232a24..75d02d527a 100644 --- a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts @@ -20,7 +20,7 @@ import type Bull from 'bull'; @Injectable() export class ResyncChartsProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -41,11 +41,11 @@ export class ResyncChartsProcessorService { private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('resync-charts'); + this.logger = this.queueLoggerService.logger.createSubLogger('resync-charts'); } public async process(job: Bull.Job>, done: () => void): Promise { - this.#logger.info('Resync charts...'); + this.logger.info('Resync charts...'); // TODO: ユーザーごとのチャートも更新する // TODO: インスタンスごとのチャートも更新する @@ -55,7 +55,7 @@ export class ResyncChartsProcessorService { this.usersChart.resync(), ]); - this.#logger.succ('All charts successfully resynced.'); + this.logger.succ('All charts successfully resynced.'); done(); } } diff --git a/packages/backend/src/queue/processors/TickChartsProcessorService.ts b/packages/backend/src/queue/processors/TickChartsProcessorService.ts index d1ca3c4576..e16956df0c 100644 --- a/packages/backend/src/queue/processors/TickChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/TickChartsProcessorService.ts @@ -20,7 +20,7 @@ import type Bull from 'bull'; @Injectable() export class TickChartsProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -41,11 +41,11 @@ export class TickChartsProcessorService { private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('tick-charts'); + this.logger = this.queueLoggerService.logger.createSubLogger('tick-charts'); } public async process(job: Bull.Job>, done: () => void): Promise { - this.#logger.info('Tick charts...'); + this.logger.info('Tick charts...'); await Promise.all([ this.federationChart.tick(false), @@ -62,7 +62,7 @@ export class TickChartsProcessorService { this.apRequestChart.tick(false), ]); - this.#logger.succ('All charts successfully ticked.'); + this.logger.succ('All charts successfully ticked.'); done(); } } diff --git a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts index 5723fe2eeb..27243be51b 100644 --- a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts @@ -12,7 +12,7 @@ import type { WebhookDeliverJobData } from '../types.js'; @Injectable() export class WebhookDeliverProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -24,12 +24,12 @@ export class WebhookDeliverProcessorService { private httpRequestService: HttpRequestService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('webhook'); + this.logger = this.queueLoggerService.logger.createSubLogger('webhook'); } public async process(job: Bull.Job): Promise { try { - this.#logger.debug(`delivering ${job.data.webhookId}`); + this.logger.debug(`delivering ${job.data.webhookId}`); const res = await this.httpRequestService.getResponse({ url: job.data.to, diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 281da5175d..21ecc7177a 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -58,7 +58,7 @@ export class ActivityPubServerService { ) { } - #setResponseType(ctx: Router.RouterContext) { + private setResponseType(ctx: Router.RouterContext) { const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON); if (accept === LD_JSON) { ctx.response.type = LD_JSON; @@ -71,7 +71,7 @@ export class ActivityPubServerService { * Pack Create or Announce Activity * @param note Note */ - async #packActivity(note: Note): Promise { + private async packActivity(note: Note): Promise { if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { const renote = await Notes.findOneByOrFail({ id: note.renoteId }); return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note); @@ -80,7 +80,7 @@ export class ActivityPubServerService { return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note); } - #inbox(ctx: Router.RouterContext) { + private inbox(ctx: Router.RouterContext) { let signature; try { @@ -95,7 +95,7 @@ export class ActivityPubServerService { ctx.status = 202; } - async #followers(ctx: Router.RouterContext) { + private async followers(ctx: Router.RouterContext) { const userId = ctx.params.user; const cursor = ctx.request.query.cursor; @@ -169,17 +169,17 @@ export class ActivityPubServerService { ); ctx.body = this.apRendererService.renderActivity(rendered); - this.#setResponseType(ctx); + this.setResponseType(ctx); } else { // index page const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`); ctx.body = this.apRendererService.renderActivity(rendered); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); } } - async #following(ctx: Router.RouterContext) { + private async following(ctx: Router.RouterContext) { const userId = ctx.params.user; const cursor = ctx.request.query.cursor; @@ -253,17 +253,17 @@ export class ActivityPubServerService { ); ctx.body = this.apRendererService.renderActivity(rendered); - this.#setResponseType(ctx); + this.setResponseType(ctx); } else { // index page const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`); ctx.body = this.apRendererService.renderActivity(rendered); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); } } - async #featured(ctx: Router.RouterContext) { + private async featured(ctx: Router.RouterContext) { const userId = ctx.params.user; const user = await this.usersRepository.findOneBy({ @@ -293,10 +293,10 @@ export class ActivityPubServerService { ctx.body = this.apRendererService.renderActivity(rendered); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); } - async #outbox(ctx: Router.RouterContext) { + private async outbox(ctx: Router.RouterContext) { const userId = ctx.params.user; const sinceId = ctx.request.query.since_id; @@ -344,7 +344,7 @@ export class ActivityPubServerService { if (sinceId) notes.reverse(); - const activities = await Promise.all(notes.map(note => this.#packActivity(note))); + const activities = await Promise.all(notes.map(note => this.packActivity(note))); const rendered = this.apRendererService.renderOrderedCollectionPage( `${partOf}?${url.query({ page: 'true', @@ -363,7 +363,7 @@ export class ActivityPubServerService { ); ctx.body = this.apRendererService.renderActivity(rendered); - this.#setResponseType(ctx); + this.setResponseType(ctx); } else { // index page const rendered = this.apRendererService.renderOrderedCollection(partOf, user.notesCount, @@ -372,11 +372,11 @@ export class ActivityPubServerService { ); ctx.body = this.apRendererService.renderActivity(rendered); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); } } - async #userInfo(ctx: Router.RouterContext, user: User | null) { + private async userInfo(ctx: Router.RouterContext, user: User | null) { if (user == null) { ctx.status = 404; return; @@ -384,7 +384,7 @@ export class ActivityPubServerService { ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderPerson(user as ILocalUser)); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); } public createRouter() { @@ -399,8 +399,8 @@ export class ActivityPubServerService { } // inbox - router.post('/inbox', json(), ctx => this.#inbox(ctx)); - router.post('/users/:user/inbox', json(), ctx => this.#inbox(ctx)); + router.post('/inbox', json(), ctx => this.inbox(ctx)); + router.post('/users/:user/inbox', json(), ctx => this.inbox(ctx)); // note router.get('/notes/:note', async (ctx, next) => { @@ -429,7 +429,7 @@ export class ActivityPubServerService { ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderNote(note, false)); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); }); // note activity @@ -446,22 +446,22 @@ export class ActivityPubServerService { return; } - ctx.body = this.apRendererService.renderActivity(await this.#packActivity(note)); + ctx.body = this.apRendererService.renderActivity(await this.packActivity(note)); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); }); // outbox - router.get('/users/:user/outbox', (ctx) => this.#outbox(ctx)); + router.get('/users/:user/outbox', (ctx) => this.outbox(ctx)); // followers - router.get('/users/:user/followers', (ctx) => this.#followers(ctx)); + router.get('/users/:user/followers', (ctx) => this.followers(ctx)); // following - router.get('/users/:user/following', (ctx) => this.#following(ctx)); + router.get('/users/:user/following', (ctx) => this.following(ctx)); // featured - router.get('/users/:user/collections/featured', (ctx) => this.#featured(ctx)); + router.get('/users/:user/collections/featured', (ctx) => this.featured(ctx)); // publickey router.get('/users/:user/publickey', async ctx => { @@ -482,7 +482,7 @@ export class ActivityPubServerService { if (this.userEntityService.isLocalUser(user)) { ctx.body = this.apRendererService.renderActivity(this.apRendererService.renderKey(user, keypair)); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); } else { ctx.status = 400; } @@ -499,7 +499,7 @@ export class ActivityPubServerService { isSuspended: false, }); - await this.#userInfo(ctx, user); + await this.userInfo(ctx, user); }); router.get('/@:user', async (ctx, next) => { @@ -511,7 +511,7 @@ export class ActivityPubServerService { isSuspended: false, }); - await this.#userInfo(ctx, user); + await this.userInfo(ctx, user); }); //#endregion @@ -529,7 +529,7 @@ export class ActivityPubServerService { ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderEmoji(emoji)); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); }); // like @@ -550,7 +550,7 @@ export class ActivityPubServerService { ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderLike(reaction, note)); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); }); // follow @@ -576,7 +576,7 @@ export class ActivityPubServerService { ctx.body = this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee)); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); }); return router; diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index e2b0ea5afe..becf0592d7 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -29,7 +29,7 @@ const assets = `${_dirname}/../../server/file/assets/`; @Injectable() export class FileServerService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -45,12 +45,12 @@ export class FileServerService { private internalStorageService: InternalStorageService, private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('server', 'gray', false); + this.logger = this.loggerService.getLogger('server', 'gray', false); } public commonReadableHandlerGenerator(ctx: Koa.Context) { return (e: Error): void => { - this.#logger.error(e); + this.logger.error(e); ctx.status = 500; ctx.set('Cache-Control', 'max-age=300'); }; @@ -74,8 +74,8 @@ export class FileServerService { ctx.set('Cache-Control', 'max-age=31536000, immutable'); }); - router.get('/:key', ctx => this.#sendDriveFile(ctx)); - router.get('/:key/(.*)', ctx => this.#sendDriveFile(ctx)); + router.get('/:key', ctx => this.sendDriveFile(ctx)); + router.get('/:key/(.*)', ctx => this.sendDriveFile(ctx)); // Register router app.use(router.routes()); @@ -83,7 +83,7 @@ export class FileServerService { return app; } - async #sendDriveFile(ctx: Koa.Context) { + private async sendDriveFile(ctx: Koa.Context) { const key = ctx.params.key; // Fetch drive file @@ -139,7 +139,7 @@ export class FileServerService { ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream'); ctx.set('Cache-Control', 'max-age=31536000, immutable'); } catch (err) { - this.#logger.error(`${err}`); + this.logger.error(`${err}`); if (err instanceof StatusError && err.isClientError) { ctx.status = err.statusCode; diff --git a/packages/backend/src/server/MediaProxyServerService.ts b/packages/backend/src/server/MediaProxyServerService.ts index 5344d3a9f4..1e0385602c 100644 --- a/packages/backend/src/server/MediaProxyServerService.ts +++ b/packages/backend/src/server/MediaProxyServerService.ts @@ -19,7 +19,7 @@ import { LoggerService } from '@/core/LoggerService.js'; @Injectable() export class MediaProxyServerService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -30,7 +30,7 @@ export class MediaProxyServerService { private imageProcessingService: ImageProcessingService, private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('server', 'gray', false); + this.logger = this.loggerService.getLogger('server', 'gray', false); } public createServer() { @@ -44,7 +44,7 @@ export class MediaProxyServerService { // Init router const router = new Router(); - router.get('/:url*', ctx => this.#handler(ctx)); + router.get('/:url*', ctx => this.handler(ctx)); // Register router app.use(router.routes()); @@ -52,7 +52,7 @@ export class MediaProxyServerService { return app; } - async #handler(ctx: Koa.Context) { + private async handler(ctx: Koa.Context) { const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url; if (typeof url !== 'string') { @@ -126,7 +126,7 @@ export class MediaProxyServerService { ctx.set('Cache-Control', 'max-age=31536000, immutable'); ctx.body = image.data; } catch (err) { - this.#logger.error(`${err}`); + this.logger.error(`${err}`); if (err instanceof StatusError && (err.statusCode === 302 || err.isClientError)) { ctx.status = err.statusCode; diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 8349e1e9f5..14d5ed45ab 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -30,7 +30,7 @@ import { ClientServerService } from './web/ClientServerService.js'; @Injectable() export class ServerService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -54,7 +54,7 @@ export class ServerService { private globalEventService: GlobalEventService, private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('server', 'gray', false); + this.logger = this.loggerService.getLogger('server', 'gray', false); } public launch() { @@ -65,7 +65,7 @@ export class ServerService { if (!['production', 'test'].includes(process.env.NODE_ENV ?? '')) { // Logger koa.use(koaLogger(str => { - this.#logger.info(str); + this.logger.info(str); })); // Delay @@ -157,13 +157,13 @@ export class ServerService { server.on('error', err => { switch ((err as any).code) { case 'EACCES': - this.#logger.error(`You do not have permission to listen on port ${this.config.port}.`); + this.logger.error(`You do not have permission to listen on port ${this.config.port}.`); break; case 'EADDRINUSE': - this.#logger.error(`Port ${this.config.port} is already in use by another process.`); + this.logger.error(`Port ${this.config.port} is already in use by another process.`); break; default: - this.#logger.error(err); + this.logger.error(err); break; } diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index f2ead3d4a1..d13b8d5ced 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -23,9 +23,9 @@ const accessDenied = { @Injectable() export class ApiCallService implements OnApplicationShutdown { - #logger: Logger; - #userIpHistories: Map>; - #userIpHistoriesClearIntervalId: NodeJS.Timer; + private logger: Logger; + private userIpHistories: Map>; + private userIpHistoriesClearIntervalId: NodeJS.Timer; constructor( @Inject(DI.userIpsRepository) @@ -36,11 +36,11 @@ export class ApiCallService implements OnApplicationShutdown { private rateLimiterService: RateLimiterService, private apiLoggerService: ApiLoggerService, ) { - this.#logger = this.apiLoggerService.logger; - this.#userIpHistories = new Map>(); + this.logger = this.apiLoggerService.logger; + this.userIpHistories = new Map>(); - this.#userIpHistoriesClearIntervalId = setInterval(() => { - this.#userIpHistories.clear(); + this.userIpHistoriesClearIntervalId = setInterval(() => { + this.userIpHistories.clear(); }, 1000 * 60 * 60); } @@ -76,7 +76,7 @@ export class ApiCallService implements OnApplicationShutdown { // Authentication this.authenticateService.authenticate(body['i']).then(([user, app]) => { // API invoking - this.#call(endpoint, exec, user, app, body, ctx).then((res: any) => { + this.call(endpoint, exec, user, app, body, ctx).then((res: any) => { if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) { ctx.set('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`); } @@ -90,10 +90,10 @@ export class ApiCallService implements OnApplicationShutdown { this.metaService.fetch().then(meta => { if (!meta.enableIpLogging) return; const ip = ctx.ip; - const ips = this.#userIpHistories.get(user.id); + const ips = this.userIpHistories.get(user.id); if (ips == null || !ips.has(ip)) { if (ips == null) { - this.#userIpHistories.set(user.id, new Set([ip])); + this.userIpHistories.set(user.id, new Set([ip])); } else { ips.add(ip); } @@ -123,7 +123,7 @@ export class ApiCallService implements OnApplicationShutdown { }); } - async #call( + private async call( ep: IEndpoint, exec: any, user: CacheableLocalUser | null | undefined, @@ -225,7 +225,7 @@ export class ApiCallService implements OnApplicationShutdown { if (err instanceof ApiError) { throw err; } else { - this.#logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, { + this.logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, { ep: ep.name, ps: data, e: { @@ -247,12 +247,12 @@ export class ApiCallService implements OnApplicationShutdown { const after = performance.now(); const time = after - before; if (time > 1000) { - this.#logger.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`); + this.logger.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`); } }); } public onApplicationShutdown(signal?: string | undefined) { - clearInterval(this.#userIpHistoriesClearIntervalId); + clearInterval(this.userIpHistoriesClearIntervalId); } } diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts index b8bd09509a..29d6ba78f0 100644 --- a/packages/backend/src/server/api/AuthenticateService.ts +++ b/packages/backend/src/server/api/AuthenticateService.ts @@ -17,7 +17,7 @@ export class AuthenticationError extends Error { @Injectable() export class AuthenticateService { - #appCache: Cache; + private appCache: Cache; constructor( @Inject(DI.usersRepository) @@ -31,7 +31,7 @@ export class AuthenticateService { private userCacheService: UserCacheService, ) { - this.#appCache = new Cache(Infinity); + this.appCache = new Cache(Infinity); } public async authenticate(token: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> { @@ -71,7 +71,7 @@ export class AuthenticateService { }) as Promise); if (accessToken.appId) { - const app = await this.#appCache.fetch(accessToken.appId, + const app = await this.appCache.fetch(accessToken.appId, () => this.appsRepository.findOneByOrFail({ id: accessToken.appId! })); return [user, { diff --git a/packages/backend/src/server/api/RateLimiterService.ts b/packages/backend/src/server/api/RateLimiterService.ts index 61b7b1ff47..35f28bfd63 100644 --- a/packages/backend/src/server/api/RateLimiterService.ts +++ b/packages/backend/src/server/api/RateLimiterService.ts @@ -8,7 +8,7 @@ import type { IEndpointMeta } from './endpoints.js'; @Injectable() export class RateLimiterService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.redis) @@ -16,7 +16,7 @@ export class RateLimiterService { private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('limiter'); + this.logger = this.loggerService.getLogger('limiter'); } public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable }, actor: string) { @@ -37,7 +37,7 @@ export class RateLimiterService { return reject('ERR'); } - this.#logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); + this.logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); if (info.remaining === 0) { reject('BRIEF_REQUEST_INTERVAL'); @@ -65,7 +65,7 @@ export class RateLimiterService { return reject('ERR'); } - this.#logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); + this.logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); if (info.remaining === 0) { reject('RATE_LIMIT_EXCEEDED'); diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index fa057dadb6..d9266aac6c 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -71,13 +71,13 @@ export default class extends Endpoint { (async () => { await this.userSuspendService.doPostSuspend(user).catch(e => {}); - await this.#unFollowAll(user).catch(e => {}); - await this.#readAllNotify(user).catch(e => {}); + await this.unFollowAll(user).catch(e => {}); + await this.readAllNotify(user).catch(e => {}); })(); }); } - async #unFollowAll(follower: User) { + private async unFollowAll(follower: User) { const followings = await this.followingsRepository.findBy({ followerId: follower.id, }); @@ -95,7 +95,7 @@ export default class extends Endpoint { } } - async #readAllNotify(notifier: User) { + private async readAllNotify(notifier: User) { await this.notificationsRepository.update({ notifierId: notifier.id, isRead: false, diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index eaf93ee977..e291b5908a 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -100,7 +100,7 @@ export default class extends Endpoint { private apNoteService: ApNoteService, ) { super(meta, paramDef, async (ps, me) => { - const object = await this.#fetchAny(ps.uri, me); + const object = await this.fetchAny(ps.uri, me); if (object) { return object; } else { @@ -112,12 +112,12 @@ export default class extends Endpoint { /*** * URIからUserかNoteを解決する */ - async #fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise | null> { + private async fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise | null> { // ブロックしてたら中断 const fetchedMeta = await this.metaService.fetch(); if (fetchedMeta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) return null; - let local = await this.#mergePack(me, ...await Promise.all([ + let local = await this.mergePack(me, ...await Promise.all([ this.apDbResolverService.getUserFromApId(uri), this.apDbResolverService.getNoteFromApId(uri), ])); @@ -130,21 +130,21 @@ export default class extends Endpoint { // /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する // これはDBに存在する可能性があるため再度DB検索 if (uri !== object.id) { - local = await this.#mergePack(me, ...await Promise.all([ + local = await this.mergePack(me, ...await Promise.all([ this.apDbResolverService.getUserFromApId(object.id), this.apDbResolverService.getNoteFromApId(object.id), ])); if (local != null) return local; } - return await this.#mergePack( + return await this.mergePack( me, isActor(object) ? await this.apPersonService.createPerson(getApId(object)) : null, isPost(object) ? await this.apNoteService.createNote(getApId(object), undefined, true) : null, ); } - async #mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise | null> { + private async mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise | null> { if (user != null) { return { type: 'User', diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index 604013c454..ea044c27d5 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -42,12 +42,12 @@ export class DiscordServerService { const router = new Router(); router.get('/disconnect/discord', async ctx => { - if (!this.#compareOrigin(ctx)) { + if (!this.compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; } - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); if (!userToken) { ctx.throw(400, 'signin required'); return; @@ -91,12 +91,12 @@ export class DiscordServerService { }; router.get('/connect/discord', async ctx => { - if (!this.#compareOrigin(ctx)) { + if (!this.compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; } - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); if (!userToken) { ctx.throw(400, 'signin required'); return; @@ -138,7 +138,7 @@ export class DiscordServerService { }); router.get('/dc/cb', async ctx => { - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); const oauth2 = await getOAuth2(); @@ -299,11 +299,11 @@ export class DiscordServerService { return router; } - #getUserToken(ctx: Koa.BaseContext): string | null { + private getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } - #compareOrigin(ctx: Koa.BaseContext): boolean { + private compareOrigin(ctx: Koa.BaseContext): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; } diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index 6864f21523..58b170d0e4 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -42,12 +42,12 @@ export class GithubServerService { const router = new Router(); router.get('/disconnect/github', async ctx => { - if (!this.#compareOrigin(ctx)) { + if (!this.compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; } - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); if (!userToken) { ctx.throw(400, 'signin required'); return; @@ -91,12 +91,12 @@ export class GithubServerService { }; router.get('/connect/github', async ctx => { - if (!this.#compareOrigin(ctx)) { + if (!this.compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; } - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); if (!userToken) { ctx.throw(400, 'signin required'); return; @@ -136,7 +136,7 @@ export class GithubServerService { }); router.get('/gh/cb', async ctx => { - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); const oauth2 = await getOath2(); @@ -271,11 +271,11 @@ export class GithubServerService { return router; } - #getUserToken(ctx: Koa.BaseContext): string | null { + private getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } - #compareOrigin(ctx: Koa.BaseContext): boolean { + private compareOrigin(ctx: Koa.BaseContext): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; } diff --git a/packages/backend/src/server/api/integration/TwitterServerService.ts b/packages/backend/src/server/api/integration/TwitterServerService.ts index 14b5f40ea5..a4a67f6c8c 100644 --- a/packages/backend/src/server/api/integration/TwitterServerService.ts +++ b/packages/backend/src/server/api/integration/TwitterServerService.ts @@ -42,12 +42,12 @@ export class TwitterServerService { const router = new Router(); router.get('/disconnect/twitter', async ctx => { - if (!this.#compareOrigin(ctx)) { + if (!this.compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; } - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); if (userToken == null) { ctx.throw(400, 'signin required'); return; @@ -90,12 +90,12 @@ export class TwitterServerService { }; router.get('/connect/twitter', async ctx => { - if (!this.#compareOrigin(ctx)) { + if (!this.compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; } - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); if (userToken == null) { ctx.throw(400, 'signin required'); return; @@ -125,7 +125,7 @@ export class TwitterServerService { }); router.get('/tw/cb', async ctx => { - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); const twAuth = await getTwAuth(); @@ -214,11 +214,11 @@ export class TwitterServerService { return router; } - #getUserToken(ctx: Koa.BaseContext): string | null { + private getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } - #compareOrigin(ctx: Koa.BaseContext): boolean { + private compareOrigin(ctx: Koa.BaseContext): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; } diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 67a7efaa25..85b31312b3 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -84,7 +84,7 @@ export class ClientServerService { ) { } - async #manifestHandler(ctx: Koa.Context) { + private async manifestHandler(ctx: Koa.Context) { // TODO //const res = structuredClone(manifest); const res = JSON.parse(JSON.stringify(manifest)); @@ -264,7 +264,7 @@ export class ClientServerService { }); // Manifest - router.get('/manifest.json', ctx => this.#manifestHandler(ctx)); + router.get('/manifest.json', ctx => this.manifestHandler(ctx)); router.get('/robots.txt', async ctx => { await send(ctx as any, '/robots.txt', { diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 726b158340..1cbb3f36c2 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -12,7 +12,7 @@ import type Koa from 'koa'; @Injectable() export class UrlPreviewService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -25,10 +25,10 @@ export class UrlPreviewService { private httpRequestService: HttpRequestService, private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('url-preview'); + this.logger = this.loggerService.getLogger('url-preview'); } - #wrap(url?: string): string | null { + private wrap(url?: string): string | null { return url != null ? url.match(/^https?:\/\//) ? `${this.config.url}/proxy/preview.webp?${query({ @@ -54,7 +54,7 @@ export class UrlPreviewService { const meta = await this.metaService.fetch(); - this.#logger.info(meta.summalyProxy + this.logger.info(meta.summalyProxy ? `(Proxy) Getting preview of ${url}@${lang} ...` : `Getting preview of ${url}@${lang} ...`); @@ -67,17 +67,17 @@ export class UrlPreviewService { lang: lang ?? 'ja-JP', }); - this.#logger.succ(`Got preview of ${url}: ${summary.title}`); + this.logger.succ(`Got preview of ${url}: ${summary.title}`); - summary.icon = this.#wrap(summary.icon); - summary.thumbnail = this.#wrap(summary.thumbnail); + summary.icon = this.wrap(summary.icon); + summary.thumbnail = this.wrap(summary.thumbnail); // Cache 7days ctx.set('Cache-Control', 'max-age=604800, immutable'); ctx.body = summary; } catch (err) { - this.#logger.warn(`Failed to get preview of ${url}: ${err}`); + this.logger.warn(`Failed to get preview of ${url}: ${err}`); ctx.status = 200; ctx.set('Cache-Control', 'max-age=86400, immutable'); ctx.body = '{}'; -- cgit v1.2.3-freya From 567c5501209d370c98a08ba7d25bfb274244a1ac Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 20 Sep 2022 05:32:18 +0900 Subject: lint --- packages/backend/src/core/DriveService.ts | 6 +++--- packages/backend/src/misc/gen-identicon.ts | 2 +- packages/backend/src/misc/is-instance-muted.ts | 10 +++++----- packages/backend/src/misc/is-quote.ts | 2 +- packages/backend/src/misc/show-machine-info.ts | 2 +- packages/backend/src/server/api/common/GetterService.ts | 2 +- packages/backend/src/server/api/common/inject-featured.ts | 13 +++++-------- packages/backend/src/server/api/common/inject-promo.ts | 5 ++--- packages/backend/src/server/api/endpoints/endpoint.ts | 2 +- .../src/server/api/endpoints/notes/thread-muting/create.ts | 6 +++--- .../src/server/api/endpoints/notes/thread-muting/delete.ts | 2 +- packages/backend/src/server/api/openapi/schemas.ts | 5 +++-- packages/client/.eslintrc.js | 2 +- 13 files changed, 28 insertions(+), 31 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 467f3c1cde..e356fa0009 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -147,7 +147,7 @@ export class DriveService { } const baseUrl = meta.objectStorageBaseUrl - || `${ meta.objectStorageUseSSL ? 'https' : 'http' }://${ meta.objectStorageEndpoint }${ meta.objectStoragePort ? `:${meta.objectStoragePort}` : '' }/${ meta.objectStorageBucket }`; + ?? `${ meta.objectStorageUseSSL ? 'https' : 'http' }://${ meta.objectStorageEndpoint }${ meta.objectStoragePort ? `:${meta.objectStoragePort}` : '' }/${ meta.objectStorageBucket }`; // for original const key = `${meta.objectStoragePrefix}/${uuid()}${ext}`; @@ -285,7 +285,7 @@ export class DriveService { satisfyWebpublic = !!( type !== 'image/svg+xml' && type !== 'image/webp' && - !(metadata.exif || metadata.iptc || metadata.xmp || metadata.tifftagPhotoshop) && + !(metadata.exif ?? metadata.iptc ?? metadata.xmp ?? metadata.tifftagPhotoshop) && metadata.width && metadata.width <= 2048 && metadata.height && metadata.height <= 2048 ); @@ -438,7 +438,7 @@ export class DriveService { //} // detect name - const detectedName = name || (info.type.ext ? `untitled.${info.type.ext}` : 'untitled'); + const detectedName = name ?? (info.type.ext ? `untitled.${info.type.ext}` : 'untitled'); if (user && !force) { // Check if there is a file with the same hash diff --git a/packages/backend/src/misc/gen-identicon.ts b/packages/backend/src/misc/gen-identicon.ts index 322ffee22e..4a70d7a4bb 100644 --- a/packages/backend/src/misc/gen-identicon.ts +++ b/packages/backend/src/misc/gen-identicon.ts @@ -3,9 +3,9 @@ * https://en.wikipedia.org/wiki/Identicon */ -import { WriteStream } from 'node:fs'; import * as p from 'pureimage'; import gen from 'random-seed'; +import type { WriteStream } from 'node:fs'; const size = 128; // px const n = 5; // resolution diff --git a/packages/backend/src/misc/is-instance-muted.ts b/packages/backend/src/misc/is-instance-muted.ts index a74ba524e3..e11a18bb70 100644 --- a/packages/backend/src/misc/is-instance-muted.ts +++ b/packages/backend/src/misc/is-instance-muted.ts @@ -1,15 +1,15 @@ -import { Packed } from './schema.js'; +import type { Packed } from './schema.js'; export function isInstanceMuted(note: Packed<'Note'>, mutedInstances: Set): boolean { - if (mutedInstances.has(note?.user?.host ?? '')) return true; - if (mutedInstances.has(note?.reply?.user?.host ?? '')) return true; - if (mutedInstances.has(note?.renote?.user?.host ?? '')) return true; + if (mutedInstances.has(note.user.host ?? '')) return true; + if (mutedInstances.has(note.reply?.user.host ?? '')) return true; + if (mutedInstances.has(note.renote?.user.host ?? '')) return true; return false; } export function isUserFromMutedInstance(notif: Packed<'Notification'>, mutedInstances: Set): boolean { - if (mutedInstances.has(notif?.user?.host ?? '')) return true; + if (mutedInstances.has(notif.user?.host ?? '')) return true; return false; } diff --git a/packages/backend/src/misc/is-quote.ts b/packages/backend/src/misc/is-quote.ts index 6ea71cd878..248b25a0bf 100644 --- a/packages/backend/src/misc/is-quote.ts +++ b/packages/backend/src/misc/is-quote.ts @@ -1,4 +1,4 @@ -import { Note } from '@/models/entities/Note.js'; +import type { Note } from '@/models/entities/Note.js'; export default function(note: Note): boolean { return note.renoteId != null && (note.text != null || note.hasPoll || (note.fileIds != null && note.fileIds.length > 0)); diff --git a/packages/backend/src/misc/show-machine-info.ts b/packages/backend/src/misc/show-machine-info.ts index bfb1b85f33..fa5a53e313 100644 --- a/packages/backend/src/misc/show-machine-info.ts +++ b/packages/backend/src/misc/show-machine-info.ts @@ -1,6 +1,6 @@ import * as os from 'node:os'; import sysUtils from 'systeminformation'; -import Logger from '@/core/logger.js'; +import type Logger from '@/logger.js'; export async function showMachineInfo(parentLogger: Logger) { const logger = parentLogger.createSubLogger('machine'); diff --git a/packages/backend/src/server/api/common/GetterService.ts b/packages/backend/src/server/api/common/GetterService.ts index a6b60d1f5a..5523539b91 100644 --- a/packages/backend/src/server/api/common/GetterService.ts +++ b/packages/backend/src/server/api/common/GetterService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { NotesRepository, UsersRepository } from '@/models/index.js'; +import { NotesRepository, UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; diff --git a/packages/backend/src/server/api/common/inject-featured.ts b/packages/backend/src/server/api/common/inject-featured.ts index 75126fa304..d59bec33ab 100644 --- a/packages/backend/src/server/api/common/inject-featured.ts +++ b/packages/backend/src/server/api/common/inject-featured.ts @@ -1,9 +1,6 @@ import rndstr from 'rndstr'; -import { Note } from '@/models/entities/Note.js'; -import { User } from '@/models/entities/User.js'; -import { Notes, UserProfiles, NoteReactions } from '@/models/index.js'; -import { generateMutedUserQuery } from './generate-muted-user-query.js'; -import { generateBlockedUserQuery } from './generate-block-query.js'; +import type { Note } from '@/models/entities/Note.js'; +import type { User } from '@/models/entities/User.js'; // TODO: リアクション、Renote、返信などをしたノートは除外する @@ -21,9 +18,9 @@ export async function injectFeatured(timeline: Note[], user?: User | null) { const query = Notes.createQueryBuilder('note') .addSelect('note.score') .where('note.userHost IS NULL') - .andWhere(`note.score > 0`) - .andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) }) - .andWhere(`note.visibility = 'public'`) + .andWhere('note.score > 0') + .andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) }) + .andWhere('note.visibility = \'public\'') .innerJoinAndSelect('note.user', 'user'); if (user) { diff --git a/packages/backend/src/server/api/common/inject-promo.ts b/packages/backend/src/server/api/common/inject-promo.ts index 454f5dbb0e..fe02897509 100644 --- a/packages/backend/src/server/api/common/inject-promo.ts +++ b/packages/backend/src/server/api/common/inject-promo.ts @@ -1,7 +1,6 @@ import rndstr from 'rndstr'; -import { Note } from '@/models/entities/Note.js'; -import { User } from '@/models/entities/User.js'; -import { PromoReads, PromoNotes, Notes, Users } from '@/models/index.js'; +import type { Note } from '@/models/entities/Note.js'; +import type { User } from '@/models/entities/User.js'; export async function injectPromo(timeline: Note[], user?: User | null) { if (timeline.length < 5) return; diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts index 2141dfbeb0..a337a05f8c 100644 --- a/packages/backend/src/server/api/endpoints/endpoint.ts +++ b/packages/backend/src/server/api/endpoints/endpoint.ts @@ -25,7 +25,7 @@ export default class extends Endpoint { const ep = endpoints.find(x => x.name === ps.endpoint); if (ep == null) return null; return { - params: Object.entries(ep.params.properties || {}).map(([k, v]) => ({ + params: Object.entries(ep.params.properties ?? {}).map(([k, v]) => ({ name: k, type: v.type.charAt(0).toUpperCase() + v.type.slice(1), })), diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index 1c83adddff..060581d74b 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -53,9 +53,9 @@ export default class extends Endpoint { const mutedNotes = await this.notesRepository.find({ where: [{ - id: note.threadId || note.id, + id: note.threadId ?? note.id, }, { - threadId: note.threadId || note.id, + threadId: note.threadId ?? note.id, }], }); @@ -64,7 +64,7 @@ export default class extends Endpoint { await this.noteThreadMutingsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), - threadId: note.threadId || note.id, + threadId: note.threadId ?? note.id, userId: me.id, }); }); diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts index 1f896734d1..aed15852d4 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -45,7 +45,7 @@ export default class extends Endpoint { }); await this.noteThreadMutingsRepository.delete({ - threadId: note.threadId || note.id, + threadId: note.threadId ?? note.id, userId: me.id, }); }); diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts index 14bef9cab1..796383f5e3 100644 --- a/packages/backend/src/server/api/openapi/schemas.ts +++ b/packages/backend/src/server/api/openapi/schemas.ts @@ -1,4 +1,5 @@ -import { refs, Schema } from '@/misc/schema.js'; +import type { Schema } from '@/misc/schema.js'; +import { refs } from '@/misc/schema.js'; export function convertSchemaToOpenApiSchema(schema: Schema) { const res: any = schema; @@ -55,6 +56,6 @@ export const schemas = { }, ...Object.fromEntries( - Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema)]) + Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema)]), ), }; diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index 01dedd1c69..c3a78bd5fd 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -38,7 +38,7 @@ module.exports = { 'vue/no-multi-spaces': ['error', { 'ignoreProperties': false, }], - 'vue/no-v-html': 'error', + 'vue/no-v-html': 'warn', 'vue/order-in-components': 'error', 'vue/html-indent': ['warn', 'tab', { 'attribute': 1, -- cgit v1.2.3-freya From dc43fc68ef2b8e0d6f2cfd94911b367fd915f445 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 21 Sep 2022 02:35:49 +0900 Subject: enhance(backend): metaのポーリング頻度を減らし、redisで更新を受け取るように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/MetaService.ts | 33 +++++++++++++++++++--- .../src/server/api/endpoints/admin/update-meta.ts | 16 +++++++++-- packages/backend/src/server/api/stream/types.ts | 2 ++ 3 files changed, 45 insertions(+), 6 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index 4099e340be..5ae9a6fc83 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import type { UsersRepository } from '@/models/index.js'; +import Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import { Meta } from '@/models/entities/Meta.js'; import type { OnApplicationShutdown } from '@nestjs/common'; @@ -11,19 +11,27 @@ export class MetaService implements OnApplicationShutdown { private intervalId: NodeJS.Timer; constructor( + @Inject(DI.redisSubscriber) + private redisSubscriber: Redis.Redis, + @Inject(DI.db) private db: DataSource, ) { + this.onMessage = this.onMessage.bind(this); + if (process.env.NODE_ENV !== 'test') { this.intervalId = setInterval(() => { this.fetch(true).then(meta => { + // fetch内でもセットしてるけど仕様変更の可能性もあるため一応 this.cache = meta; }); - }, 1000 * 10); + }, 1000 * 60 * 5); } + + this.redisSubscriber.on('message', this.onMessage); } - async fetch(noCache = false): Promise { + public async fetch(noCache = false): Promise { if (!noCache && this.cache) return this.cache; return await this.db.transaction(async transactionalEntityManager => { @@ -56,8 +64,25 @@ export class MetaService implements OnApplicationShutdown { } }); } - + + private async onMessage(_, data) { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message; + switch (type) { + case 'metaUpdated': { + this.cache = body; + break; + } + default: + break; + } + } + } + public onApplicationShutdown(signal?: string | undefined) { clearInterval(this.intervalId); + this.redisSubscriber.off('message', this.onMessage); } } diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 968ed4d26d..0ab0780e3d 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -5,6 +5,7 @@ import { ModerationLogService } from '@/core/ModerationLogService.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; export const meta = { tags: ['admin'], @@ -115,6 +116,7 @@ export default class extends Endpoint { @Inject(DI.db) private db: DataSource, + private globalEventService: GlobalEventService, private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { @@ -436,7 +438,7 @@ export default class extends Endpoint { set.enableActiveEmailValidation = ps.enableActiveEmailValidation; } - await this.db.transaction(async transactionalEntityManager => { + const updated = await this.db.transaction(async transactionalEntityManager => { const metas = await transactionalEntityManager.find(Meta, { order: { id: 'DESC', @@ -447,11 +449,21 @@ export default class extends Endpoint { if (meta) { await transactionalEntityManager.update(Meta, meta.id, set); + + const metas = await transactionalEntityManager.find(Meta, { + order: { + id: 'DESC', + }, + }); + + return metas[0]; } else { - await transactionalEntityManager.save(Meta, set); + return await transactionalEntityManager.save(Meta, set); } }); + this.globalEventService.publishInternalEvent('metaUpdated', updated); + this.moderationLogService.insertModerationLog(me, 'updateMeta'); }); } diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 000f9a25dd..ec05be56ee 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -13,6 +13,7 @@ import type { Signin } from '@/models/entities/Signin.js'; import type { Page } from '@/models/entities/Page.js'; import type { Packed } from '@/misc/schema.js'; import type { Webhook } from '@/models/entities/Webhook.js'; +import type { Meta } from '@/models/entities/Meta.js'; import type Emitter from 'strict-event-emitter-types'; import type { EventEmitter } from 'events'; @@ -29,6 +30,7 @@ export interface InternalStreamTypes { antennaCreated: Antenna; antennaDeleted: Antenna; antennaUpdated: Antenna; + metaUpdated: Meta, } export interface BroadcastTypes { -- cgit v1.2.3-freya From 3bc6205150c627699a9597158bc662ae8a2c0afe Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 21 Sep 2022 02:52:19 +0900 Subject: refactor(backend): ロジックをサービスに切り出す MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/MetaService.ts | 57 +++++++++++++++++----- .../src/server/api/endpoints/admin/update-meta.ts | 32 ++---------- 2 files changed, 49 insertions(+), 40 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index 5ae9a6fc83..1744f64217 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import { Meta } from '@/models/entities/Meta.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() @@ -16,6 +17,8 @@ export class MetaService implements OnApplicationShutdown { @Inject(DI.db) private db: DataSource, + + private globalEventService: GlobalEventService, ) { this.onMessage = this.onMessage.bind(this); @@ -31,6 +34,22 @@ export class MetaService implements OnApplicationShutdown { this.redisSubscriber.on('message', this.onMessage); } + private async onMessage(_, data): Promise { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message; + switch (type) { + case 'metaUpdated': { + this.cache = body; + break; + } + default: + break; + } + } + } + public async fetch(noCache = false): Promise { if (!noCache && this.cache) return this.cache; @@ -65,20 +84,34 @@ export class MetaService implements OnApplicationShutdown { }); } - private async onMessage(_, data) { - const obj = JSON.parse(data); + public async update(data: Partial): Promise { + const updated = await this.db.transaction(async transactionalEntityManager => { + const metas = await transactionalEntityManager.find(Meta, { + order: { + id: 'DESC', + }, + }); - if (obj.channel === 'internal') { - const { type, body } = obj.message; - switch (type) { - case 'metaUpdated': { - this.cache = body; - break; - } - default: - break; + const meta = metas[0]; + + if (meta) { + await transactionalEntityManager.update(Meta, meta.id, data); + + const metas = await transactionalEntityManager.find(Meta, { + order: { + id: 'DESC', + }, + }); + + return metas[0]; + } else { + return await transactionalEntityManager.save(Meta, data); } - } + }); + + this.globalEventService.publishInternalEvent('metaUpdated', updated); + + return updated; } public onApplicationShutdown(signal?: string | undefined) { diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 0ab0780e3d..48fae9b947 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -1,11 +1,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { Meta } from '@/models/entities/Meta.js'; +import type { Meta } from '@/models/entities/Meta.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { MetaService } from '@/core/MetaService.js'; export const meta = { tags: ['admin'], @@ -116,7 +117,7 @@ export default class extends Endpoint { @Inject(DI.db) private db: DataSource, - private globalEventService: GlobalEventService, + private metaService: MetaService, private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { @@ -438,32 +439,7 @@ export default class extends Endpoint { set.enableActiveEmailValidation = ps.enableActiveEmailValidation; } - const updated = await this.db.transaction(async transactionalEntityManager => { - const metas = await transactionalEntityManager.find(Meta, { - order: { - id: 'DESC', - }, - }); - - const meta = metas[0]; - - if (meta) { - await transactionalEntityManager.update(Meta, meta.id, set); - - const metas = await transactionalEntityManager.find(Meta, { - order: { - id: 'DESC', - }, - }); - - return metas[0]; - } else { - return await transactionalEntityManager.save(Meta, set); - } - }); - - this.globalEventService.publishInternalEvent('metaUpdated', updated); - + await this.metaService.update(set); this.moderationLogService.insertModerationLog(me, 'updateMeta'); }); } -- cgit v1.2.3-freya From 01d4d55e78fd977b9d44a68a2504e6091d346b7a Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 21 Sep 2022 05:33:11 +0900 Subject: fix import type --- packages/backend/src/core/AccountUpdateService.ts | 4 ++-- packages/backend/src/core/AiService.ts | 2 +- packages/backend/src/core/AntennaService.ts | 2 +- packages/backend/src/core/CaptchaService.ts | 2 +- packages/backend/src/core/CreateNotificationService.ts | 2 +- packages/backend/src/core/CustomEmojiService.ts | 4 ++-- packages/backend/src/core/DeleteAccountService.ts | 2 +- packages/backend/src/core/DownloadService.ts | 2 +- packages/backend/src/core/DriveService.ts | 4 ++-- packages/backend/src/core/EmailService.ts | 4 ++-- packages/backend/src/core/FederatedInstanceService.ts | 2 +- packages/backend/src/core/FetchInstanceMetadataService.ts | 2 +- packages/backend/src/core/GlobalEventService.ts | 2 +- packages/backend/src/core/HashtagService.ts | 2 +- packages/backend/src/core/HttpRequestService.ts | 2 +- packages/backend/src/core/IdService.ts | 2 +- packages/backend/src/core/ImageProcessingService.ts | 2 +- packages/backend/src/core/InstanceActorService.ts | 2 +- packages/backend/src/core/InternalStorageService.ts | 2 +- packages/backend/src/core/LoggerService.ts | 2 +- packages/backend/src/core/MessagingService.ts | 4 ++-- packages/backend/src/core/MfmService.ts | 2 +- packages/backend/src/core/ModerationLogService.ts | 2 +- packages/backend/src/core/NoteCreateService.ts | 4 ++-- packages/backend/src/core/NoteDeleteService.ts | 4 ++-- packages/backend/src/core/NotePiningService.ts | 4 ++-- packages/backend/src/core/NotificationService.ts | 2 +- packages/backend/src/core/PollService.ts | 2 +- packages/backend/src/core/ProxyAccountService.ts | 2 +- packages/backend/src/core/PushNotificationService.ts | 4 ++-- packages/backend/src/core/QueueService.ts | 4 ++-- packages/backend/src/core/ReactionService.ts | 2 +- packages/backend/src/core/RelayService.ts | 2 +- packages/backend/src/core/S3Service.ts | 2 +- packages/backend/src/core/SignupService.ts | 4 ++-- packages/backend/src/core/TwoFactorAuthenticationService.ts | 4 ++-- packages/backend/src/core/UserCacheService.ts | 2 +- packages/backend/src/core/UserKeypairStoreService.ts | 2 +- packages/backend/src/core/UserListService.ts | 2 +- packages/backend/src/core/UserMutingService.ts | 2 +- packages/backend/src/core/UserSuspendService.ts | 4 ++-- packages/backend/src/core/UtilityService.ts | 2 +- packages/backend/src/core/VideoProcessingService.ts | 2 +- packages/backend/src/core/WebhookService.ts | 2 +- packages/backend/src/core/chart/charts/federation.ts | 2 +- packages/backend/src/core/chart/charts/instance.ts | 2 +- packages/backend/src/core/chart/charts/notes.ts | 2 +- packages/backend/src/core/chart/charts/per-user-drive.ts | 2 +- packages/backend/src/core/chart/charts/per-user-following.ts | 2 +- packages/backend/src/core/chart/charts/per-user-notes.ts | 2 +- packages/backend/src/core/chart/charts/users.ts | 2 +- packages/backend/src/core/entities/AbuseUserReportEntityService.ts | 2 +- packages/backend/src/core/entities/AntennaEntityService.ts | 2 +- packages/backend/src/core/entities/AppEntityService.ts | 2 +- packages/backend/src/core/entities/AuthSessionEntityService.ts | 2 +- packages/backend/src/core/entities/BlockingEntityService.ts | 2 +- packages/backend/src/core/entities/ChannelEntityService.ts | 2 +- packages/backend/src/core/entities/ClipEntityService.ts | 2 +- packages/backend/src/core/entities/DriveFileEntityService.ts | 4 ++-- packages/backend/src/core/entities/DriveFolderEntityService.ts | 2 +- packages/backend/src/core/entities/EmojiEntityService.ts | 2 +- packages/backend/src/core/entities/FollowRequestEntityService.ts | 2 +- packages/backend/src/core/entities/FollowingEntityService.ts | 2 +- packages/backend/src/core/entities/GalleryLikeEntityService.ts | 2 +- packages/backend/src/core/entities/GalleryPostEntityService.ts | 2 +- packages/backend/src/core/entities/HashtagEntityService.ts | 2 +- packages/backend/src/core/entities/InstanceEntityService.ts | 2 +- packages/backend/src/core/entities/MessagingMessageEntityService.ts | 2 +- packages/backend/src/core/entities/ModerationLogEntityService.ts | 2 +- packages/backend/src/core/entities/MutingEntityService.ts | 2 +- packages/backend/src/core/entities/NoteEntityService.ts | 2 +- packages/backend/src/core/entities/NoteFavoriteEntityService.ts | 2 +- packages/backend/src/core/entities/NoteReactionEntityService.ts | 2 +- packages/backend/src/core/entities/NotificationEntityService.ts | 2 +- packages/backend/src/core/entities/PageEntityService.ts | 2 +- packages/backend/src/core/entities/PageLikeEntityService.ts | 2 +- packages/backend/src/core/entities/SigninEntityService.ts | 2 +- packages/backend/src/core/entities/UserEntityService.ts | 4 ++-- packages/backend/src/core/entities/UserGroupEntityService.ts | 2 +- .../backend/src/core/entities/UserGroupInvitationEntityService.ts | 2 +- packages/backend/src/core/entities/UserListEntityService.ts | 2 +- packages/backend/src/core/remote/ResolveUserService.ts | 4 ++-- packages/backend/src/core/remote/WebfingerService.ts | 2 +- packages/backend/src/core/remote/activitypub/ApDbResolverService.ts | 4 ++-- .../backend/src/core/remote/activitypub/ApDeliverManagerService.ts | 4 ++-- packages/backend/src/core/remote/activitypub/ApInboxService.ts | 2 +- packages/backend/src/core/remote/activitypub/ApMfmService.ts | 2 +- packages/backend/src/core/remote/activitypub/ApRendererService.ts | 2 +- packages/backend/src/core/remote/activitypub/ApRequestService.ts | 2 +- packages/backend/src/core/remote/activitypub/ApResolverService.ts | 4 ++-- .../backend/src/core/remote/activitypub/models/ApImageService.ts | 4 ++-- .../backend/src/core/remote/activitypub/models/ApMentionService.ts | 2 +- packages/backend/src/core/remote/activitypub/models/ApNoteService.ts | 4 ++-- .../backend/src/core/remote/activitypub/models/ApPersonService.ts | 4 ++-- .../backend/src/core/remote/activitypub/models/ApQuestionService.ts | 4 ++-- packages/backend/src/daemons/JanitorService.ts | 2 +- packages/backend/src/queue/DbQueueProcessorsService.ts | 2 +- packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts | 2 +- packages/backend/src/queue/QueueProcessorService.ts | 2 +- packages/backend/src/queue/SystemQueueProcessorsService.ts | 2 +- .../src/queue/processors/CheckExpiredMutingsProcessorService.ts | 4 ++-- packages/backend/src/queue/processors/CleanChartsProcessorService.ts | 2 +- packages/backend/src/queue/processors/CleanProcessorService.ts | 4 ++-- .../backend/src/queue/processors/CleanRemoteFilesProcessorService.ts | 4 ++-- .../backend/src/queue/processors/DeleteAccountProcessorService.ts | 4 ++-- .../backend/src/queue/processors/DeleteDriveFilesProcessorService.ts | 4 ++-- packages/backend/src/queue/processors/DeleteFileProcessorService.ts | 2 +- packages/backend/src/queue/processors/DeliverProcessorService.ts | 4 ++-- .../src/queue/processors/EndedPollNotificationProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ExportBlockingProcessorService.ts | 5 ++--- .../src/queue/processors/ExportCustomEmojisProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ExportFollowingProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ExportMutingProcessorService.ts | 4 ++-- packages/backend/src/queue/processors/ExportNotesProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ExportUserListsProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ImportBlockingProcessorService.ts | 4 ++-- .../src/queue/processors/ImportCustomEmojisProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ImportFollowingProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ImportMutingProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ImportUserListsProcessorService.ts | 4 ++-- packages/backend/src/queue/processors/InboxProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ResyncChartsProcessorService.ts | 2 +- packages/backend/src/queue/processors/TickChartsProcessorService.ts | 2 +- .../backend/src/queue/processors/WebhookDeliverProcessorService.ts | 4 ++-- packages/backend/src/server/ActivityPubServerService.ts | 4 ++-- packages/backend/src/server/FileServerService.ts | 4 ++-- packages/backend/src/server/MediaProxyServerService.ts | 2 +- packages/backend/src/server/NodeinfoServerService.ts | 4 ++-- packages/backend/src/server/ServerService.ts | 4 ++-- packages/backend/src/server/WellKnownServerService.ts | 4 ++-- packages/backend/src/server/api/ApiCallService.ts | 2 +- packages/backend/src/server/api/ApiServerService.ts | 4 ++-- packages/backend/src/server/api/AuthenticateService.ts | 2 +- packages/backend/src/server/api/SigninApiService.ts | 4 ++-- packages/backend/src/server/api/SigninService.ts | 4 ++-- packages/backend/src/server/api/SignupApiService.ts | 4 ++-- packages/backend/src/server/api/StreamingApiServerService.ts | 4 ++-- packages/backend/src/server/api/common/GetterService.ts | 2 +- .../backend/src/server/api/endpoints/admin/abuse-user-reports.ts | 2 +- packages/backend/src/server/api/endpoints/admin/accounts/create.ts | 2 +- packages/backend/src/server/api/endpoints/admin/accounts/delete.ts | 2 +- packages/backend/src/server/api/endpoints/admin/ad/create.ts | 2 +- packages/backend/src/server/api/endpoints/admin/ad/delete.ts | 2 +- packages/backend/src/server/api/endpoints/admin/ad/list.ts | 2 +- packages/backend/src/server/api/endpoints/admin/ad/update.ts | 2 +- .../backend/src/server/api/endpoints/admin/announcements/create.ts | 2 +- .../backend/src/server/api/endpoints/admin/announcements/delete.ts | 2 +- .../backend/src/server/api/endpoints/admin/announcements/list.ts | 2 +- .../backend/src/server/api/endpoints/admin/announcements/update.ts | 2 +- packages/backend/src/server/api/endpoints/admin/delete-account.ts | 2 +- .../src/server/api/endpoints/admin/delete-all-files-of-a-user.ts | 2 +- .../src/server/api/endpoints/admin/drive-capacity-override.ts | 2 +- packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts | 2 +- packages/backend/src/server/api/endpoints/admin/drive/files.ts | 2 +- packages/backend/src/server/api/endpoints/admin/drive/show-file.ts | 2 +- .../backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/add.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/copy.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/delete.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/list.ts | 2 +- .../src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts | 2 +- .../backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts | 2 +- .../src/server/api/endpoints/admin/emoji/set-category-bulk.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/update.ts | 2 +- .../src/server/api/endpoints/admin/federation/delete-all-files.ts | 2 +- .../endpoints/admin/federation/refresh-remote-instance-metadata.ts | 2 +- .../server/api/endpoints/admin/federation/remove-all-following.ts | 2 +- .../src/server/api/endpoints/admin/federation/update-instance.ts | 2 +- packages/backend/src/server/api/endpoints/admin/get-user-ips.ts | 2 +- packages/backend/src/server/api/endpoints/admin/invite.ts | 2 +- packages/backend/src/server/api/endpoints/admin/meta.ts | 2 +- packages/backend/src/server/api/endpoints/admin/moderators/add.ts | 2 +- packages/backend/src/server/api/endpoints/admin/moderators/remove.ts | 2 +- packages/backend/src/server/api/endpoints/admin/promo/create.ts | 2 +- packages/backend/src/server/api/endpoints/admin/reset-password.ts | 2 +- .../src/server/api/endpoints/admin/resolve-abuse-user-report.ts | 2 +- .../backend/src/server/api/endpoints/admin/show-moderation-logs.ts | 2 +- packages/backend/src/server/api/endpoints/admin/show-user.ts | 2 +- packages/backend/src/server/api/endpoints/admin/show-users.ts | 2 +- packages/backend/src/server/api/endpoints/admin/silence-user.ts | 2 +- packages/backend/src/server/api/endpoints/admin/suspend-user.ts | 2 +- packages/backend/src/server/api/endpoints/admin/unsilence-user.ts | 2 +- packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts | 2 +- packages/backend/src/server/api/endpoints/admin/update-user-note.ts | 2 +- packages/backend/src/server/api/endpoints/announcements.ts | 2 +- packages/backend/src/server/api/endpoints/antennas/create.ts | 2 +- packages/backend/src/server/api/endpoints/antennas/delete.ts | 2 +- packages/backend/src/server/api/endpoints/antennas/list.ts | 2 +- packages/backend/src/server/api/endpoints/antennas/notes.ts | 2 +- packages/backend/src/server/api/endpoints/antennas/show.ts | 2 +- packages/backend/src/server/api/endpoints/antennas/update.ts | 2 +- packages/backend/src/server/api/endpoints/ap/show.ts | 2 +- packages/backend/src/server/api/endpoints/app/create.ts | 2 +- packages/backend/src/server/api/endpoints/app/show.ts | 2 +- packages/backend/src/server/api/endpoints/auth/accept.ts | 2 +- packages/backend/src/server/api/endpoints/auth/session/generate.ts | 4 ++-- packages/backend/src/server/api/endpoints/auth/session/show.ts | 2 +- packages/backend/src/server/api/endpoints/auth/session/userkey.ts | 2 +- packages/backend/src/server/api/endpoints/blocking/create.ts | 2 +- packages/backend/src/server/api/endpoints/blocking/delete.ts | 2 +- packages/backend/src/server/api/endpoints/blocking/list.ts | 2 +- packages/backend/src/server/api/endpoints/channels/create.ts | 2 +- packages/backend/src/server/api/endpoints/channels/featured.ts | 2 +- packages/backend/src/server/api/endpoints/channels/follow.ts | 2 +- packages/backend/src/server/api/endpoints/channels/followed.ts | 2 +- packages/backend/src/server/api/endpoints/channels/owned.ts | 2 +- packages/backend/src/server/api/endpoints/channels/show.ts | 2 +- packages/backend/src/server/api/endpoints/channels/timeline.ts | 2 +- packages/backend/src/server/api/endpoints/channels/unfollow.ts | 2 +- packages/backend/src/server/api/endpoints/channels/update.ts | 2 +- packages/backend/src/server/api/endpoints/clips/add-note.ts | 2 +- packages/backend/src/server/api/endpoints/clips/create.ts | 2 +- packages/backend/src/server/api/endpoints/clips/delete.ts | 2 +- packages/backend/src/server/api/endpoints/clips/list.ts | 2 +- packages/backend/src/server/api/endpoints/clips/notes.ts | 2 +- packages/backend/src/server/api/endpoints/clips/remove-note.ts | 2 +- packages/backend/src/server/api/endpoints/clips/show.ts | 2 +- packages/backend/src/server/api/endpoints/clips/update.ts | 2 +- packages/backend/src/server/api/endpoints/drive/files.ts | 2 +- .../backend/src/server/api/endpoints/drive/files/attached-notes.ts | 2 +- .../backend/src/server/api/endpoints/drive/files/check-existence.ts | 2 +- packages/backend/src/server/api/endpoints/drive/files/create.ts | 2 +- packages/backend/src/server/api/endpoints/drive/files/delete.ts | 2 +- .../backend/src/server/api/endpoints/drive/files/find-by-hash.ts | 2 +- packages/backend/src/server/api/endpoints/drive/files/find.ts | 2 +- packages/backend/src/server/api/endpoints/drive/files/show.ts | 2 +- packages/backend/src/server/api/endpoints/drive/files/update.ts | 2 +- .../backend/src/server/api/endpoints/drive/files/upload-from-url.ts | 2 +- packages/backend/src/server/api/endpoints/drive/folders.ts | 2 +- packages/backend/src/server/api/endpoints/drive/folders/create.ts | 2 +- packages/backend/src/server/api/endpoints/drive/folders/delete.ts | 2 +- packages/backend/src/server/api/endpoints/drive/folders/find.ts | 2 +- packages/backend/src/server/api/endpoints/drive/folders/show.ts | 2 +- packages/backend/src/server/api/endpoints/drive/folders/update.ts | 2 +- packages/backend/src/server/api/endpoints/drive/stream.ts | 2 +- packages/backend/src/server/api/endpoints/federation/followers.ts | 2 +- packages/backend/src/server/api/endpoints/federation/following.ts | 2 +- packages/backend/src/server/api/endpoints/federation/instances.ts | 2 +- .../backend/src/server/api/endpoints/federation/show-instance.ts | 2 +- packages/backend/src/server/api/endpoints/federation/stats.ts | 2 +- packages/backend/src/server/api/endpoints/federation/users.ts | 2 +- packages/backend/src/server/api/endpoints/fetch-rss.ts | 2 +- packages/backend/src/server/api/endpoints/following/create.ts | 2 +- packages/backend/src/server/api/endpoints/following/delete.ts | 2 +- packages/backend/src/server/api/endpoints/following/invalidate.ts | 2 +- .../backend/src/server/api/endpoints/following/requests/cancel.ts | 2 +- packages/backend/src/server/api/endpoints/following/requests/list.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/featured.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/popular.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/posts.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/posts/create.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/posts/delete.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/posts/like.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/posts/show.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/posts/update.ts | 2 +- packages/backend/src/server/api/endpoints/get-online-users-count.ts | 2 +- packages/backend/src/server/api/endpoints/hashtags/list.ts | 2 +- packages/backend/src/server/api/endpoints/hashtags/search.ts | 2 +- packages/backend/src/server/api/endpoints/hashtags/show.ts | 2 +- packages/backend/src/server/api/endpoints/hashtags/trend.ts | 2 +- packages/backend/src/server/api/endpoints/hashtags/users.ts | 2 +- packages/backend/src/server/api/endpoints/i.ts | 2 +- packages/backend/src/server/api/endpoints/i/2fa/done.ts | 2 +- packages/backend/src/server/api/endpoints/i/2fa/key-done.ts | 4 ++-- packages/backend/src/server/api/endpoints/i/2fa/password-less.ts | 2 +- packages/backend/src/server/api/endpoints/i/2fa/register-key.ts | 2 +- packages/backend/src/server/api/endpoints/i/2fa/register.ts | 4 ++-- packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts | 2 +- packages/backend/src/server/api/endpoints/i/2fa/unregister.ts | 2 +- packages/backend/src/server/api/endpoints/i/apps.ts | 2 +- packages/backend/src/server/api/endpoints/i/authorized-apps.ts | 2 +- packages/backend/src/server/api/endpoints/i/change-password.ts | 2 +- packages/backend/src/server/api/endpoints/i/delete-account.ts | 2 +- packages/backend/src/server/api/endpoints/i/favorites.ts | 2 +- packages/backend/src/server/api/endpoints/i/gallery/likes.ts | 2 +- packages/backend/src/server/api/endpoints/i/gallery/posts.ts | 2 +- .../backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts | 2 +- packages/backend/src/server/api/endpoints/i/import-blocking.ts | 2 +- packages/backend/src/server/api/endpoints/i/import-following.ts | 2 +- packages/backend/src/server/api/endpoints/i/import-muting.ts | 2 +- packages/backend/src/server/api/endpoints/i/import-user-lists.ts | 2 +- packages/backend/src/server/api/endpoints/i/notifications.ts | 2 +- packages/backend/src/server/api/endpoints/i/page-likes.ts | 2 +- packages/backend/src/server/api/endpoints/i/pages.ts | 2 +- .../src/server/api/endpoints/i/read-all-messaging-messages.ts | 2 +- packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts | 2 +- packages/backend/src/server/api/endpoints/i/read-announcement.ts | 2 +- packages/backend/src/server/api/endpoints/i/regenerate-token.ts | 2 +- packages/backend/src/server/api/endpoints/i/registry/get-all.ts | 2 +- packages/backend/src/server/api/endpoints/i/registry/get-detail.ts | 2 +- packages/backend/src/server/api/endpoints/i/registry/get.ts | 2 +- .../backend/src/server/api/endpoints/i/registry/keys-with-type.ts | 2 +- packages/backend/src/server/api/endpoints/i/registry/keys.ts | 2 +- packages/backend/src/server/api/endpoints/i/registry/remove.ts | 2 +- packages/backend/src/server/api/endpoints/i/registry/scopes.ts | 2 +- packages/backend/src/server/api/endpoints/i/registry/set.ts | 2 +- packages/backend/src/server/api/endpoints/i/revoke-token.ts | 2 +- packages/backend/src/server/api/endpoints/i/signin-history.ts | 2 +- packages/backend/src/server/api/endpoints/i/update-email.ts | 4 ++-- packages/backend/src/server/api/endpoints/i/update.ts | 2 +- packages/backend/src/server/api/endpoints/i/user-group-invites.ts | 2 +- packages/backend/src/server/api/endpoints/i/webhooks/create.ts | 2 +- packages/backend/src/server/api/endpoints/i/webhooks/delete.ts | 2 +- packages/backend/src/server/api/endpoints/i/webhooks/list.ts | 2 +- packages/backend/src/server/api/endpoints/i/webhooks/show.ts | 2 +- packages/backend/src/server/api/endpoints/i/webhooks/update.ts | 2 +- packages/backend/src/server/api/endpoints/messaging/history.ts | 2 +- packages/backend/src/server/api/endpoints/messaging/messages.ts | 2 +- .../backend/src/server/api/endpoints/messaging/messages/create.ts | 2 +- .../backend/src/server/api/endpoints/messaging/messages/delete.ts | 2 +- packages/backend/src/server/api/endpoints/messaging/messages/read.ts | 2 +- packages/backend/src/server/api/endpoints/meta.ts | 4 ++-- packages/backend/src/server/api/endpoints/miauth/gen-token.ts | 2 +- packages/backend/src/server/api/endpoints/mute/create.ts | 2 +- packages/backend/src/server/api/endpoints/mute/delete.ts | 2 +- packages/backend/src/server/api/endpoints/mute/list.ts | 2 +- packages/backend/src/server/api/endpoints/my/apps.ts | 2 +- packages/backend/src/server/api/endpoints/notes.ts | 2 +- packages/backend/src/server/api/endpoints/notes/children.ts | 2 +- packages/backend/src/server/api/endpoints/notes/clips.ts | 2 +- packages/backend/src/server/api/endpoints/notes/conversation.ts | 2 +- packages/backend/src/server/api/endpoints/notes/create.ts | 2 +- packages/backend/src/server/api/endpoints/notes/delete.ts | 2 +- packages/backend/src/server/api/endpoints/notes/favorites/create.ts | 2 +- packages/backend/src/server/api/endpoints/notes/favorites/delete.ts | 2 +- packages/backend/src/server/api/endpoints/notes/featured.ts | 2 +- packages/backend/src/server/api/endpoints/notes/global-timeline.ts | 2 +- packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts | 2 +- packages/backend/src/server/api/endpoints/notes/local-timeline.ts | 2 +- packages/backend/src/server/api/endpoints/notes/mentions.ts | 2 +- .../backend/src/server/api/endpoints/notes/polls/recommendation.ts | 2 +- packages/backend/src/server/api/endpoints/notes/polls/vote.ts | 2 +- packages/backend/src/server/api/endpoints/notes/reactions.ts | 2 +- packages/backend/src/server/api/endpoints/notes/renotes.ts | 2 +- packages/backend/src/server/api/endpoints/notes/replies.ts | 2 +- packages/backend/src/server/api/endpoints/notes/search-by-tag.ts | 2 +- packages/backend/src/server/api/endpoints/notes/search.ts | 4 ++-- packages/backend/src/server/api/endpoints/notes/show.ts | 2 +- packages/backend/src/server/api/endpoints/notes/state.ts | 2 +- .../backend/src/server/api/endpoints/notes/thread-muting/create.ts | 2 +- .../backend/src/server/api/endpoints/notes/thread-muting/delete.ts | 2 +- packages/backend/src/server/api/endpoints/notes/timeline.ts | 2 +- packages/backend/src/server/api/endpoints/notes/translate.ts | 4 ++-- packages/backend/src/server/api/endpoints/notes/unrenote.ts | 2 +- .../backend/src/server/api/endpoints/notes/user-list-timeline.ts | 2 +- .../src/server/api/endpoints/notifications/mark-all-as-read.ts | 2 +- packages/backend/src/server/api/endpoints/page-push.ts | 2 +- packages/backend/src/server/api/endpoints/pages/create.ts | 2 +- packages/backend/src/server/api/endpoints/pages/delete.ts | 2 +- packages/backend/src/server/api/endpoints/pages/featured.ts | 2 +- packages/backend/src/server/api/endpoints/pages/like.ts | 2 +- packages/backend/src/server/api/endpoints/pages/show.ts | 2 +- packages/backend/src/server/api/endpoints/pages/unlike.ts | 2 +- packages/backend/src/server/api/endpoints/pages/update.ts | 2 +- packages/backend/src/server/api/endpoints/pinned-users.ts | 2 +- packages/backend/src/server/api/endpoints/promo/read.ts | 2 +- packages/backend/src/server/api/endpoints/request-reset-password.ts | 4 ++-- packages/backend/src/server/api/endpoints/reset-password.ts | 2 +- packages/backend/src/server/api/endpoints/stats.ts | 2 +- packages/backend/src/server/api/endpoints/sw/register.ts | 2 +- packages/backend/src/server/api/endpoints/sw/unregister.ts | 2 +- packages/backend/src/server/api/endpoints/username/available.ts | 2 +- packages/backend/src/server/api/endpoints/users.ts | 2 +- packages/backend/src/server/api/endpoints/users/clips.ts | 2 +- packages/backend/src/server/api/endpoints/users/followers.ts | 2 +- packages/backend/src/server/api/endpoints/users/following.ts | 2 +- packages/backend/src/server/api/endpoints/users/gallery/posts.ts | 2 +- .../src/server/api/endpoints/users/get-frequently-replied-users.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/create.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/delete.ts | 2 +- .../src/server/api/endpoints/users/groups/invitations/accept.ts | 2 +- .../src/server/api/endpoints/users/groups/invitations/reject.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/invite.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/joined.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/leave.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/owned.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/pull.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/show.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/transfer.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/update.ts | 2 +- packages/backend/src/server/api/endpoints/users/lists/create.ts | 2 +- packages/backend/src/server/api/endpoints/users/lists/delete.ts | 2 +- packages/backend/src/server/api/endpoints/users/lists/list.ts | 2 +- packages/backend/src/server/api/endpoints/users/lists/pull.ts | 2 +- packages/backend/src/server/api/endpoints/users/lists/push.ts | 2 +- packages/backend/src/server/api/endpoints/users/lists/show.ts | 2 +- packages/backend/src/server/api/endpoints/users/lists/update.ts | 2 +- packages/backend/src/server/api/endpoints/users/notes.ts | 2 +- packages/backend/src/server/api/endpoints/users/pages.ts | 2 +- packages/backend/src/server/api/endpoints/users/reactions.ts | 2 +- packages/backend/src/server/api/endpoints/users/recommendation.ts | 2 +- packages/backend/src/server/api/endpoints/users/relation.ts | 2 +- packages/backend/src/server/api/endpoints/users/report-abuse.ts | 2 +- .../src/server/api/endpoints/users/search-by-username-and-host.ts | 2 +- packages/backend/src/server/api/endpoints/users/search.ts | 2 +- packages/backend/src/server/api/endpoints/users/show.ts | 2 +- packages/backend/src/server/api/integration/DiscordServerService.ts | 4 ++-- packages/backend/src/server/api/integration/GithubServerService.ts | 4 ++-- packages/backend/src/server/api/integration/TwitterServerService.ts | 4 ++-- packages/backend/src/server/api/stream/channels/messaging.ts | 2 +- packages/backend/src/server/api/stream/channels/user-list.ts | 2 +- packages/backend/src/server/web/ClientServerService.ts | 2 +- packages/backend/src/server/web/FeedService.ts | 4 ++-- packages/backend/src/server/web/UrlPreviewService.ts | 4 ++-- packages/shared/.eslintrc.js | 2 +- 408 files changed, 474 insertions(+), 475 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/core/AccountUpdateService.ts b/packages/backend/src/core/AccountUpdateService.ts index 204e1d0170..6fe0e05c6d 100644 --- a/packages/backend/src/core/AccountUpdateService.ts +++ b/packages/backend/src/core/AccountUpdateService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { User } from '@/models/entities/User.js'; import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; import { RelayService } from '@/core/RelayService.js'; diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index e6102a1b91..15084b8ff1 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -4,7 +4,7 @@ import { dirname } from 'node:path'; import { Inject, Injectable } from '@nestjs/common'; import * as nsfw from 'nsfwjs'; import si from 'systeminformation'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; const _filename = fileURLToPath(import.meta.url); diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index e0af033952..0146f959e6 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -10,7 +10,7 @@ import * as Acct from '@/misc/acct.js'; import { Cache } from '@/misc/cache.js'; import type { Packed } from '@/misc/schema.js'; import { DI } from '@/di-symbols.js'; -import { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; import { UtilityService } from './UtilityService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index b1b52fd6a9..67b4b90061 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { HttpRequestService } from './HttpRequestService.js'; type CaptchaResponse = { diff --git a/packages/backend/src/core/CreateNotificationService.ts b/packages/backend/src/core/CreateNotificationService.ts index 525fac6d92..feb82dcbf9 100644 --- a/packages/backend/src/core/CreateNotificationService.ts +++ b/packages/backend/src/core/CreateNotificationService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { MutingsRepository, NotificationsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { MutingsRepository, NotificationsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import type { Notification } from '@/models/entities/Notification.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 32dad70d1c..e1355fff07 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -2,14 +2,14 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In, IsNull } from 'typeorm'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { IdService } from '@/core/IdService.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Emoji } from '@/models/entities/Emoji.js'; import { Cache } from '@/misc/cache.js'; import { query } from '@/misc/prelude/url.js'; import type { Note } from '@/models/entities/Note.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { UtilityService } from './UtilityService.js'; import { ReactionService } from './ReactionService.js'; diff --git a/packages/backend/src/core/DeleteAccountService.ts b/packages/backend/src/core/DeleteAccountService.ts index ba67bc499e..53d48c450b 100644 --- a/packages/backend/src/core/DeleteAccountService.ts +++ b/packages/backend/src/core/DeleteAccountService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { QueueService } from '@/core/QueueService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts index 81939d5f51..25965b7ac4 100644 --- a/packages/backend/src/core/DownloadService.ts +++ b/packages/backend/src/core/DownloadService.ts @@ -7,7 +7,7 @@ import PrivateIp from 'private-ip'; import got, * as Got from 'got'; import chalk from 'chalk'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { createTemp } from '@/misc/create-temp.js'; import { StatusError } from '@/misc/status-error.js'; diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index e356fa0009..643c51c37d 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -4,8 +4,8 @@ import { v4 as uuid } from 'uuid'; import sharp from 'sharp'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import Logger from '@/logger.js'; import type { IRemoteUser, User } from '@/models/entities/User.js'; import { MetaService } from '@/core/MetaService.js'; diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 521ab7fd81..4c5cf7dfc4 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -3,9 +3,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { validate as validateEmail } from 'deep-email-validator'; import { MetaService } from '@/core/MetaService.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; -import { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/index.js'; import { LoggerService } from '@/core/LoggerService.js'; @Injectable() diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index a4894a4376..b98a41f757 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/index.js'; import type { Instance } from '@/models/entities/Instance.js'; import { Cache } from '@/misc/cache.js'; import { IdService } from '@/core/IdService.js'; diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 376617914e..9a51ed0c0a 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -4,7 +4,7 @@ import { JSDOM } from 'jsdom'; import fetch from 'node-fetch'; import tinycolor from 'tinycolor2'; import type { Instance } from '@/models/entities/Instance.js'; -import { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/index.js'; import { AppLockService } from '@/core/AppLockService.js'; import type Logger from '@/logger.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index c36de63fde..df0c9b5ccb 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -24,7 +24,7 @@ import type { } from '@/server/api/stream/types.js'; import type { Packed } from '@/misc/schema.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; @Injectable() export class GlobalEventService { diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts index f6c06d48f4..83950aa890 100644 --- a/packages/backend/src/core/HashtagService.ts +++ b/packages/backend/src/core/HashtagService.ts @@ -5,7 +5,7 @@ import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { IdService } from '@/core/IdService.js'; import type { Hashtag } from '@/models/entities/Hashtag.js'; import HashtagChart from '@/core/chart/charts/hashtag.js'; -import { HashtagsRepository, UsersRepository } from '@/models/index.js'; +import type { HashtagsRepository, UsersRepository } from '@/models/index.js'; import { UserEntityService } from './entities/UserEntityService.js'; @Injectable() diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index f4c00cd259..396fefad1c 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -5,7 +5,7 @@ import fetch from 'node-fetch'; import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { StatusError } from '@/misc/status-error.js'; import type { Response } from 'node-fetch'; import type { URL } from 'node:url'; diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts index 345b72bac5..997be17937 100644 --- a/packages/backend/src/core/IdService.ts +++ b/packages/backend/src/core/IdService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { ulid } from 'ulid'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { genAid } from '@/misc/id/aid.js'; import { genMeid } from '@/misc/id/meid.js'; import { genMeidg } from '@/misc/id/meidg.js'; diff --git a/packages/backend/src/core/ImageProcessingService.ts b/packages/backend/src/core/ImageProcessingService.ts index d215be2131..3a50361a42 100644 --- a/packages/backend/src/core/ImageProcessingService.ts +++ b/packages/backend/src/core/ImageProcessingService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import sharp from 'sharp'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; export type IImage = { data: Buffer; diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts index 57d55870b1..fa906df4a2 100644 --- a/packages/backend/src/core/InstanceActorService.ts +++ b/packages/backend/src/core/InstanceActorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import type { ILocalUser } from '@/models/entities/User.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; import { DI } from '@/di-symbols.js'; import { CreateSystemUserService } from './CreateSystemUserService.js'; diff --git a/packages/backend/src/core/InternalStorageService.ts b/packages/backend/src/core/InternalStorageService.ts index 9bc3597baf..6d2a9b2db6 100644 --- a/packages/backend/src/core/InternalStorageService.ts +++ b/packages/backend/src/core/InternalStorageService.ts @@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); diff --git a/packages/backend/src/core/LoggerService.ts b/packages/backend/src/core/LoggerService.ts index 558e3016dc..a3192c0262 100644 --- a/packages/backend/src/core/LoggerService.ts +++ b/packages/backend/src/core/LoggerService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as SyslogPro from 'syslog-pro'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import Logger from '@/logger.js'; @Injectable() diff --git a/packages/backend/src/core/MessagingService.ts b/packages/backend/src/core/MessagingService.ts index 1819b32a45..0603da0651 100644 --- a/packages/backend/src/core/MessagingService.ts +++ b/packages/backend/src/core/MessagingService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; import type { Note } from '@/models/entities/Note.js'; @@ -10,7 +10,7 @@ import type { UserGroup } from '@/models/entities/UserGroup.js'; import { QueueService } from '@/core/QueueService.js'; import { toArray } from '@/misc/prelude/array.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { MessagingMessagesRepository, MutingsRepository, UserGroupJoiningsRepository, UsersRepository } from '@/models/index.js'; +import type { MessagingMessagesRepository, MutingsRepository, UserGroupJoiningsRepository, UsersRepository } from '@/models/index.js'; import { IdService } from './IdService.js'; import { GlobalEventService } from './GlobalEventService.js'; import { UserEntityService } from './entities/UserEntityService.js'; diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index 236be4bbf8..2e03bf3cc0 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -4,7 +4,7 @@ import * as parse5 from 'parse5'; import { JSDOM } from 'jsdom'; import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { intersperse } from '@/misc/prelude/array.js'; import type { IMentionedRemoteUsers } from '@/models/entities/Note.js'; import * as TreeAdapter from '../../node_modules/parse5/dist/tree-adapters/default.js'; diff --git a/packages/backend/src/core/ModerationLogService.ts b/packages/backend/src/core/ModerationLogService.ts index 191148ac25..81ae322b95 100644 --- a/packages/backend/src/core/ModerationLogService.ts +++ b/packages/backend/src/core/ModerationLogService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { ModerationLogsRepository } from '@/models/index.js'; +import type { ModerationLogsRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { IdService } from '@/core/IdService.js'; diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 5acc07fba6..6e5cce8f62 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -6,7 +6,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf import { extractHashtags } from '@/misc/extract-hashtags.js'; import type { IMentionedRemoteUsers } from '@/models/entities/Note.js'; import { Note } from '@/models/entities/Note.js'; -import { ChannelFollowingsRepository, ChannelsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { ChannelFollowingsRepository, ChannelsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { App } from '@/models/entities/App.js'; import { concat } from '@/misc/prelude/array.js'; @@ -23,7 +23,7 @@ import type { UserProfile } from '@/models/entities/UserProfile.js'; import { RelayService } from '@/core/RelayService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import NotesChart from '@/core/chart/charts/notes.js'; import PerUserNotesChart from '@/core/chart/charts/per-user-notes.js'; import InstanceChart from '@/core/chart/charts/instance.js'; diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index 48d0d31e88..ccc583c5b6 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -2,11 +2,11 @@ import { Brackets, In } from 'typeorm'; import { Injectable, Inject } from '@nestjs/common'; import type { User, ILocalUser, IRemoteUser } from '@/models/entities/User.js'; import type { Note, IMentionedRemoteUsers } from '@/models/entities/Note.js'; -import { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js'; +import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js'; import { RelayService } from '@/core/RelayService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import NotesChart from '@/core/chart/charts/notes.js'; import PerUserNotesChart from '@/core/chart/charts/per-user-notes.js'; import InstanceChart from '@/core/chart/charts/instance.js'; diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts index 576e90bd43..b70c051efd 100644 --- a/packages/backend/src/core/NotePiningService.ts +++ b/packages/backend/src/core/NotePiningService.ts @@ -1,13 +1,13 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; import { IdService } from '@/core/IdService.js'; import type { UserNotePining } from '@/models/entities/UserNotePining.js'; import { RelayService } from '@/core/RelayService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { UserEntityService } from './entities/UserEntityService.js'; import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js'; import { ApRendererService } from './remote/activitypub/ApRendererService.js'; diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index ca9e60889d..2606ca4de0 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { NotificationsRepository } from '@/models/index.js'; +import type { NotificationsRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import type { Notification } from '@/models/entities/Notification.js'; diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index 8bc94c8a82..1bb68f7804 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { NotesRepository, UsersRepository, BlockingsRepository } from '@/models/index.js'; +import type { NotesRepository, UsersRepository, BlockingsRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import { RelayService } from '@/core/RelayService.js'; import type { CacheableUser } from '@/models/entities/User.js'; diff --git a/packages/backend/src/core/ProxyAccountService.ts b/packages/backend/src/core/ProxyAccountService.ts index 40ccc8226a..07d8d0dbd5 100644 --- a/packages/backend/src/core/ProxyAccountService.ts +++ b/packages/backend/src/core/ProxyAccountService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import type { ILocalUser, User } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from './MetaService.js'; diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index 31d29bed97..5eaaed00eb 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -1,10 +1,10 @@ import { Inject, Injectable } from '@nestjs/common'; import push from 'web-push'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { Packed } from '@/misc/schema'; import { getNoteSummary } from '@/misc/get-note-summary.js'; -import { SwSubscriptionsRepository } from '@/models/index.js'; +import type { SwSubscriptionsRepository } from '@/models/index.js'; import { MetaService } from './MetaService.js'; // Defined also packages/sw/types.ts#L14-L21 diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 7e771c100f..12be57c7fb 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -3,9 +3,9 @@ import { v4 as uuid } from 'uuid'; import type { IActivity } from '@/core/remote/activitypub/type.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Webhook, webhookEventTypes } from '@/models/entities/Webhook.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; -import { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from './queue/QueueModule.js'; +import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from './queue/QueueModule.js'; import type { ThinUser } from '../queue/types.js'; import type httpSignature from '@peertube/http-signature'; diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 3006456577..d5b3c0e799 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { EmojisRepository, BlockingsRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/index.js'; +import type { EmojisRepository, BlockingsRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { IRemoteUser, User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts index 688ea03d34..5324826ec1 100644 --- a/packages/backend/src/core/RelayService.ts +++ b/packages/backend/src/core/RelayService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import type { ILocalUser, User } from '@/models/entities/User.js'; -import { RelaysRepository, UsersRepository } from '@/models/index.js'; +import type { RelaysRepository, UsersRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Cache } from '@/misc/cache.js'; import type { Relay } from '@/models/entities/Relay.js'; diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts index 9549e19990..723a79dc59 100644 --- a/packages/backend/src/core/S3Service.ts +++ b/packages/backend/src/core/S3Service.ts @@ -2,7 +2,7 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import S3 from 'aws-sdk/clients/s3.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { Meta } from '@/models/entities/Meta.js'; import { HttpRequestService } from './HttpRequestService.js'; diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index a876668b94..8b72d73af6 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import { DataSource, IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { UsedUsernamesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsedUsernamesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { User } from '@/models/entities/User.js'; import { UserProfile } from '@/models/entities/UserProfile.js'; import { IdService } from '@/core/IdService.js'; diff --git a/packages/backend/src/core/TwoFactorAuthenticationService.ts b/packages/backend/src/core/TwoFactorAuthenticationService.ts index be31534c02..0962f88a7c 100644 --- a/packages/backend/src/core/TwoFactorAuthenticationService.ts +++ b/packages/backend/src/core/TwoFactorAuthenticationService.ts @@ -2,8 +2,8 @@ import * as crypto from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import * as jsrsasign from 'jsrsasign'; import { DI } from '@/di-symbols.js'; -import { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; const ECC_PRELUDE = Buffer.from([0x04]); const NULL_BYTE = Buffer.from([0]); diff --git a/packages/backend/src/core/UserCacheService.ts b/packages/backend/src/core/UserCacheService.ts index 8212abf7bb..666bef3f49 100644 --- a/packages/backend/src/core/UserCacheService.ts +++ b/packages/backend/src/core/UserCacheService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import Redis from 'ioredis'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; import type { CacheableLocalUser, CacheableUser, ILocalUser } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/UserKeypairStoreService.ts b/packages/backend/src/core/UserKeypairStoreService.ts index e53f37b714..8eca03a10b 100644 --- a/packages/backend/src/core/UserKeypairStoreService.ts +++ b/packages/backend/src/core/UserKeypairStoreService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import type { User } from '@/models/entities/User.js'; -import { UserKeypairsRepository } from '@/models/index.js'; +import type { UserKeypairsRepository } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; import type { UserKeypair } from '@/models/entities/UserKeypair.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 03113f042a..b1d01a1565 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListJoiningsRepository, UsersRepository } from '@/models/index.js'; +import type { UserListJoiningsRepository, UsersRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import type { UserList } from '@/models/entities/UserList.js'; import type { UserListJoining } from '@/models/entities/UserListJoining.js'; diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts index 9146360df1..4c09e450c8 100644 --- a/packages/backend/src/core/UserMutingService.ts +++ b/packages/backend/src/core/UserMutingService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, MutingsRepository } from '@/models/index.js'; +import type { UsersRepository, MutingsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index 068341cb2c..82c2e98236 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -1,11 +1,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { Not, IsNull } from 'typeorm'; -import { FollowingsRepository, UsersRepository } from '@/models/index.js'; +import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { ApRendererService } from './remote/activitypub/ApRendererService.js'; import { UserEntityService } from './entities/UserEntityService.js'; diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index ba03dfc069..15dd684286 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -2,7 +2,7 @@ import { URL } from 'node:url'; import { toASCII } from 'punycode'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; @Injectable() export class UtilityService { diff --git a/packages/backend/src/core/VideoProcessingService.ts b/packages/backend/src/core/VideoProcessingService.ts index 70b9664c76..af4036a291 100644 --- a/packages/backend/src/core/VideoProcessingService.ts +++ b/packages/backend/src/core/VideoProcessingService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import FFmpeg from 'fluent-ffmpeg'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { ImageProcessingService } from '@/core/ImageProcessingService.js'; import type { IImage } from '@/core/ImageProcessingService.js'; import { createTempDir } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index 1d74290dd9..347b2b16c4 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import Redis from 'ioredis'; -import { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/index.js'; import type { Webhook } from '@/models/entities/Webhook.js'; import { DI } from '@/di-symbols.js'; import type { OnApplicationShutdown } from '@nestjs/common'; diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts index 4366d4cce1..21e4cedea3 100644 --- a/packages/backend/src/core/chart/charts/federation.ts +++ b/packages/backend/src/core/chart/charts/federation.ts @@ -1,6 +1,6 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { FollowingsRepository, InstancesRepository } from '@/models/index.js'; +import type { FollowingsRepository, InstancesRepository } from '@/models/index.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; diff --git a/packages/backend/src/core/chart/charts/instance.ts b/packages/backend/src/core/chart/charts/instance.ts index be70bc79c0..2e0f4c7126 100644 --- a/packages/backend/src/core/chart/charts/instance.ts +++ b/packages/backend/src/core/chart/charts/instance.ts @@ -1,6 +1,6 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { DriveFilesRepository, FollowingsRepository, UsersRepository, NotesRepository } from '@/models/index.js'; +import type { DriveFilesRepository, FollowingsRepository, UsersRepository, NotesRepository } from '@/models/index.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; diff --git a/packages/backend/src/core/chart/charts/notes.ts b/packages/backend/src/core/chart/charts/notes.ts index e1bfeabf99..2153cfe4b4 100644 --- a/packages/backend/src/core/chart/charts/notes.ts +++ b/packages/backend/src/core/chart/charts/notes.ts @@ -1,6 +1,6 @@ import { Injectable, Inject } from '@nestjs/common'; import { Not, IsNull, DataSource } from 'typeorm'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/chart/charts/per-user-drive.ts b/packages/backend/src/core/chart/charts/per-user-drive.ts index 752203daaf..a44460bb4e 100644 --- a/packages/backend/src/core/chart/charts/per-user-drive.ts +++ b/packages/backend/src/core/chart/charts/per-user-drive.ts @@ -1,6 +1,6 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/chart/charts/per-user-following.ts b/packages/backend/src/core/chart/charts/per-user-following.ts index 48bf3d7c62..5ea08a0872 100644 --- a/packages/backend/src/core/chart/charts/per-user-following.ts +++ b/packages/backend/src/core/chart/charts/per-user-following.ts @@ -4,7 +4,7 @@ import type { User } from '@/models/entities/User.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/index.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-following.js'; diff --git a/packages/backend/src/core/chart/charts/per-user-notes.ts b/packages/backend/src/core/chart/charts/per-user-notes.ts index ffe52dcd56..5c14309d89 100644 --- a/packages/backend/src/core/chart/charts/per-user-notes.ts +++ b/packages/backend/src/core/chart/charts/per-user-notes.ts @@ -4,7 +4,7 @@ import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-notes.js'; diff --git a/packages/backend/src/core/chart/charts/users.ts b/packages/backend/src/core/chart/charts/users.ts index b3187997cf..f0359968eb 100644 --- a/packages/backend/src/core/chart/charts/users.ts +++ b/packages/backend/src/core/chart/charts/users.ts @@ -4,7 +4,7 @@ import type { User } from '@/models/entities/User.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/users.js'; diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts index 6cc511fb48..1660894571 100644 --- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts +++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { AbuseUserReportsRepository } from '@/models/index.js'; +import type { AbuseUserReportsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { AbuseUserReport } from '@/models/entities/AbuseUserReport.js'; import { UserEntityService } from './UserEntityService.js'; diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts index 9193cb81d7..44110e7364 100644 --- a/packages/backend/src/core/entities/AntennaEntityService.ts +++ b/packages/backend/src/core/entities/AntennaEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { Antenna } from '@/models/entities/Antenna.js'; diff --git a/packages/backend/src/core/entities/AppEntityService.ts b/packages/backend/src/core/entities/AppEntityService.ts index 6491b0b2d9..1cc7ca11dc 100644 --- a/packages/backend/src/core/entities/AppEntityService.ts +++ b/packages/backend/src/core/entities/AppEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { AccessTokensRepository, AppsRepository } from '@/models/index.js'; +import type { AccessTokensRepository, AppsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { App } from '@/models/entities/App.js'; diff --git a/packages/backend/src/core/entities/AuthSessionEntityService.ts b/packages/backend/src/core/entities/AuthSessionEntityService.ts index a4dab3d9cf..bf8efa5f78 100644 --- a/packages/backend/src/core/entities/AuthSessionEntityService.ts +++ b/packages/backend/src/core/entities/AuthSessionEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { AuthSessionsRepository } from '@/models/index.js'; +import type { AuthSessionsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { AuthSession } from '@/models/entities/AuthSession.js'; diff --git a/packages/backend/src/core/entities/BlockingEntityService.ts b/packages/backend/src/core/entities/BlockingEntityService.ts index 74ce6830b6..49a96037ca 100644 --- a/packages/backend/src/core/entities/BlockingEntityService.ts +++ b/packages/backend/src/core/entities/BlockingEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { BlockingsRepository } from '@/models/index.js'; +import type { BlockingsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { Blocking } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts index fec76e4e63..860967443e 100644 --- a/packages/backend/src/core/entities/ChannelEntityService.ts +++ b/packages/backend/src/core/entities/ChannelEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NoteUnreadsRepository } from '@/models/index.js'; +import type { ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NoteUnreadsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts index 27637e42e8..7a5d2f7f0a 100644 --- a/packages/backend/src/core/entities/ClipEntityService.ts +++ b/packages/backend/src/core/entities/ClipEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index 521bf51da0..f0ac6518d0 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -2,8 +2,8 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { DataSource, In } from 'typeorm'; import * as mfm from 'mfm-js'; import { DI } from '@/di-symbols.js'; -import { NotesRepository, DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { NotesRepository, DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { Packed } from '@/misc/schema.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { User } from '@/models/entities/User.js'; diff --git a/packages/backend/src/core/entities/DriveFolderEntityService.ts b/packages/backend/src/core/entities/DriveFolderEntityService.ts index beebbc3206..5761fa37bc 100644 --- a/packages/backend/src/core/entities/DriveFolderEntityService.ts +++ b/packages/backend/src/core/entities/DriveFolderEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts index 10ed0f19ee..fc09b5a2c7 100644 --- a/packages/backend/src/core/entities/EmojiEntityService.ts +++ b/packages/backend/src/core/entities/EmojiEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/FollowRequestEntityService.ts b/packages/backend/src/core/entities/FollowRequestEntityService.ts index f7e7fd42e4..4a60c1263f 100644 --- a/packages/backend/src/core/entities/FollowRequestEntityService.ts +++ b/packages/backend/src/core/entities/FollowRequestEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { FollowRequestsRepository } from '@/models/index.js'; +import type { FollowRequestsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/FollowingEntityService.ts b/packages/backend/src/core/entities/FollowingEntityService.ts index 93fed85f72..c7e040a57b 100644 --- a/packages/backend/src/core/entities/FollowingEntityService.ts +++ b/packages/backend/src/core/entities/FollowingEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/GalleryLikeEntityService.ts b/packages/backend/src/core/entities/GalleryLikeEntityService.ts index 9473ed90b7..bf7c2b7f8d 100644 --- a/packages/backend/src/core/entities/GalleryLikeEntityService.ts +++ b/packages/backend/src/core/entities/GalleryLikeEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { GalleryPosts, GalleryLikesRepository } from '@/models/index.js'; +import type { GalleryPosts, GalleryLikesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts index 82b41697c9..ca98687d7b 100644 --- a/packages/backend/src/core/entities/GalleryPostEntityService.ts +++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { GalleryLikesRepository, GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryLikesRepository, GalleryPostsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/HashtagEntityService.ts b/packages/backend/src/core/entities/HashtagEntityService.ts index 6dcbf49030..511992c44f 100644 --- a/packages/backend/src/core/entities/HashtagEntityService.ts +++ b/packages/backend/src/core/entities/HashtagEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { HashtagsRepository } from '@/models/index.js'; +import type { HashtagsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index c58c2f8f34..c54285d9df 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/MessagingMessageEntityService.ts b/packages/backend/src/core/entities/MessagingMessageEntityService.ts index 04467b94e4..b7c42a5760 100644 --- a/packages/backend/src/core/entities/MessagingMessageEntityService.ts +++ b/packages/backend/src/core/entities/MessagingMessageEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { MessagingMessagesRepository } from '@/models/index.js'; +import type { MessagingMessagesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/ModerationLogEntityService.ts b/packages/backend/src/core/entities/ModerationLogEntityService.ts index 45d15088fc..2f508710b8 100644 --- a/packages/backend/src/core/entities/ModerationLogEntityService.ts +++ b/packages/backend/src/core/entities/ModerationLogEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { ModerationLogsRepository } from '@/models/index.js'; +import type { ModerationLogsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/MutingEntityService.ts b/packages/backend/src/core/entities/MutingEntityService.ts index c5245bf203..862be009da 100644 --- a/packages/backend/src/core/entities/MutingEntityService.ts +++ b/packages/backend/src/core/entities/MutingEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { MutingsRepository } from '@/models/index.js'; +import type { MutingsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 669680758d..a9c63566b4 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -4,7 +4,7 @@ import * as mfm from 'mfm-js'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; import type { Notes, Polls, PollVotes, DriveFiles, Channels, Followings, Users, NoteReactions } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { Packed } from '@/misc/schema.js'; import { nyaize } from '@/misc/nyaize.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; diff --git a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts index f0bbf27b6a..1a68a5c628 100644 --- a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts +++ b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { NoteFavoritesRepository } from '@/models/index.js'; +import type { NoteFavoritesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index e64f2af681..47008ee08e 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { NoteReactionsRepository } from '@/models/index.js'; +import type { NoteReactionsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { OnModuleInit } from '@nestjs/common'; diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index 6a0683d543..c415599fea 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; -import { AccessTokensRepository, NoteReactionsRepository, NotificationsRepository } from '@/models/index.js'; +import type { AccessTokensRepository, NoteReactionsRepository, NotificationsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Notification } from '@/models/entities/Notification.js'; import type { NoteReaction } from '@/models/entities/NoteReaction.js'; diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts index cbd193fe0a..004443759b 100644 --- a/packages/backend/src/core/entities/PageEntityService.ts +++ b/packages/backend/src/core/entities/PageEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository, PagesRepository, PageLikesRepository } from '@/models/index.js'; +import type { DriveFilesRepository, PagesRepository, PageLikesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/PageLikeEntityService.ts b/packages/backend/src/core/entities/PageLikeEntityService.ts index dccaf2edec..62d9c82ca6 100644 --- a/packages/backend/src/core/entities/PageLikeEntityService.ts +++ b/packages/backend/src/core/entities/PageLikeEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { PageLikesRepository } from '@/models/index.js'; +import type { PageLikesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/SigninEntityService.ts b/packages/backend/src/core/entities/SigninEntityService.ts index 521c7669e7..fd89662f7d 100644 --- a/packages/backend/src/core/entities/SigninEntityService.ts +++ b/packages/backend/src/core/entities/SigninEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { SigninsRepository } from '@/models/index.js'; +import type { SigninsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 343e42df07..a35703e80e 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -1,9 +1,9 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; -import { EntityRepository, Repository, In, Not } from 'typeorm'; +import type { EntityRepository, Repository, In, Not } from 'typeorm'; import Ajv from 'ajv'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { Packed } from '@/misc/schema.js'; import type { Promiseable } from '@/misc/prelude/await-all.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; diff --git a/packages/backend/src/core/entities/UserGroupEntityService.ts b/packages/backend/src/core/entities/UserGroupEntityService.ts index acd26ea1eb..e399197618 100644 --- a/packages/backend/src/core/entities/UserGroupEntityService.ts +++ b/packages/backend/src/core/entities/UserGroupEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { UserGroupJoiningsRepository, UserGroupsRepository } from '@/models/index.js'; +import type { UserGroupJoiningsRepository, UserGroupsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts b/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts index 50ff2231ab..f5c9be3475 100644 --- a/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts +++ b/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { UserGroupInvitationsRepository } from '@/models/index.js'; +import type { UserGroupInvitationsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts index 05434d9c8a..e2b0814914 100644 --- a/packages/backend/src/core/entities/UserListEntityService.ts +++ b/packages/backend/src/core/entities/UserListEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; +import type { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/remote/ResolveUserService.ts b/packages/backend/src/core/remote/ResolveUserService.ts index b45168fb02..2fd9e7c378 100644 --- a/packages/backend/src/core/remote/ResolveUserService.ts +++ b/packages/backend/src/core/remote/ResolveUserService.ts @@ -3,9 +3,9 @@ import { Inject, Injectable } from '@nestjs/common'; import chalk from 'chalk'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import type { IRemoteUser, User } from '@/models/entities/User.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { UtilityService } from '../UtilityService.js'; import { WebfingerService } from './WebfingerService.js'; diff --git a/packages/backend/src/core/remote/WebfingerService.ts b/packages/backend/src/core/remote/WebfingerService.ts index ab46314792..d2a88be583 100644 --- a/packages/backend/src/core/remote/WebfingerService.ts +++ b/packages/backend/src/core/remote/WebfingerService.ts @@ -1,7 +1,7 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { query as urlQuery } from '@/misc/prelude/url.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts b/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts index 6f197985da..77d200c3c8 100644 --- a/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import escapeRegexp from 'escape-regexp'; import { DI } from '@/di-symbols.js'; -import { MessagingMessagesRepository, NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { MessagingMessagesRepository, NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js'; import { Cache } from '@/misc/cache.js'; import type { UserPublickey } from '@/models/entities/UserPublickey.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts index a6ee857526..6fc75a0397 100644 --- a/packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts +++ b/packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { FollowingsRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; import { QueueService } from '@/core/QueueService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApInboxService.ts b/packages/backend/src/core/remote/activitypub/ApInboxService.ts index 0482e029d2..a3cb08063d 100644 --- a/packages/backend/src/core/remote/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/remote/activitypub/ApInboxService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { CacheableRemoteUser } from '@/models/entities/User.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { ReactionService } from '@/core/ReactionService.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApMfmService.ts b/packages/backend/src/core/remote/activitypub/ApMfmService.ts index 3c3b98b139..8804fde64a 100644 --- a/packages/backend/src/core/remote/activitypub/ApMfmService.ts +++ b/packages/backend/src/core/remote/activitypub/ApMfmService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as mfm from 'mfm-js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { MfmService } from '@/core/MfmService.js'; import type { Note } from '@/models/entities/Note.js'; import { extractApHashtagObjects } from './models/tag.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApRendererService.ts b/packages/backend/src/core/remote/activitypub/ApRendererService.ts index 5a4cef63e0..6058929d35 100644 --- a/packages/backend/src/core/remote/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/remote/activitypub/ApRendererService.ts @@ -4,7 +4,7 @@ import { In, IsNull } from 'typeorm'; import { v4 as uuid } from 'uuid'; import * as mfm from 'mfm-js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; import type { IMentionedRemoteUsers, Note } from '@/models/entities/Note.js'; import type { Blocking } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApRequestService.ts b/packages/backend/src/core/remote/activitypub/ApRequestService.ts index 2abaca06af..baad46d668 100644 --- a/packages/backend/src/core/remote/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/remote/activitypub/ApRequestService.ts @@ -2,7 +2,7 @@ import * as crypto from 'node:crypto'; import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { User } from '@/models/entities/User.js'; import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApResolverService.ts b/packages/backend/src/core/remote/activitypub/ApResolverService.ts index 9d8e177758..e2d6a77370 100644 --- a/packages/backend/src/core/remote/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/remote/activitypub/ApResolverService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import type { ILocalUser } from '@/models/entities/User.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; -import { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/remote/activitypub/models/ApImageService.ts b/packages/backend/src/core/remote/activitypub/models/ApImageService.ts index da6ed61c56..9bf87f19d4 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApImageService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { CacheableRemoteUser } from '@/models/entities/User.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { MetaService } from '@/core/MetaService.js'; diff --git a/packages/backend/src/core/remote/activitypub/models/ApMentionService.ts b/packages/backend/src/core/remote/activitypub/models/ApMentionService.ts index 898da07a26..710e1acfaf 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApMentionService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApMentionService.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import promiseLimit from 'promise-limit'; import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { toArray, unique } from '@/misc/prelude/array.js'; import type { CacheableUser } from '@/models/entities/User.js'; import { isMention } from '../type.js'; diff --git a/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts b/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts index 1efe62333b..a34a1d1eb8 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts @@ -1,9 +1,9 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import promiseLimit from 'promise-limit'; import { DI } from '@/di-symbols.js'; -import { MessagingMessagesRepository, PollsRepository, EmojisRepository } from '@/models/index.js'; +import type { MessagingMessagesRepository, PollsRepository, EmojisRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { CacheableRemoteUser } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; import { toArray, toSingle, unique } from '@/misc/prelude/array.js'; diff --git a/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts b/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts index d088fa5554..5135473862 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts @@ -3,8 +3,8 @@ import promiseLimit from 'promise-limit'; import { DataSource } from 'typeorm'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; -import { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { CacheableUser, IRemoteUser } from '@/models/entities/User.js'; import { User } from '@/models/entities/User.js'; import { truncate } from '@/misc/truncate.js'; diff --git a/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts index 2b89cb030b..acd5cdae83 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { NotesRepository, PollsRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { NotesRepository, PollsRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { IPoll } from '@/models/entities/Poll.js'; import type Logger from '@/logger.js'; import { isQuestion } from '../type.js'; diff --git a/packages/backend/src/daemons/JanitorService.ts b/packages/backend/src/daemons/JanitorService.ts index a51f570725..dbad576abe 100644 --- a/packages/backend/src/daemons/JanitorService.ts +++ b/packages/backend/src/daemons/JanitorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { LessThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { AttestationChallengesRepository } from '@/models/index.js'; +import type { AttestationChallengesRepository } from '@/models/index.js'; import type { OnApplicationShutdown } from '@nestjs/common'; const interval = 30 * 60 * 1000; diff --git a/packages/backend/src/queue/DbQueueProcessorsService.ts b/packages/backend/src/queue/DbQueueProcessorsService.ts index fcc9873a6f..7622ab8800 100644 --- a/packages/backend/src/queue/DbQueueProcessorsService.ts +++ b/packages/backend/src/queue/DbQueueProcessorsService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { DbJobData } from '@/queue/types.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js'; import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js'; import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js'; diff --git a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts index 402c038be0..659e9b8e42 100644 --- a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts +++ b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { ObjectStorageJobData } from '@/queue/types.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js'; import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js'; import type Bull from 'bull'; diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 753df8cad6..a4879cef3d 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import { QueueService } from '@/core/QueueService.js'; diff --git a/packages/backend/src/queue/SystemQueueProcessorsService.ts b/packages/backend/src/queue/SystemQueueProcessorsService.ts index 7c227296e7..ccb040fae5 100644 --- a/packages/backend/src/queue/SystemQueueProcessorsService.ts +++ b/packages/backend/src/queue/SystemQueueProcessorsService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js'; import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js'; import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js'; diff --git a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts index 17337837a3..e91cba9d10 100644 --- a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts +++ b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { MutingsRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { MutingsRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; diff --git a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts index 6f2fb8dea0..e8e90f1422 100644 --- a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import FederationChart from '@/core/chart/charts/federation.js'; import NotesChart from '@/core/chart/charts/notes.js'; diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts index 830f0c56b6..6eb457ce9f 100644 --- a/packages/backend/src/queue/processors/CleanProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, LessThan, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { UserIpsRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UserIpsRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; diff --git a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts index c3c68be1bc..4d46cdeaf9 100644 --- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan, Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index ab82f87d5e..4d6ed2008a 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository, UserProfilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository, UserProfilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; diff --git a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts index 430fbf19e9..682382b2db 100644 --- a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { UsersRepository, DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; diff --git a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts index 72923b80a9..6740643fe2 100644 --- a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 1bf51c1bc6..9042a21d2c 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository, InstancesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository, InstancesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { MetaService } from '@/core/MetaService.js'; import { ApRequestService } from '@/core/remote/activitypub/ApRequestService.js'; diff --git a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts index 3e55a351a1..2fc7fe219e 100644 --- a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts +++ b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { PollVotesRepository, NotesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { PollVotesRepository, NotesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index cbc483698f..db149b68cf 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -3,9 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import { UsersRepository, BlockingsRepository } from '@/models/index.js'; -import type { DriveFilesRepository, UserProfilesRepository, NotesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsersRepository, BlockingsRepository, DriveFilesRepository, UserProfilesRepository, NotesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index c49a47561b..aa7a0ef929 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -6,8 +6,8 @@ import { ulid } from 'ulid'; import mime from 'mime-types'; import archiver from 'archiver'; import { DI } from '@/di-symbols.js'; -import { EmojisRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { EmojisRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp, createTempDir } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index 4c6162432a..eac9c6925e 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, MoreThan, Not } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import { FollowingsRepository, MutingsRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { FollowingsRepository, MutingsRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index 7781d2787f..e263c245fd 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import { MutingsRepository, UsersRepository, BlockingsRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { MutingsRepository, UsersRepository, BlockingsRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index 62b3a53c49..533d4bd7c6 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import { NotesRepository, PollsRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { NotesRepository, PollsRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index 097835ac81..8c3e3dbe17 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, IsNull, MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import { UserListJoiningsRepository, UserListsRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UserListJoiningsRepository, UserListsRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index 44c8800a68..12f77638bb 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { BlockingsRepository, DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { BlockingsRepository, DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index 4919fb2f7b..492f17f9ff 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan, DataSource } from 'typeorm'; import unzipper from 'unzipper'; import { DI } from '@/di-symbols.js'; -import { EmojisRepository, DriveFilesRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { EmojisRepository, DriveFilesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { createTempDir } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index 5e49678d05..f649014399 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index c613c7e74e..f004f2d64b 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index 96c862e5c9..168f850718 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository, UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository, UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 4593b4fb61..ad72263a8d 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import httpSignature from '@peertube/http-signature'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { MetaService } from '@/core/MetaService.js'; import { ApRequestService } from '@/core/remote/activitypub/ApRequestService.js'; diff --git a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts index 75d02d527a..bf2fdeb7a0 100644 --- a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import FederationChart from '@/core/chart/charts/federation.js'; import NotesChart from '@/core/chart/charts/notes.js'; diff --git a/packages/backend/src/queue/processors/TickChartsProcessorService.ts b/packages/backend/src/queue/processors/TickChartsProcessorService.ts index e16956df0c..96607e1d68 100644 --- a/packages/backend/src/queue/processors/TickChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/TickChartsProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import FederationChart from '@/core/chart/charts/federation.js'; import NotesChart from '@/core/chart/charts/notes.js'; diff --git a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts index 27243be51b..43e3f37201 100644 --- a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { WebhooksRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { WebhooksRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { StatusError } from '@/misc/status-error.js'; diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 21ecc7177a..b2fcf9f81a 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -4,9 +4,9 @@ import json from 'koa-json-body'; import httpSignature from '@peertube/http-signature'; import { Brackets, In, IsNull, LessThan, Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; +import type { EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; import * as url from '@/misc/prelude/url.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; import { QueueService } from '@/core/QueueService.js'; import type { ILocalUser, User } from '@/models/entities/User.js'; diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index becf0592d7..dc073e34ac 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -7,8 +7,8 @@ import cors from '@koa/cors'; import Router from '@koa/router'; import send from 'koa-send'; import rename from 'rename'; -import { Config } from '@/config.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { createTemp } from '@/misc/create-temp.js'; import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; diff --git a/packages/backend/src/server/MediaProxyServerService.ts b/packages/backend/src/server/MediaProxyServerService.ts index 1e0385602c..31841d39df 100644 --- a/packages/backend/src/server/MediaProxyServerService.ts +++ b/packages/backend/src/server/MediaProxyServerService.ts @@ -5,7 +5,7 @@ import cors from '@koa/cors'; import Router from '@koa/router'; import sharp from 'sharp'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { isMimeImage } from '@/misc/is-mime-image.js'; import { createTemp } from '@/misc/create-temp.js'; import { DownloadService } from '@/core/DownloadService.js'; diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index 04a5f1484b..b07dc4cf8f 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -2,8 +2,8 @@ import { Inject, Injectable } from '@nestjs/common'; import Router from '@koa/router'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { NotesRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { NotesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Cache } from '@/misc/cache.js'; diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 14d5ed45ab..d42972614f 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -9,8 +9,8 @@ import koaLogger from 'koa-logger'; import * as slow from 'koa-slow'; import { IsNull } from 'typeorm'; import { GlobalEventService } from '@/core/GlobalEventService.js'; -import { Config } from '@/config.js'; -import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import { envOption } from '@/env.js'; diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts index 7f827d439b..f2eee88e09 100644 --- a/packages/backend/src/server/WellKnownServerService.ts +++ b/packages/backend/src/server/WellKnownServerService.ts @@ -2,8 +2,8 @@ import { Inject, Injectable } from '@nestjs/common'; import Router from '@koa/router'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js'; import type { User } from '@/models/entities/User.js'; import * as Acct from '@/misc/acct.js'; diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index d13b8d5ced..c3ce12e0c3 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -5,7 +5,7 @@ import { getIpHash } from '@/misc/get-ip-hash.js'; import type { CacheableLocalUser, User } from '@/models/entities/User.js'; import type { AccessToken } from '@/models/entities/AccessToken.js'; import type Logger from '@/logger.js'; -import { UserIpsRepository } from '@/models/index.js'; +import type { UserIpsRepository } from '@/models/index.js'; import { MetaService } from '@/core/MetaService.js'; import { ApiError } from './error.js'; import { RateLimiterService } from './RateLimiterService.js'; diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index cfe39238dd..52654dbaee 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -5,8 +5,8 @@ import multer from '@koa/multer'; import bodyParser from 'koa-bodyparser'; import cors from '@koa/cors'; import { ModuleRef } from '@nestjs/core'; -import { Config } from '@/config.js'; -import { UsersRepository, InstancesRepository, AccessTokensRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { UsersRepository, InstancesRepository, AccessTokensRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import endpoints from './endpoints.js'; diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts index 29d6ba78f0..4ce9b91f42 100644 --- a/packages/backend/src/server/api/AuthenticateService.ts +++ b/packages/backend/src/server/api/AuthenticateService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/index.js'; +import type { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/index.js'; import type { CacheableLocalUser, ILocalUser } from '@/models/entities/User.js'; import type { AccessToken } from '@/models/entities/AccessToken.js'; import { Cache } from '@/misc/cache.js'; diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 5cda3c6205..a5e2b09012 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -4,8 +4,8 @@ import bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { UserSecurityKeysRepository, SigninsRepository, UserProfilesRepository, AttestationChallengesRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UserSecurityKeysRepository, SigninsRepository, UserProfilesRepository, AttestationChallengesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; import type { ILocalUser } from '@/models/entities/User.js'; import { IdService } from '@/core/IdService.js'; diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts index 19d14bad67..3b96dfee6f 100644 --- a/packages/backend/src/server/api/SigninService.ts +++ b/packages/backend/src/server/api/SigninService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { SigninsRepository } from '@/models/index.js'; +import type { SigninsRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { IdService } from '@/core/IdService.js'; import type { ILocalUser } from '@/models/entities/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index df040ddcf8..06b8b29134 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -2,8 +2,8 @@ import { Inject, Injectable } from '@nestjs/common'; import rndstr from 'rndstr'; import bcrypt from 'bcryptjs'; import { DI } from '@/di-symbols.js'; -import { RegistrationTicketsRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { RegistrationTicketsRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; import { CaptchaService } from '@/core/CaptchaService.js'; import { IdService } from '@/core/IdService.js'; diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index b08b01aef9..8a6906e5fe 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import Redis from 'ioredis'; import * as websocket from 'websocket'; import { DI } from '@/di-symbols.js'; -import { UsersRepository, BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsersRepository, BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { NoteReadService } from '@/core/NoteReadService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { NotificationService } from '@/core/NotificationService.js'; diff --git a/packages/backend/src/server/api/common/GetterService.ts b/packages/backend/src/server/api/common/GetterService.ts index 5523539b91..a6b60d1f5a 100644 --- a/packages/backend/src/server/api/common/GetterService.ts +++ b/packages/backend/src/server/api/common/GetterService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { NotesRepository, UsersRepository } from '@/models/index.js'; +import type { NotesRepository, UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 480ae7166e..30183ed88b 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AbuseUserReportsRepository } from '@/models/index.js'; +import type { AbuseUserReportsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index 1b173379a0..c76ece9e05 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { SignupService } from '@/core/SignupService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { localUsernameSchema, passwordSchema } from '@/models/entities/User.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts index 2e0222f0c6..dc2d499191 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts index 6b32391e8d..8fcbde591b 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AdsRepository } from '@/models/index.js'; +import type { AdsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts index 7abefe156b..f4c9885408 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AdsRepository } from '@/models/index.js'; +import type { AdsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts index efece31bbf..29e245ab95 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AdsRepository } from '@/models/index.js'; +import type { AdsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts index 098a593379..195300666e 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AdsRepository } from '@/models/index.js'; +import type { AdsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index ee07170d62..751b6be7f4 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AnnouncementsRepository } from '@/models/index.js'; +import type { AnnouncementsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts index 9a67bdb1aa..18d50b8b2a 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AnnouncementsRepository } from '@/models/index.js'; +import type { AnnouncementsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 35c14abda2..9b20494129 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/index.js'; +import type { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/index.js'; import type { Announcement } from '@/models/entities/Announcement.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index 38358dff10..2393c2441c 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AnnouncementsRepository } from '@/models/index.js'; +import type { AnnouncementsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/delete-account.ts b/packages/backend/src/server/api/endpoints/admin/delete-account.ts index c8b67fe1c0..d0485fddd8 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-account.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DeleteAccountService } from '@/core/DeleteAccountService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts index 051e4c60fb..22b78bf19d 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DriveService } from '@/core/DriveService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts index 770bade06d..f4d39cd872 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts index 3927a89f90..4f7e02fe92 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts @@ -1,7 +1,7 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DriveService } from '@/core/DriveService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index 88529ab0aa..2459a479ab 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index 45ea9cdb50..c17f67cf2b 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts index 0b6e744ef8..7c24e8baa8 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index daa57e8eb2..c4e1987d73 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import rndstr from 'rndstr'; import { DataSource } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository, EmojisRepository } from '@/models/index.js'; +import type { DriveFilesRepository, EmojisRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index 08d40834c1..2cdd9c36bd 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts index 81b095cb57..8b2031e6dd 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts index e4278dc33a..dd7cd4cede 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts index 9d6fa53417..c03d27878c 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index 736d664cc3..e50f924044 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import type { Emoji } from '@/models/entities/Emoji.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts index d6c70eaae7..99512a26b3 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts index c438b7f9b7..697999cc7c 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts index 4a9b31fd28..00a5b162bf 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index e6eb9eb9a6..c576950ac7 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts index 789838661c..38fe99b222 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DriveService } from '@/core/DriveService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts index 476b821523..b7f2858a77 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/index.js'; import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts index 67165dc47e..b073209a5b 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { FollowingsRepository, UsersRepository } from '@/models/index.js'; +import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index b9eade5b40..0a529ecb08 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/index.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts index eddaade919..947a673def 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserIpsRepository } from '@/models/index.js'; +import type { UserIpsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/invite.ts b/packages/backend/src/server/api/endpoints/admin/invite.ts index 5fe341e5ca..bc42bf792a 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite.ts @@ -1,7 +1,7 @@ import rndstr from 'rndstr'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistrationTicketsRepository } from '@/models/index.js'; +import type { RegistrationTicketsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 615c0a0e70..5b43c180d8 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { MetaService } from '@/core/MetaService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts index fe200da6ad..2fc5a35e8e 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts index 3dc7158ba9..f0d7a3f12d 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index a179f163df..0cff6bae6a 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { PromoNotesRepository } from '@/models/index.js'; +import type { PromoNotesRepository } from '@/models/index.js'; import { GetterService } from '@/server/api/common/GetterService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index 7446746b45..f7d27be9cb 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import rndstr from 'rndstr'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index b5828ae9be..a6e59276fb 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; +import type { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; import { QueueService } from '@/core/QueueService.js'; import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts index 2424cac425..ac0a84128c 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ModerationLogsRepository } from '@/models/index.js'; +import type { ModerationLogsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index b50564210b..e4031cf960 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, SigninsRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, SigninsRepository, UserProfilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 8d11e3ea7a..d28f9c71b7 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/silence-user.ts b/packages/backend/src/server/api/endpoints/admin/silence-user.ts index bec8f7719e..b9dbd211e0 100644 --- a/packages/backend/src/server/api/endpoints/admin/silence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/silence-user.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index d9266aac6c..d30facd125 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, FollowingsRepository, NotificationsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository, NotificationsRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts index b4671a2f41..3a9d410de0 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index 96283d251f..2805c21a74 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts index 1ea0e6aac4..33808ee70f 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index aa44dfd5dc..74168481f6 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; -import { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models'; +import type { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models'; export const meta = { tags: ['meta'], diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 56bd343d55..fe24a10300 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import { UserListsRepository, UserGroupJoiningsRepository, AntennasRepository } from '@/models/index.js'; +import type { UserListsRepository, UserGroupJoiningsRepository, AntennasRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/antennas/delete.ts b/packages/backend/src/server/api/endpoints/antennas/delete.ts index 127aca0c33..5da7a2cb66 100644 --- a/packages/backend/src/server/api/endpoints/antennas/delete.ts +++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AntennasRepository } from '@/models/index.js'; +import type { AntennasRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/antennas/list.ts b/packages/backend/src/server/api/endpoints/antennas/list.ts index bdc44895cc..a0f8979574 100644 --- a/packages/backend/src/server/api/endpoints/antennas/list.ts +++ b/packages/backend/src/server/api/endpoints/antennas/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AntennasRepository } from '@/models/index.js'; +import type { AntennasRepository } from '@/models/index.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index eba42afe56..fb3c713154 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { NotesRepository, AntennaNotesRepository } from '@/models/index.js'; +import type { NotesRepository, AntennaNotesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteReadService } from '@/core/NoteReadService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/antennas/show.ts b/packages/backend/src/server/api/endpoints/antennas/show.ts index 8bd8ad124d..ef7ed5b72c 100644 --- a/packages/backend/src/server/api/endpoints/antennas/show.ts +++ b/packages/backend/src/server/api/endpoints/antennas/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AntennasRepository } from '@/models/index.js'; +import type { AntennasRepository } from '@/models/index.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 59bba04ee1..1955eac949 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AntennasRepository, UserListsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { AntennasRepository, UserListsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index e291b5908a..0a5fc31751 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, NotesRepository } from '@/models/index.js'; +import type { UsersRepository, NotesRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import type { CacheableLocalUser, User } from '@/models/entities/User.js'; import { isActor, isPost, getApId } from '@/core/remote/activitypub/type.js'; diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index f52d18f7fe..c1d0a9dd74 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AppsRepository } from '@/models/index.js'; +import type { AppsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { unique } from '@/misc/prelude/array.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; diff --git a/packages/backend/src/server/api/endpoints/app/show.ts b/packages/backend/src/server/api/endpoints/app/show.ts index f94fed5344..eaafa8dc1b 100644 --- a/packages/backend/src/server/api/endpoints/app/show.ts +++ b/packages/backend/src/server/api/endpoints/app/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AppsRepository } from '@/models/index.js'; +import type { AppsRepository } from '@/models/index.js'; import { AppEntityService } from '@/core/entities/AppEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index 6032b59bef..cb2e661bfb 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -1,7 +1,7 @@ import * as crypto from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AuthSessionsRepository, AppsRepository, AccessTokensRepository } from '@/models/index.js'; +import type { AuthSessionsRepository, AppsRepository, AccessTokensRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts index 7f8325dbbd..6108d8202d 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -1,9 +1,9 @@ import { v4 as uuid } from 'uuid'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AppsRepository, AuthSessionsRepository } from '@/models/index.js'; +import type { AppsRepository, AuthSessionsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/auth/session/show.ts b/packages/backend/src/server/api/endpoints/auth/session/show.ts index dff4c74340..db3bf7aa63 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/show.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AuthSessionsRepository } from '@/models/index.js'; +import type { AuthSessionsRepository } from '@/models/index.js'; import { AuthSessionEntityService } from '@/core/entities/AuthSessionEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts index 9c9f13f502..b1e7bbfded 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, AppsRepository, AccessTokensRepository, AuthSessionsRepository } from '@/models/index.js'; +import type { UsersRepository, AppsRepository, AccessTokensRepository, AuthSessionsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index 33614a1554..aa6d2ecf20 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, BlockingsRepository } from '@/models/index.js'; +import type { UsersRepository, BlockingsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index f2cc28e922..46a499943c 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, BlockingsRepository } from '@/models/index.js'; +import type { UsersRepository, BlockingsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/blocking/list.ts b/packages/backend/src/server/api/endpoints/blocking/list.ts index 4f5e11cd68..969aae06f9 100644 --- a/packages/backend/src/server/api/endpoints/blocking/list.ts +++ b/packages/backend/src/server/api/endpoints/blocking/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { BlockingsRepository } from '@/models/index.js'; +import type { BlockingsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { BlockingEntityService } from '@/core/entities/BlockingEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index 21979884f9..10f8b24629 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelsRepository, DriveFilesRepository } from '@/models/index.js'; +import type { ChannelsRepository, DriveFilesRepository } from '@/models/index.js'; import type { Channel } from '@/models/entities/Channel.js'; import { IdService } from '@/core/IdService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/featured.ts b/packages/backend/src/server/api/endpoints/channels/featured.ts index 0c3f9509d1..d25faae38d 100644 --- a/packages/backend/src/server/api/endpoints/channels/featured.ts +++ b/packages/backend/src/server/api/endpoints/channels/featured.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelsRepository } from '@/models/index.js'; +import type { ChannelsRepository } from '@/models/index.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index 6c6b498a94..871d3927bc 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelFollowingsRepository, ChannelsRepository } from '@/models/index.js'; +import type { ChannelFollowingsRepository, ChannelsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/followed.ts b/packages/backend/src/server/api/endpoints/channels/followed.ts index 5a8ab26af9..f49f3105d5 100644 --- a/packages/backend/src/server/api/endpoints/channels/followed.ts +++ b/packages/backend/src/server/api/endpoints/channels/followed.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelFollowingsRepository } from '@/models/index.js'; +import type { ChannelFollowingsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/owned.ts b/packages/backend/src/server/api/endpoints/channels/owned.ts index 8b8b5819e6..59df0616be 100644 --- a/packages/backend/src/server/api/endpoints/channels/owned.ts +++ b/packages/backend/src/server/api/endpoints/channels/owned.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelsRepository } from '@/models/index.js'; +import type { ChannelsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts index 54ae31790b..8718615db2 100644 --- a/packages/backend/src/server/api/endpoints/channels/show.ts +++ b/packages/backend/src/server/api/endpoints/channels/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelsRepository } from '@/models/index.js'; +import type { ChannelsRepository } from '@/models/index.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 1c7f1360b9..58f8835279 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelsRepository, NotesRepository } from '@/models/index.js'; +import type { ChannelsRepository, NotesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts index b464c55097..ac2ef825be 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelFollowingsRepository, ChannelsRepository } from '@/models/index.js'; +import type { ChannelFollowingsRepository, ChannelsRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index ba62e9d371..d006e89bd2 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository, ChannelsRepository } from '@/models/index.js'; +import type { DriveFilesRepository, ChannelsRepository } from '@/models/index.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index c733d28657..77d02815e0 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; -import { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; +import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { ApiError } from '../../error.js'; import { GetterService } from '../../common/GetterService.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index 8eca3d66d1..d300203a21 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/index.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/delete.ts b/packages/backend/src/server/api/endpoints/clips/delete.ts index ea361ae9c0..077a9ec40f 100644 --- a/packages/backend/src/server/api/endpoints/clips/delete.ts +++ b/packages/backend/src/server/api/endpoints/clips/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index b57affd1c4..63ca069364 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/index.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index 4282498931..6818d31cc4 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { NotesRepository, ClipsRepository, ClipNotesRepository } from '@/models/index.js'; +import type { NotesRepository, ClipsRepository, ClipNotesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/remove-note.ts b/packages/backend/src/server/api/endpoints/clips/remove-note.ts index 3fc60e3639..93805af089 100644 --- a/packages/backend/src/server/api/endpoints/clips/remove-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/remove-note.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; +import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; import { GetterService } from '../../common/GetterService.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts index 4e93540054..e6d3f4f1f8 100644 --- a/packages/backend/src/server/api/endpoints/clips/show.ts +++ b/packages/backend/src/server/api/endpoints/clips/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/index.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts index 9880505d06..597b67c442 100644 --- a/packages/backend/src/server/api/endpoints/clips/update.ts +++ b/packages/backend/src/server/api/endpoints/clips/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/index.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index 56055d1340..f6fad50fd9 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index 9f11eb8b53..328d0e4643 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { NotesRepository, DriveFilesRepository } from '@/models/index.js'; +import type { NotesRepository, DriveFilesRepository } from '@/models/index.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index 176031d808..290cd4d2ce 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index bff83876d7..d394f5c3da 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -1,6 +1,6 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index 9d2ea6011a..be7b050907 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DriveService } from '@/core/DriveService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts index 6299ca8f6b..d6d85f4e77 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts index e4cd5213dd..858063eb4b 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index bae4d7d66f..474d599cb6 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import type { DriveFile } from '@/models/entities/DriveFile.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index 03e3663f08..703f92d8c6 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index f4f8df3c2b..19ab03a337 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -1,6 +1,6 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/folders.ts b/packages/backend/src/server/api/endpoints/drive/folders.ts index 703dc83ecd..b41eaf4463 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts index 7604eaf489..e7c11a8c13 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts index dcbaecf8af..d921bc1b17 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFoldersRepository, DriveFilesRepository } from '@/models/index.js'; +import type { DriveFoldersRepository, DriveFilesRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/folders/find.ts b/packages/backend/src/server/api/endpoints/drive/folders/find.ts index 96a87344a9..ee24db11f2 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/find.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/index.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/folders/show.ts b/packages/backend/src/server/api/endpoints/drive/folders/show.ts index 4c25bc705c..c06263b902 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/index.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts index 4fcd37bbbf..ee63d291b2 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/index.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/stream.ts b/packages/backend/src/server/api/endpoints/drive/stream.ts index aba73c2092..61bcfea0c3 100644 --- a/packages/backend/src/server/api/endpoints/drive/stream.ts +++ b/packages/backend/src/server/api/endpoints/drive/stream.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/federation/followers.ts b/packages/backend/src/server/api/endpoints/federation/followers.ts index e5222fcbfd..be1d6c8e58 100644 --- a/packages/backend/src/server/api/endpoints/federation/followers.ts +++ b/packages/backend/src/server/api/endpoints/federation/followers.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/federation/following.ts b/packages/backend/src/server/api/endpoints/federation/following.ts index a20c5a31b3..74656ce863 100644 --- a/packages/backend/src/server/api/endpoints/federation/following.ts +++ b/packages/backend/src/server/api/endpoints/federation/following.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index e7f8cefff5..81276a7ab0 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/index.js'; import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; import { MetaService } from '@/core/MetaService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts index f855b54537..66502748b3 100644 --- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts +++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/index.js'; import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/federation/stats.ts b/packages/backend/src/server/api/endpoints/federation/stats.ts index d07a08637f..19418e698c 100644 --- a/packages/backend/src/server/api/endpoints/federation/stats.ts +++ b/packages/backend/src/server/api/endpoints/federation/stats.ts @@ -1,6 +1,6 @@ import { IsNull, MoreThan, Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { FollowingsRepository, InstancesRepository } from '@/models/index.js'; +import type { FollowingsRepository, InstancesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts index 0400cacd02..a028930f21 100644 --- a/packages/backend/src/server/api/endpoints/federation/users.ts +++ b/packages/backend/src/server/api/endpoints/federation/users.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/fetch-rss.ts b/packages/backend/src/server/api/endpoints/fetch-rss.ts index 9e6a3cc717..58fa01ac48 100644 --- a/packages/backend/src/server/api/endpoints/fetch-rss.ts +++ b/packages/backend/src/server/api/endpoints/fetch-rss.ts @@ -1,7 +1,7 @@ import Parser from 'rss-parser'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 3a06c63d52..be322e2896 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 07366bc821..afb59dd2c2 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index 8285189d66..e67e136ead 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts index 0b79a80649..213b5ce32e 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/following/requests/list.ts b/packages/backend/src/server/api/endpoints/following/requests/list.ts index c36d4a077f..5b11633e6f 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/list.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { FollowRequestsRepository } from '@/models/index.js'; +import type { FollowRequestsRepository } from '@/models/index.js'; import { FollowRequestEntityService } from '@/core/entities/FollowRequestEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/featured.ts b/packages/backend/src/server/api/endpoints/gallery/featured.ts index 3b892ef522..9994ce90d7 100644 --- a/packages/backend/src/server/api/endpoints/gallery/featured.ts +++ b/packages/backend/src/server/api/endpoints/gallery/featured.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/index.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/popular.ts b/packages/backend/src/server/api/endpoints/gallery/popular.ts index 551ea64835..55d3dabfb0 100644 --- a/packages/backend/src/server/api/endpoints/gallery/popular.ts +++ b/packages/backend/src/server/api/endpoints/gallery/popular.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/index.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts.ts b/packages/backend/src/server/api/endpoints/gallery/posts.ts index 4afcbce816..e94003eb79 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index 9e8bcac131..2842308510 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js'; +import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js'; import { GalleryPost } from '@/models/entities/GalleryPost.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { IdService } from '@/core/IdService.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts index ad5f95c853..6cdcc17b39 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index 8aca98119b..519e56ed6a 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryLikesRepository, GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryLikesRepository, GalleryPostsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts index 723906d60f..f7e828142b 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/index.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts index d878582998..cfbedcc4d9 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository, GalleryLikesRepository } from '@/models/index.js'; +import type { GalleryPostsRepository, GalleryLikesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index 1900afaeb6..d261aaa966 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js'; +import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js'; import { GalleryPost } from '@/models/entities/GalleryPost.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index 2d9bf29dd9..dea0f4799c 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -1,7 +1,7 @@ import { MoreThan } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { USER_ONLINE_THRESHOLD } from '@/const.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/hashtags/list.ts b/packages/backend/src/server/api/endpoints/hashtags/list.ts index a7e7e6ba35..226a11de0b 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/list.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { HashtagsRepository } from '@/models/index.js'; +import type { HashtagsRepository } from '@/models/index.js'; import { HashtagEntityService } from '@/core/entities/HashtagEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts index 3fb77bef9b..7f787ea38f 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/search.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { HashtagsRepository } from '@/models/index.js'; +import type { HashtagsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/hashtags/show.ts b/packages/backend/src/server/api/endpoints/hashtags/show.ts index 59170f6d0e..06b0d6e9b2 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/show.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { HashtagsRepository } from '@/models/index.js'; +import type { HashtagsRepository } from '@/models/index.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { HashtagEntityService } from '@/core/entities/HashtagEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/hashtags/trend.ts b/packages/backend/src/server/api/endpoints/hashtags/trend.ts index 7e483ea214..cf45cc6c24 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/trend.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/trend.ts @@ -1,7 +1,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import { safeForSql } from '@/misc/safe-for-sql.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index 10a88fbefa..c3f2ea9ea7 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index 815b3168b4..3bcd6ff8fb 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts index bcf3931b04..ec9ac1ef90 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -1,7 +1,7 @@ import * as speakeasy from 'speakeasy'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index f2f4c2044e..6e0849f2b2 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -4,11 +4,11 @@ import * as cbor from 'cbor'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; -import { AttestationChallengesRepository, UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; +import type { AttestationChallengesRepository, UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; const cborDecodeFirst = promisify(cbor.decodeFirst) as any; diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts index 3eb9f43c2b..0655a86350 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index df37db4c6a..19c77365c6 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -3,7 +3,7 @@ import * as crypto from 'node:crypto'; import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserProfilesRepository, AttestationChallengesRepository } from '@/models/index.js'; +import type { UserProfilesRepository, AttestationChallengesRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index e20911f35e..a539c5c221 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -2,10 +2,10 @@ import bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; import * as QRCode from 'qrcode'; import { Inject, Injectable } from '@nestjs/common'; -import { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; export const meta = { requireCredential: true, diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index 1889dd7893..f40ec9797d 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -1,7 +1,7 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; +import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index 4607e5d981..4c5b151f78 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -1,7 +1,7 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index 8d5851659b..3361e5a4d3 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AccessTokensRepository } from '@/models/index.js'; +import type { AccessTokensRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts index a5592d20de..d0bdb5695b 100644 --- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts +++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AccessTokensRepository } from '@/models/index.js'; +import type { AccessTokensRepository } from '@/models/index.js'; import { AppEntityService } from '@/core/entities/AppEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index cc5b712ecf..873835a36c 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -1,7 +1,7 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index a1804599df..77a03d9811 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -1,6 +1,6 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DeleteAccountService } from '@/core/DeleteAccountService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts index 350abd9f7b..ce8ab4962a 100644 --- a/packages/backend/src/server/api/endpoints/i/favorites.ts +++ b/packages/backend/src/server/api/endpoints/i/favorites.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { NoteFavoritesRepository } from '@/models/index.js'; +import type { NoteFavoritesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteFavoriteEntityService } from '@/core/entities/NoteFavoriteEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts index ff6bcc01ab..d1b04cb655 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryLikesRepository } from '@/models/index.js'; +import type { GalleryLikesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { GalleryLikeEntityService } from '@/core/entities/GalleryLikeEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts index 927be51f79..32d14293f7 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts index 0695abdd85..3179457817 100644 --- a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts +++ b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MutedNotesRepository } from '@/models/index.js'; +import type { MutedNotesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index bfba1fc36f..8c1c158ab1 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index c7cb2e0330..383bdc02b5 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index 060c37c13f..345ad916cb 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index a5e17283e5..875af7ec23 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index 96927dad49..13de3382dd 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotificationsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotificationsRepository } from '@/models/index.js'; import { notificationTypes } from '@/types.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; diff --git a/packages/backend/src/server/api/endpoints/i/page-likes.ts b/packages/backend/src/server/api/endpoints/i/page-likes.ts index 9a909eedf4..70e6e0a6a8 100644 --- a/packages/backend/src/server/api/endpoints/i/page-likes.ts +++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { PageLikesRepository } from '@/models/index.js'; +import type { PageLikesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { PageLikeEntityService } from '@/core/entities/PageLikeEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts index 7c4e4a6c7d..285aa34e91 100644 --- a/packages/backend/src/server/api/endpoints/i/pages.ts +++ b/packages/backend/src/server/api/endpoints/i/pages.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { PagesRepository } from '@/models/index.js'; +import type { PagesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts index 36c3566f55..109d6d1068 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts index b4bb83c6eb..b92de4b739 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { NoteUnreadsRepository } from '@/models/index.js'; +import type { NoteUnreadsRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts index 5a7909674f..cb5b4b0a60 100644 --- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts +++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models/index.js'; +import type { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts index 7796fd97cb..f942f43cc8 100644 --- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -1,7 +1,7 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; import generateUserToken from '@/misc/generate-native-user-token.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts index 3b4db5fae3..17154c1f76 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts index d24dff95b0..233686dbe1 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts index 98d94a4c02..99cdf95bad 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts index d1a05d9d06..362a5e89f4 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys.ts b/packages/backend/src/server/api/endpoints/i/registry/keys.ts index 6df5f4ecc3..99f69d8bed 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/remove.ts b/packages/backend/src/server/api/endpoints/i/registry/remove.ts index b5870f099d..78a641f5e2 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/remove.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/remove.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts index 58085ddbc5..0a4ecb9c51 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/set.ts b/packages/backend/src/server/api/endpoints/i/registry/set.ts index 585aac2e01..c8e72203c4 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/set.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/set.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts index 86a82e6a6c..5e1dddb6b7 100644 --- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AccessTokensRepository } from '@/models/index.js'; +import type { AccessTokensRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/signin-history.ts b/packages/backend/src/server/api/endpoints/i/signin-history.ts index 410cd72065..9b30a24336 100644 --- a/packages/backend/src/server/api/endpoints/i/signin-history.ts +++ b/packages/backend/src/server/api/endpoints/i/signin-history.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { SigninsRepository } from '@/models/index.js'; +import type { SigninsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { SigninEntityService } from '@/core/entities/SigninEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 719cc14f09..b656c5c51d 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -3,10 +3,10 @@ import rndstr from 'rndstr'; import ms from 'ms'; import bcrypt from 'bcryptjs'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { EmailService } from '@/core/EmailService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 4b904d4696..9bf0616e3a 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -3,7 +3,7 @@ import * as mfm from 'mfm-js'; import { Inject, Injectable } from '@nestjs/common'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractHashtags } from '@/misc/extract-hashtags.js'; -import { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/index.js'; +import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/entities/User.js'; import type { UserProfile } from '@/models/entities/UserProfile.js'; diff --git a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts index 6dd1626bb8..1ad2f7d68f 100644 --- a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts +++ b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserGroupInvitationsRepository } from '@/models/index.js'; +import type { UserGroupInvitationsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { UserGroupInvitationEntityService } from '@/core/entities/UserGroupInvitationEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts index 016b1b5d6a..584c2ba6a4 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/index.js'; import { webhookEventTypes } from '@/models/entities/Webhook.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts index 53b553b43e..7bdad136aa 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts index 8e4aff45dd..58c84938cc 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts index 622c2ade98..d15ca0050d 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts index 3a0ef1a526..50098f96e7 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/index.js'; import { webhookEventTypes } from '@/models/entities/Webhook.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/messaging/history.ts b/packages/backend/src/server/api/endpoints/messaging/history.ts index da3ba59df9..0b6099d4ac 100644 --- a/packages/backend/src/server/api/endpoints/messaging/history.ts +++ b/packages/backend/src/server/api/endpoints/messaging/history.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; -import { MutingsRepository, UserGroupJoiningsRepository, MessagingMessagesRepository } from '@/models/index.js'; +import type { MutingsRepository, UserGroupJoiningsRepository, MessagingMessagesRepository } from '@/models/index.js'; import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts index 6579b03987..f563da3278 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository } from '@/models/index.js'; -import { UserGroupsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts index e02afcbcfd..f61662af75 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { BlockingsRepository, UserGroupJoiningsRepository, DriveFilesRepository, UserGroupsRepository } from '@/models/index.js'; +import type { BlockingsRepository, UserGroupJoiningsRepository, DriveFilesRepository, UserGroupsRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import type { UserGroup } from '@/models/entities/UserGroup.js'; import { GetterService } from '@/server/api/common/GetterService.js'; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts index 5baecb9114..cd74f5f197 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MessagingMessagesRepository } from '@/models/index.js'; +import type { MessagingMessagesRepository } from '@/models/index.js'; import { MessagingService } from '@/core/MessagingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts index 6e66cafe1e..bddb6d932d 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MessagingMessagesRepository } from '@/models/index.js'; +import type { MessagingMessagesRepository } from '@/models/index.js'; import { MessagingService } from '@/core/MessagingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 9a6258d7dd..5c09c33941 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -1,13 +1,13 @@ import { IsNull, MoreThan } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { AdsRepository, EmojisRepository, UsersRepository } from '@/models/index.js'; +import type { AdsRepository, EmojisRepository, UsersRepository } from '@/models/index.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { MetaService } from '@/core/MetaService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts index d8eb89c0e6..97def86262 100644 --- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts +++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AccessTokensRepository } from '@/models/index.js'; +import type { AccessTokensRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index cbdd001185..3b4507281f 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import { MutingsRepository } from '@/models/index.js'; +import type { MutingsRepository } from '@/models/index.js'; import type { Muting } from '@/models/entities/Muting.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts index c7098059d5..2fc5cee95e 100644 --- a/packages/backend/src/server/api/endpoints/mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/mute/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MutingsRepository } from '@/models/index.js'; +import type { MutingsRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/mute/list.ts b/packages/backend/src/server/api/endpoints/mute/list.ts index 11c05eb795..9ec6d17273 100644 --- a/packages/backend/src/server/api/endpoints/mute/list.ts +++ b/packages/backend/src/server/api/endpoints/mute/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MutingsRepository } from '@/models/index.js'; +import type { MutingsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { MutingEntityService } from '@/core/entities/MutingEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/my/apps.ts b/packages/backend/src/server/api/endpoints/my/apps.ts index 90cd53a133..4b7ed80123 100644 --- a/packages/backend/src/server/api/endpoints/my/apps.ts +++ b/packages/backend/src/server/api/endpoints/my/apps.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AppsRepository } from '@/models/index.js'; +import type { AppsRepository } from '@/models/index.js'; import { AppEntityService } from '@/core/entities/AppEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts index 288e195316..0a8f2292ac 100644 --- a/packages/backend/src/server/api/endpoints/notes.ts +++ b/packages/backend/src/server/api/endpoints/notes.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index 86f90e049f..ea7a825f9d 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index 7d893f32a1..579466d4fd 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -1,6 +1,6 @@ import { In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; +import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index 2f8324ed62..ddfee31525 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import type { Note } from '@/models/entities/Note.js'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 30b7a889fc..92bc8a7595 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -2,7 +2,7 @@ import ms from 'ms'; import { In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import type { User } from '@/models/entities/User.js'; -import { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesRepository, ChannelsRepository } from '@/models/index.js'; +import type { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesRepository, ChannelsRepository } from '@/models/index.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Note } from '@/models/entities/Note.js'; import type { Channel } from '@/models/entities/Channel.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 4769c8bdf1..5765dfe66f 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -1,6 +1,6 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteDeleteService } from '@/core/NoteDeleteService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index bfdd1acd22..edbd300e08 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NoteFavoritesRepository } from '@/models/index.js'; +import type { NoteFavoritesRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/common/GetterService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index 6b3a02b101..8f4f2b2b9e 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/common/GetterService.js'; import { DI } from '@/di-symbols.js'; -import { NoteFavoritesRepository } from '@/models/index.js'; +import type { NoteFavoritesRepository } from '@/models/index.js'; import { ApiError } from '../../../error.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index 9985f9d257..76834cfde9 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 73b5afa40a..b6eaccb5ac 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index c6458223eb..6573e9454a 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import type { NotesRepository, FollowingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 7b8859639d..fac14fa225 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index 9b2dabc88b..92b82eb5de 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import type { NotesRepository, FollowingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index 11bfdbba0f..6cdc9b902c 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -1,6 +1,6 @@ import { Brackets, In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository, MutingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; +import type { NotesRepository, MutingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 76f07528d7..515f03dcce 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -1,6 +1,6 @@ import { Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, BlockingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; +import type { UsersRepository, BlockingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; import type { IRemoteUser } from '@/models/entities/User.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index d57950f012..02ae212a30 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -1,6 +1,6 @@ import { DeepPartial } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NoteReactionsRepository } from '@/models/index.js'; +import type { NoteReactionsRepository } from '@/models/index.js'; import type { NoteReaction } from '@/models/entities/NoteReaction.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 57b7aeae0d..97ef1a17ec 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index 7020d0c681..4df95962c8 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index 0727c9af6c..061e371d65 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { safeForSql } from '@/misc/safe-for-sql.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index 484cfc1128..27b477e141 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -1,10 +1,10 @@ import { In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index c3f5b9dfb0..7849cfa401 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index 7756d39f7c..a02b8d2559 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository, NoteThreadMutingsRepository, NoteFavoritesRepository } from '@/models/index.js'; +import type { NotesRepository, NoteThreadMutingsRepository, NoteFavoritesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index 060581d74b..b8ddf83f3c 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository, NoteThreadMutingsRepository } from '@/models/index.js'; +import type { NotesRepository, NoteThreadMutingsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/common/GetterService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts index aed15852d4..54e9b4939f 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NoteThreadMutingsRepository } from '@/models/index.js'; +import type { NoteThreadMutingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/common/GetterService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 53a1ae1348..8542af17db 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import type { NotesRepository, FollowingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index c24f1e401e..7a3daf741e 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -1,9 +1,9 @@ import { URLSearchParams } from 'node:url'; import fetch from 'node-fetch'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { MetaService } from '@/core/MetaService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index c0048888b4..7378c4b600 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -1,6 +1,6 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, NotesRepository } from '@/models/index.js'; +import type { UsersRepository, NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteDeleteService } from '@/core/NoteDeleteService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 87a464578c..9b23103fd4 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import type { NotesRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts index 3d1eb2b39c..09134cf48f 100644 --- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotificationsRepository } from '@/models/index.js'; +import type { NotificationsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { PushNotificationService } from '@/core/PushNotificationService.js'; diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts index 1b0299c3c6..1841a84539 100644 --- a/packages/backend/src/server/api/endpoints/page-push.ts +++ b/packages/backend/src/server/api/endpoints/page-push.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { PagesRepository } from '@/models/index.js'; +import type { PagesRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index ac80849aa0..eae8f18403 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -1,6 +1,6 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import { DriveFilesRepository, PagesRepository } from '@/models/index.js'; +import type { DriveFilesRepository, PagesRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Page } from '@/models/entities/Page.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index 4e97755761..e64733131c 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { PagesRepository } from '@/models/index.js'; +import type { PagesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/pages/featured.ts b/packages/backend/src/server/api/endpoints/pages/featured.ts index 3e3dbb0832..31844165e2 100644 --- a/packages/backend/src/server/api/endpoints/pages/featured.ts +++ b/packages/backend/src/server/api/endpoints/pages/featured.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { PagesRepository } from '@/models/index.js'; +import type { PagesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index f3c55fed8b..41a11d1a31 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { PagesRepository, PageLikesRepository } from '@/models/index.js'; +import type { PagesRepository, PageLikesRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts index 6d73889d39..651252afbb 100644 --- a/packages/backend/src/server/api/endpoints/pages/show.ts +++ b/packages/backend/src/server/api/endpoints/pages/show.ts @@ -1,6 +1,6 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, PagesRepository } from '@/models/index.js'; +import type { UsersRepository, PagesRepository } from '@/models/index.js'; import type { Page } from '@/models/entities/Page.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts index 88386739be..e397e2a23b 100644 --- a/packages/backend/src/server/api/endpoints/pages/unlike.ts +++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { PagesRepository, PageLikesRepository } from '@/models/index.js'; +import type { PagesRepository, PageLikesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index 8980ac4906..4db0f80b26 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { PagesRepository, DriveFilesRepository } from '@/models/index.js'; +import type { PagesRepository, DriveFilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 573331e0d8..6c941314e2 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -1,6 +1,6 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import * as Acct from '@/misc/acct.js'; import type { User } from '@/models/entities/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index 7c8188ce3c..a28229add3 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { PromoReadsRepository } from '@/models/index.js'; +import type { PromoReadsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 4766239533..42b10a4fb3 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -2,10 +2,10 @@ import rndstr from 'rndstr'; import ms from 'ms'; import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { PasswordResetRequestsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { PasswordResetRequestsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { EmailService } from '@/core/EmailService.js'; import { ApiError } from '../error.js'; diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 48edde5196..cf7fcb7afd 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -1,6 +1,6 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; -import { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/index.js'; +import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index 17af75578b..3adf0a4bb8 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; -import { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js'; +import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index 73a084c2ad..ddec877dd4 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { IdService } from '@/core/IdService.js'; -import { SwSubscriptionsRepository } from '@/models/index.js'; +import type { SwSubscriptionsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { MetaService } from '@/core/MetaService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index feb6730154..5772eeee26 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { SwSubscriptionsRepository } from '@/models/index.js'; +import type { SwSubscriptionsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index 56474d6988..c80b6efdcd 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -1,6 +1,6 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsedUsernamesRepository, UsersRepository } from '@/models/index.js'; +import type { UsedUsernamesRepository, UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { localUsernameSchema } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 3d05ec2e1d..b015129a7a 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index 2d5545cbab..e3fd0920c9 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 08bcdd9f88..17ce920011 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -1,6 +1,6 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 225ab5210a..6dbda0d72f 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -1,6 +1,6 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts index 2d28d6ca07..6e57eee5fb 100644 --- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index 3eeca7562f..22c49860bd 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -1,7 +1,7 @@ import { Not, In, IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { maximum } from '@/misc/prelude/array.js'; -import { NotesRepository, UsersRepository } from '@/models/index.js'; +import type { NotesRepository, UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts index 5d7ad84ae0..c1f4f48445 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import type { UserGroup } from '@/models/entities/UserGroup.js'; import type { UserGroupJoining } from '@/models/entities/UserGroupJoining.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts index 50156b049e..d238ae9f16 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository } from '@/models/index.js'; +import type { UserGroupsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts index 0490fd41a0..f154a57f61 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupInvitationsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupInvitationsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import type { UserGroupJoining } from '@/models/entities/UserGroupJoining.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts index 26efc1ecf3..1fd3b2f4b3 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupInvitationsRepository } from '@/models/index.js'; +import type { UserGroupInvitationsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index 4ae32a6bda..127f4ca65b 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository, UserGroupJoiningsRepository, UserGroupInvitationsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, UserGroupJoiningsRepository, UserGroupInvitationsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import type { UserGroupInvitation } from '@/models/entities/UserGroupInvitation.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts index e7e69f257d..8daee3a6f5 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -1,6 +1,6 @@ import { Not, In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts index 0a63dbb7f1..846f80e64d 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts index c9ae39561f..0bc6e8b3fc 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository } from '@/models/index.js'; +import type { UserGroupsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index e6f60eef0a..3474f22c6e 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/common/GetterService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts index 1cebfcd204..2b0f403f33 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/show.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index a8b2533b73..e0b2b13c7f 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; import { GetterService } from '@/server/api/common/GetterService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts index b679625c85..5af849de14 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/update.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository } from '@/models/index.js'; +import type { UserGroupsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index aa64ca1229..99f0751ea8 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import type { UserList } from '@/models/entities/UserList.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts index 0f4125a39f..237cb075ab 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts index 919de22377..2104c4377d 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index 89d97be93e..7e54d33376 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import type { UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GetterService } from '@/server/api/common/GetterService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 77ad772b13..06ea43d654 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js'; +import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/common/GetterService.js'; import { UserListService } from '@/core/UserListService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 62e730b2f7..77f9cba808 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts index c6669d24d1..6453d7d980 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index bb8104584c..d2c9616f68 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index 96c7ef1e70..e007aa57b2 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; -import { PagesRepository } from '@/models'; +import type { PagesRepository } from '@/models'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index 6b4d882b7c..9ec911f322 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserProfilesRepository, NoteReactionsRepository } from '@/models/index.js'; +import type { UserProfilesRepository, NoteReactionsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index e50a5706d9..5498b8c854 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -1,6 +1,6 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index aea75ae799..ac9104bf92 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index 5c211a9017..bb37dd2715 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -1,6 +1,6 @@ import * as sanitizeHtml from 'sanitize-html'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; +import type { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index 1747dc93f6..f13df3ee9d 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { USER_ACTIVE_THRESHOLD } from '@/const.js'; import type { User } from '@/models/entities/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 9879b1b68b..ba07714972 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 98f5f03063..1e15025bf4 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -1,6 +1,6 @@ import { In, IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index ea044c27d5..cc3f4e0633 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -4,8 +4,8 @@ import Router from '@koa/router'; import { OAuth2 } from 'oauth'; import { v4 as uuid } from 'uuid'; import { IsNull } from 'typeorm'; -import { Config } from '@/config.js'; -import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import type { ILocalUser } from '@/models/entities/User.js'; diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index 58b170d0e4..2195e82813 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -4,8 +4,8 @@ import Router from '@koa/router'; import { OAuth2 } from 'oauth'; import { v4 as uuid } from 'uuid'; import { IsNull } from 'typeorm'; -import { Config } from '@/config.js'; -import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import type { ILocalUser } from '@/models/entities/User.js'; diff --git a/packages/backend/src/server/api/integration/TwitterServerService.ts b/packages/backend/src/server/api/integration/TwitterServerService.ts index a4a67f6c8c..7f209ea285 100644 --- a/packages/backend/src/server/api/integration/TwitterServerService.ts +++ b/packages/backend/src/server/api/integration/TwitterServerService.ts @@ -4,8 +4,8 @@ import Router from '@koa/router'; import { v4 as uuid } from 'uuid'; import { IsNull } from 'typeorm'; import autwh from 'autwh'; -import { Config } from '@/config.js'; -import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import type { ILocalUser } from '@/models/entities/User.js'; diff --git a/packages/backend/src/server/api/stream/channels/messaging.ts b/packages/backend/src/server/api/stream/channels/messaging.ts index 5bf20c4101..b6ce6c217e 100644 --- a/packages/backend/src/server/api/stream/channels/messaging.ts +++ b/packages/backend/src/server/api/stream/channels/messaging.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupJoiningsRepository, UsersRepository, MessagingMessagesRepository } from '@/models/index.js'; +import type { UserGroupJoiningsRepository, UsersRepository, MessagingMessagesRepository } from '@/models/index.js'; import type { User, ILocalUser, IRemoteUser } from '@/models/entities/User.js'; import type { UserGroup } from '@/models/entities/UserGroup.js'; import { MessagingService } from '@/core/MessagingService.js'; diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index a45c7d9468..f9f0d02558 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; +import type { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; import type { NotesRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { isUserRelated } from '@/misc/is-user-related.js'; diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 85b31312b3..44acd12796 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -13,7 +13,7 @@ import { createBullBoard } from '@bull-board/api'; import { BullAdapter } from '@bull-board/api/bullAdapter.js'; import { KoaAdapter } from '@bull-board/koa'; import { In, IsNull } from 'typeorm'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; import { DI } from '@/di-symbols.js'; import * as Acct from '@/misc/acct.js'; diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts index 8b676aebe5..1d7d49961d 100644 --- a/packages/backend/src/server/web/FeedService.ts +++ b/packages/backend/src/server/web/FeedService.ts @@ -2,8 +2,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, IsNull } from 'typeorm'; import { Feed } from 'feed'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { User } from '@/models/entities/User.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 1cbb3f36c2..f5dddd2db7 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import summaly from 'summaly'; import { DI } from '@/di-symbols.js'; -import { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import type Logger from '@/logger.js'; diff --git a/packages/shared/.eslintrc.js b/packages/shared/.eslintrc.js index 2952b8ba16..6d38a9fb9f 100644 --- a/packages/shared/.eslintrc.js +++ b/packages/shared/.eslintrc.js @@ -73,7 +73,7 @@ module.exports = { '@typescript-eslint/no-misused-promises': ['error', { 'checksVoidReturn': false, }], - '@typescript-eslint/consistent-type-imports': 'error', + '@typescript-eslint/consistent-type-imports': 'off', '@typescript-eslint/prefer-nullish-coalescing': [ 'error', ], -- cgit v1.2.3-freya From 2e0075e79cea052eff0d2ed48674f51a8620e1b3 Mon Sep 17 00:00:00 2001 From: Takuya Yoshida Date: Wed, 21 Sep 2022 10:12:18 +0900 Subject: Fix ioredis import typo (#9099) * Fix import typo * Fix type --- packages/backend/src/server/api/integration/DiscordServerService.ts | 4 ++-- packages/backend/src/server/api/integration/GithubServerService.ts | 4 ++-- packages/backend/src/server/api/integration/TwitterServerService.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index cc3f4e0633..79ce9c8973 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { Redis } from 'ioredis'; +import Redis from 'ioredis'; import Router from '@koa/router'; import { OAuth2 } from 'oauth'; import { v4 as uuid } from 'uuid'; @@ -22,7 +22,7 @@ export class DiscordServerService { private config: Config, @Inject(DI.redis) - private redisClient: Redis, + private redisClient: Redis.Redis, @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index 2195e82813..7fb31c39a2 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { Redis } from 'ioredis'; +import Redis from 'ioredis'; import Router from '@koa/router'; import { OAuth2 } from 'oauth'; import { v4 as uuid } from 'uuid'; @@ -22,7 +22,7 @@ export class GithubServerService { private config: Config, @Inject(DI.redis) - private redisClient: Redis, + private redisClient: Redis.Redis, @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/integration/TwitterServerService.ts b/packages/backend/src/server/api/integration/TwitterServerService.ts index 7f209ea285..57c977bc54 100644 --- a/packages/backend/src/server/api/integration/TwitterServerService.ts +++ b/packages/backend/src/server/api/integration/TwitterServerService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { Redis } from 'ioredis'; +import Redis from 'ioredis'; import Router from '@koa/router'; import { v4 as uuid } from 'uuid'; import { IsNull } from 'typeorm'; @@ -22,7 +22,7 @@ export class TwitterServerService { private config: Config, @Inject(DI.redis) - private redisClient: Redis, + private redisClient: Redis.Redis, @Inject(DI.usersRepository) private usersRepository: UsersRepository, -- cgit v1.2.3-freya From bcbda6940a55d7d258c7ed9c5f3ae8d32f59ea47 Mon Sep 17 00:00:00 2001 From: こけっち <50144466+sim1222@users.noreply.github.com> Date: Thu, 22 Sep 2022 08:20:31 +0900 Subject: feat: Youtube window player (#9095) * wip: feat: Youtube Player Window * fix: player fill window * fix: improve design * fix: disable at mobile and creanup code * fix: tailing comma * fix: delete debug output * fix: eslint * fix: switch to component * fix(backend): add missing dependency Fix #9101 Co-authored-by: syuilo --- .../src/server/api/endpoints/admin/show-users.ts | 3 + packages/client/src/components/MkUrlPreview.vue | 15 ++++- packages/client/src/components/MkYoutubePlayer.vue | 72 ++++++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/components/MkYoutubePlayer.vue (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index d28f9c71b7..5471ef9b4c 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; export const meta = { tags: ['admin'], @@ -45,6 +46,8 @@ export default class extends Endpoint { constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, + + private userEntityService: UserEntityService, ) { super(meta, paramDef, async (ps, me) => { const query = this.usersRepository.createQueryBuilder('user'); diff --git a/packages/client/src/components/MkUrlPreview.vue b/packages/client/src/components/MkUrlPreview.vue index 9b2a785351..af27f644ed 100644 --- a/packages/client/src/components/MkUrlPreview.vue +++ b/packages/client/src/components/MkUrlPreview.vue @@ -10,7 +10,7 @@
- +
@@ -33,9 +33,11 @@ + + -- cgit v1.2.3-freya From 31f2f6616cd136668d6987fc124f80bf43ddab9b Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 23 Sep 2022 06:07:29 +0900 Subject: chore: fix type import --- packages/backend/src/server/web/ClientServerService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 44acd12796..30d430775c 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -18,13 +18,14 @@ import { getNoteSummary } from '@/misc/get-note-summary.js'; import { DI } from '@/di-symbols.js'; import * as Acct from '@/misc/acct.js'; import { MetaService } from '@/core/MetaService.js'; -import { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/queue/QueueModule.js'; +import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/queue/QueueModule.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import type { ChannelsRepository, ClipsRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import manifest from './manifest.json' assert { type: 'json' }; import { FeedService } from './FeedService.js'; import { UrlPreviewService } from './UrlPreviewService.js'; -- cgit v1.2.3-freya From 174a8b1b3e705ac350ebeb00299fbca94c916ddf Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 23 Sep 2022 06:21:31 +0900 Subject: fixes --- packages/backend/src/core/NotePiningService.ts | 2 +- packages/backend/src/core/NoteReadService.ts | 1 + packages/backend/src/core/PollService.ts | 2 +- packages/backend/src/core/QueryService.ts | 1 + packages/backend/src/core/QueueService.ts | 2 +- packages/backend/src/core/UserBlockingService.ts | 2 ++ packages/backend/src/core/UserFollowingService.ts | 1 + packages/backend/src/core/entities/NoteEntityService.ts | 4 ++-- packages/backend/src/core/entities/UserEntityService.ts | 3 ++- packages/backend/src/core/remote/activitypub/ApInboxService.ts | 1 + .../backend/src/core/remote/activitypub/ApRendererService.ts | 1 + .../backend/src/core/remote/activitypub/LdSignatureService.ts | 4 ++-- .../backend/src/core/remote/activitypub/misc/get-note-html.ts | 8 -------- packages/backend/src/queue/DbQueueProcessorsService.ts | 2 +- .../backend/src/queue/ObjectStorageQueueProcessorsService.ts | 4 ++-- .../src/queue/processors/DeleteAccountProcessorService.ts | 2 +- .../src/queue/processors/ExportFollowingProcessorService.ts | 2 +- .../src/queue/processors/ImportBlockingProcessorService.ts | 2 +- .../src/queue/processors/ImportFollowingProcessorService.ts | 2 +- .../src/queue/processors/ImportMutingProcessorService.ts | 3 +-- .../src/queue/processors/ImportUserListsProcessorService.ts | 2 +- packages/backend/src/queue/processors/InboxProcessorService.ts | 2 +- packages/backend/src/server/ActivityPubServerService.ts | 4 ++-- packages/backend/src/server/api/common/GetterService.ts | 3 +++ packages/backend/src/server/api/endpoints/announcements.ts | 2 +- packages/backend/src/server/api/endpoints/users/pages.ts | 2 +- packages/backend/src/server/api/endpoints/users/stats.ts | 10 ++++++---- 27 files changed, 39 insertions(+), 35 deletions(-) delete mode 100644 packages/backend/src/core/remote/activitypub/misc/get-note-html.ts (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts index b70c051efd..8c4a761ba6 100644 --- a/packages/backend/src/core/NotePiningService.ts +++ b/packages/backend/src/core/NotePiningService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { NotesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index b1572c631a..2c84e1d4d5 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -7,6 +7,7 @@ import type { Packed } from '@/misc/schema.js'; import type { Note } from '@/models/entities/Note.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; +import type { UsersRepository, NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository, FollowingsRepository, ChannelFollowingsRepository, AntennaNotesRepository } from '@/models/index.js'; import { UserEntityService } from './entities/UserEntityService.js'; import { NotificationService } from './NotificationService.js'; import { AntennaService } from './AntennaService.js'; diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index 1bb68f7804..e3e12b5320 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { NotesRepository, UsersRepository, BlockingsRepository } from '@/models/index.js'; +import type { NotesRepository, UsersRepository, BlockingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import { RelayService } from '@/core/RelayService.js'; import type { CacheableUser } from '@/models/entities/User.js'; diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index 1613f70c80..771adeaed5 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { User } from '@/models/entities/User.js'; +import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository } from '@/models/index.js'; import type { SelectQueryBuilder } from 'typeorm'; @Injectable() diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 12be57c7fb..d9ad26747f 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -24,7 +24,7 @@ export class QueueService { @Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue, ) {} - public deliver(user: ThinUser, content: IActivity, to: string | null) { + public deliver(user: ThinUser, content: IActivity | null, to: string | null) { if (content == null) return null; if (to == null) return null; diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 9efb021f64..5ff76b48a9 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -7,6 +7,8 @@ import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; import { DI } from '@/di-symbols.js'; +import logger from '@/logger.js'; +import type { UsersRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; import { UserEntityService } from './entities/UserEntityService.js'; import { WebhookService } from './WebhookService.js'; import { ApRendererService } from './remote/activitypub/ApRendererService.js'; diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index ff86d4343f..31e08c1366 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -12,6 +12,7 @@ import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { WebhookService } from '@/core/WebhookService.js'; import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { DI } from '@/di-symbols.js'; +import type { BlockingsRepository, FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import Logger from '../logger.js'; import { UserEntityService } from './entities/UserEntityService.js'; import { ApRendererService } from './remote/activitypub/ApRendererService.js'; diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index a9c63566b4..098f072a51 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -3,7 +3,6 @@ import { DataSource, In } from 'typeorm'; import * as mfm from 'mfm-js'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; -import type { Notes, Polls, PollVotes, DriveFiles, Channels, Followings, Users, NoteReactions } from '@/models/index.js'; import type { Config } from '@/config.js'; import type { Packed } from '@/misc/schema.js'; import { nyaize } from '@/misc/nyaize.js'; @@ -11,6 +10,7 @@ import { awaitAll } from '@/misc/prelude/await-all.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; import type { NoteReaction } from '@/models/entities/NoteReaction.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository, DriveFilesRepository } from '@/models/index.js'; import type { OnModuleInit } from '@nestjs/common'; import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { ReactionService } from '../ReactionService.js'; @@ -267,7 +267,7 @@ export class NoteEntityService implements OnModuleInit { let text = note.text; if (note.name && (note.url ?? note.uri)) { - text = `【${note.name}】\n${(note.text || '').trim()}\n\n${note.url ?? note.uri}`; + text = `【${note.name}】\n${(note.text ?? '').trim()}\n\n${note.url ?? note.uri}`; } const channel = note.channelId diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 539a9cc9eb..c691eaebdf 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -1,5 +1,5 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; -import { EntityRepository, Repository, In, Not } from 'typeorm'; +import { In, Not } from 'typeorm'; import Ajv from 'ajv'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; @@ -12,6 +12,7 @@ import { Cache } from '@/misc/cache.js'; import type { Instance } from '@/models/entities/Instance.js'; import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js'; +import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository } from '@/models/index.js'; import type { OnModuleInit } from '@nestjs/common'; import type { AntennaService } from '../AntennaService.js'; import type { CustomEmojiService } from '../CustomEmojiService.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApInboxService.ts b/packages/backend/src/core/remote/activitypub/ApInboxService.ts index a3cb08063d..7525aa8d47 100644 --- a/packages/backend/src/core/remote/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/remote/activitypub/ApInboxService.ts @@ -21,6 +21,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { QueueService } from '@/core/QueueService.js'; import { MessagingService } from '@/core/MessagingService.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, MessagingMessagesRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js'; import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; import { ApNoteService } from './models/ApNoteService.js'; import { ApLoggerService } from './ApLoggerService.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApRendererService.ts b/packages/backend/src/core/remote/activitypub/ApRendererService.ts index 6058929d35..cf10c83f39 100644 --- a/packages/backend/src/core/remote/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/remote/activitypub/ApRendererService.ts @@ -20,6 +20,7 @@ import { MfmService } from '@/core/MfmService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import type { UserKeypair } from '@/models/entities/UserKeypair.js'; +import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, EmojisRepository, PollsRepository } from '@/models/index.js'; import { LdSignatureService } from './LdSignatureService.js'; import { ApMfmService } from './ApMfmService.js'; import type { IActivity } from './type.js'; diff --git a/packages/backend/src/core/remote/activitypub/LdSignatureService.ts b/packages/backend/src/core/remote/activitypub/LdSignatureService.ts index ea0d2daf3d..b9c57f4ba6 100644 --- a/packages/backend/src/core/remote/activitypub/LdSignatureService.ts +++ b/packages/backend/src/core/remote/activitypub/LdSignatureService.ts @@ -81,12 +81,12 @@ class LdSignature { delete transformedOptions['id']; delete transformedOptions['signatureValue']; const canonizedOptions = await this.normalize(transformedOptions); - const optionsHash = this.sha256(canonizedOptions); + const optionsHash = this.sha256(canonizedOptions.toString()); const transformedData = { ...data }; delete transformedData['signature']; const cannonidedData = await this.normalize(transformedData); if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`); - const documentHash = this.sha256(cannonidedData); + const documentHash = this.sha256(cannonidedData.toString()); const verifyData = `${optionsHash}${documentHash}`; return verifyData; } diff --git a/packages/backend/src/core/remote/activitypub/misc/get-note-html.ts b/packages/backend/src/core/remote/activitypub/misc/get-note-html.ts deleted file mode 100644 index af23a04a71..0000000000 --- a/packages/backend/src/core/remote/activitypub/misc/get-note-html.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as mfm from 'mfm-js'; -import type { Note } from '@/models/entities/Note.js'; -import { toHtml } from '../../../../mfm/to-html.js'; - -export default function(note: Note) { - if (!note.text) return ''; - return toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); -} diff --git a/packages/backend/src/queue/DbQueueProcessorsService.ts b/packages/backend/src/queue/DbQueueProcessorsService.ts index 7622ab8800..f79100e6ce 100644 --- a/packages/backend/src/queue/DbQueueProcessorsService.ts +++ b/packages/backend/src/queue/DbQueueProcessorsService.ts @@ -53,7 +53,7 @@ export class DbQueueProcessorsService { importBlocking: (job, done) => this.importBlockingProcessorService.process(job, done), importUserLists: (job, done) => this.importUserListsProcessorService.process(job, done), importCustomEmojis: (job, done) => this.importCustomEmojisProcessorService.process(job, done), - deleteAccount: (job, done) => this.deleteAccountProcessorService.process(job, done), + deleteAccount: (job) => this.deleteAccountProcessorService.process(job), } as Record>>; for (const [k, v] of Object.entries(jobs)) { diff --git a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts index 659e9b8e42..e031581488 100644 --- a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts +++ b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts @@ -19,8 +19,8 @@ export class ObjectStorageQueueProcessorsService { public start(q: Bull.Queue) { const jobs = { - deleteFile: (job, done) => this.deleteFileProcessorService.process(job, done), - cleanRemoteFiles: (job, done) => this.cleanRemoteFilesProcessorService.process(job, done), + deleteFile: (job) => this.deleteFileProcessorService.process(job), + cleanRemoteFiles: (job) => this.cleanRemoteFilesProcessorService.process(job), } as Record>>; for (const [k, v] of Object.entries(jobs)) { diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index 4d6ed2008a..5e4c8bdd69 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, UserProfilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index eac9c6925e..1e3fba06b5 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -3,7 +3,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, MoreThan, Not } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import type { FollowingsRepository, MutingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository, MutingsRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index 12f77638bb..6c6789cff4 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { BlockingsRepository, DriveFilesRepository } from '@/models/index.js'; +import type { UsersRepository, BlockingsRepository, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index f649014399..9f2e5e801a 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index f004f2d64b..62ad3b5c88 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -1,13 +1,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; import { DownloadService } from '@/core/DownloadService.js'; -import type { UserFollowingService } from '@/core/UserFollowingService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index 168f850718..f214d59e1c 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; +import type { UsersRepository, DriveFilesRepository, UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index ad72263a8d..e8cd7dcaca 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -3,7 +3,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import httpSignature from '@peertube/http-signature'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { InstancesRepository, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { MetaService } from '@/core/MetaService.js'; diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index b2fcf9f81a..2e7bd4dcb2 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -4,7 +4,7 @@ import json from 'koa-json-body'; import httpSignature from '@peertube/http-signature'; import { Brackets, In, IsNull, LessThan, Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; +import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; import * as url from '@/misc/prelude/url.js'; import type { Config } from '@/config.js'; import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; @@ -73,7 +73,7 @@ export class ActivityPubServerService { */ private async packActivity(note: Note): Promise { if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { - const renote = await Notes.findOneByOrFail({ id: note.renoteId }); + const renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId }); return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note); } diff --git a/packages/backend/src/server/api/common/GetterService.ts b/packages/backend/src/server/api/common/GetterService.ts index a6b60d1f5a..70ab46ec35 100644 --- a/packages/backend/src/server/api/common/GetterService.ts +++ b/packages/backend/src/server/api/common/GetterService.ts @@ -4,6 +4,7 @@ import type { NotesRepository, UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; @Injectable() export class GetterService { @@ -13,6 +14,8 @@ export class GetterService { @Inject(DI.notesRepository) private notesRepository: NotesRepository, + + private userEntityService: UserEntityService, ) { } diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index 74168481f6..79788be4e2 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; -import type { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models'; +import type { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models/index.js'; export const meta = { tags: ['meta'], diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index e007aa57b2..a105103f16 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; -import type { PagesRepository } from '@/models'; +import type { PagesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index 71f4ca0cfa..7479793afe 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -3,6 +3,7 @@ import { awaitAll } from '@/misc/prelude/await-all.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, DriveFilesRepository, NoteReactionsRepository, PageLikesRepository, NoteFavoritesRepository, PollVotesRepository } from '@/models/index.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -217,10 +218,11 @@ export default class extends Endpoint { driveUsage: this.driveFileEntityService.calcDriveUsageOf(user), }); - result.followingCount = result.localFollowingCount + result.remoteFollowingCount; - result.followersCount = result.localFollowersCount + result.remoteFollowersCount; - - return result; + return { + ...result, + followingCount: result.localFollowingCount + result.remoteFollowingCount, + followersCount: result.localFollowersCount + result.remoteFollowersCount, + }; }); } } -- cgit v1.2.3-freya From 417f52359dd4e708478ed43e2e07efb09ecf1fd2 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 06:45:44 +0900 Subject: fixes --- packages/backend/src/boot/master.ts | 3 +- packages/backend/src/core/AntennaService.ts | 2 +- .../queue/ObjectStorageQueueProcessorsService.ts | 2 +- .../backend/src/server/NodeinfoServerService.ts | 2 +- .../src/server/api/StreamingApiServerService.ts | 2 +- .../api/endpoints/admin/abuse-user-reports.ts | 2 + .../server/api/endpoints/admin/accounts/delete.ts | 2 + .../api/endpoints/admin/drive-capacity-override.ts | 2 + .../src/server/api/endpoints/admin/drive/files.ts | 2 + .../server/api/endpoints/admin/drive/show-file.ts | 8 ++- .../src/server/api/endpoints/admin/emoji/list.ts | 2 + .../api/endpoints/admin/queue/deliver-delayed.ts | 2 +- .../api/endpoints/admin/queue/inbox-delayed.ts | 2 +- .../src/server/api/endpoints/admin/queue/stats.ts | 2 +- .../src/server/api/endpoints/admin/suspend-user.ts | 2 + .../src/server/api/endpoints/antennas/notes.ts | 2 +- .../src/server/api/endpoints/channels/follow.ts | 2 +- packages/backend/src/server/api/endpoints/stats.ts | 2 +- .../server/api/integration/DiscordServerService.ts | 66 +++++++++++----------- .../server/api/integration/GithubServerService.ts | 48 ++++++++-------- packages/backend/tsconfig.json | 1 + 21 files changed, 88 insertions(+), 70 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index ec09b4350b..fc07510ffb 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -119,8 +119,7 @@ function loadConfigBoot(): Config { if (typeof exception === 'string') { configLogger.error(exception); process.exit(1); - } - if (exception.code === 'ENOENT') { + } else if ((exception as any).code === 'ENOENT') { configLogger.error('Configuration file not found', null, true); process.exit(1); } diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 0146f959e6..af76767f31 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -60,7 +60,7 @@ export class AntennaService implements OnApplicationShutdown { this.redisSubscriber.off('message', this.onRedisMessage); } - private async onRedisMessage(_, data) { + private async onRedisMessage(_: string, data: string): Promise { const obj = JSON.parse(data); if (obj.channel === 'internal') { diff --git a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts index e031581488..097e4448a5 100644 --- a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts +++ b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts @@ -20,7 +20,7 @@ export class ObjectStorageQueueProcessorsService { public start(q: Bull.Queue) { const jobs = { deleteFile: (job) => this.deleteFileProcessorService.process(job), - cleanRemoteFiles: (job) => this.cleanRemoteFilesProcessorService.process(job), + cleanRemoteFiles: (job, done) => this.cleanRemoteFilesProcessorService.process(job, done), } as Record>>; for (const [k, v] of Object.entries(jobs)) { diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index b07dc4cf8f..ef4ec74a35 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -118,7 +118,7 @@ export class NodeinfoServerService { router.get(nodeinfo2_0path, async ctx => { const base = await cache.fetch(null, () => nodeinfo2()); - delete base.software.repository; + delete (base as any).software.repository; ctx.body = { version: '2.0', ...base }; ctx.set('Cache-Control', 'public, max-age=600'); diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index 8a6906e5fe..46eaf8566e 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -72,7 +72,7 @@ export class StreamingApiServerService { const ev = new EventEmitter(); - async function onRedisMessage(_: string, data: string) { + async function onRedisMessage(_: string, data: string): Promise { const parsed = JSON.parse(data); ev.emit(parsed.channel, parsed.message); } diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 30183ed88b..9bba16166f 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -3,6 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { AbuseUserReportsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; +import { AbuseUserReportEntityService } from '@/core/entities/AbuseUserReportEntityService.js'; export const meta = { tags: ['admin'], @@ -93,6 +94,7 @@ export default class extends Endpoint { @Inject(DI.abuseUserReportsRepository) private abuseUserReportsRepository: AbuseUserReportsRepository, + private abuseUserReportEntityService: AbuseUserReportEntityService, private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts index dc2d499191..b7081987ca 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -5,6 +5,7 @@ import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; export const meta = { tags: ['admin'], @@ -28,6 +29,7 @@ export default class extends Endpoint { @Inject(DI.usersRepository) private usersRepository: UsersRepository, + private userEntityService: UserEntityService, private queueService: QueueService, private globalEventService: GlobalEventService, private userSuspendService: UserSuspendService, diff --git a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts index f4d39cd872..665e2a8cce 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts @@ -3,6 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository } from '@/models/index.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { DI } from '@/di-symbols.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; export const meta = { tags: ['admin'], @@ -27,6 +28,7 @@ export default class extends Endpoint { @Inject(DI.usersRepository) private usersRepository: UsersRepository, + private userEntityService: UserEntityService, private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index 2459a479ab..53a37cb691 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -3,6 +3,7 @@ import type { DriveFilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; export const meta = { tags: ['admin'], @@ -47,6 +48,7 @@ export default class extends Endpoint { @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + private driveFileEntityService: DriveFileEntityService, private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index c17f67cf2b..25d13d13a3 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -192,12 +192,14 @@ export default class extends Endpoint { throw new ApiError(meta.errors.noSuchFile); } + const res = file as Partial; + if (!me.isAdmin) { - delete file.requestIp; - delete file.requestHeaders; + delete res.requestIp; + delete res.requestHeaders; } - return file; + return res; }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index e50f924044..8624229db8 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -4,6 +4,7 @@ import type { EmojisRepository } from '@/models/index.js'; import type { Emoji } from '@/models/entities/Emoji.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; export const meta = { tags: ['admin'], @@ -71,6 +72,7 @@ export default class extends Endpoint { @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, + private emojiEntityService: EmojiEntityService, private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts index 4b5be70d56..526efa9f9d 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts @@ -1,7 +1,7 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DeliverQueue } from '@/core/queue/QueueModule.js'; +import type { DeliverQueue } from '@/core/queue/QueueModule.js'; export const meta = { tags: ['admin'], diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts index 715974e917..b8934428c3 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts @@ -1,7 +1,7 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { InboxQueue } from '@/core/queue/QueueModule.js'; +import type { InboxQueue } from '@/core/queue/QueueModule.js'; export const meta = { tags: ['admin'], diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts index f2ca81a8da..605ea3d042 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/queue/QueueModule.js'; +import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/queue/QueueModule.js'; export const meta = { tags: ['admin'], diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index d30facd125..53de8d9495 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -7,6 +7,7 @@ import { ModerationLogService } from '@/core/ModerationLogService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; export const meta = { tags: ['admin'], @@ -36,6 +37,7 @@ export default class extends Endpoint { @Inject(DI.notificationsRepository) private notificationsRepository: NotificationsRepository, + private userEntityService: UserEntityService, private userFollowingService: UserFollowingService, private userSuspendService: UserSuspendService, private moderationLogService: ModerationLogService, diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index fb3c713154..fbb5acf617 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { NotesRepository, AntennaNotesRepository } from '@/models/index.js'; +import type { NotesRepository, AntennaNotesRepository, AntennasRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteReadService } from '@/core/NoteReadService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index 871d3927bc..91693918f2 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -44,7 +44,7 @@ export default class extends Endpoint { private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { - const channel = await Channels.findOneBy({ + const channel = await this.channelsRepository.findOneBy({ id: ps.channelId, }); diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index 3adf0a4bb8..96b22b0261 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; -import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js'; +import type { InstancesRepository, NoteReactionsRepository, NotesRepository, UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index 79ce9c8973..8e4f595ada 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -159,6 +159,7 @@ export class DiscordServerService { const { redirect_uri, state } = await new Promise((res, rej) => { this.redisClient.get(sessid, async (_, state) => { + if (state == null) throw new Error('empty state'); res(JSON.parse(state)); }); }); @@ -169,22 +170,22 @@ export class DiscordServerService { } 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 this.httpRequestService.getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { 'Authorization': `Bearer ${accessToken}`, @@ -230,6 +231,7 @@ export class DiscordServerService { const { redirect_uri, state } = await new Promise((res, rej) => { this.redisClient.get(userToken, async (_, state) => { + if (state == null) throw new Error('empty state'); res(JSON.parse(state)); }); }); @@ -240,22 +242,22 @@ export class DiscordServerService { } 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 this.httpRequestService.getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { 'Authorization': `Bearer ${accessToken}`, diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index 7fb31c39a2..2ccea36dc4 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -157,6 +157,7 @@ export class GithubServerService { const { redirect_uri, state } = await new Promise((res, rej) => { this.redisClient.get(sessid, async (_, state) => { + if (state == null) throw new Error('empty state'); res(JSON.parse(state)); }); }); @@ -167,17 +168,17 @@ export class GithubServerService { } 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 this.httpRequestService.getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { 'Authorization': `bearer ${accessToken}`, @@ -208,6 +209,7 @@ export class GithubServerService { const { redirect_uri, state } = await new Promise((res, rej) => { this.redisClient.get(userToken, async (_, state) => { + if (state == null) throw new Error('empty state'); res(JSON.parse(state)); }); }); @@ -218,18 +220,18 @@ export class GithubServerService { } 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 this.httpRequestService.getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { 'Authorization': `bearer ${accessToken}`, diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index 0c95925744..2c8adf7708 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -18,6 +18,7 @@ "strict": true, "strictNullChecks": true, "strictPropertyInitialization": false, + "skipLibCheck": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "resolveJsonModule": true, -- cgit v1.2.3-freya From c8f6bc0dabcbe119533ef8f0edfb35d9405818c8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 07:12:11 +0900 Subject: fixes --- packages/backend/src/core/DriveService.ts | 2 +- .../src/core/FetchInstanceMetadataService.ts | 33 +++++++++++++--------- packages/backend/src/core/MetaService.ts | 2 +- packages/backend/src/core/NoteCreateService.ts | 5 +--- packages/backend/src/core/SignupService.ts | 2 +- .../src/core/TwoFactorAuthenticationService.ts | 2 ++ packages/backend/src/core/UserBlockingService.ts | 8 +++++- packages/backend/src/core/UserCacheService.ts | 2 +- packages/backend/src/core/WebhookService.ts | 2 +- .../src/server/api/endpoints/i/authorized-apps.ts | 4 ++- 10 files changed, 37 insertions(+), 25 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 643c51c37d..e0bdd29c0f 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -61,7 +61,7 @@ type AddFileArgs = { type UploadFromUrlArgs = { url: string; - user: { id: User['id']; host: User['host'] } | null; + user: { id: User['id']; host: User['host']; driveCapacityOverrideMb: User['driveCapacityOverrideMb'] } | null; folderId?: DriveFolder['id'] | null; uri?: string | null; sensitive?: boolean; diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 9a51ed0c0a..184404123c 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -13,20 +13,21 @@ import { HttpRequestService } from './HttpRequestService.js'; import type { DOMWindow } from 'jsdom'; type NodeInfo = { - openRegistrations?: any; + openRegistrations?: unknown; software?: { - name?: any; - version?: any; + name?: unknown; + version?: unknown; }; metadata?: { - name?: any; - nodeName?: any; - nodeDescription?: any; - description?: any; + name?: unknown; + nodeName?: unknown; + nodeDescription?: unknown; + description?: unknown; maintainer?: { - name?: any; - email?: any; + name?: unknown; + email?: unknown; }; + themeColor?: unknown; }; }; @@ -81,7 +82,7 @@ export class FetchInstanceMetadataService { } as Record; if (info) { - updates.softwareName = info.software?.name.toLowerCase(); + updates.softwareName = typeof info.software?.name === 'string' ? info.software.name.toLowerCase() : '?'; updates.softwareVersion = info.software?.version; updates.openRegistrations = info.openRegistrations; updates.maintainerName = info.metadata ? info.metadata.maintainer ? (info.metadata.maintainer.name ?? null) : null : null; @@ -238,8 +239,10 @@ export class FetchInstanceMetadataService { private async getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (info && info.metadata) { - if (info.metadata.nodeName || info.metadata.name) { - return info.metadata.nodeName ?? info.metadata.name; + if (typeof info.metadata.nodeName === 'string') { + return info.metadata.nodeName; + } else if (typeof info.metadata.name === 'string') { + return info.metadata.name; } } @@ -260,8 +263,10 @@ export class FetchInstanceMetadataService { private async getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (info && info.metadata) { - if (info.metadata.nodeDescription || info.metadata.description) { - return info.metadata.nodeDescription ?? info.metadata.description; + if (typeof info.metadata.nodeDescription === 'string') { + return info.metadata.nodeDescription; + } else if (typeof info.metadata.description === 'string') { + return info.metadata.description; } } diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index 1744f64217..c3d41bfccb 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -34,7 +34,7 @@ export class MetaService implements OnApplicationShutdown { this.redisSubscriber.on('message', this.onMessage); } - private async onMessage(_, data): Promise { + private async onMessage(_: string, data: string): Promise { const obj = JSON.parse(data); if (obj.channel === 'internal') { diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 6e5cce8f62..a23e105674 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -534,7 +534,6 @@ export class NoteCreateService { }); const nm = new NotificationManager(this.mutingsRepository, this.createNotificationService, user, note); - const nmRelatedPromises = []; await this.createMentionedEvents(mentionedUsers, note, nm); @@ -583,9 +582,7 @@ export class NoteCreateService { } } - Promise.all(nmRelatedPromises).then(() => { - nm.deliver(); - }); + nm.deliver(); //#region AP deliver if (this.userEntityService.isLocalUser(user)) { diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 8b72d73af6..2239d5fd83 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -3,7 +3,7 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import { DataSource, IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UsedUsernamesRepository } from '@/models/index.js'; +import type { UsedUsernamesRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import { User } from '@/models/entities/User.js'; import { UserProfile } from '@/models/entities/UserProfile.js'; diff --git a/packages/backend/src/core/TwoFactorAuthenticationService.ts b/packages/backend/src/core/TwoFactorAuthenticationService.ts index 0962f88a7c..54e87ec36f 100644 --- a/packages/backend/src/core/TwoFactorAuthenticationService.ts +++ b/packages/backend/src/core/TwoFactorAuthenticationService.ts @@ -67,6 +67,8 @@ function verifyCertificateChain(certificates: string[]) { const CACert = i + 1 >= certificates.length ? Cert : certificates[i + 1]; const certStruct = jsrsasign.ASN1HEX.getTLVbyList(certificate.hex!, 0, [0]); + if (certStruct == null) throw new Error('certStruct is null'); + const algorithm = certificate.getSignatureAlgorithmField(); const signatureHex = certificate.getSignatureValueHex(); diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 5ff76b48a9..b7a434684e 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -9,12 +9,16 @@ import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; import { DI } from '@/di-symbols.js'; import logger from '@/logger.js'; import type { UsersRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import Logger from '@/logger.js'; import { UserEntityService } from './entities/UserEntityService.js'; import { WebhookService } from './WebhookService.js'; import { ApRendererService } from './remote/activitypub/ApRendererService.js'; +import { LoggerService } from './LoggerService.js'; @Injectable() export class UserBlockingService { + private logger: Logger; + constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -41,7 +45,9 @@ export class UserBlockingService { private webhookService: WebhookService, private apRendererService: ApRendererService, private perUserFollowingChart: PerUserFollowingChart, + private loggerService: LoggerService, ) { + this.logger = this.loggerService.getLogger('user-block'); } public async block(blocker: User, blockee: User) { @@ -181,7 +187,7 @@ export class UserBlockingService { }); if (blocking == null) { - logger.warn('ブロック解除がリクエストされましたがブロックしていませんでした'); + this.logger.warn('ブロック解除がリクエストされましたがブロックしていませんでした'); return; } diff --git a/packages/backend/src/core/UserCacheService.ts b/packages/backend/src/core/UserCacheService.ts index 666bef3f49..b7166010ee 100644 --- a/packages/backend/src/core/UserCacheService.ts +++ b/packages/backend/src/core/UserCacheService.ts @@ -33,7 +33,7 @@ export class UserCacheService implements OnApplicationShutdown { this.redisSubscriber.on('message', this.onMessage); } - private async onMessage(_, data) { + private async onMessage(_: string, data: string): Promise { const obj = JSON.parse(data); if (obj.channel === 'internal') { diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index 347b2b16c4..1a690314f8 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -32,7 +32,7 @@ export class WebhookService implements OnApplicationShutdown { return this.webhooks; } - private async onMessage(_, data) { + private async onMessage(_: string, data: string): Promise { const obj = JSON.parse(data); if (obj.channel === 'internal') { diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts index d0bdb5695b..f5a946eb91 100644 --- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts +++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import { IsNull, Not } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { AccessTokensRepository } from '@/models/index.js'; import { AppEntityService } from '@/core/entities/AppEntityService.js'; @@ -34,6 +35,7 @@ export default class extends Endpoint { const tokens = await this.accessTokensRepository.find({ where: { userId: me.id, + appId: Not(IsNull()), }, take: ps.limit, skip: ps.offset, @@ -42,7 +44,7 @@ export default class extends Endpoint { }, }); - return await Promise.all(tokens.map(token => this.appEntityService.pack(token.appId, me, { + return await Promise.all(tokens.map(token => this.appEntityService.pack(token.appId!, me, { detail: true, }))); }); -- cgit v1.2.3-freya From 786f1d8be841f19ecefe200a13974c15ab13606d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 07:13:01 +0900 Subject: remove unused files --- .../src/server/api/common/inject-featured.ts | 53 ---------------------- .../backend/src/server/api/common/inject-promo.ts | 33 -------------- 2 files changed, 86 deletions(-) delete mode 100644 packages/backend/src/server/api/common/inject-featured.ts delete mode 100644 packages/backend/src/server/api/common/inject-promo.ts (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/common/inject-featured.ts b/packages/backend/src/server/api/common/inject-featured.ts deleted file mode 100644 index d59bec33ab..0000000000 --- a/packages/backend/src/server/api/common/inject-featured.ts +++ /dev/null @@ -1,53 +0,0 @@ -import rndstr from 'rndstr'; -import type { Note } from '@/models/entities/Note.js'; -import type { User } from '@/models/entities/User.js'; - -// TODO: リアクション、Renote、返信などをしたノートは除外する - -export async function injectFeatured(timeline: Note[], user?: User | null) { - if (timeline.length < 5) return; - - if (user) { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - if (!profile.injectFeaturedNote) return; - } - - const max = 30; - const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで - - const query = Notes.createQueryBuilder('note') - .addSelect('note.score') - .where('note.userHost IS NULL') - .andWhere('note.score > 0') - .andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) }) - .andWhere('note.visibility = \'public\'') - .innerJoinAndSelect('note.user', 'user'); - - if (user) { - query.andWhere('note.userId != :userId', { userId: user.id }); - - generateMutedUserQuery(query, user); - generateBlockedUserQuery(query, user); - - const reactionQuery = NoteReactions.createQueryBuilder('reaction') - .select('reaction.noteId') - .where('reaction.userId = :userId', { userId: user.id }); - - query.andWhere(`note.id NOT IN (${ reactionQuery.getQuery() })`); - } - - const notes = await query - .orderBy('note.score', 'DESC') - .take(max) - .getMany(); - - if (notes.length === 0) return; - - // Pick random one - const featured = notes[Math.floor(Math.random() * notes.length)]; - - (featured as any)._featuredId_ = rndstr('a-z0-9', 8); - - // Inject featured - timeline.splice(3, 0, featured); -} diff --git a/packages/backend/src/server/api/common/inject-promo.ts b/packages/backend/src/server/api/common/inject-promo.ts deleted file mode 100644 index fe02897509..0000000000 --- a/packages/backend/src/server/api/common/inject-promo.ts +++ /dev/null @@ -1,33 +0,0 @@ -import rndstr from 'rndstr'; -import type { Note } from '@/models/entities/Note.js'; -import type { User } from '@/models/entities/User.js'; - -export async function injectPromo(timeline: Note[], user?: User | null) { - if (timeline.length < 5) return; - - // TODO: readやexpireフィルタはクエリ側でやる - - const reads = user ? await PromoReads.findBy({ - userId: user.id, - }) : []; - - let promos = await PromoNotes.find(); - - promos = promos.filter(n => n.expiresAt.getTime() > Date.now()); - promos = promos.filter(n => !reads.map(r => r.noteId).includes(n.noteId)); - - if (promos.length === 0) return; - - // Pick random promo - const promo = promos[Math.floor(Math.random() * promos.length)]; - - const note = await Notes.findOneByOrFail({ id: promo.noteId }); - - // Join - note.user = await Users.findOneByOrFail({ id: note.userId }); - - (note as any)._prId_ = rndstr('a-z0-9', 8); - - // Inject promo - timeline.splice(3, 0, note); -} -- cgit v1.2.3-freya From 614b11951b6116a6485bee85126858189ecaa681 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 07:15:16 +0900 Subject: refactor --- packages/backend/src/server/ServerModule.ts | 2 +- packages/backend/src/server/api/EndpointsModule.ts | 2 +- packages/backend/src/server/api/GetterService.ts | 74 ++++++++++++++++++++++ .../backend/src/server/api/common/GetterService.ts | 74 ---------------------- .../src/server/api/endpoints/admin/promo/create.ts | 2 +- .../src/server/api/endpoints/blocking/create.ts | 2 +- .../src/server/api/endpoints/blocking/delete.ts | 2 +- .../src/server/api/endpoints/clips/add-note.ts | 2 +- .../src/server/api/endpoints/clips/remove-note.ts | 2 +- .../api/endpoints/federation/update-remote-user.ts | 2 +- .../src/server/api/endpoints/following/create.ts | 2 +- .../src/server/api/endpoints/following/delete.ts | 2 +- .../server/api/endpoints/following/invalidate.ts | 2 +- .../api/endpoints/following/requests/accept.ts | 2 +- .../api/endpoints/following/requests/cancel.ts | 5 +- .../api/endpoints/following/requests/reject.ts | 2 +- .../src/server/api/endpoints/messaging/messages.ts | 5 +- .../api/endpoints/messaging/messages/create.ts | 2 +- .../src/server/api/endpoints/mute/create.ts | 2 +- .../src/server/api/endpoints/mute/delete.ts | 2 +- .../src/server/api/endpoints/notes/clips.ts | 2 +- .../src/server/api/endpoints/notes/conversation.ts | 2 +- .../src/server/api/endpoints/notes/delete.ts | 2 +- .../server/api/endpoints/notes/favorites/create.ts | 2 +- .../server/api/endpoints/notes/favorites/delete.ts | 2 +- .../src/server/api/endpoints/notes/polls/vote.ts | 2 +- .../server/api/endpoints/notes/reactions/create.ts | 2 +- .../server/api/endpoints/notes/reactions/delete.ts | 2 +- .../src/server/api/endpoints/notes/renotes.ts | 2 +- .../backend/src/server/api/endpoints/notes/show.ts | 2 +- .../api/endpoints/notes/thread-muting/create.ts | 2 +- .../api/endpoints/notes/thread-muting/delete.ts | 2 +- .../src/server/api/endpoints/notes/translate.ts | 2 +- .../src/server/api/endpoints/notes/unrenote.ts | 2 +- .../backend/src/server/api/endpoints/promo/read.ts | 2 +- .../users/get-frequently-replied-users.ts | 2 +- .../server/api/endpoints/users/groups/invite.ts | 2 +- .../src/server/api/endpoints/users/groups/pull.ts | 2 +- .../server/api/endpoints/users/groups/transfer.ts | 2 +- .../src/server/api/endpoints/users/lists/pull.ts | 2 +- .../src/server/api/endpoints/users/lists/push.ts | 2 +- .../src/server/api/endpoints/users/notes.ts | 2 +- .../src/server/api/endpoints/users/report-abuse.ts | 2 +- 43 files changed, 117 insertions(+), 119 deletions(-) create mode 100644 packages/backend/src/server/api/GetterService.ts delete mode 100644 packages/backend/src/server/api/common/GetterService.ts (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts index f05eda1cb8..474edafe41 100644 --- a/packages/backend/src/server/ServerModule.ts +++ b/packages/backend/src/server/ServerModule.ts @@ -7,7 +7,7 @@ import { MediaProxyServerService } from './MediaProxyServerService.js'; import { NodeinfoServerService } from './NodeinfoServerService.js'; import { ServerService } from './ServerService.js'; import { WellKnownServerService } from './WellKnownServerService.js'; -import { GetterService } from './api/common/GetterService.js'; +import { GetterService } from './api/GetterService.js'; import { DiscordServerService } from './api/integration/DiscordServerService.js'; import { GithubServerService } from './api/integration/GithubServerService.js'; import { TwitterServerService } from './api/integration/TwitterServerService.js'; diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index d2dfd78fd4..e41ed388b4 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -313,7 +313,7 @@ import * as ep___users_show from './endpoints/users/show.js'; import * as ep___users_stats from './endpoints/users/stats.js'; import * as ep___fetchRss from './endpoints/fetch-rss.js'; import * as ep___admin_driveCapOverride from './endpoints/admin/drive-capacity-override.js'; -import { GetterService } from './common/GetterService.js'; +import { GetterService } from './GetterService.js'; import { ApiLoggerService } from './ApiLoggerService.js'; import type { Provider } from '@nestjs/common'; diff --git a/packages/backend/src/server/api/GetterService.ts b/packages/backend/src/server/api/GetterService.ts new file mode 100644 index 0000000000..70ab46ec35 --- /dev/null +++ b/packages/backend/src/server/api/GetterService.ts @@ -0,0 +1,74 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { NotesRepository, UsersRepository } from '@/models/index.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import type { User } from '@/models/entities/User.js'; +import type { Note } from '@/models/entities/Note.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; + +@Injectable() +export class GetterService { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private userEntityService: UserEntityService, + ) { + } + + /** + * Get note for API processing + */ + public async getNote(noteId: Note['id']) { + const note = await this.notesRepository.findOneBy({ id: noteId }); + + if (note == null) { + throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); + } + + return note; + } + + /** + * Get user for API processing + */ + public async getUser(userId: User['id']) { + const user = await this.usersRepository.findOneBy({ id: userId }); + + if (user == null) { + throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.'); + } + + return user; + } + + /** + * Get remote user for API processing + */ + public async getRemoteUser(userId: User['id']) { + const user = await this.getUser(userId); + + if (!this.userEntityService.isRemoteUser(user)) { + throw new Error('user is not a remote user'); + } + + return user; + } + + /** + * Get local user for API processing + */ + public async getLocalUser(userId: User['id']) { + const user = await this.getUser(userId); + + if (!this.userEntityService.isLocalUser(user)) { + throw new Error('user is not a local user'); + } + + return user; + } +} + diff --git a/packages/backend/src/server/api/common/GetterService.ts b/packages/backend/src/server/api/common/GetterService.ts deleted file mode 100644 index 70ab46ec35..0000000000 --- a/packages/backend/src/server/api/common/GetterService.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { DI } from '@/di-symbols.js'; -import type { NotesRepository, UsersRepository } from '@/models/index.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import type { User } from '@/models/entities/User.js'; -import type { Note } from '@/models/entities/Note.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; - -@Injectable() -export class GetterService { - constructor( - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - private userEntityService: UserEntityService, - ) { - } - - /** - * Get note for API processing - */ - public async getNote(noteId: Note['id']) { - const note = await this.notesRepository.findOneBy({ id: noteId }); - - if (note == null) { - throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); - } - - return note; - } - - /** - * Get user for API processing - */ - public async getUser(userId: User['id']) { - const user = await this.usersRepository.findOneBy({ id: userId }); - - if (user == null) { - throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.'); - } - - return user; - } - - /** - * Get remote user for API processing - */ - public async getRemoteUser(userId: User['id']) { - const user = await this.getUser(userId); - - if (!this.userEntityService.isRemoteUser(user)) { - throw new Error('user is not a remote user'); - } - - return user; - } - - /** - * Get local user for API processing - */ - public async getLocalUser(userId: User['id']) { - const user = await this.getUser(userId); - - if (!this.userEntityService.isLocalUser(user)) { - throw new Error('user is not a local user'); - } - - return user; - } -} - diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index 0cff6bae6a..bee1ffbaee 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { PromoNotesRepository } from '@/models/index.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index aa6d2ecf20..c468010bce 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -6,7 +6,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['account'], diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index 46a499943c..46dd26a45a 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -6,7 +6,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['account'], diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index 77d02815e0..a242124e6a 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -4,7 +4,7 @@ import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['account', 'notes', 'clips'], diff --git a/packages/backend/src/server/api/endpoints/clips/remove-note.ts b/packages/backend/src/server/api/endpoints/clips/remove-note.ts index 93805af089..55778c7ecb 100644 --- a/packages/backend/src/server/api/endpoints/clips/remove-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/remove-note.ts @@ -3,7 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['account', 'notes', 'clips'], diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts index 57497bbf62..30e77aab45 100644 --- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts +++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { ApPersonService } from '@/core/remote/activitypub/models/ApPersonService.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['federation'], diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index be322e2896..f879429372 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -7,7 +7,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['following', 'users'], diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index afb59dd2c2..4f12db1273 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -6,7 +6,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['following', 'users'], diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index e67e136ead..22304cacda 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -6,7 +6,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['following', 'users'], diff --git a/packages/backend/src/server/api/endpoints/following/requests/accept.ts b/packages/backend/src/server/api/endpoints/following/requests/accept.ts index 5f082c087a..dcb98485da 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/accept.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/accept.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts index 213b5ce32e..f39c4e3767 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts @@ -1,10 +1,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { FollowingsRepository } from '@/models/index.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/following/requests/reject.ts b/packages/backend/src/server/api/endpoints/following/requests/reject.ts index 663c659bf3..ab5706e8ef 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/reject.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/reject.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts index f563da3278..3673e252ae 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts @@ -1,15 +1,14 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository } from '@/models/index.js'; -import type { UserGroupsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UsersRepository, UserGroupsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; import { MessagingService } from '@/core/MessagingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['messaging'], diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts index f61662af75..00e65b4875 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts @@ -3,7 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { BlockingsRepository, UserGroupJoiningsRepository, DriveFilesRepository, UserGroupsRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import type { UserGroup } from '@/models/entities/UserGroup.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { MessagingService } from '@/core/MessagingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 3b4507281f..5ead470314 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -6,7 +6,7 @@ import type { Muting } from '@/models/entities/Muting.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['account'], diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts index 2fc5cee95e..612c4a4c04 100644 --- a/packages/backend/src/server/api/endpoints/mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/mute/delete.ts @@ -4,7 +4,7 @@ import type { MutingsRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['account'], diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index 579466d4fd..d5caec6e1d 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -5,7 +5,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['clips', 'notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index ddfee31525..5ecf7cf458 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -5,7 +5,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 5765dfe66f..3c6e7bf768 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -5,7 +5,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteDeleteService } from '@/core/NoteDeleteService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index edbd300e08..e742c1bb35 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { NoteFavoritesRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index 8f4f2b2b9e..bb3a7c501a 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import type { NoteFavoritesRepository } from '@/models/index.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 515f03dcce..6b3b062c15 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -4,7 +4,7 @@ import type { UsersRepository, BlockingsRepository, PollsRepository, PollVotesRe import type { IRemoteUser } from '@/models/entities/User.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { QueueService } from '@/core/QueueService.js'; import { PollService } from '@/core/PollService.js'; import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts index 2af734307d..839f893db2 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { ReactionService } from '@/core/ReactionService.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts index 31ed962922..cf90d7b5f6 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { ReactionService } from '@/core/ReactionService.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 97ef1a17ec..026a1baa3e 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -5,7 +5,7 @@ import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index 7849cfa401..6b1b84a18e 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -4,7 +4,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index b8ddf83f3c..140614d36e 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { NotesRepository, NoteThreadMutingsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { NoteReadService } from '@/core/NoteReadService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts index 54e9b4939f..30016d48bc 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { NoteThreadMutingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index 7a3daf741e..ec16965998 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -9,7 +9,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { MetaService } from '@/core/MetaService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index 7378c4b600..74e459b426 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -5,7 +5,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteDeleteService } from '@/core/NoteDeleteService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index a28229add3..90febdbce7 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -4,7 +4,7 @@ import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index 22c49860bd..09f6acde9c 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -6,7 +6,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['users'], diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index 127f4ca65b..2e040c0601 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -3,7 +3,7 @@ import type { UserGroupsRepository, UserGroupJoiningsRepository, UserGroupInvita import { IdService } from '@/core/IdService.js'; import type { UserGroupInvitation } from '@/models/entities/UserGroupInvitation.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index 3474f22c6e..409006b0b0 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index e0b2b13c7f..3130d98ed1 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index 7e54d33376..d2dd5731ee 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 06ea43d654..c3a1308286 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { UserListService } from '@/core/UserListService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index d2c9616f68..aab32cc58c 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -5,8 +5,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['users', 'notes'], diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index bb37dd2715..13badab727 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -8,7 +8,7 @@ import { MetaService } from '@/core/MetaService.js'; import { EmailService } from '@/core/EmailService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['users'], -- cgit v1.2.3-freya From 6eace8894a4ec818dbc729372de28fe766c4d8be Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 08:41:27 +0900 Subject: fixes --- packages/backend/src/core/chart/charts/test-grouped.ts | 2 +- packages/backend/src/core/chart/charts/test-intersection.ts | 2 +- packages/backend/src/core/chart/charts/test-unique.ts | 2 +- packages/backend/src/core/chart/charts/test.ts | 2 +- packages/backend/src/core/entities/GalleryLikeEntityService.ts | 2 +- packages/backend/src/core/remote/activitypub/ApInboxService.ts | 8 ++++++-- packages/backend/src/core/remote/activitypub/ApRendererService.ts | 2 +- packages/backend/src/core/remote/activitypub/ApResolverService.ts | 8 ++++---- packages/backend/src/core/remote/activitypub/type.ts | 3 ++- packages/backend/src/server/api/endpoints.ts | 4 ++-- 10 files changed, 20 insertions(+), 15 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/core/chart/charts/test-grouped.ts b/packages/backend/src/core/chart/charts/test-grouped.ts index 500e85f9f0..bc215f3942 100644 --- a/packages/backend/src/core/chart/charts/test-grouped.ts +++ b/packages/backend/src/core/chart/charts/test-grouped.ts @@ -20,7 +20,7 @@ export default class TestGroupedChart extends Chart { private db: DataSource, private appLockService: AppLockService, - private logger: Logger, + logger: Logger, ) { super(db, (k) => appLockService.getChartInsertLock(k), logger, name, schema, true); } diff --git a/packages/backend/src/core/chart/charts/test-intersection.ts b/packages/backend/src/core/chart/charts/test-intersection.ts index ff63e9976b..a074a7dded 100644 --- a/packages/backend/src/core/chart/charts/test-intersection.ts +++ b/packages/backend/src/core/chart/charts/test-intersection.ts @@ -18,7 +18,7 @@ export default class TestIntersectionChart extends Chart { private db: DataSource, private appLockService: AppLockService, - private logger: Logger, + logger: Logger, ) { super(db, (k) => appLockService.getChartInsertLock(k), logger, name, schema); } diff --git a/packages/backend/src/core/chart/charts/test-unique.ts b/packages/backend/src/core/chart/charts/test-unique.ts index 3be4b0df2b..4d3e2f2403 100644 --- a/packages/backend/src/core/chart/charts/test-unique.ts +++ b/packages/backend/src/core/chart/charts/test-unique.ts @@ -18,7 +18,7 @@ export default class TestUniqueChart extends Chart { private db: DataSource, private appLockService: AppLockService, - private logger: Logger, + logger: Logger, ) { super(db, (k) => appLockService.getChartInsertLock(k), logger, name, schema); } diff --git a/packages/backend/src/core/chart/charts/test.ts b/packages/backend/src/core/chart/charts/test.ts index 89f64c4c1d..72caf79e0f 100644 --- a/packages/backend/src/core/chart/charts/test.ts +++ b/packages/backend/src/core/chart/charts/test.ts @@ -20,7 +20,7 @@ export default class TestChart extends Chart { private db: DataSource, private appLockService: AppLockService, - private logger: Logger, + logger: Logger, ) { super(db, (k) => appLockService.getChartInsertLock(k), logger, name, schema); } diff --git a/packages/backend/src/core/entities/GalleryLikeEntityService.ts b/packages/backend/src/core/entities/GalleryLikeEntityService.ts index bf7c2b7f8d..7e599113cc 100644 --- a/packages/backend/src/core/entities/GalleryLikeEntityService.ts +++ b/packages/backend/src/core/entities/GalleryLikeEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { GalleryPosts, GalleryLikesRepository } from '@/models/index.js'; +import type { GalleryLikesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApInboxService.ts b/packages/backend/src/core/remote/activitypub/ApInboxService.ts index 7525aa8d47..baeeb1ad3e 100644 --- a/packages/backend/src/core/remote/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/remote/activitypub/ApInboxService.ts @@ -295,8 +295,9 @@ export class ApInboxService { let renote; try { renote = await this.apNoteService.resolveNote(targetUri); + if (renote == null) throw new Error('announce target is null'); } catch (err) { - // 対象が4xxならスキップ + // 対象が4xxならスキップ if (err instanceof StatusError) { if (err.isClientError) { this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`); @@ -308,7 +309,10 @@ export class ApInboxService { throw err; } - if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) return 'skip: invalid actor for this activity'; + if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) { + this.logger.warn('skip: invalid actor for this activity'); + return; + } this.logger.info(`Creating the (Re)Note: ${uri}`); diff --git a/packages/backend/src/core/remote/activitypub/ApRendererService.ts b/packages/backend/src/core/remote/activitypub/ApRendererService.ts index cf10c83f39..a401b7424f 100644 --- a/packages/backend/src/core/remote/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/remote/activitypub/ApRendererService.ts @@ -243,7 +243,7 @@ export class ApRendererService { }; } - public async renderLike(noteReaction: NoteReaction, note: Note) { + public async renderLike(noteReaction: NoteReaction, note: { uri: string | null }) { const reaction = noteReaction.reaction; const object = { diff --git a/packages/backend/src/core/remote/activitypub/ApResolverService.ts b/packages/backend/src/core/remote/activitypub/ApResolverService.ts index e2d6a77370..fe57c82ccc 100644 --- a/packages/backend/src/core/remote/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/remote/activitypub/ApResolverService.ts @@ -156,8 +156,8 @@ export class Resolver { return this.notesRepository.findOneByOrFail({ id: parsed.id }) .then(note => { if (parsed.rest === 'activity') { - // this refers to the create activity and not the note itself - return this.apRendererService.renderActivity(this.apRendererService.renderCreate(this.apRendererService.renderNote(note))); + // this refers to the create activity and not the note itself + return this.apRendererService.renderActivity(this.apRendererService.renderCreate(this.apRendererService.renderNote(note), note)); } else { return this.apRendererService.renderNote(note); } @@ -174,7 +174,7 @@ export class Resolver { .then(([note, poll]) => this.apRendererService.renderQuestion({ id: note.userId }, note, poll)); case 'likes': return this.noteReactionsRepository.findOneByOrFail({ id: parsed.id }).then(reaction => - this.apRendererService.renderActivity(this.apRendererService.renderLike(reaction, { uri: null }))); + this.apRendererService.renderActivity(this.apRendererService.renderLike(reaction, { uri: null }))!); case 'follows': // rest should be if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI'); @@ -184,7 +184,7 @@ export class Resolver { ) .then(([follower, followee]) => this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee, url))); default: - throw new Error(`resolveLocal: type ${type} unhandled`); + throw new Error(`resolveLocal: type ${parsed.type} unhandled`); } } } diff --git a/packages/backend/src/core/remote/activitypub/type.ts b/packages/backend/src/core/remote/activitypub/type.ts index de7eb0ed83..dcc5110aa5 100644 --- a/packages/backend/src/core/remote/activitypub/type.ts +++ b/packages/backend/src/core/remote/activitypub/type.ts @@ -111,8 +111,9 @@ export interface IPost extends IObject { mediaType: string; }; _misskey_quote?: string; + _misskey_content?: string; quoteUrl?: string; - _misskey_talk: boolean; + _misskey_talk?: boolean; } export interface IQuestion extends IObject { diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index a05bb5a7e0..b2ab36e070 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -725,10 +725,10 @@ export interface IEndpoint { params: Schema; } -const endpoints: IEndpoint[] = eps.map(([name, ep]) => { +const endpoints: IEndpoint[] = (eps as [string, any]).map(([name, ep]) => { return { name: name, - meta: ep.meta || {}, + meta: ep.meta ?? {}, params: ep.paramDef, }; }); -- cgit v1.2.3-freya From 2c45c5b13ead5386fb1e615dc493e3d24498812b Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 09:03:04 +0900 Subject: fix --- packages/backend/src/server/api/SignupApiService.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 06b8b29134..6552dac4bf 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -10,6 +10,7 @@ import { IdService } from '@/core/IdService.js'; import { SignupService } from '@/core/SignupService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { EmailService } from '@/core/EmailService.js'; +import { ILocalUser } from '@/models/entities/User.js'; import { SigninService } from './SigninService.js'; import type Koa from 'koa'; @@ -117,7 +118,7 @@ export class SignupApiService { const link = `${this.config.url}/signup-complete/${code}`; - sendEmail(emailAddress, 'Signup', + this.emailService.sendEmail(emailAddress, 'Signup', `To complete signup, please click this link:
${link}`, `To complete signup, please click this link: ${link}`); @@ -167,7 +168,7 @@ export class SignupApiService { emailVerifyCode: null, }); - this.signinService.signin(ctx, account); + this.signinService.signin(ctx, account as ILocalUser); } catch (e) { ctx.throw(400, e); } -- cgit v1.2.3-freya From f4b981cefed8ffbd18cc10efe79ebec51a9fd053 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 10:25:32 +0900 Subject: fix type --- .../server/api/endpoints/admin/drive/show-file.ts | 61 ++++++++++++---------- 1 file changed, 33 insertions(+), 28 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index 25d13d13a3..6180eeae2b 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -76,23 +76,6 @@ export const meta = { properties: { type: 'object', optional: false, nullable: false, - properties: { - width: { - type: 'number', - optional: false, nullable: false, - example: 1280, - }, - height: { - type: 'number', - optional: false, nullable: false, - example: 720, - }, - avgColor: { - type: 'string', - optional: true, nullable: false, - example: 'rgb(40,65,87)', - }, - }, }, storedInternal: { type: 'boolean', @@ -116,15 +99,15 @@ export const meta = { }, accessKey: { type: 'string', - optional: false, nullable: false, + optional: false, nullable: true, }, thumbnailAccessKey: { type: 'string', - optional: false, nullable: false, + optional: false, nullable: true, }, webpublicAccessKey: { type: 'string', - optional: false, nullable: false, + optional: false, nullable: true, }, uri: { type: 'string', @@ -192,14 +175,36 @@ export default class extends Endpoint { throw new ApiError(meta.errors.noSuchFile); } - const res = file as Partial; - - if (!me.isAdmin) { - delete res.requestIp; - delete res.requestHeaders; - } - - return res; + return { + id: file.id, + userId: file.userId, + userHost: file.userHost, + isLink: file.isLink, + maybePorn: file.maybePorn, + maybeSensitive: file.maybeSensitive, + isSensitive: file.isSensitive, + folderId: file.folderId, + src: file.src, + uri: file.uri, + webpublicAccessKey: file.webpublicAccessKey, + thumbnailAccessKey: file.thumbnailAccessKey, + accessKey: file.accessKey, + webpublicType: file.webpublicType, + webpublicUrl: file.webpublicUrl, + thumbnailUrl: file.thumbnailUrl, + url: file.url, + storedInternal: file.storedInternal, + properties: file.properties, + blurhash: file.blurhash, + comment: file.comment, + size: file.size, + type: file.type, + name: file.name, + md5: file.md5, + createdAt: file.createdAt.toISOString(), + requestIp: me.isAdmin ? file.requestIp : null, + requestHeaders: me.isAdmin ? file.requestHeaders : null, + }; }); } } -- cgit v1.2.3-freya From 0f7cbb59228d3dfc86c153d25d8fcbf8e2a244e3 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 10:39:17 +0900 Subject: fix type --- .../backend/src/server/api/endpoints/notes/state.ts | 4 ---- packages/client/src/scripts/get-note-menu.ts | 19 ++----------------- 2 files changed, 2 insertions(+), 21 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index a02b8d2559..d0036f0fb7 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -16,10 +16,6 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, - isWatching: { - type: 'boolean', - optional: false, nullable: false, - }, isMutedThread: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts index a5cf982066..692986cd55 100644 --- a/packages/client/src/scripts/get-note-menu.ts +++ b/packages/client/src/scripts/get-note-menu.ts @@ -60,12 +60,6 @@ export function getNoteMenu(props: { }); } - function toggleWatch(watch: boolean): void { - os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { - noteId: appearNote.id, - }); - } - function toggleThreadMute(mute: boolean): void { os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { noteId: appearNote.id, @@ -245,15 +239,6 @@ export function getNoteMenu(props: { text: i18n.ts.clip, action: () => clip(), }, - (appearNote.userId !== $i.id) ? statePromise.then(state => state.isWatching ? { - icon: 'fas fa-eye-slash', - text: i18n.ts.unwatch, - action: () => toggleWatch(false), - } : { - icon: 'fas fa-eye', - text: i18n.ts.watch, - action: () => toggleWatch(true), - }) : undefined, statePromise.then(state => state.isMutedThread ? { icon: 'fas fa-comment-slash', text: i18n.ts.unmuteThread, @@ -312,7 +297,7 @@ export function getNoteMenu(props: { }] : [] )] - .filter(x => x !== undefined); + .filter(x => x !== undefined); } else { menu = [{ icon: 'fas fa-copy', @@ -329,7 +314,7 @@ export function getNoteMenu(props: { window.open(appearNote.url || appearNote.uri, '_blank'); }, } : undefined] - .filter(x => x !== undefined); + .filter(x => x !== undefined); } if (noteActions.length > 0) { -- cgit v1.2.3-freya From 349f37bf57a7b0e332f762eb9c4f0e9de7e94372 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 10:43:23 +0900 Subject: fix --- packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts index ac0a84128c..24335a21cc 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts @@ -3,6 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { ModerationLogsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; +import { ModerationLogEntityService } from '@/core/entities/ModerationLogEntityService.js'; export const meta = { tags: ['admin'], @@ -67,6 +68,7 @@ export default class extends Endpoint { @Inject(DI.moderationLogsRepository) private moderationLogsRepository: ModerationLogsRepository, + private moderationLogEntityService: ModerationLogEntityService, private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { -- cgit v1.2.3-freya From 89ef21e3b0ea4c27d5cb5ef5b28fb6d376878925 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 16:53:53 +0900 Subject: fix type --- packages/backend/package.json | 1 + packages/backend/src/core/EmailService.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/list.ts | 4 ++-- packages/backend/yarn.lock | 7 +++++++ 4 files changed, 11 insertions(+), 3 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/package.json b/packages/backend/package.json index 79a57fa353..eccba10ed1 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -32,6 +32,7 @@ "@sinonjs/fake-timers": "9.1.2", "@syuilo/aiscript": "0.11.1", "@types/pg": "8.6.5", + "@types/unzipper": "0.10.5", "ajv": "8.11.0", "archiver": "5.3.1", "autobind-decorator": "2.4.0", diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 4c5cf7dfc4..019b9087cd 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -159,7 +159,7 @@ export class EmailService { validateTypo: false, // TLDを見ているみたいだけどclubとか弾かれるので validateDisposable: true, // 捨てアドかどうかチェック validateSMTP: false, // 日本だと25ポートが殆どのプロバイダーで塞がれていてタイムアウトになるので - }) : { valid: true }; + }) : { valid: true, reason: null }; const available = exist === 0 && validated.valid; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index 8624229db8..271b142126 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -41,8 +41,8 @@ export const meta = { optional: false, nullable: true, }, host: { - type: 'null', - optional: false, + type: 'string', + optional: false, nullable: true, description: 'The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files.', }, url: { diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock index 34127e1ad8..f2ea0b0a58 100644 --- a/packages/backend/yarn.lock +++ b/packages/backend/yarn.lock @@ -1762,6 +1762,13 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A== +"@types/unzipper@0.10.5": + version "0.10.5" + resolved "https://registry.yarnpkg.com/@types/unzipper/-/unzipper-0.10.5.tgz#36a963cf025162b4ac31642590cb4192971d633b" + integrity sha512-NrLJb29AdnBARpg9S/4ktfPEisbJ0AvaaAr3j7Q1tg8AgcEUsq2HqbNzvgLRoWyRtjzeLEv7vuL39u1mrNIyNA== + dependencies: + "@types/node" "*" + "@types/uuid@8.3.4": version "8.3.4" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" -- cgit v1.2.3-freya From 3c4b7d3bd044ba270e5af5e272bbde76dfad9f71 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 17:02:19 +0900 Subject: fix type --- packages/backend/src/server/api/endpoints/i/update.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 9bf0616e3a..c2231f5f75 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -78,7 +78,7 @@ export const paramDef = { description: { ...descriptionSchema, nullable: true }, location: { ...locationSchema, nullable: true }, birthday: { ...birthdaySchema, nullable: true }, - lang: { type: 'string', enum: [null, ...Object.keys(langmap)], nullable: true }, + lang: { type: 'string', enum: [null, ...Object.keys(langmap)] as string[], nullable: true }, avatarId: { type: 'string', format: 'misskey:id', nullable: true }, bannerId: { type: 'string', format: 'misskey:id', nullable: true }, fields: { @@ -109,9 +109,7 @@ export const paramDef = { alwaysMarkNsfw: { type: 'boolean' }, autoSensitive: { type: 'boolean' }, ffVisibility: { type: 'string', enum: ['public', 'followers', 'private'] }, - pinnedPageId: { type: 'array', items: { - type: 'string', format: 'misskey:id', - } }, + pinnedPageId: { type: 'string', format: 'misskey:id' }, mutedWords: { type: 'array' }, mutedInstances: { type: 'array', items: { type: 'string', @@ -266,7 +264,7 @@ export default class extends Endpoint { // Publish meUpdated event this.globalEventService.publishMainStream(user.id, 'meUpdated', iObj); - this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', await this.userProfilesRepository.findOneBy({ userId: user.id })); + this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', await this.userProfilesRepository.findOneByOrFail({ userId: user.id })); // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 if (user.isLocked && ps.isLocked === false) { -- cgit v1.2.3-freya From 7670f364e39cbcfe234007e28a3038e7c7eac717 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 17:13:09 +0900 Subject: type --- .../backend/src/server/api/integration/DiscordServerService.ts | 10 +++++----- .../backend/src/server/api/integration/GithubServerService.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index 8e4f595ada..1fd103797c 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -80,11 +80,11 @@ export class DiscordServerService { if (meta.enableDiscordIntegration) { return new OAuth2( - meta.discordClientId!, - meta.discordClientSecret!, - 'https://discord.com/', - 'api/oauth2/authorize', - 'api/oauth2/token'); + meta.discordClientId!, + meta.discordClientSecret!, + 'https://discord.com/', + 'api/oauth2/authorize', + 'api/oauth2/token'); } else { return null; } diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index 2ccea36dc4..98d6230749 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -167,7 +167,7 @@ export class GithubServerService { return; } - const { accessToken } = await new Promise((res, rej) => + const { accessToken } = await new Promise<{ accessToken: string }>((res, rej) => oauth2!.getOAuthAccessToken(code, { redirect_uri, }, (err, accessToken, refresh, result) => { @@ -219,7 +219,7 @@ export class GithubServerService { return; } - const { accessToken } = await new Promise((res, rej) => + const { accessToken } = await new Promise<{ accessToken: string }>((res, rej) => oauth2!.getOAuthAccessToken( code, { redirect_uri }, -- cgit v1.2.3-freya From 1309367884197f4f4d94686fddfbd99fa20262bc Mon Sep 17 00:00:00 2001 From: CyberRex Date: Thu, 13 Oct 2022 09:19:57 +0900 Subject: Add Cloudflare Turnstile CAPTCHA support (#9111) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Cloudflare Turnstile CAPTCHA support * Update packages/client/src/components/MkCaptcha.vue Co-authored-by: Acid Chicken (硫酸鶏) Co-authored-by: Acid Chicken (硫酸鶏) --- locales/ja-JP.yml | 4 ++++ .../backend/migration/1664694635394-turnstile.js | 15 ++++++++++++++ packages/backend/src/core/CaptchaService.ts | 11 ++++++++++ packages/backend/src/models/entities/Meta.ts | 17 +++++++++++++++ .../backend/src/server/api/SignupApiService.ts | 6 ++++++ .../backend/src/server/api/endpoints/admin/meta.ts | 15 ++++++++++++++ .../src/server/api/endpoints/admin/update-meta.ts | 15 ++++++++++++++ packages/backend/src/server/api/endpoints/meta.ts | 11 ++++++++++ packages/client/src/components/MkCaptcha.vue | 4 +++- packages/client/src/components/MkSignup.vue | 6 ++++++ packages/client/src/pages/admin/bot-protection.vue | 24 +++++++++++++++++++++- packages/client/src/pages/admin/index.vue | 2 +- packages/client/src/pages/admin/security.vue | 3 +++ 13 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 packages/backend/migration/1664694635394-turnstile.js (limited to 'packages/backend/src/server') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index cedfe7b1b2..2fd44588c0 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -349,6 +349,10 @@ recaptcha: "reCAPTCHA" enableRecaptcha: "reCAPTCHAを有効にする" recaptchaSiteKey: "サイトキー" recaptchaSecretKey: "シークレットキー" +turnstile: "Turnstile" +enableTurnstile: "Turnstileを有効にする" +turnstileSiteKey: "サイトキー" +turnstileSecretKey: "シークレットキー" avoidMultiCaptchaConfirm: "複数のCaptchaを使用すると干渉を起こす可能性があります。他のCaptchaを無効にしますか?キャンセルして複数のCaptchaを有効化したままにすることも可能です。" antennas: "アンテナ" manageAntennas: "アンテナの管理" diff --git a/packages/backend/migration/1664694635394-turnstile.js b/packages/backend/migration/1664694635394-turnstile.js new file mode 100644 index 0000000000..4a33443950 --- /dev/null +++ b/packages/backend/migration/1664694635394-turnstile.js @@ -0,0 +1,15 @@ +export class turnstile1664694635394 { + name = 'turnstile1664694635394' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "enableTurnstile" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "meta" ADD "turnstileSiteKey" character varying(64)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "turnstileSecretKey" character varying(64)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "turnstileSecretKey"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "turnstileSiteKey"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableTurnstile"`); + } +} diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index 67b4b90061..acfa7d5910 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -66,5 +66,16 @@ export class CaptchaService { throw `hcaptcha-failed: ${errorCodes}`; } } + + public async verifyTurnstile(secret: string, response: string): Promise { + const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(e => { + throw `turnstile-request-failed: ${e}`; + }); + + if (result.success !== true) { + const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : ''; + throw `turnstile-failed: ${errorCodes}`; + } + } } diff --git a/packages/backend/src/models/entities/Meta.ts b/packages/backend/src/models/entities/Meta.ts index f528b7ac08..fb25e370d2 100644 --- a/packages/backend/src/models/entities/Meta.ts +++ b/packages/backend/src/models/entities/Meta.ts @@ -188,6 +188,23 @@ export class Meta { }) public recaptchaSecretKey: string | null; + @Column('boolean', { + default: false, + }) + public enableTurnstile: boolean; + + @Column('varchar', { + length: 64, + nullable: true, + }) + public turnstileSiteKey: string | null; + + @Column('varchar', { + length: 64, + nullable: true, + }) + public turnstileSecretKey: string | null; + @Column('enum', { enum: ['none', 'all', 'local', 'remote'], default: 'none', diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 6552dac4bf..edb8e4e8e6 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -61,6 +61,12 @@ export class SignupApiService { ctx.throw(400, e); }); } + + if (instance.enableTurnstile && instance.turnstileSecretKey) { + await this.captchaService.verifyTurnstile(instance.turnstileSecretKey, body['turnstile-response']).catch(e => { + ctx.throw(400, e); + }); + } } const username = body['username']; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 5b43c180d8..e5b8b6f8fe 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -47,6 +47,14 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + enableTurnstile: { + type: 'boolean', + optional: false, nullable: false, + }, + turnstileSiteKey: { + type: 'string', + optional: false, nullable: true, + }, swPublickey: { type: 'string', optional: false, nullable: true, @@ -197,6 +205,10 @@ export const meta = { type: 'string', optional: true, nullable: true, }, + turnstileSecretKey: { + type: 'string', + optional: true, nullable: true, + } sensitiveMediaDetection: { type: 'string', optional: true, nullable: false, @@ -374,6 +386,8 @@ export default class extends Endpoint { hcaptchaSiteKey: instance.hcaptchaSiteKey, enableRecaptcha: instance.enableRecaptcha, recaptchaSiteKey: instance.recaptchaSiteKey, + enableTurnstile: instance.enableTurnstile, + turnstileSiteKey: instance.turnstileSiteKey, swPublickey: instance.swPublicKey, themeColor: instance.themeColor, mascotImageUrl: instance.mascotImageUrl, @@ -400,6 +414,7 @@ export default class extends Endpoint { blockedHosts: instance.blockedHosts, hcaptchaSecretKey: instance.hcaptchaSecretKey, recaptchaSecretKey: instance.recaptchaSecretKey, + turnstileSecretKey: instance.turnstileSecretKey, sensitiveMediaDetection: instance.sensitiveMediaDetection, sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity, setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically, diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 48fae9b947..2a19b1df5f 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -52,6 +52,9 @@ export const paramDef = { enableRecaptcha: { type: 'boolean' }, recaptchaSiteKey: { type: 'string', nullable: true }, recaptchaSecretKey: { type: 'string', nullable: true }, + enableTurnstile: { type: 'boolean' }, + turnstileSiteKey: { type: 'string', nullable: true }, + turnstileSecretKey: { type: 'string', nullable: true }, sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] }, sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] }, setSensitiveFlagAutomatically: { type: 'boolean' }, @@ -231,6 +234,18 @@ export default class extends Endpoint { set.recaptchaSecretKey = ps.recaptchaSecretKey; } + if (ps.enableTurnstile !== undefined) { + set.enableTurnstile = ps.enableTurnstile; + } + + if (ps.turnstileSiteKey !== undefined) { + set.turnstileSiteKey = ps.turnstileSiteKey; + } + + if (ps.turnstileSecretKey !== undefined) { + set.turnstileSecretKey = ps.turnstileSecretKey; + } + if (ps.sensitiveMediaDetection !== undefined) { set.sensitiveMediaDetection = ps.sensitiveMediaDetection; } diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 5c09c33941..f2e6e6aea8 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -119,6 +119,14 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + enableTurnstile: { + type: 'boolean', + optional: false, nullable: false, + }, + turnstileSiteKey: { + type: 'string', + optional: false, nullable: true, + }, swPublickey: { type: 'string', optional: false, nullable: true, @@ -372,6 +380,8 @@ export default class extends Endpoint { hcaptchaSiteKey: instance.hcaptchaSiteKey, enableRecaptcha: instance.enableRecaptcha, recaptchaSiteKey: instance.recaptchaSiteKey, + enableTurnstile: instance.enableTurnstile, + turnstileSiteKey: instance.turnstileSiteKey, swPublickey: instance.swPublicKey, themeColor: instance.themeColor, mascotImageUrl: instance.mascotImageUrl, @@ -423,6 +433,7 @@ export default class extends Endpoint { elasticsearch: this.config.elasticsearch ? true : false, hcaptcha: instance.enableHcaptcha, recaptcha: instance.enableRecaptcha, + turnstile: instance.enableTurnstile, objectStorage: instance.useObjectStorage, twitter: instance.enableTwitterIntegration, github: instance.enableGithubIntegration, diff --git a/packages/client/src/components/MkCaptcha.vue b/packages/client/src/components/MkCaptcha.vue index 7360734914..b399bb8921 100644 --- a/packages/client/src/components/MkCaptcha.vue +++ b/packages/client/src/components/MkCaptcha.vue @@ -20,7 +20,7 @@ type Captcha = { getResponse(id: string): string; }; -type CaptchaProvider = 'hcaptcha' | 'recaptcha'; +type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile'; type CaptchaContainer = { readonly [_ in CaptchaProvider]?: Captcha; @@ -48,6 +48,7 @@ const variable = computed(() => { switch (props.provider) { case 'hcaptcha': return 'hcaptcha'; case 'recaptcha': return 'grecaptcha'; + case 'turnstile': return 'turnstile'; } }); @@ -57,6 +58,7 @@ const src = computed(() => { switch (props.provider) { case 'hcaptcha': return 'https://js.hcaptcha.com/1/api.js?render=explicit&recaptchacompat=off'; case 'recaptcha': return 'https://www.recaptcha.net/recaptcha/api.js?render=explicit'; + case 'turnstile': return 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'; } }); diff --git a/packages/client/src/components/MkSignup.vue b/packages/client/src/components/MkSignup.vue index a324bb6f5a..c1f91b18c2 100644 --- a/packages/client/src/components/MkSignup.vue +++ b/packages/client/src/components/MkSignup.vue @@ -59,6 +59,7 @@ + {{ i18n.ts.start }} @@ -92,6 +93,7 @@ const host = toUnicode(config.host); let hcaptcha = $ref(); let recaptcha = $ref(); +let turnstile = $ref(); let username: string = $ref(''); let password: string = $ref(''); @@ -106,12 +108,14 @@ let submitting: boolean = $ref(false); let ToSAgreement: boolean = $ref(false); let hCaptchaResponse = $ref(null); let reCaptchaResponse = $ref(null); +let turnstileResponse = $ref(null); const shouldDisableSubmitting = $computed((): boolean => { return submitting || instance.tosUrl && !ToSAgreement || instance.enableHcaptcha && !hCaptchaResponse || instance.enableRecaptcha && !reCaptchaResponse || + instance.enableTurnstile && !turnstileResponse || passwordRetypeState === 'not-match'; }); @@ -198,6 +202,7 @@ function onSubmit(): void { invitationCode, 'hcaptcha-response': hCaptchaResponse, 'g-recaptcha-response': reCaptchaResponse, + 'turnstile-response': turnstileResponse, }).then(() => { if (instance.emailRequiredForSignup) { os.alert({ @@ -222,6 +227,7 @@ function onSubmit(): void { submitting = false; hcaptcha.reset?.(); recaptcha.reset?.(); + turnstile.reset?.(); os.alert({ type: 'error', diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue index 72d5e379de..484a9d1a1a 100644 --- a/packages/client/src/pages/admin/bot-protection.vue +++ b/packages/client/src/pages/admin/bot-protection.vue @@ -6,6 +6,7 @@ + + {{ i18n.ts.save }} @@ -61,6 +76,8 @@ let hcaptchaSiteKey: string | null = $ref(null); let hcaptchaSecretKey: string | null = $ref(null); let recaptchaSiteKey: string | null = $ref(null); let recaptchaSecretKey: string | null = $ref(null); +let turnstileSiteKey: string | null = $ref(null); +let turnstileSecretKey: string | null = $ref(null); async function init() { const meta = await os.api('admin/meta'); @@ -68,8 +85,10 @@ async function init() { hcaptchaSecretKey = meta.hcaptchaSecretKey; recaptchaSiteKey = meta.recaptchaSiteKey; recaptchaSecretKey = meta.recaptchaSecretKey; + turnstileSiteKey = meta.turnstileSiteKey; + turnstileSecretKey = meta.turnstileSecretKey; - provider = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : null; + provider = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : meta.enableTurnstile ? 'turnstile' : null; } function save() { @@ -80,6 +99,9 @@ function save() { enableRecaptcha: provider === 'recaptcha', recaptchaSiteKey, recaptchaSecretKey, + enableTurnstile: provider === 'turnstile', + turnstileSiteKey, + turnstileSecretKey, }).then(() => { fetchInstance(); }); diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue index 9200b5d547..20f82bba28 100644 --- a/packages/client/src/pages/admin/index.vue +++ b/packages/client/src/pages/admin/index.vue @@ -53,7 +53,7 @@ let view = $ref(null); let el = $ref(null); let pageProps = $ref({}); let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail); -let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha; +let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableTurnstile; let noEmailServer = !instance.enableEmail; let thereIsUnresolvedAbuseReport = $ref(false); let currentPage = $computed(() => router.currentRef.value.child); diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue index c36cedb312..65d079c2cf 100644 --- a/packages/client/src/pages/admin/security.vue +++ b/packages/client/src/pages/admin/security.vue @@ -9,6 +9,7 @@ + @@ -120,6 +121,7 @@ import { definePageMetadata } from '@/scripts/page-metadata'; let summalyProxy: string = $ref(''); let enableHcaptcha: boolean = $ref(false); let enableRecaptcha: boolean = $ref(false); +let enableTurnstile: boolean = $ref(false); let sensitiveMediaDetection: string = $ref('none'); let sensitiveMediaDetectionSensitivity: number = $ref(0); let setSensitiveFlagAutomatically: boolean = $ref(false); @@ -132,6 +134,7 @@ async function init() { summalyProxy = meta.summalyProxy; enableHcaptcha = meta.enableHcaptcha; enableRecaptcha = meta.enableRecaptcha; + enableTurnstile = meta.enableTurnstile; sensitiveMediaDetection = meta.sensitiveMediaDetection; sensitiveMediaDetectionSensitivity = meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0 : -- cgit v1.2.3-freya From 2492f4e81e9403d6c6efca28f0f65f4eed57ea75 Mon Sep 17 00:00:00 2001 From: squidicuz Date: Thu, 10 Nov 2022 21:22:31 -0500 Subject: fix for pinned users. update changelog (#9159) --- CHANGELOG.md | 1 + packages/backend/src/server/api/endpoints/pinned-users.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'packages/backend/src/server') diff --git a/CHANGELOG.md b/CHANGELOG.md index 66959cd716..d7513acdb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ You should also include the user name that made the change. ### Improvements ### Bugfixes +- Server: Bug fix for Pinned Users lookup on instance @squidicuzz ## 12.119.0 (2022/09/10) diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 6c941314e2..f2c6e798ef 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -43,12 +43,12 @@ export default class extends Endpoint { super(meta, paramDef, async (ps, me) => { const meta = await this.metaService.fetch(); - const users = await Promise.all(meta.pinnedthis.usersRepository.map(acct => Acct.parse(acct)).map(acct => this.usersRepository.findOneBy({ + const users = await Promise.all(meta.pinnedUsers.map(acct => Acct.parse(acct)).map(acct => this.usersRepository.findOneBy({ usernameLower: acct.username.toLowerCase(), host: acct.host ?? IsNull(), }))); - return await this.userEntityService.packMany(users.filter(x => x !== undefined) as User[], me, { detail: true }); + return await this.userEntityService.packMany(users.filter(x => x !== null) as User[], me, { detail: true }); }); } } -- cgit v1.2.3-freya From 1751bfea5f49c53c37558e512a83fb03a3231a48 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 13 Nov 2022 11:23:14 +0900 Subject: update node to v18 --- .node-version | 2 +- CHANGELOG.md | 1 + Dockerfile | 4 ++-- packages/backend/src/core/RelayService.ts | 4 +--- packages/backend/src/core/entities/DriveFileEntityService.ts | 4 +--- packages/backend/src/server/web/ClientServerService.ts | 4 +--- 6 files changed, 7 insertions(+), 12 deletions(-) (limited to 'packages/backend/src/server') diff --git a/.node-version b/.node-version index 7fd023741b..e44a38e080 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v16.15.0 +v18.12.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 92e47077de..f1bc0a96fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ You should also include the user name that made the change. ## 12.x.x (unreleased) ### Changes +- Node.js 18.x or later is required - Elasticsearchのサポートが削除されました - 代わりに今後任意の検索プロバイダを設定できる仕組みを構想しています。その仕組みを使えば今まで通りElasticsearchも利用できます - ノートのウォッチ機能が削除されました diff --git a/Dockerfile b/Dockerfile index 81dc726375..0151797a7b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16.15.1-bullseye AS builder +FROM node:18.12.1-bullseye AS builder ARG NODE_ENV=production @@ -13,7 +13,7 @@ RUN yarn install RUN yarn build RUN rm -rf .git -FROM node:16.15.1-bullseye-slim AS runner +FROM node:18.12.1-bullseye-slim AS runner WORKDIR /misskey diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts index 5324826ec1..563eeac0f0 100644 --- a/packages/backend/src/core/RelayService.ts +++ b/packages/backend/src/core/RelayService.ts @@ -105,9 +105,7 @@ export class RelayService { })); if (relays.length === 0) return; - // TODO - //const copy = structuredClone(activity); - const copy = JSON.parse(JSON.stringify(activity)); + const copy = structuredClone(activity); if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public']; const signed = await this.apRendererService.attachLdSignature(copy, user); diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index f0ac6518d0..d9430e1497 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -55,9 +55,7 @@ export class DriveFileEntityService { public getPublicProperties(file: DriveFile): DriveFile['properties'] { if (file.properties.orientation != null) { - // TODO - //const properties = structuredClone(file.properties); - const properties = JSON.parse(JSON.stringify(file.properties)); + const properties = structuredClone(file.properties); if (file.properties.orientation >= 5) { [properties.width, properties.height] = [properties.height, properties.width]; } diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 30d430775c..44450245a6 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -86,9 +86,7 @@ export class ClientServerService { } private async manifestHandler(ctx: Koa.Context) { - // TODO - //const res = structuredClone(manifest); - const res = JSON.parse(JSON.stringify(manifest)); + const res = structuredClone(manifest); const instance = await this.metaService.fetch(true); -- cgit v1.2.3-freya From d5aee2ea58a16e0cf65213fab9e46192882feba9 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 17 Nov 2022 09:31:07 +0900 Subject: improve performance --- packages/backend/src/core/RelayService.ts | 3 +- .../src/core/entities/DriveFileEntityService.ts | 3 +- packages/backend/src/misc/clone.ts | 18 +++++++++ .../backend/src/server/web/ClientServerService.ts | 3 +- packages/client/src/components/MkNote.vue | 5 ++- packages/client/src/components/MkNoteDetailed.vue | 5 ++- packages/client/src/components/MkPostForm.vue | 3 +- packages/client/src/pages/settings/reaction.vue | 5 ++- .../src/pages/settings/statusbar.statusbar.vue | 7 ++-- packages/client/src/scripts/clone.ts | 18 +++++++++ packages/client/src/scripts/theme.ts | 3 +- packages/client/src/ui/deck/deck-store.ts | 45 ++++++++++------------ packages/client/src/widgets/job-queue.vue | 7 ++-- packages/client/src/widgets/widget.ts | 7 ++-- 14 files changed, 88 insertions(+), 44 deletions(-) create mode 100644 packages/backend/src/misc/clone.ts create mode 100644 packages/client/src/scripts/clone.ts (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts index 563eeac0f0..3c67e0573f 100644 --- a/packages/backend/src/core/RelayService.ts +++ b/packages/backend/src/core/RelayService.ts @@ -9,6 +9,7 @@ import { QueueService } from '@/core/QueueService.js'; import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; import { DI } from '@/di-symbols.js'; +import { deepClone } from '@/misc/clone.js'; const ACTOR_USERNAME = 'relay.actor' as const; @@ -105,7 +106,7 @@ export class RelayService { })); if (relays.length === 0) return; - const copy = structuredClone(activity); + const copy = deepClone(activity); if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public']; const signed = await this.apRendererService.attachLdSignature(copy, user); diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index d9430e1497..e0aeb70dfc 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -9,6 +9,7 @@ import { awaitAll } from '@/misc/prelude/await-all.js'; import type { User } from '@/models/entities/User.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { appendQuery, query } from '@/misc/prelude/url.js'; +import { deepClone } from '@/misc/clone.js'; import { UtilityService } from '../UtilityService.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFolderEntityService } from './DriveFolderEntityService.js'; @@ -55,7 +56,7 @@ export class DriveFileEntityService { public getPublicProperties(file: DriveFile): DriveFile['properties'] { if (file.properties.orientation != null) { - const properties = structuredClone(file.properties); + const properties = deepClone(file.properties); if (file.properties.orientation >= 5) { [properties.width, properties.height] = [properties.height, properties.width]; } diff --git a/packages/backend/src/misc/clone.ts b/packages/backend/src/misc/clone.ts new file mode 100644 index 0000000000..16fad24129 --- /dev/null +++ b/packages/backend/src/misc/clone.ts @@ -0,0 +1,18 @@ +// structredCloneが遅いため +// SEE: http://var.blog.jp/archives/86038606.html + +type Cloneable = string | number | boolean | null | { [key: string]: Cloneable } | Cloneable[]; + +export function deepClone(x: T): T { + if (typeof x === 'object') { + if (x === null) return x; + if (Array.isArray(x)) return x.map(deepClone) as T; + const obj = {} as Record; + for (const [k, v] of Object.entries(x)) { + obj[k] = deepClone(v); + } + return obj as T; + } else { + return x; + } +} diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 44450245a6..8957a91309 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -26,6 +26,7 @@ import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityServi import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import type { ChannelsRepository, ClipsRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import { deepClone } from '@/misc/clone.js'; import manifest from './manifest.json' assert { type: 'json' }; import { FeedService } from './FeedService.js'; import { UrlPreviewService } from './UrlPreviewService.js'; @@ -86,7 +87,7 @@ export class ClientServerService { } private async manifestHandler(ctx: Koa.Context) { - const res = structuredClone(manifest); + const res = deepClone(manifest); const instance = await this.metaService.fetch(true); diff --git a/packages/client/src/components/MkNote.vue b/packages/client/src/components/MkNote.vue index efe786ba4b..97eadb1945 100644 --- a/packages/client/src/components/MkNote.vue +++ b/packages/client/src/components/MkNote.vue @@ -129,6 +129,7 @@ import { $i } from '@/account'; import { i18n } from '@/i18n'; import { getNoteMenu } from '@/scripts/get-note-menu'; import { useNoteCapture } from '@/scripts/use-note-capture'; +import { deepClone } from '@/scripts/clone'; const props = defineProps<{ note: misskey.entities.Note; @@ -137,12 +138,12 @@ const props = defineProps<{ const inChannel = inject('inChannel', null); -let note = $ref(JSON.parse(JSON.stringify(props.note))); +let note = $ref(deepClone(props.note)); // plugin if (noteViewInterruptors.length > 0) { onMounted(async () => { - let result = JSON.parse(JSON.stringify(note)); + let result = deepClone(note); for (const interruptor of noteViewInterruptors) { result = await interruptor.handler(result); } diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue index 0bf8f330ba..82468027fd 100644 --- a/packages/client/src/components/MkNoteDetailed.vue +++ b/packages/client/src/components/MkNoteDetailed.vue @@ -139,6 +139,7 @@ import { $i } from '@/account'; import { i18n } from '@/i18n'; import { getNoteMenu } from '@/scripts/get-note-menu'; import { useNoteCapture } from '@/scripts/use-note-capture'; +import { deepClone } from '@/scripts/clone'; const props = defineProps<{ note: misskey.entities.Note; @@ -147,12 +148,12 @@ const props = defineProps<{ const inChannel = inject('inChannel', null); -let note = $ref(JSON.parse(JSON.stringify(props.note))); +let note = $ref(deepClone(props.note)); // plugin if (noteViewInterruptors.length > 0) { onMounted(async () => { - let result = JSON.parse(JSON.stringify(note)); + let result = deepClone(note); for (const interruptor of noteViewInterruptors) { result = await interruptor.handler(result); } diff --git a/packages/client/src/components/MkPostForm.vue b/packages/client/src/components/MkPostForm.vue index 0c57a5a57a..24f2bfb9e6 100644 --- a/packages/client/src/components/MkPostForm.vue +++ b/packages/client/src/components/MkPostForm.vue @@ -89,6 +89,7 @@ import { i18n } from '@/i18n'; import { instance } from '@/instance'; import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account'; import { uploadFile } from '@/scripts/upload'; +import { deepClone } from '@/scripts/clone'; const modal = inject('modal'); @@ -575,7 +576,7 @@ async function post() { // plugin if (notePostInterruptors.length > 0) { for (const interruptor of notePostInterruptors) { - postData = await interruptor.handler(JSON.parse(JSON.stringify(postData))); + postData = await interruptor.handler(deepClone(postData)); } } diff --git a/packages/client/src/pages/settings/reaction.vue b/packages/client/src/pages/settings/reaction.vue index c23c1c2375..f8d57cbcd5 100644 --- a/packages/client/src/pages/settings/reaction.vue +++ b/packages/client/src/pages/settings/reaction.vue @@ -66,8 +66,9 @@ import * as os from '@/os'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { deepClone } from '@/scripts/clone'; -let reactions = $ref(JSON.parse(JSON.stringify(defaultStore.state.reactions))); +let reactions = $ref(deepClone(defaultStore.state.reactions)); const reactionPickerSize = $computed(defaultStore.makeGetterSetter('reactionPickerSize')); const reactionPickerWidth = $computed(defaultStore.makeGetterSetter('reactionPickerWidth')); @@ -101,7 +102,7 @@ async function setDefault() { }); if (canceled) return; - reactions = JSON.parse(JSON.stringify(defaultStore.def.reactions.default)); + reactions = deepClone(defaultStore.def.reactions.default); } function chooseEmoji(ev: MouseEvent) { diff --git a/packages/client/src/pages/settings/statusbar.statusbar.vue b/packages/client/src/pages/settings/statusbar.statusbar.vue index 98a1825b95..608222386e 100644 --- a/packages/client/src/pages/settings/statusbar.statusbar.vue +++ b/packages/client/src/pages/settings/statusbar.statusbar.vue @@ -91,13 +91,14 @@ import FormRange from '@/components/form/range.vue'; import * as os from '@/os'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; +import { deepClone } from '@/scripts/clone'; const props = defineProps<{ _id: string; userLists: any[] | null; }>(); -const statusbar = reactive(JSON.parse(JSON.stringify(defaultStore.state.statusbars.find(x => x.id === props._id)))); +const statusbar = reactive(deepClone(defaultStore.state.statusbars.find(x => x.id === props._id))); watch(() => statusbar.type, () => { if (statusbar.type === 'rss') { @@ -128,8 +129,8 @@ watch(statusbar, save); async function save() { const i = defaultStore.state.statusbars.findIndex(x => x.id === props._id); - const statusbars = JSON.parse(JSON.stringify(defaultStore.state.statusbars)); - statusbars[i] = JSON.parse(JSON.stringify(statusbar)); + const statusbars = deepClone(defaultStore.state.statusbars); + statusbars[i] = deepClone(statusbar); defaultStore.set('statusbars', statusbars); } diff --git a/packages/client/src/scripts/clone.ts b/packages/client/src/scripts/clone.ts new file mode 100644 index 0000000000..16fad24129 --- /dev/null +++ b/packages/client/src/scripts/clone.ts @@ -0,0 +1,18 @@ +// structredCloneが遅いため +// SEE: http://var.blog.jp/archives/86038606.html + +type Cloneable = string | number | boolean | null | { [key: string]: Cloneable } | Cloneable[]; + +export function deepClone(x: T): T { + if (typeof x === 'object') { + if (x === null) return x; + if (Array.isArray(x)) return x.map(deepClone) as T; + const obj = {} as Record; + for (const [k, v] of Object.entries(x)) { + obj[k] = deepClone(v); + } + return obj as T; + } else { + return x; + } +} diff --git a/packages/client/src/scripts/theme.ts b/packages/client/src/scripts/theme.ts index 3f55d9ae86..62a2b9459a 100644 --- a/packages/client/src/scripts/theme.ts +++ b/packages/client/src/scripts/theme.ts @@ -13,6 +13,7 @@ export type Theme = { import lightTheme from '@/themes/_light.json5'; import darkTheme from '@/themes/_dark.json5'; +import { deepClone } from './clone'; export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X')); @@ -60,7 +61,7 @@ export function applyTheme(theme: Theme, persist = true) { const colorSchema = theme.base === 'dark' ? 'dark' : 'light'; // Deep copy - const _theme = JSON.parse(JSON.stringify(theme)); + const _theme = deepClone(theme); if (_theme.base) { const base = [lightTheme, darkTheme].find(x => x.id === _theme.base); diff --git a/packages/client/src/ui/deck/deck-store.ts b/packages/client/src/ui/deck/deck-store.ts index 67fcff4807..56db7398e5 100644 --- a/packages/client/src/ui/deck/deck-store.ts +++ b/packages/client/src/ui/deck/deck-store.ts @@ -4,6 +4,7 @@ import { notificationTypes } from 'misskey-js'; import { Storage } from '../../pizzax'; import { i18n } from '@/i18n'; import { api } from '@/os'; +import { deepClone } from '@/scripts/clone'; type ColumnWidget = { name: string; @@ -25,10 +26,6 @@ export type Column = { tl?: 'home' | 'local' | 'social' | 'global'; }; -function copy(x: T): T { - return JSON.parse(JSON.stringify(x)); -} - export const deckStore = markRaw(new Storage('deck', { profile: { where: 'deviceAccount', @@ -128,7 +125,7 @@ export function swapColumn(a: Column['id'], b: Column['id']) { const aY = deckStore.state.layout[aX].findIndex(id => id === a); const bX = deckStore.state.layout.findIndex(ids => ids.indexOf(b) !== -1); const bY = deckStore.state.layout[bX].findIndex(id => id === b); - const layout = copy(deckStore.state.layout); + const layout = deepClone(deckStore.state.layout); layout[aX][aY] = b; layout[bX][bY] = a; deckStore.set('layout', layout); @@ -136,7 +133,7 @@ export function swapColumn(a: Column['id'], b: Column['id']) { } export function swapLeftColumn(id: Column['id']) { - const layout = copy(deckStore.state.layout); + const layout = deepClone(deckStore.state.layout); deckStore.state.layout.some((ids, i) => { if (ids.includes(id)) { const left = deckStore.state.layout[i - 1]; @@ -152,7 +149,7 @@ export function swapLeftColumn(id: Column['id']) { } export function swapRightColumn(id: Column['id']) { - const layout = copy(deckStore.state.layout); + const layout = deepClone(deckStore.state.layout); deckStore.state.layout.some((ids, i) => { if (ids.includes(id)) { const right = deckStore.state.layout[i + 1]; @@ -168,9 +165,9 @@ export function swapRightColumn(id: Column['id']) { } export function swapUpColumn(id: Column['id']) { - const layout = copy(deckStore.state.layout); + const layout = deepClone(deckStore.state.layout); const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id)); - const ids = copy(deckStore.state.layout[idsIndex]); + const ids = deepClone(deckStore.state.layout[idsIndex]); ids.some((x, i) => { if (x === id) { const up = ids[i - 1]; @@ -188,9 +185,9 @@ export function swapUpColumn(id: Column['id']) { } export function swapDownColumn(id: Column['id']) { - const layout = copy(deckStore.state.layout); + const layout = deepClone(deckStore.state.layout); const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id)); - const ids = copy(deckStore.state.layout[idsIndex]); + const ids = deepClone(deckStore.state.layout[idsIndex]); ids.some((x, i) => { if (x === id) { const down = ids[i + 1]; @@ -208,7 +205,7 @@ export function swapDownColumn(id: Column['id']) { } export function stackLeftColumn(id: Column['id']) { - let layout = copy(deckStore.state.layout); + let layout = deepClone(deckStore.state.layout); const i = deckStore.state.layout.findIndex(ids => ids.includes(id)); layout = layout.map(ids => ids.filter(_id => _id !== id)); layout[i - 1].push(id); @@ -218,7 +215,7 @@ export function stackLeftColumn(id: Column['id']) { } export function popRightColumn(id: Column['id']) { - let layout = copy(deckStore.state.layout); + let layout = deepClone(deckStore.state.layout); const i = deckStore.state.layout.findIndex(ids => ids.includes(id)); const affected = layout[i]; layout = layout.map(ids => ids.filter(_id => _id !== id)); @@ -226,7 +223,7 @@ export function popRightColumn(id: Column['id']) { layout = layout.filter(ids => ids.length > 0); deckStore.set('layout', layout); - const columns = copy(deckStore.state.columns); + const columns = deepClone(deckStore.state.columns); for (const column of columns) { if (affected.includes(column.id)) { column.active = true; @@ -238,9 +235,9 @@ export function popRightColumn(id: Column['id']) { } export function addColumnWidget(id: Column['id'], widget: ColumnWidget) { - const columns = copy(deckStore.state.columns); + const columns = deepClone(deckStore.state.columns); const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); - const column = copy(deckStore.state.columns[columnIndex]); + const column = deepClone(deckStore.state.columns[columnIndex]); if (column == null) return; if (column.widgets == null) column.widgets = []; column.widgets.unshift(widget); @@ -250,9 +247,9 @@ export function addColumnWidget(id: Column['id'], widget: ColumnWidget) { } export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) { - const columns = copy(deckStore.state.columns); + const columns = deepClone(deckStore.state.columns); const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); - const column = copy(deckStore.state.columns[columnIndex]); + const column = deepClone(deckStore.state.columns[columnIndex]); if (column == null) return; column.widgets = column.widgets.filter(w => w.id !== widget.id); columns[columnIndex] = column; @@ -261,9 +258,9 @@ export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) { } export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) { - const columns = copy(deckStore.state.columns); + const columns = deepClone(deckStore.state.columns); const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); - const column = copy(deckStore.state.columns[columnIndex]); + const column = deepClone(deckStore.state.columns[columnIndex]); if (column == null) return; column.widgets = widgets; columns[columnIndex] = column; @@ -272,9 +269,9 @@ export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) { } export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) { - const columns = copy(deckStore.state.columns); + const columns = deepClone(deckStore.state.columns); const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); - const column = copy(deckStore.state.columns[columnIndex]); + const column = deepClone(deckStore.state.columns[columnIndex]); if (column == null) return; column.widgets = column.widgets.map(w => w.id === widgetId ? { ...w, @@ -286,9 +283,9 @@ export function updateColumnWidget(id: Column['id'], widgetId: string, widgetDat } export function updateColumn(id: Column['id'], column: Partial) { - const columns = copy(deckStore.state.columns); + const columns = deepClone(deckStore.state.columns); const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); - const currentColumn = copy(deckStore.state.columns[columnIndex]); + const currentColumn = deepClone(deckStore.state.columns[columnIndex]); if (currentColumn == null) return; for (const [k, v] of Object.entries(column)) { currentColumn[k] = v; diff --git a/packages/client/src/widgets/job-queue.vue b/packages/client/src/widgets/job-queue.vue index 8897f240bd..363d1b3ea0 100644 --- a/packages/client/src/widgets/job-queue.vue +++ b/packages/client/src/widgets/job-queue.vue @@ -47,12 +47,13 @@ diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index 515fc47819..7e57dcb4af 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -29,11 +29,14 @@ export const api = ((endpoint: string, data: Record = {}, token?: s if (token !== undefined) (data as any).i = token; // Send request - fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, { + window.fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, { method: 'POST', body: JSON.stringify(data), credentials: 'omit', cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, }).then(async (res) => { const body = res.status === 204 ? null : await res.json(); @@ -63,7 +66,7 @@ export const apiGet = ((endpoint: string, data: Record = {}) => { const promise = new Promise((resolve, reject) => { // Send request - fetch(`${apiUrl}/${endpoint}?${query}`, { + window.fetch(`${apiUrl}/${endpoint}?${query}`, { method: 'GET', credentials: 'omit', cache: 'default', diff --git a/packages/client/src/ui/_common_/statusbar-rss.vue b/packages/client/src/ui/_common_/statusbar-rss.vue index e75e13bb48..e7f88e4984 100644 --- a/packages/client/src/ui/_common_/statusbar-rss.vue +++ b/packages/client/src/ui/_common_/statusbar-rss.vue @@ -37,7 +37,7 @@ const fetching = ref(true); let key = $ref(0); const tick = () => { - fetch(`/api/fetch-rss?url=${props.url}`, {}).then(res => { + window.fetch(`/api/fetch-rss?url=${props.url}`, {}).then(res => { res.json().then(feed => { if (props.shuffle) { shuffle(feed.items); diff --git a/packages/client/src/widgets/rss-ticker.vue b/packages/client/src/widgets/rss-ticker.vue index 58c16983c8..82a2f59ae9 100644 --- a/packages/client/src/widgets/rss-ticker.vue +++ b/packages/client/src/widgets/rss-ticker.vue @@ -83,7 +83,7 @@ const fetching = ref(true); let key = $ref(0); const tick = () => { - fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => { + window.fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => { res.json().then(feed => { if (widgetProps.shuffle) { shuffle(feed.items); diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue index 3258b6c028..f392a8249a 100644 --- a/packages/client/src/widgets/rss.vue +++ b/packages/client/src/widgets/rss.vue @@ -51,7 +51,7 @@ const items = ref([]); const fetching = ref(true); const tick = () => { - fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => { + window.fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => { res.json().then(feed => { items.value = feed.items; fetching.value = false; diff --git a/yarn.lock b/yarn.lock index 61d81c0017..06bf9d8fd1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -417,40 +417,6 @@ __metadata: languageName: node linkType: hard -"@bull-board/api@npm:4.3.1": - version: 4.3.1 - resolution: "@bull-board/api@npm:4.3.1" - dependencies: - redis-info: ^3.0.8 - checksum: 05113b1e888e79f8efecdffdc1043455fa6f8714c55a1e973d8a0a7f60cf574b00487b5b86324523ff91641784a55ff14c469edc8dd985295dcfc27cf55b4c4a - languageName: node - linkType: hard - -"@bull-board/koa@npm:4.3.1": - version: 4.3.1 - resolution: "@bull-board/koa@npm:4.3.1" - dependencies: - "@bull-board/api": 4.3.1 - "@bull-board/ui": 4.3.1 - ejs: ^3.1.7 - koa: ^2.13.1 - koa-mount: ^4.0.0 - koa-router: ^10.0.0 - koa-static: ^5.0.0 - koa-views: ^7.0.1 - checksum: 08f198cdaaa28fe8e254288a0d4c13e9cd481a97e40e5e9152fb9094cbac54459e86901da5d90c46fe2dccf310f78a50ef8763bf5980b98d33180299c64fbc3f - languageName: node - linkType: hard - -"@bull-board/ui@npm:4.3.1": - version: 4.3.1 - resolution: "@bull-board/ui@npm:4.3.1" - dependencies: - "@bull-board/api": 4.3.1 - checksum: 7bc4787ba8f9e3dda5cb580b4374872bc7b0870a08a504cfc2f380a39dda164ae71518e7b1921e53ff8abc4224c0861504b26b63510b6c9c9d23d647bdab54b2 - languageName: node - linkType: hard - "@chainsafe/is-ip@npm:^2.0.1": version: 2.0.1 resolution: "@chainsafe/is-ip@npm:2.0.1" @@ -654,18 +620,6 @@ __metadata: languageName: node linkType: hard -"@elastic/elasticsearch@npm:7.17.0": - version: 7.17.0 - resolution: "@elastic/elasticsearch@npm:7.17.0" - dependencies: - debug: ^4.3.1 - hpagent: ^0.1.1 - ms: ^2.1.3 - secure-json-parse: ^2.4.0 - checksum: 08113bcb14203c5700e6575cb720aa32f5573a1776e13b78bf101ffeae46308c3664b94f16f7e2c5ec26e14c459d8d1491e234940e635cddfe3ee61a52fb51f9 - languageName: node - linkType: hard - "@esbuild/android-arm@npm:0.15.16": version: 0.15.16 resolution: "@esbuild/android-arm@npm:0.15.16" @@ -704,6 +658,117 @@ __metadata: languageName: node linkType: hard +"@fastify/accept-negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "@fastify/accept-negotiator@npm:1.0.0" + checksum: 9b6be6bfd0f1475e4d06ffbd6359d7cf70841cc8e37abe6fe32f2e0e8185da6077da0d5c6610ade356ed3d4af4d3a642094b4861b123607987491bef2a384957 + languageName: node + linkType: hard + +"@fastify/accepts@npm:4.0.1": + version: 4.0.1 + resolution: "@fastify/accepts@npm:4.0.1" + dependencies: + accepts: ^1.3.5 + fastify-plugin: ^4.0.0 + checksum: bd14a998dececc66cbfc331aac6e9063c19baa8915e44288a621ef13737df5ca11e83f4be5767951ed6b8ddded2b2a40c19ec630c68a3ac9c299afe50d5e4153 + languageName: node + linkType: hard + +"@fastify/ajv-compiler@npm:^3.3.1": + version: 3.4.0 + resolution: "@fastify/ajv-compiler@npm:3.4.0" + dependencies: + ajv: ^8.11.0 + ajv-formats: ^2.1.1 + fast-uri: ^2.0.0 + checksum: 3e03f9673f0f13ce343bfb4a84f4e908d12bd775a2b82ff4bdf09ac062d09c6b89b62df7f96fab970dd61f77a9e43be2908eb28cd59e27654b25931444bde825 + languageName: node + linkType: hard + +"@fastify/busboy@npm:^1.0.0": + version: 1.1.0 + resolution: "@fastify/busboy@npm:1.1.0" + dependencies: + text-decoding: ^1.0.0 + checksum: 8ef01870c5e2ddae787fb8775c844b26e54c366732565287d5f7fb7d8c3c746bd4f0ad8f8695f4006e4b63af2fdd9a206ca74b2bc6c5d3c96d88abc07daa16f5 + languageName: node + linkType: hard + +"@fastify/cors@npm:8.2.0": + version: 8.2.0 + resolution: "@fastify/cors@npm:8.2.0" + dependencies: + fastify-plugin: ^4.0.0 + mnemonist: 0.39.5 + checksum: b2e30602d3aad7b2170a153b60e2b0dba8ad7df67ac3b7918374d202097f60b8a252baeafbf37f4323190fa87170960a3162aa6120540f825ae7750414c3feea + languageName: node + linkType: hard + +"@fastify/deepmerge@npm:^1.0.0": + version: 1.2.0 + resolution: "@fastify/deepmerge@npm:1.2.0" + checksum: 40f39aa859dbf90cf5cd09b0a06e86783e68f8046baad51f79e42c77db4c6ffe436e130103ade6731d81df3916818d1437ad88d1a6c53c56e809aa1a910f4c9a + languageName: node + linkType: hard + +"@fastify/error@npm:^3.0.0": + version: 3.0.0 + resolution: "@fastify/error@npm:3.0.0" + checksum: d9ea16db2d17e4d54f34ad2daf7bbd223fd3fd5682e55406f61dae66616a2fd79fa7585736e6e3b46e9dc60da6e96018f92ebb2f87fd100b4e8ad27308aa9c74 + languageName: node + linkType: hard + +"@fastify/fast-json-stringify-compiler@npm:^4.1.0": + version: 4.1.0 + resolution: "@fastify/fast-json-stringify-compiler@npm:4.1.0" + dependencies: + fast-json-stringify: ^5.0.0 + checksum: 5f848f606e23b04904189bf98c44ccae70c4ceaa793d619d3804ba4a9969d4b9846ceef4ac8a53d536a1cf8f1d3c30a4602850a44fc62bdc1893e341442b6e4f + languageName: node + linkType: hard + +"@fastify/multipart@npm:7.3.0": + version: 7.3.0 + resolution: "@fastify/multipart@npm:7.3.0" + dependencies: + "@fastify/busboy": ^1.0.0 + "@fastify/deepmerge": ^1.0.0 + "@fastify/error": ^3.0.0 + end-of-stream: ^1.4.4 + fastify-plugin: ^4.0.0 + hexoid: ^1.0.0 + secure-json-parse: ^2.4.0 + stream-wormhole: ^1.1.0 + checksum: 192fc4f0892c34d342a3673c6522e13c0987747c4972b52ea48ca7978ea54b5a892d4594778b643dc35f37f429496b13a4e244d8c7eef60a852fadb52144fcbe + languageName: node + linkType: hard + +"@fastify/static@npm:6.5.0": + version: 6.5.0 + resolution: "@fastify/static@npm:6.5.0" + dependencies: + "@fastify/accept-negotiator": ^1.0.0 + content-disposition: ^0.5.3 + fastify-plugin: ^4.0.0 + glob: ^8.0.1 + p-limit: ^3.1.0 + readable-stream: ^4.0.0 + send: ^0.18.0 + checksum: 31ef10916847c51fb4c360860f56acee1cbd7a896b66a05440d4eff70473c5b4cdf908b4719a5a22a876617fdc4a4e0fe3673bd5b40c9d61260a90e08abc1d3d + languageName: node + linkType: hard + +"@fastify/view@npm:7.1.2": + version: 7.1.2 + resolution: "@fastify/view@npm:7.1.2" + dependencies: + fastify-plugin: ^4.0.0 + hashlru: ^2.3.0 + checksum: 0d5c960dc4241ca09faf4b990aabf6ccdac27348da2e75682a738d5665973a133fbf6907309bbf002f5cb36d55240b698283eac372f521cf2a482a9c80ff788d + languageName: node + linkType: hard + "@fortawesome/fontawesome-free@npm:6.1.2": version: 6.1.2 resolution: "@fortawesome/fontawesome-free@npm:6.1.2" @@ -1110,37 +1175,6 @@ __metadata: languageName: node linkType: hard -"@koa/cors@npm:3.3.0": - version: 3.3.0 - resolution: "@koa/cors@npm:3.3.0" - dependencies: - vary: ^1.1.2 - checksum: bb49c680e0d151aec1b19c24c14d61b65f430eb379e63d83789602cc7d8e52706ebcd74867cdb60b1d50ddb6f3d59be04e1c46328fae5721aeaf50e0d4fc2d28 - languageName: node - linkType: hard - -"@koa/multer@npm:3.0.0": - version: 3.0.0 - resolution: "@koa/multer@npm:3.0.0" - peerDependencies: - multer: "*" - checksum: 7671ffed2ab23224b30b4378b44b2db1749d36f82216b0b67ae349fd6fe86b40c4a72c98412499e059f69916b454fba152451f140adf288866462ba765eb6c32 - languageName: node - linkType: hard - -"@koa/router@npm:9.0.1": - version: 9.0.1 - resolution: "@koa/router@npm:9.0.1" - dependencies: - debug: ^4.1.1 - http-errors: ^1.7.3 - koa-compose: ^4.1.0 - methods: ^1.1.2 - path-to-regexp: ^6.1.0 - checksum: 0013bfd26c1acd44c772a08adae5edac8168c662fd2a06c7e174e2a899806908e73e077234c26888fad3ddc54478b27e5b8c7f5db41cd2e27f3b837c54b2f9b8 - languageName: node - linkType: hard - "@mapbox/node-pre-gyp@npm:1.0.9": version: 1.0.9 resolution: "@mapbox/node-pre-gyp@npm:1.0.9" @@ -1894,7 +1928,7 @@ __metadata: languageName: node linkType: hard -"@types/accepts@npm:*": +"@types/accepts@npm:1.3.5": version: 1.3.5 resolution: "@types/accepts@npm:1.3.5" dependencies: @@ -1960,16 +1994,6 @@ __metadata: languageName: node linkType: hard -"@types/body-parser@npm:*": - version: 1.19.2 - resolution: "@types/body-parser@npm:1.19.2" - dependencies: - "@types/connect": "*" - "@types/node": "*" - checksum: e17840c7d747a549f00aebe72c89313d09fbc4b632b949b2470c5cb3b1cb73863901ae84d9335b567a79ec5efcfb8a28ff8e3f36bc8748a9686756b6d5681f40 - languageName: node - linkType: hard - "@types/bull@npm:4.10.0": version: 4.10.0 resolution: "@types/bull@npm:4.10.0" @@ -2000,34 +2024,6 @@ __metadata: languageName: node linkType: hard -"@types/connect@npm:*": - version: 3.4.35 - resolution: "@types/connect@npm:3.4.35" - dependencies: - "@types/node": "*" - checksum: fe81351470f2d3165e8b12ce33542eef89ea893e36dd62e8f7d72566dfb7e448376ae962f9f3ea888547ce8b55a40020ca0e01d637fab5d99567673084542641 - languageName: node - linkType: hard - -"@types/content-disposition@npm:*": - version: 0.5.5 - resolution: "@types/content-disposition@npm:0.5.5" - checksum: fdf7379db1d509990bcf9a21d85f05aad878596f28b1418f9179f6436cb22513262c670ce88c6055054a7f5804a9303eeacb70aa59a5e11ffdc1434559db9692 - languageName: node - linkType: hard - -"@types/cookies@npm:*": - version: 0.7.7 - resolution: "@types/cookies@npm:0.7.7" - dependencies: - "@types/connect": "*" - "@types/express": "*" - "@types/keygrip": "*" - "@types/node": "*" - checksum: d3759efc1182cb0651808570ae13638677b67b0ea724eef7b174e58ffe6ea044b62c7c2715e532f76f88fce4dd8101ed32ac6fbb73226db654017924e8a2a1e6 - languageName: node - linkType: hard - "@types/disposable-email-domains@npm:^1.0.1": version: 1.0.2 resolution: "@types/disposable-email-domains@npm:1.0.2" @@ -2056,29 +2052,6 @@ __metadata: languageName: node linkType: hard -"@types/express-serve-static-core@npm:^4.17.18": - version: 4.17.31 - resolution: "@types/express-serve-static-core@npm:4.17.31" - dependencies: - "@types/node": "*" - "@types/qs": "*" - "@types/range-parser": "*" - checksum: 009bfbe1070837454a1056aa710d0390ee5fb8c05dfe5a1691cc3e2ca88dc256f80e1ca27cb51a978681631d2f6431bfc9ec352ea46dd0c6eb183d0170bde5df - languageName: node - linkType: hard - -"@types/express@npm:*": - version: 4.17.14 - resolution: "@types/express@npm:4.17.14" - dependencies: - "@types/body-parser": "*" - "@types/express-serve-static-core": ^4.17.18 - "@types/qs": "*" - "@types/serve-static": "*" - checksum: 15c1af46d02de834e4a225eccaa9d85c0370fdbb3ed4e1bc2d323d24872309961542b993ae236335aeb3e278630224a6ea002078d39e651d78a3b0356b1eaa79 - languageName: node - linkType: hard - "@types/fluent-ffmpeg@npm:2.1.20": version: 2.1.20 resolution: "@types/fluent-ffmpeg@npm:2.1.20" @@ -2138,13 +2111,6 @@ __metadata: languageName: node linkType: hard -"@types/http-assert@npm:*": - version: 1.5.3 - resolution: "@types/http-assert@npm:1.5.3" - checksum: 9553e5a0b8bcfdac4b51d3fa3b89a91b5450171861a667a5b4c47204e0f4a1ca865d97396e6ceaf220e87b64d06b7a8bad7bfba15ef97acb41a87507c9940dbc - languageName: node - linkType: hard - "@types/http-cache-semantics@npm:*, @types/http-cache-semantics@npm:^4.0.1": version: 4.0.1 resolution: "@types/http-cache-semantics@npm:4.0.1" @@ -2152,13 +2118,6 @@ __metadata: languageName: node linkType: hard -"@types/http-errors@npm:*": - version: 2.0.1 - resolution: "@types/http-errors@npm:2.0.1" - checksum: 3bb0c50b0a652e679a84c30cd0340d696c32ef6558518268c238840346c077f899315daaf1c26c09c57ddd5dc80510f2a7f46acd52bf949e339e35ed3ee9654f - languageName: node - linkType: hard - "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.4 resolution: "@types/istanbul-lib-coverage@npm:2.0.4" @@ -2247,13 +2206,6 @@ __metadata: languageName: node linkType: hard -"@types/keygrip@npm:*": - version: 1.0.2 - resolution: "@types/keygrip@npm:1.0.2" - checksum: 60bc2738a4f107070ee3d96f44709cb38f3a96c7ccabab09f56c1b2b4d85f869fd8fb9f1f2937e863d0e9e781f005c2223b823bf32b859185b4f52370c352669 - languageName: node - linkType: hard - "@types/keyv@npm:^3.1.4": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4" @@ -2263,121 +2215,6 @@ __metadata: languageName: node linkType: hard -"@types/koa-bodyparser@npm:4.3.8": - version: 4.3.8 - resolution: "@types/koa-bodyparser@npm:4.3.8" - dependencies: - "@types/koa": "*" - checksum: df4501f4c29e24e6ffe4149accaaa4ac3aee9e0cac8fe0239f53f36bff4acfde4ffb4294212453efaa8a4231c66d139a9577f0d25cc491832c1b2904a5943f89 - languageName: node - linkType: hard - -"@types/koa-compose@npm:*": - version: 3.2.5 - resolution: "@types/koa-compose@npm:3.2.5" - dependencies: - "@types/koa": "*" - checksum: 5d1147c4b057eb158195f442f0384f06503f3e69dba99fb517b30a05261a9f92928945c12bb1cfc17a5b7d60db003f38b455a3a9b125f12e4fc81fffa396b3cf - languageName: node - linkType: hard - -"@types/koa-cors@npm:0.0.2": - version: 0.0.2 - resolution: "@types/koa-cors@npm:0.0.2" - dependencies: - "@types/koa": "*" - checksum: 7218bd8f4600fede227626e01fabe2022c691ee8721945792eb3dba3b348b10ddc438c3a679734de783172be512eb6b780d0600ed7052c3f881ed234a601656e - languageName: node - linkType: hard - -"@types/koa-favicon@npm:2.0.21": - version: 2.0.21 - resolution: "@types/koa-favicon@npm:2.0.21" - dependencies: - "@types/koa": "*" - checksum: 7e3da0dd430a96a007845be4d0d918281d5177ee36511c182f23048179168aac9b8b1fcd2a3de0b6e0f89b70fcb2a23c7b278de735e6daf71ef5cdea50761593 - languageName: node - linkType: hard - -"@types/koa-logger@npm:3.1.2": - version: 3.1.2 - resolution: "@types/koa-logger@npm:3.1.2" - dependencies: - "@types/koa": "*" - checksum: 8e4cfcdb2491052bbff35c8f0a1f0bf839e966c3903afcf39656bf21bd3089b1a50945ce6a92bea430a83c9341d714c968360953d3a52a5cc10cdb3fb0af4218 - languageName: node - linkType: hard - -"@types/koa-mount@npm:4.0.1": - version: 4.0.1 - resolution: "@types/koa-mount@npm:4.0.1" - dependencies: - "@types/koa": "*" - checksum: c010bfe6b2d81e6b1ca163b7699a5a0c90414079fcbc1d44c4005c896486db0d22b8220bd5f68a1cca7be481dba6a3b4f1c05b6affd80954c724d81170532f33 - languageName: node - linkType: hard - -"@types/koa-send@npm:4.1.3": - version: 4.1.3 - resolution: "@types/koa-send@npm:4.1.3" - dependencies: - "@types/koa": "*" - checksum: f20f6a0dcccd0d090348c7cce3635220cc82420b9579fa521dc6deae23c242aa8adb760a5a3fc84d7590a7f393b41b71b18312f9519c1c4a0b16ee24aae2e104 - languageName: node - linkType: hard - -"@types/koa-views@npm:7.0.0": - version: 7.0.0 - resolution: "@types/koa-views@npm:7.0.0" - dependencies: - koa-views: "*" - checksum: 03253380413e82806ef14c8f3ecadd659e0b00e4e8483fa9d8fe52f369edbfff02a779a5e09fca11eeaabf555dcc139378c8b123d1e4fc49125cb14ed27b3cab - languageName: node - linkType: hard - -"@types/koa@npm:*, @types/koa@npm:2.13.5": - version: 2.13.5 - resolution: "@types/koa@npm:2.13.5" - dependencies: - "@types/accepts": "*" - "@types/content-disposition": "*" - "@types/cookies": "*" - "@types/http-assert": "*" - "@types/http-errors": "*" - "@types/keygrip": "*" - "@types/koa-compose": "*" - "@types/node": "*" - checksum: e3b634d934b79ce8f394bf4130511596081f9c073dbfb4309aa32e4c421c47049a002b65111f8d9687eabec55d5a27b1b9ae0699afa83894cb7032c3536bfa17 - languageName: node - linkType: hard - -"@types/koa__cors@npm:3.3.0": - version: 3.3.0 - resolution: "@types/koa__cors@npm:3.3.0" - dependencies: - "@types/koa": "*" - checksum: c1aeb10b070e72b6c01a2f6abb4b0a936017794ef4eab3469697a4e24ef2054bc371519afa90c8e6c5ea9dbeda58395a64400bd499c3fda207cb593b751b44ca - languageName: node - linkType: hard - -"@types/koa__multer@npm:2.0.4": - version: 2.0.4 - resolution: "@types/koa__multer@npm:2.0.4" - dependencies: - "@types/koa": "*" - checksum: 4a945061a6a44ef981081132e85ce9d0c171c4a895439b511313d628f0567d2624635166ac3e2455bdc216b15da83819ab52537f9609bfa1540ffc0d1b09519b - languageName: node - linkType: hard - -"@types/koa__router@npm:8.0.11": - version: 8.0.11 - resolution: "@types/koa__router@npm:8.0.11" - dependencies: - "@types/koa": "*" - checksum: 81f55ed77273871728c81a20fa546ee906bebfe72fd72e3723d983a19504eb7d9578908a0fb8ef230764c5495031412df4245eee93479161bd7bd5135ca1ea04 - languageName: node - linkType: hard - "@types/long@npm:^4.0.1": version: 4.0.2 resolution: "@types/long@npm:4.0.2" @@ -2399,13 +2236,6 @@ __metadata: languageName: node linkType: hard -"@types/mime@npm:*": - version: 3.0.1 - resolution: "@types/mime@npm:3.0.1" - checksum: 4040fac73fd0cea2460e29b348c1a6173da747f3a87da0dbce80dd7a9355a3d0e51d6d9a401654f3e5550620e3718b5a899b2ec1debf18424e298a2c605346e7 - languageName: node - linkType: hard - "@types/minimatch@npm:*": version: 5.1.2 resolution: "@types/minimatch@npm:5.1.2" @@ -2526,13 +2356,6 @@ __metadata: languageName: node linkType: hard -"@types/qs@npm:*": - version: 6.9.7 - resolution: "@types/qs@npm:6.9.7" - checksum: 7fd6f9c25053e9b5bb6bc9f9f76c1d89e6c04f7707a7ba0e44cc01f17ef5284adb82f230f542c2d5557d69407c9a40f0f3515e8319afd14e1e16b5543ac6cdba - languageName: node - linkType: hard - "@types/random-seed@npm:0.3.3": version: 0.3.3 resolution: "@types/random-seed@npm:0.3.3" @@ -2540,13 +2363,6 @@ __metadata: languageName: node linkType: hard -"@types/range-parser@npm:*": - version: 1.2.4 - resolution: "@types/range-parser@npm:1.2.4" - checksum: b7c0dfd5080a989d6c8bb0b6750fc0933d9acabeb476da6fe71d8bdf1ab65e37c136169d84148034802f48378ab94e3c37bb4ef7656b2bec2cb9c0f8d4146a95 - languageName: node - linkType: hard - "@types/ratelimiter@npm:3.4.4": version: 3.4.4 resolution: "@types/ratelimiter@npm:3.4.4" @@ -2609,16 +2425,6 @@ __metadata: languageName: node linkType: hard -"@types/serve-static@npm:*": - version: 1.15.0 - resolution: "@types/serve-static@npm:1.15.0" - dependencies: - "@types/mime": "*" - "@types/node": "*" - checksum: b6ac93d471fb0f53ddcac1f9b67572a09cd62806f7db5855244b28f6f421139626f24799392566e97d1ffc61b12f9de7f30380c39fcae3c8a161fe161d44edf2 - languageName: node - linkType: hard - "@types/sharp@npm:0.31.0": version: 0.31.0 resolution: "@types/sharp@npm:0.31.0" @@ -2727,6 +2533,15 @@ __metadata: languageName: node linkType: hard +"@types/vary@npm:1.1.0": + version: 1.1.0 + resolution: "@types/vary@npm:1.1.0" + dependencies: + "@types/node": "*" + checksum: 6f434a96966ebd4b091592a085cf326f9bfe417aca8714092cc8d3a6d9da775d03c9c799b191f9bbb91ee558831f8b47b2a62f7d7ee0097e8aeee2466d01d515 + languageName: node + linkType: hard + "@types/vinyl-fs@npm:*": version: 2.4.12 resolution: "@types/vinyl-fs@npm:2.4.12" @@ -3088,7 +2903,14 @@ __metadata: languageName: node linkType: hard -"accepts@npm:^1.3.5": +"abstract-logging@npm:^2.0.1": + version: 2.0.1 + resolution: "abstract-logging@npm:2.0.1" + checksum: 6967d15e5abbafd17f56eaf30ba8278c99333586fa4f7935fd80e93cfdc006c37fcc819c5d63ee373a12e6cb2d0417f7c3c6b9e42b957a25af9937d26749415e + languageName: node + linkType: hard + +"accepts@npm:^1.3.5, accepts@npm:^1.3.8": version: 1.3.8 resolution: "accepts@npm:1.3.8" dependencies: @@ -3188,6 +3010,20 @@ __metadata: languageName: node linkType: hard +"ajv-formats@npm:^2.1.1": + version: 2.1.1 + resolution: "ajv-formats@npm:2.1.1" + dependencies: + ajv: ^8.0.0 + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: 4a287d937f1ebaad4683249a4c40c0fa3beed30d9ddc0adba04859026a622da0d317851316ea64b3680dc60f5c3c708105ddd5d5db8fe595d9d0207fd19f90b7 + languageName: node + linkType: hard + "ajv-keywords@npm:^3.5.2": version: 3.5.2 resolution: "ajv-keywords@npm:3.5.2" @@ -3197,7 +3033,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:8.11.2": +"ajv@npm:8.11.2, ajv@npm:^8.0.0, ajv@npm:^8.10.0, ajv@npm:^8.11.0": version: 8.11.2 resolution: "ajv@npm:8.11.2" dependencies: @@ -3358,13 +3194,6 @@ __metadata: languageName: node linkType: hard -"append-field@npm:^1.0.0": - version: 1.0.0 - resolution: "append-field@npm:1.0.0" - checksum: 482ba08acc0ecef00fe7da6bf2f8e48359a9905ee1af525f3120c9260c02e91eedf0579b59d898e8d8455b6c199e340bc0a2fd4b9e02adaa29a8a86c722b37f9 - languageName: node - linkType: hard - "aproba@npm:^1.0.3 || ^2.0.0": version: 2.0.0 resolution: "aproba@npm:2.0.0" @@ -3691,6 +3520,13 @@ __metadata: languageName: node linkType: hard +"atomic-sleep@npm:^1.0.0": + version: 1.0.0 + resolution: "atomic-sleep@npm:1.0.0" + checksum: b95275afb2f80732f22f43a60178430c468906a415a7ff18bcd0feeebc8eec3930b51250aeda91a476062a90e07132b43a1794e8d8ffcf9b650e8139be75fa36 + languageName: node + linkType: hard + "autobind-decorator@npm:2.4.0, autobind-decorator@npm:^2.4.0": version: 2.4.0 resolution: "autobind-decorator@npm:2.4.0" @@ -3735,6 +3571,17 @@ __metadata: languageName: node linkType: hard +"avvio@npm:^8.2.0": + version: 8.2.0 + resolution: "avvio@npm:8.2.0" + dependencies: + archy: ^1.0.0 + debug: ^4.0.0 + fastq: ^1.6.1 + checksum: bbd06eeb1f9ef428dbc32a32e06c350a7b320f60348698fd234145a4100f3688ce5d0999b966eb6ca70f9511d0c35fed5ef4651d276715e7e3e94a2d465cb56d + languageName: node + linkType: hard + "aws-sdk@npm:2.1262.0": version: 2.1262.0 resolution: "aws-sdk@npm:2.1262.0" @@ -3891,14 +3738,12 @@ __metadata: version: 0.0.0-use.local resolution: "backend@workspace:packages/backend" dependencies: - "@bull-board/api": 4.3.1 - "@bull-board/koa": 4.3.1 - "@bull-board/ui": 4.3.1 "@discordapp/twemoji": 14.0.2 - "@elastic/elasticsearch": 7.17.0 - "@koa/cors": 3.3.0 - "@koa/multer": 3.0.0 - "@koa/router": 9.0.1 + "@fastify/accepts": 4.0.1 + "@fastify/cors": 8.2.0 + "@fastify/multipart": 7.3.0 + "@fastify/static": 6.5.0 + "@fastify/view": 7.1.2 "@nestjs/common": 9.2.0 "@nestjs/core": 9.2.0 "@nestjs/testing": 9.2.0 @@ -3910,6 +3755,7 @@ __metadata: "@syuilo/aiscript": 0.11.1 "@tensorflow/tfjs": ^4.1.0 "@tensorflow/tfjs-node": 4.1.0 + "@types/accepts": 1.3.5 "@types/archiver": 5.3.1 "@types/bcryptjs": 2.4.2 "@types/bull": 4.10.0 @@ -3921,17 +3767,6 @@ __metadata: "@types/jsdom": 20.0.1 "@types/jsonld": 1.5.8 "@types/jsrsasign": 10.5.4 - "@types/koa": 2.13.5 - "@types/koa-bodyparser": 4.3.8 - "@types/koa-cors": 0.0.2 - "@types/koa-favicon": 2.0.21 - "@types/koa-logger": 3.1.2 - "@types/koa-mount": 4.0.1 - "@types/koa-send": 4.1.3 - "@types/koa-views": 7.0.0 - "@types/koa__cors": 3.3.0 - "@types/koa__multer": 2.0.4 - "@types/koa__router": 8.0.11 "@types/mime-types": 2.1.1 "@types/node": 18.11.9 "@types/node-fetch": 3.0.3 @@ -3954,11 +3789,13 @@ __metadata: "@types/tmp": 0.2.3 "@types/unzipper": 0.10.5 "@types/uuid": 8.3.4 + "@types/vary": 1.1.0 "@types/web-push": 3.3.2 "@types/websocket": 1.0.5 "@types/ws": 8.5.3 "@typescript-eslint/eslint-plugin": 5.45.0 "@typescript-eslint/parser": 5.45.0 + accepts: ^1.3.8 ajv: 8.11.2 archiver: 5.3.1 autobind-decorator: 2.4.0 @@ -3982,6 +3819,7 @@ __metadata: eslint: 8.28.0 eslint-plugin-import: 2.26.0 execa: 6.1.0 + fastify: 4.10.0 feed: 4.2.2 file-type: 18.0.0 fluent-ffmpeg: 2.1.2 @@ -3999,20 +3837,10 @@ __metadata: json5-loader: 4.0.1 jsonld: 8.1.0 jsrsasign: 10.6.1 - koa: 2.13.4 - koa-bodyparser: 4.3.0 - koa-favicon: 2.1.0 - koa-json-body: 5.3.0 - koa-logger: 3.2.1 - koa-mount: 4.0.0 - koa-send: 5.0.1 - koa-slow: 2.1.0 - koa-views: 7.0.2 mfm-js: 0.23.0 mime-types: 2.1.35 misskey-js: 0.0.14 ms: 3.0.0-canary.1 - multer: 1.4.4 nested-property: 4.0.0 node-fetch: 3.3.0 nodemailer: 6.8.0 @@ -4060,6 +3888,7 @@ __metadata: ulid: 2.3.0 unzipper: 0.10.11 uuid: 9.0.0 + vary: 1.1.2 web-push: 3.5.0 websocket: 1.0.34 ws: 8.11.0 @@ -4430,16 +4259,6 @@ __metadata: languageName: node linkType: hard -"busboy@npm:^0.2.11": - version: 0.2.14 - resolution: "busboy@npm:0.2.14" - dependencies: - dicer: 0.2.5 - readable-stream: 1.1.x - checksum: 9df9fca6d96dab9edd03f568bde31f215794e6fabd73c75d2b39a4be2e8b73a45121d987dea5db881f3fb499737c261b372106fe72d08b8db92afaed8d751165 - languageName: node - linkType: hard - "busboy@npm:^1.6.0": version: 1.6.0 resolution: "busboy@npm:1.6.0" @@ -4449,13 +4268,6 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2, bytes@npm:^3.1.0": - version: 3.1.2 - resolution: "bytes@npm:3.1.2" - checksum: e4bcd3948d289c5127591fbedf10c0b639ccbf00243504e4e127374a15c3bc8eed0d28d4aaab08ff6f1cf2abc0cce6ba3085ed32f4f90e82a5683ce0014e1b6e - languageName: node - linkType: hard - "cacache@npm:^16.1.0": version: 16.1.3 resolution: "cacache@npm:16.1.3" @@ -4702,7 +4514,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^2.0.0, chalk@npm:^2.4.2": +"chalk@npm:^2.0.0": version: 2.4.2 resolution: "chalk@npm:2.4.2" dependencies: @@ -4713,7 +4525,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.2": +"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -5118,30 +4930,6 @@ __metadata: languageName: node linkType: hard -"co-body@npm:^5.0.0": - version: 5.2.0 - resolution: "co-body@npm:5.2.0" - dependencies: - inflation: ^2.0.0 - qs: ^6.4.0 - raw-body: ^2.2.0 - type-is: ^1.6.14 - checksum: 48e1ffe00b8717b68154a939fa19f36d75aa66bba627f2977f28d11b732da56bdda445acda7053f7a85dfbac8a09a8aa257bceedaff7b6467cb25ab08ada9c8d - languageName: node - linkType: hard - -"co-body@npm:^6.0.0": - version: 6.1.0 - resolution: "co-body@npm:6.1.0" - dependencies: - inflation: ^2.0.0 - qs: ^6.5.2 - raw-body: ^2.3.3 - type-is: ^1.6.16 - checksum: d0a78831a6651f2085fce16b0ecdc49f45fb5baf4f94148c2f499e7ec89d188205362548b9c500eae15a819360cfda208079e68a72c204cf66ca3ffa2fc0f57e - languageName: node - linkType: hard - "co@npm:^4.6.0": version: 4.6.0 resolution: "co@npm:4.6.0" @@ -5315,7 +5103,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^2.19.0, commander@npm:^2.20.0": +"commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" checksum: ab8c07884e42c3a8dbc5dd9592c606176c7eb5c1ca5ff274bcf907039b2c41de3626f684ea75ccf4d361ba004bbaff1f577d5384c155f3871e456bdf27becf9e @@ -5383,7 +5171,7 @@ __metadata: languageName: node linkType: hard -"concat-stream@npm:^1.5.2, concat-stream@npm:^1.6.0": +"concat-stream@npm:^1.6.0": version: 1.6.2 resolution: "concat-stream@npm:1.6.2" dependencies: @@ -5395,27 +5183,6 @@ __metadata: languageName: node linkType: hard -"condense-newlines@npm:^0.2.1": - version: 0.2.1 - resolution: "condense-newlines@npm:0.2.1" - dependencies: - extend-shallow: ^2.0.1 - is-whitespace: ^0.3.0 - kind-of: ^3.0.2 - checksum: 3c20ff6ee88b5d2e81c122f33b5ba5d6976cdf86d83527fadea308b3020ed70af7ed98c2e2d94d36f27fcd723a7a477941c19575e0d2c8db6afc4aac6926a54e - languageName: node - linkType: hard - -"config-chain@npm:^1.1.13": - version: 1.1.13 - resolution: "config-chain@npm:1.1.13" - dependencies: - ini: ^1.3.4 - proto-list: ~1.2.1 - checksum: 828137a28e7c2fc4b7fb229bd0cd6c1397bcf83434de54347e608154008f411749041ee392cbe42fab6307e02de4c12480260bf769b7d44b778fdea3839eafab - languageName: node - linkType: hard - "consola@npm:^2.15.0": version: 2.15.3 resolution: "consola@npm:2.15.3" @@ -5430,15 +5197,6 @@ __metadata: languageName: node linkType: hard -"consolidate@npm:^0.16.0": - version: 0.16.0 - resolution: "consolidate@npm:0.16.0" - dependencies: - bluebird: ^3.7.2 - checksum: f17164ffb2c4f79b4cbf685f1c76a49f59d329a40954b436425498861dc137b46fe821b2aadafa2dcfeb7eebd46846f35bd2c36b4a704d38521b4210a22a7515 - languageName: node - linkType: hard - "constantinople@npm:^4.0.1": version: 4.0.1 resolution: "constantinople@npm:4.0.1" @@ -5449,7 +5207,7 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:0.5.4, content-disposition@npm:~0.5.2": +"content-disposition@npm:0.5.4, content-disposition@npm:^0.5.3, content-disposition@npm:~0.5.2": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" dependencies: @@ -5479,6 +5237,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^0.5.0": + version: 0.5.0 + resolution: "cookie@npm:0.5.0" + checksum: 1f4bd2ca5765f8c9689a7e8954183f5332139eb72b6ff783d8947032ec1fdf43109852c178e21a953a30c0dd42257828185be01b49d1eb1a67fd054ca588a180 + languageName: node + linkType: hard + "cookies@npm:~0.8.0": version: 0.8.0 resolution: "cookies@npm:0.8.0" @@ -5506,13 +5271,6 @@ __metadata: languageName: node linkType: hard -"copy-to@npm:^2.0.1": - version: 2.0.1 - resolution: "copy-to@npm:2.0.1" - checksum: 05ea12875bdc96ae053a3b30148e9d992026035ff2bfcc0b615e8d49d1cf8fc3d1f40843f9a4b7b1b6d9118eeebcba31e621076d7de525828aa9c07d22a81dab - languageName: node - linkType: hard - "core-js@npm:3": version: 3.26.1 resolution: "core-js@npm:3.26.1" @@ -5838,7 +5596,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:2, debug@npm:^2.2.0, debug@npm:^2.3.3, debug@npm:^2.5.2, debug@npm:^2.6.9": +"debug@npm:2, debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3, debug@npm:^2.5.2, debug@npm:^2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" dependencies: @@ -5847,7 +5605,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -6086,7 +5844,7 @@ __metadata: languageName: node linkType: hard -"destroy@npm:^1.0.4": +"destroy@npm:1.2.0, destroy@npm:^1.0.4": version: 1.2.0 resolution: "destroy@npm:1.2.0" checksum: 0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 @@ -6130,16 +5888,6 @@ __metadata: languageName: node linkType: hard -"dicer@npm:0.2.5": - version: 0.2.5 - resolution: "dicer@npm:0.2.5" - dependencies: - readable-stream: 1.1.x - streamsearch: 0.1.2 - checksum: a6f0ce9ac5099c7ffeaec7398d711eea1dd803eb99036d0f05342e9ed46a4235a5ed0ea01ad5d6c785fdb0aae6d61d2722e6e64f9fabdfe39885f7f52eb635ee - languageName: node - linkType: hard - "diff-sequences@npm:^29.3.1": version: 29.3.1 resolution: "diff-sequences@npm:29.3.1" @@ -6369,20 +6117,6 @@ __metadata: languageName: node linkType: hard -"editorconfig@npm:^0.15.3": - version: 0.15.3 - resolution: "editorconfig@npm:0.15.3" - dependencies: - commander: ^2.19.0 - lru-cache: ^4.1.5 - semver: ^5.6.0 - sigmund: ^1.0.1 - bin: - editorconfig: bin/editorconfig - checksum: a94afeda19f12a4bcc4a573f0858df13dd3a2d1a3268cc0f17a6326ebe7ddd6cb0c026f8e4e73c17d34f3892bf6f8b561512d9841e70063f61da71b4c57dc5f0 - languageName: node - linkType: hard - "ee-first@npm:1.1.1": version: 1.1.1 resolution: "ee-first@npm:1.1.1" @@ -6390,17 +6124,6 @@ __metadata: languageName: node linkType: hard -"ejs@npm:*, ejs@npm:^3.1.7": - version: 3.1.8 - resolution: "ejs@npm:3.1.8" - dependencies: - jake: ^10.8.5 - bin: - ejs: bin/cli.js - checksum: 1d40d198ad52e315ccf37e577bdec06e24eefdc4e3c27aafa47751a03a0c7f0ec4310254c9277a5f14763c3cd4bbacce27497332b2d87c74232b9b1defef8efc - languageName: node - linkType: hard - "electron-to-chromium@npm:^1.2.7, electron-to-chromium@npm:^1.4.251": version: 1.4.284 resolution: "electron-to-chromium@npm:1.4.284" @@ -6436,7 +6159,7 @@ __metadata: languageName: node linkType: hard -"encodeurl@npm:^1.0.2": +"encodeurl@npm:^1.0.2, encodeurl@npm:~1.0.2": version: 1.0.2 resolution: "encodeurl@npm:1.0.2" checksum: e50e3d508cdd9c4565ba72d2012e65038e5d71bdc9198cb125beb6237b5b1ade6c0d343998da9e170fb2eae52c1bed37d4d6d98a46ea423a0cddbed5ac3f780c @@ -6452,7 +6175,7 @@ __metadata: languageName: node linkType: hard -"end-of-stream@npm:^1.0.0, end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": +"end-of-stream@npm:^1.0.0, end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1, end-of-stream@npm:^1.4.4": version: 1.4.4 resolution: "end-of-stream@npm:1.4.4" dependencies: @@ -7074,7 +6797,7 @@ __metadata: languageName: node linkType: hard -"escape-html@npm:^1.0.3": +"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" checksum: 6213ca9ae00d0ab8bccb6d8d4e0a98e76237b2410302cf7df70aaa6591d509a2a37ce8998008cbecae8fc8ffaadf3fb0229535e6a145f3ce0b211d060decbb24 @@ -7361,6 +7084,13 @@ __metadata: languageName: node linkType: hard +"etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 571aeb3dbe0f2bbd4e4fadbdb44f325fc75335cd5f6f6b6a091e6a06a9f25ed5392f0863c5442acb0646787446e816f13cbfc6edce5b07658541dff573cab1ff + languageName: node + linkType: hard + "event-stream@npm:=3.3.4": version: 3.3.4 resolution: "event-stream@npm:3.3.4" @@ -7411,6 +7141,13 @@ __metadata: languageName: node linkType: hard +"events@npm:^3.3.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: f6f487ad2198aa41d878fa31452f1a3c00958f46e9019286ff4787c84aac329332ab45c9cdc8c445928fc6d7ded294b9e005a7fce9426488518017831b272780 + languageName: node + linkType: hard + "execa@npm:4.1.0": version: 4.1.0 resolution: "execa@npm:4.1.0" @@ -7616,6 +7353,13 @@ __metadata: languageName: node linkType: hard +"fast-decode-uri-component@npm:^1.0.1": + version: 1.0.1 + resolution: "fast-decode-uri-component@npm:1.0.1" + checksum: 427a48fe0907e76f0e9a2c228e253b4d8a8ab21d130ee9e4bb8339c5ba4086235cf9576831f7b20955a752eae4b525a177ff9d5825dd8d416e7726939194fbee + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -7643,6 +7387,20 @@ __metadata: languageName: node linkType: hard +"fast-json-stringify@npm:^5.0.0": + version: 5.4.1 + resolution: "fast-json-stringify@npm:5.4.1" + dependencies: + "@fastify/deepmerge": ^1.0.0 + ajv: ^8.10.0 + ajv-formats: ^2.1.1 + fast-deep-equal: ^3.1.3 + fast-uri: ^2.1.0 + rfdc: ^1.2.0 + checksum: 62efefaf135ff03d810fb362adca1d3471787e4e17ef10e34c8e1d61d361c09736091b1948df2cb408e6b05f18c10985e89bcdd9c08f8f5ba21e148e52a9c5fc + languageName: node + linkType: hard + "fast-levenshtein@npm:^1.0.0": version: 1.1.4 resolution: "fast-levenshtein@npm:1.1.4" @@ -7657,6 +7415,22 @@ __metadata: languageName: node linkType: hard +"fast-querystring@npm:^1.0.0": + version: 1.0.0 + resolution: "fast-querystring@npm:1.0.0" + dependencies: + fast-decode-uri-component: ^1.0.1 + checksum: 5f70df27d02fcf86ea2baa16ea59e0da8bbd891e3a97aa1e95b1c0c64d5445aeab3bde5ce3e603b21d48c87db70a458febf05150a9dbe7c099aced5f123b3ffd + languageName: node + linkType: hard + +"fast-redact@npm:^3.1.1": + version: 3.1.2 + resolution: "fast-redact@npm:3.1.2" + checksum: a30eb6b6830333ab213e0def55f46453ca777544dbd3a883016cb590a0eeb95e6fdf546553c1a13d509896bfba889b789991160a6d0996ceb19fce0a02e8b753 + languageName: node + linkType: hard + "fast-safe-stringify@npm:2.1.1": version: 2.1.1 resolution: "fast-safe-stringify@npm:2.1.1" @@ -7664,6 +7438,13 @@ __metadata: languageName: node linkType: hard +"fast-uri@npm:^2.0.0, fast-uri@npm:^2.1.0": + version: 2.1.0 + resolution: "fast-uri@npm:2.1.0" + checksum: 60ecece5ab05515729ec04d1732ee68bd4429cab8c06ebf8db512a094a0077ddc5af6a27c75922875bc9e13b58e947832242cdcb2cb23c51dc753412222dca83 + languageName: node + linkType: hard + "fast-xml-parser@npm:^3.19.0": version: 3.21.1 resolution: "fast-xml-parser@npm:3.21.1" @@ -7675,7 +7456,36 @@ __metadata: languageName: node linkType: hard -"fastq@npm:^1.6.0": +"fastify-plugin@npm:^4.0.0": + version: 4.3.0 + resolution: "fastify-plugin@npm:4.3.0" + checksum: f4831ca6de3db276f6e5c9ae172c175631be07b24b91e1de17d0cd11c1bd29fe7f671deec591a4dfd26169d3de4f9d9ca385d0e7bddccf38c6d21b5764f8b77d + languageName: node + linkType: hard + +"fastify@npm:4.10.0": + version: 4.10.0 + resolution: "fastify@npm:4.10.0" + dependencies: + "@fastify/ajv-compiler": ^3.3.1 + "@fastify/error": ^3.0.0 + "@fastify/fast-json-stringify-compiler": ^4.1.0 + abstract-logging: ^2.0.1 + avvio: ^8.2.0 + find-my-way: ^7.3.0 + light-my-request: ^5.6.1 + pino: ^8.5.0 + process-warning: ^2.0.0 + proxy-addr: ^2.0.7 + rfdc: ^1.3.0 + secure-json-parse: ^2.5.0 + semver: ^7.3.7 + tiny-lru: ^10.0.0 + checksum: 68c905a930d1e6d31d70a1e048c39c2d5d3749e00b9653fa7f20156da791d68f7edfa2856e8b3ea898b6e38b0a70b09782555e3f503a77cda49e8fd983a1a0ba + languageName: node + linkType: hard + +"fastq@npm:^1.6.0, fastq@npm:^1.6.1": version: 1.13.0 resolution: "fastq@npm:1.13.0" dependencies: @@ -7750,15 +7560,6 @@ __metadata: languageName: node linkType: hard -"filelist@npm:^1.0.1": - version: 1.0.4 - resolution: "filelist@npm:1.0.4" - dependencies: - minimatch: ^5.0.1 - checksum: a303573b0821e17f2d5e9783688ab6fbfce5d52aaac842790ae85e704a6f5e4e3538660a63183d6453834dedf1e0f19a9dadcebfa3e926c72397694ea11f5160 - languageName: node - linkType: hard - "fill-range@npm:^4.0.0": version: 4.0.0 resolution: "fill-range@npm:4.0.0" @@ -7780,6 +7581,17 @@ __metadata: languageName: node linkType: hard +"find-my-way@npm:^7.3.0": + version: 7.3.1 + resolution: "find-my-way@npm:7.3.1" + dependencies: + fast-deep-equal: ^3.1.3 + fast-querystring: ^1.0.0 + safe-regex2: ^2.0.0 + checksum: eec65665c34fbfeb323a52989de51b106485ec0d6182996fc70d42570a73f88b9637572bb8ae89332532da9ca856615e195768116aeede75d73b929b9534bf7a + languageName: node + linkType: hard + "find-up@npm:^1.0.0": version: 1.1.2 resolution: "find-up@npm:1.1.2" @@ -7989,6 +7801,13 @@ __metadata: languageName: node linkType: hard +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: fd27e2394d8887ebd16a66ffc889dc983fbbd797d5d3f01087c020283c0f019a7d05ee85669383d8e0d216b116d720fc0cef2f6e9b7eb9f4c90c6e0bc7fd28e6 + languageName: node + linkType: hard + "fragment-cache@npm:^0.2.1": version: 0.2.1 resolution: "fragment-cache@npm:0.2.1" @@ -7998,7 +7817,7 @@ __metadata: languageName: node linkType: hard -"fresh@npm:~0.5.2": +"fresh@npm:0.5.2, fresh@npm:~0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" checksum: 13ea8b08f91e669a64e3ba3a20eb79d7ca5379a81f1ff7f4310d54e2320645503cc0c78daedc93dfb6191287295f6479544a649c64d8e41a1c0fb0c221552346 @@ -8213,15 +8032,6 @@ __metadata: languageName: node linkType: hard -"get-paths@npm:0.0.7": - version: 0.0.7 - resolution: "get-paths@npm:0.0.7" - dependencies: - pify: ^4.0.1 - checksum: a17edf61fb9934b8e58a7d8ce0d9702040b7020dda86e67ce088db865c21cb230f490f25f38064cebeb2c367abc2bf39a75db6acdfddf01da63a699a47f8aba4 - languageName: node - linkType: hard - "get-pixels-frame-info-update@npm:3.3.2": version: 3.3.2 resolution: "get-pixels-frame-info-update@npm:3.3.2" @@ -8390,7 +8200,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.1, glob@npm:^8.0.3": +"glob@npm:^8.0.1": version: 8.0.3 resolution: "glob@npm:8.0.3" dependencies: @@ -8780,6 +8590,20 @@ __metadata: languageName: node linkType: hard +"hashlru@npm:^2.3.0": + version: 2.3.0 + resolution: "hashlru@npm:2.3.0" + checksum: 38b3559e6fb9d19fa731edc52d8d7e72cd378f708dcb01cecd4a6ba0c52f06d7d06d6277249f5c43d9915d8dda9be31adad768a379eef188db213c3f2b09278d + languageName: node + linkType: hard + +"hexoid@npm:^1.0.0": + version: 1.0.0 + resolution: "hexoid@npm:1.0.0" + checksum: 27a148ca76a2358287f40445870116baaff4a0ed0acc99900bf167f0f708ffd82e044ff55e9949c71963852b580fc024146d3ac6d5d76b508b78d927fa48ae2d + languageName: node + linkType: hard + "highlight.js@npm:^10.7.1": version: 10.7.3 resolution: "highlight.js@npm:10.7.3" @@ -8810,13 +8634,6 @@ __metadata: languageName: node linkType: hard -"hpagent@npm:^0.1.1": - version: 0.1.2 - resolution: "hpagent@npm:0.1.2" - checksum: 1918518ab937d9fa615a47b94489e23662547bc1edf27069ee9bf40bfefb94da65eb142b6f42336b4b0752fce87f66c284d92b97340fd2a90b24aa3616b5450d - languageName: node - linkType: hard - "html-comment-regex@npm:^1.1.0": version: 1.1.2 resolution: "html-comment-regex@npm:1.1.2" @@ -8903,7 +8720,7 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:^1.6.3, http-errors@npm:^1.7.3, http-errors@npm:~1.8.0": +"http-errors@npm:^1.6.3, http-errors@npm:~1.8.0": version: 1.8.1 resolution: "http-errors@npm:1.8.1" dependencies: @@ -8916,18 +8733,6 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:~1.6.2": - version: 1.6.3 - resolution: "http-errors@npm:1.6.3" - dependencies: - depd: ~1.1.2 - inherits: 2.0.3 - setprototypeof: 1.1.0 - statuses: ">= 1.4.0 < 2" - checksum: a9654ee027e3d5de305a56db1d1461f25709ac23267c6dc28cdab8323e3f96caa58a9a6a5e93ac15d7285cee0c2f019378c3ada9026e7fe19c872d695f27de7c - languageName: node - linkType: hard - "http-proxy-agent@npm:^5.0.0": version: 5.0.0 resolution: "http-proxy-agent@npm:5.0.0" @@ -9040,14 +8845,16 @@ __metadata: languageName: node linkType: hard -"humanize-number@npm:0.0.2": - version: 0.0.2 - resolution: "humanize-number@npm:0.0.2" - checksum: 9c98c9d06b0f3d801960be3957199232a5df52377e2502acae92e4f71de633fa62c315a83f24bf96bef76f47b2e3e0e1e4f4157c891e27074fd3272cad6724bb +"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: ">= 2.1.2 < 3.0.0" + checksum: 3f60d47a5c8fc3313317edfd29a00a692cc87a19cac0159e2ce711d0ebc9019064108323b5e493625e25594f11c6236647d8e256fbe7a58f4a3b33b89e6d30bf languageName: node linkType: hard -"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.4": +"iconv-lite@npm:^0.4.4": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" dependencies: @@ -9056,15 +8863,6 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": - version: 0.6.3 - resolution: "iconv-lite@npm:0.6.3" - dependencies: - safer-buffer: ">= 2.1.2 < 3.0.0" - checksum: 3f60d47a5c8fc3313317edfd29a00a692cc87a19cac0159e2ce711d0ebc9019064108323b5e493625e25594f11c6236647d8e256fbe7a58f4a3b33b89e6d30bf - languageName: node - linkType: hard - "idb-keyval@npm:6.2.0, idb-keyval@npm:^6.1.0": version: 6.2.0 resolution: "idb-keyval@npm:6.2.0" @@ -9152,13 +8950,6 @@ __metadata: languageName: node linkType: hard -"inflation@npm:^2.0.0": - version: 2.0.0 - resolution: "inflation@npm:2.0.0" - checksum: a0494871b12275afdef9e2710ee1af1e0fc642b04613a9be69c05ef8b5e9627f3bd7d358a937fa47aa20235ee7313a4f30255048533add0ad4918beb918a586e - languageName: node - linkType: hard - "inflight@npm:^1.0.4": version: 1.0.6 resolution: "inflight@npm:1.0.6" @@ -9176,13 +8967,6 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2.0.3": - version: 2.0.3 - resolution: "inherits@npm:2.0.3" - checksum: 78cb8d7d850d20a5e9a7f3620db31483aa00ad5f722ce03a55b110e5a723539b3716a3b463e2b96ce3fe286f33afc7c131fa2f91407528ba80cea98a7545d4c0 - languageName: node - linkType: hard - "ini@npm:2.0.0": version: 2.0.0 resolution: "ini@npm:2.0.0" @@ -9323,6 +9107,13 @@ __metadata: languageName: node linkType: hard +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: f88d3825981486f5a1942414c8d77dd6674dd71c065adcfa46f578d677edcb99fda25af42675cb59db492fdf427b34a5abfcde3982da11a8fd83a500b41cfe77 + languageName: node + linkType: hard + "ipaddr.js@npm:^2.0.1": version: 2.0.1 resolution: "ipaddr.js@npm:2.0.1" @@ -9836,13 +9627,6 @@ __metadata: languageName: node linkType: hard -"is-whitespace@npm:^0.3.0": - version: 0.3.0 - resolution: "is-whitespace@npm:0.3.0" - checksum: dac8fc9a9b797afeef703f625269601715552883790d1385d6bb27dd04ffdafd5fddca8f2d85ee96913850211595da2ba483dac1f166829c4078fb58ce815140 - languageName: node - linkType: hard - "is-windows@npm:^1.0.1, is-windows@npm:^1.0.2": version: 1.0.2 resolution: "is-windows@npm:1.0.2" @@ -9963,20 +9747,6 @@ __metadata: languageName: node linkType: hard -"jake@npm:^10.8.5": - version: 10.8.5 - resolution: "jake@npm:10.8.5" - dependencies: - async: ^3.2.3 - chalk: ^4.0.2 - filelist: ^1.0.1 - minimatch: ^3.0.4 - bin: - jake: ./bin/cli.js - checksum: 56c913ecf5a8d74325d0af9bc17a233bad50977438d44864d925bb6c45c946e0fee8c4c1f5fe2225471ef40df5222e943047982717ebff0d624770564d3c46ba - languageName: node - linkType: hard - "jest-changed-files@npm:^29.2.0": version: 29.2.0 resolution: "jest-changed-files@npm:29.2.0" @@ -10460,22 +10230,6 @@ __metadata: languageName: node linkType: hard -"js-beautify@npm:^1.6.12": - version: 1.14.7 - resolution: "js-beautify@npm:1.14.7" - dependencies: - config-chain: ^1.1.13 - editorconfig: ^0.15.3 - glob: ^8.0.3 - nopt: ^6.0.0 - bin: - css-beautify: js/bin/css-beautify.js - html-beautify: js/bin/html-beautify.js - js-beautify: js/bin/js-beautify.js - checksum: 1950d0d3f05f8ad06b73eb77b9aac602d00b24eab7d8a6d8ea0b1841ab9c730acecd5a6f3926e360dce7a2583481bc77caf6d024490a58fa9897cbbbdfc35984 - languageName: node - linkType: hard - "js-levenshtein@npm:^1.1.6": version: 1.1.6 resolution: "js-levenshtein@npm:1.1.6" @@ -10869,173 +10623,39 @@ __metadata: checksum: f2a0102ae0cf19c4a953397e552571bad2b588b53282874f25fca7236396e650e2db50d41f9f516bd402536e4df968dbb51b8e69e4d5d4a7173def78448f7bab languageName: node linkType: hard - -"kind-of@npm:^6.0.0, kind-of@npm:^6.0.2": - version: 6.0.3 - resolution: "kind-of@npm:6.0.3" - checksum: 3ab01e7b1d440b22fe4c31f23d8d38b4d9b91d9f291df683476576493d5dfd2e03848a8b05813dd0c3f0e835bc63f433007ddeceb71f05cb25c45ae1b19c6d3b - languageName: node - linkType: hard - -"kleur@npm:^3.0.3": - version: 3.0.3 - resolution: "kleur@npm:3.0.3" - checksum: df82cd1e172f957bae9c536286265a5cdbd5eeca487cb0a3b2a7b41ef959fc61f8e7c0e9aeea9c114ccf2c166b6a8dd45a46fd619c1c569d210ecd2765ad5169 - languageName: node - linkType: hard - -"koa-bodyparser@npm:4.3.0": - version: 4.3.0 - resolution: "koa-bodyparser@npm:4.3.0" - dependencies: - co-body: ^6.0.0 - copy-to: ^2.0.1 - checksum: c227fe0fb5a55b98fc91d865e80229b60178d216d53b732b07833eb38f48a7ed6aa768a083bc06e359db33298547e9a65842fbe9d3f0fdaf5149fe0becafc88f - languageName: node - linkType: hard - -"koa-compose@npm:^4.1.0": - version: 4.1.0 - resolution: "koa-compose@npm:4.1.0" - checksum: 46cb16792d96425e977c2ae4e5cb04930280740e907242ec9c25e3fb8b4a1d7b54451d7432bc24f40ec62255edea71894d2ceeb8238501842b4e48014f2e83db - languageName: node - linkType: hard - -"koa-convert@npm:^2.0.0": - version: 2.0.0 - resolution: "koa-convert@npm:2.0.0" - dependencies: - co: ^4.6.0 - koa-compose: ^4.1.0 - checksum: 7385b3391995f59c1312142e110d5dff677f9850dbfbcf387cd36a7b0af03b5d26e82b811eb9bb008b4f3e661cdab1f8817596e46b1929da2cf6e97a2f7456ed - languageName: node - linkType: hard - -"koa-favicon@npm:2.1.0": - version: 2.1.0 - resolution: "koa-favicon@npm:2.1.0" - dependencies: - mz: ^2.7.0 - checksum: 024051a0be3560a77e65651ad87690d432a28bed3c4dd24cbcbe3249a38d6cc0c80613d37b45107c8b709cb01fecf8723e77d50cbb2ce67c8451fa29891d228f - languageName: node - linkType: hard - -"koa-json-body@npm:5.3.0": - version: 5.3.0 - resolution: "koa-json-body@npm:5.3.0" - dependencies: - co-body: ^5.0.0 - checksum: b3ae5f304cbb6f8f6a4a2ba36047d1ea6fe32005e7665d795802eb4c3dd1f341a0b8e61087fffc6bd2da6f554d71b06b047cbb3669b1eea814285f75d9bdd736 - languageName: node - linkType: hard - -"koa-logger@npm:3.2.1": - version: 3.2.1 - resolution: "koa-logger@npm:3.2.1" - dependencies: - bytes: ^3.1.0 - chalk: ^2.4.2 - humanize-number: 0.0.2 - passthrough-counter: ^1.0.0 - checksum: b29ba25eb433452bfda48e51acd5d206128411966acc09bb13ce3a0cec9192f78bb27e23efd615d0e7f46eeb2588ee8d2541d72665a4aa18d27a177e78dca909 - languageName: node - linkType: hard - -"koa-mount@npm:4.0.0, koa-mount@npm:^4.0.0": - version: 4.0.0 - resolution: "koa-mount@npm:4.0.0" - dependencies: - debug: ^4.0.1 - koa-compose: ^4.1.0 - checksum: c7e8c5cca4d2ccc4742e63c81b86b44f0290075148897b5d633acdd137e90f554c60c232fbc62e843eaedb913b67c5a49367c1142e290b8cfd9c28eb4a0480ec - languageName: node - linkType: hard - -"koa-router@npm:^10.0.0": - version: 10.1.1 - resolution: "koa-router@npm:10.1.1" - dependencies: - debug: ^4.1.1 - http-errors: ^1.7.3 - koa-compose: ^4.1.0 - methods: ^1.1.2 - path-to-regexp: ^6.1.0 - checksum: 65e6cd4a7f8a4d98c665b00ee4c2c05340cb38ca035590ce71c23a25c0a01f6d2434d9a68366d7c218af9c94e5d8e20c7fe9e7f7dfbb98d69b11b5ae3246aaf8 - languageName: node - linkType: hard - -"koa-send@npm:5.0.1, koa-send@npm:^5.0.0": - version: 5.0.1 - resolution: "koa-send@npm:5.0.1" - dependencies: - debug: ^4.1.1 - http-errors: ^1.7.3 - resolve-path: ^1.4.0 - checksum: a9fbaadbe0f50efd157a733df4a1cc2b3b79b0cdf12e67c718641e6038d1792c0bebe40913e6d4ceb707d970301155be3859b98d1ef08b0fd1766f7326b82853 - languageName: node - linkType: hard - -"koa-slow@npm:2.1.0": - version: 2.1.0 - resolution: "koa-slow@npm:2.1.0" - dependencies: - lodash.isregexp: 3.0.5 - q: 1.4.1 - checksum: 1b2fa6c709cd4016f5c5c4f45a8bd569910fdfef482c85120f2bbddd5cf274d714b0d231659ac3335d15b03f0debdb71b14f3cc54624921be7d808df7f8ac513 + +"kind-of@npm:^6.0.0, kind-of@npm:^6.0.2": + version: 6.0.3 + resolution: "kind-of@npm:6.0.3" + checksum: 3ab01e7b1d440b22fe4c31f23d8d38b4d9b91d9f291df683476576493d5dfd2e03848a8b05813dd0c3f0e835bc63f433007ddeceb71f05cb25c45ae1b19c6d3b languageName: node linkType: hard -"koa-static@npm:^5.0.0": - version: 5.0.0 - resolution: "koa-static@npm:5.0.0" - dependencies: - debug: ^3.1.0 - koa-send: ^5.0.0 - checksum: 8d9b9c4d2b3b13e8818e804245d784099c4b353b55ddd7dbeeb90f27a2e9f5b6f86bd16a4909e337cb89db4d332d9002e6c0f5056caf75749cab62f93c1f0cc5 +"kleur@npm:^3.0.3": + version: 3.0.3 + resolution: "kleur@npm:3.0.3" + checksum: df82cd1e172f957bae9c536286265a5cdbd5eeca487cb0a3b2a7b41ef959fc61f8e7c0e9aeea9c114ccf2c166b6a8dd45a46fd619c1c569d210ecd2765ad5169 languageName: node linkType: hard -"koa-views@npm:*": - version: 8.0.0 - resolution: "koa-views@npm:8.0.0" - dependencies: - consolidate: ^0.16.0 - debug: ^4.1.0 - get-paths: 0.0.7 - koa-send: ^5.0.0 - mz: ^2.4.0 - pretty: ^2.0.0 - resolve-path: ^1.4.0 - peerDependencies: - "@types/koa": ^2.13.1 - peerDependenciesMeta: - "@types/koa": - optional: true - checksum: 6e7e4df04fcab8e5c50a82b38518b7e87491fef1a80f8105e34aaf82a93c0abf051a011d02a8bed8542255724bdb2b83cda7b63e094983c01a5c3927ea08be2c +"koa-compose@npm:^4.1.0": + version: 4.1.0 + resolution: "koa-compose@npm:4.1.0" + checksum: 46cb16792d96425e977c2ae4e5cb04930280740e907242ec9c25e3fb8b4a1d7b54451d7432bc24f40ec62255edea71894d2ceeb8238501842b4e48014f2e83db languageName: node linkType: hard -"koa-views@npm:7.0.2, koa-views@npm:^7.0.1": - version: 7.0.2 - resolution: "koa-views@npm:7.0.2" +"koa-convert@npm:^2.0.0": + version: 2.0.0 + resolution: "koa-convert@npm:2.0.0" dependencies: - consolidate: ^0.16.0 - debug: ^4.1.0 - get-paths: 0.0.7 - koa-send: ^5.0.0 - mz: ^2.4.0 - pretty: ^2.0.0 - resolve-path: ^1.4.0 - peerDependencies: - "@types/koa": ^2.13.1 - peerDependenciesMeta: - "@types/koa": - optional: true - checksum: e591a131de09cf2676ae0492dabf420015404cd1198092a2aa217118c5e7df5da848f49c658f46af172028a508cecbdb0a81e45c5acf5cf40c2baf7c9d08675e + co: ^4.6.0 + koa-compose: ^4.1.0 + checksum: 7385b3391995f59c1312142e110d5dff677f9850dbfbcf387cd36a7b0af03b5d26e82b811eb9bb008b4f3e661cdab1f8817596e46b1929da2cf6e97a2f7456ed languageName: node linkType: hard -"koa@npm:2.13.4, koa@npm:^2.13.1": +"koa@npm:2.13.4": version: 2.13.4 resolution: "koa@npm:2.13.4" dependencies: @@ -11176,6 +10796,17 @@ __metadata: languageName: node linkType: hard +"light-my-request@npm:^5.6.1": + version: 5.7.0 + resolution: "light-my-request@npm:5.7.0" + dependencies: + cookie: ^0.5.0 + process-warning: ^2.0.0 + set-cookie-parser: ^2.4.1 + checksum: 3da727736c03b89d1eac4b3cebc9de3d8b725a1485fb86f63b450715fa1cb7f049d7df391b831eb9ec352287fd58f1948835c1e1e78444f193eb0a9c90216aed + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -11323,13 +10954,6 @@ __metadata: languageName: node linkType: hard -"lodash.isregexp@npm:3.0.5": - version: 3.0.5 - resolution: "lodash.isregexp@npm:3.0.5" - checksum: 973f4887f003af746bf838267d9d1ea39d912f579cf402cca67049b1e4487daf2a25b10c70e4fc1c7ad97ee3be6d43d38c9839bc9c55c40e94b62dfc60f601c7 - languageName: node - linkType: hard - "lodash.map@npm:^4.4.0": version: 4.6.0 resolution: "lodash.map@npm:4.6.0" @@ -11457,16 +11081,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^4.1.5": - version: 4.1.5 - resolution: "lru-cache@npm:4.1.5" - dependencies: - pseudomap: ^1.0.2 - yallist: ^2.1.2 - checksum: 4bb4b58a36cd7dc4dcec74cbe6a8f766a38b7426f1ff59d4cf7d82a2aa9b9565cd1cb98f6ff60ce5cd174524868d7bc9b7b1c294371851356066ca9ac4cf135a - languageName: node - linkType: hard - "lru-cache@npm:^6.0.0": version: 6.0.0 resolution: "lru-cache@npm:6.0.0" @@ -11634,13 +11248,6 @@ __metadata: languageName: node linkType: hard -"methods@npm:^1.1.2": - version: 1.1.2 - resolution: "methods@npm:1.1.2" - checksum: 0917ff4041fa8e2f2fda5425a955fe16ca411591fbd123c0d722fcf02b73971ed6f764d85f0a6f547ce49ee0221ce2c19a5fa692157931cecb422984f1dcd13a - languageName: node - linkType: hard - "mfm-js@npm:0.23.0": version: 0.23.0 resolution: "mfm-js@npm:0.23.0" @@ -11697,6 +11304,15 @@ __metadata: languageName: node linkType: hard +"mime@npm:1.6.0": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: fef25e39263e6d207580bdc629f8872a3f9772c923c7f8c7e793175cee22777bbe8bba95e5d509a40aaa292d8974514ce634ae35769faa45f22d17edda5e8557 + languageName: node + linkType: hard + "mimic-fn@npm:^2.1.0": version: 2.1.0 resolution: "mimic-fn@npm:2.1.0" @@ -11904,7 +11520,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:>=0.5 0, mkdirp@npm:^0.5.4, mkdirp@npm:^0.5.5, mkdirp@npm:~0.5.1": +"mkdirp@npm:>=0.5 0, mkdirp@npm:^0.5.5, mkdirp@npm:~0.5.1": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" dependencies: @@ -11924,6 +11540,15 @@ __metadata: languageName: node linkType: hard +"mnemonist@npm:0.39.5": + version: 0.39.5 + resolution: "mnemonist@npm:0.39.5" + dependencies: + obliterator: ^2.0.1 + checksum: 6669d687a434226924b2c84ee6eb7ce7d0f83dfc5caad8bcc164c73c0c11fb6d43cbe32636e710f068046f4b40a56c3032532554e93e02640aafc6ca3dd222e6 + languageName: node + linkType: hard + "moment@npm:^2.22.2": version: 2.29.4 resolution: "moment@npm:2.29.4" @@ -11945,6 +11570,13 @@ __metadata: languageName: node linkType: hard +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d + languageName: node + linkType: hard + "ms@npm:3.0.0-canary.1": version: 3.0.0-canary.1 resolution: "ms@npm:3.0.0-canary.1" @@ -11952,13 +11584,6 @@ __metadata: languageName: node linkType: hard -"ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": - version: 2.1.3 - resolution: "ms@npm:2.1.3" - checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d - languageName: node - linkType: hard - "msgpackr-extract@npm:^2.2.0": version: 2.2.0 resolution: "msgpackr-extract@npm:2.2.0" @@ -12002,22 +11627,6 @@ __metadata: languageName: node linkType: hard -"multer@npm:1.4.4": - version: 1.4.4 - resolution: "multer@npm:1.4.4" - dependencies: - append-field: ^1.0.0 - busboy: ^0.2.11 - concat-stream: ^1.5.2 - mkdirp: ^0.5.4 - object-assign: ^4.1.1 - on-finished: ^2.3.0 - type-is: ^1.6.4 - xtend: ^4.0.0 - checksum: b5550d250aeee9c4d630eaecd133af0899239f6b10cec4b448ddd0a808025b383520b8227198a8612f60c2cd2094bcb60de93d973084f889d4e40efe6dbd641e - languageName: node - linkType: hard - "multi-integer-range@npm:3.0.0": version: 3.0.0 resolution: "multi-integer-range@npm:3.0.0" @@ -12039,7 +11648,7 @@ __metadata: languageName: node linkType: hard -"mz@npm:^2.4.0, mz@npm:^2.7.0": +"mz@npm:^2.4.0": version: 2.7.0 resolution: "mz@npm:2.7.0" dependencies: @@ -12632,6 +12241,13 @@ __metadata: languageName: node linkType: hard +"obliterator@npm:^2.0.1": + version: 2.0.4 + resolution: "obliterator@npm:2.0.4" + checksum: f28ad35b6d812089315f375dc3e6e5f9bebf958ebe4b10ccd471c7115cbcf595e74bdac4783ae758e5b1f47e3096427fdb37cfa7bed566b132df92ff317b9a7c + languageName: node + linkType: hard + "oblivious-set@npm:1.1.1": version: 1.1.1 resolution: "oblivious-set@npm:1.1.1" @@ -12646,7 +12262,14 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:^2.3.0": +"on-exit-leak-free@npm:^2.1.0": + version: 2.1.0 + resolution: "on-exit-leak-free@npm:2.1.0" + checksum: 7334d98b87b0c89c9b69c747760b21196ff35afdedc4eaf1a0a3a02964463d7f6802481b120e4c8298967c74773ca7b914ab2eb3d9b279010eb7f67ac4960eed + languageName: node + linkType: hard + +"on-finished@npm:2.4.1, on-finished@npm:^2.3.0": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -12981,13 +12604,6 @@ __metadata: languageName: node linkType: hard -"passthrough-counter@npm:^1.0.0": - version: 1.0.0 - resolution: "passthrough-counter@npm:1.0.0" - checksum: 942a0addeb677e24ddb154b04cc29ce1c5720032efc268689446420f9350d47e94f2f1f76d469686bc87c1543c2f2165f2d004d265fe1b81465c76e02d272c63 - languageName: node - linkType: hard - "path-dirname@npm:^1.0.0": version: 1.0.2 resolution: "path-dirname@npm:1.0.2" @@ -13011,7 +12627,7 @@ __metadata: languageName: node linkType: hard -"path-is-absolute@npm:1.0.1, path-is-absolute@npm:^1.0.0": +"path-is-absolute@npm:^1.0.0": version: 1.0.1 resolution: "path-is-absolute@npm:1.0.1" checksum: 060840f92cf8effa293bcc1bea81281bd7d363731d214cbe5c227df207c34cd727430f70c6037b5159c8a870b9157cba65e775446b0ab06fd5ecc7e54615a3b8 @@ -13062,13 +12678,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:^6.1.0": - version: 6.2.1 - resolution: "path-to-regexp@npm:6.2.1" - checksum: f0227af8284ea13300f4293ba111e3635142f976d4197f14d5ad1f124aebd9118783dd2e5f1fe16f7273743cc3dbeddfb7493f237bb27c10fdae07020cc9b698 - languageName: node - linkType: hard - "path-type@npm:^1.0.0": version: 1.1.0 resolution: "path-type@npm:1.1.0" @@ -13217,13 +12826,6 @@ __metadata: languageName: node linkType: hard -"pify@npm:^4.0.1": - version: 4.0.1 - resolution: "pify@npm:4.0.1" - checksum: 9c4e34278cb09987685fa5ef81499c82546c033713518f6441778fbec623fc708777fe8ac633097c72d88470d5963094076c7305cafc7ad340aae27cfacd856b - languageName: node - linkType: hard - "pinkie-promise@npm:^2.0.0": version: 2.0.1 resolution: "pinkie-promise@npm:2.0.1" @@ -13240,6 +12842,44 @@ __metadata: languageName: node linkType: hard +"pino-abstract-transport@npm:v1.0.0": + version: 1.0.0 + resolution: "pino-abstract-transport@npm:1.0.0" + dependencies: + readable-stream: ^4.0.0 + split2: ^4.0.0 + checksum: 05dd0eda52dd99fd204b39fe7b62656744b63e863bc052cdd5105d25f226a236966d0a46e39a1ace4838f6e988c608837ff946d2d0bc92835ca7baa0a3bff8d8 + languageName: node + linkType: hard + +"pino-std-serializers@npm:^6.0.0": + version: 6.0.0 + resolution: "pino-std-serializers@npm:6.0.0" + checksum: d9dc1779b3870cdbe00dc2dff15e3931eb126bb144bc9f746d83a2c1174a28e366ed0abe63379dee2fee474e6018a088bfbb2c4b57c1e206601918f5a61e276f + languageName: node + linkType: hard + +"pino@npm:^8.5.0": + version: 8.7.0 + resolution: "pino@npm:8.7.0" + dependencies: + atomic-sleep: ^1.0.0 + fast-redact: ^3.1.1 + on-exit-leak-free: ^2.1.0 + pino-abstract-transport: v1.0.0 + pino-std-serializers: ^6.0.0 + process-warning: ^2.0.0 + quick-format-unescaped: ^4.0.3 + real-require: ^0.2.0 + safe-stable-stringify: ^2.3.1 + sonic-boom: ^3.1.0 + thread-stream: ^2.0.0 + bin: + pino: bin.js + checksum: 4aa2e320aa88f4a90fd25884ee4e3b9ef7963b3c59c514f3693b5a5c987b112cf3ab4e39a8c51efe32c861f5c058d7cfa7fcda59d964ed878f842fdbc6ab2876 + languageName: node + linkType: hard + "pirates@npm:^4.0.4": version: 4.0.5 resolution: "pirates@npm:4.0.5" @@ -13728,17 +13368,6 @@ __metadata: languageName: node linkType: hard -"pretty@npm:^2.0.0": - version: 2.0.0 - resolution: "pretty@npm:2.0.0" - dependencies: - condense-newlines: ^0.2.1 - extend-shallow: ^2.0.1 - js-beautify: ^1.6.12 - checksum: 9c41ae0559195af2fb2496d84c6f442843e045d269d4008a6dd336f8372d7481395ed5ab23e5711b6172682c27cb0542e1ab3ca11b38da48f1109c0b701d0ef9 - languageName: node - linkType: hard - "prismjs@npm:1.29.0": version: 1.29.0 resolution: "prismjs@npm:1.29.0" @@ -13788,6 +13417,20 @@ __metadata: languageName: node linkType: hard +"process-warning@npm:^2.0.0": + version: 2.0.0 + resolution: "process-warning@npm:2.0.0" + checksum: a2bb299835bced58e63cbe06a8fd6e048a648d3649e81b62c442b63112a3f0a86912e7b1a9c557daca30652232d3b0a7f1972fb87c36334e2a5a6f3d5c4a76c9 + languageName: node + linkType: hard + +"process@npm:^0.11.10": + version: 0.11.10 + resolution: "process@npm:0.11.10" + checksum: bfcce49814f7d172a6e6a14d5fa3ac92cc3d0c3b9feb1279774708a719e19acd673995226351a082a9ae99978254e320ccda4240ddc474ba31a76c79491ca7c3 + languageName: node + linkType: hard + "progress@npm:^2.0.0": version: 2.0.3 resolution: "progress@npm:2.0.3" @@ -13838,10 +13481,13 @@ __metadata: languageName: node linkType: hard -"proto-list@npm:~1.2.1": - version: 1.2.4 - resolution: "proto-list@npm:1.2.4" - checksum: 4d4826e1713cbfa0f15124ab0ae494c91b597a3c458670c9714c36e8baddf5a6aad22842776f2f5b137f259c8533e741771445eb8df82e861eea37a6eaba03f7 +"proxy-addr@npm:^2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + checksum: 29c6990ce9364648255454842f06f8c46fcd124d3e6d7c5066df44662de63cdc0bad032e9bf5a3d653ff72141cc7b6019873d685708ac8210c30458ad99f2b74 languageName: node linkType: hard @@ -13863,13 +13509,6 @@ __metadata: languageName: node linkType: hard -"pseudomap@npm:^1.0.2": - version: 1.0.2 - resolution: "pseudomap@npm:1.0.2" - checksum: 856c0aae0ff2ad60881168334448e898ad7a0e45fe7386d114b150084254c01e200c957cf378378025df4e052c7890c5bd933939b0e0d2ecfcc1dc2f0b2991f5 - languageName: node - linkType: hard - "psl@npm:^1.1.28, psl@npm:^1.1.33": version: 1.9.0 resolution: "psl@npm:1.9.0" @@ -13988,7 +13627,7 @@ __metadata: languageName: node linkType: hard -"pug@npm:*, pug@npm:3.0.2": +"pug@npm:3.0.2": version: 3.0.2 resolution: "pug@npm:3.0.2" dependencies: @@ -14060,13 +13699,6 @@ __metadata: languageName: node linkType: hard -"q@npm:1.4.1": - version: 1.4.1 - resolution: "q@npm:1.4.1" - checksum: 22c8e1f24f416d0977e6da63f24712189c5dd789489999fc040467480e4e0ef4bd0e3126cce1b8ef72c709bbe1fcce10eba0f4991a03fc64ecb5a17e05ed8d35 - languageName: node - linkType: hard - "q@npm:^1.1.2": version: 1.5.1 resolution: "q@npm:1.5.1" @@ -14088,15 +13720,6 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.4.0, qs@npm:^6.5.2": - version: 6.11.0 - resolution: "qs@npm:6.11.0" - dependencies: - side-channel: ^1.0.4 - checksum: 6e1f29dd5385f7488ec74ac7b6c92f4d09a90408882d0c208414a34dd33badc1a621019d4c799a3df15ab9b1d0292f97c1dd71dc7c045e69f81a8064e5af7297 - languageName: node - linkType: hard - "qs@npm:~6.5.2": version: 6.5.3 resolution: "qs@npm:6.5.3" @@ -14149,6 +13772,13 @@ __metadata: languageName: node linkType: hard +"quick-format-unescaped@npm:^4.0.3": + version: 4.0.4 + resolution: "quick-format-unescaped@npm:4.0.4" + checksum: 7bc32b99354a1aa46c089d2a82b63489961002bb1d654cee3e6d2d8778197b68c2d854fd23d8422436ee1fdfd0abaddc4d4da120afe700ade68bd357815b26fd + languageName: node + linkType: hard + "quick-lru@npm:^5.1.1": version: 5.1.1 resolution: "quick-lru@npm:5.1.1" @@ -14165,6 +13795,13 @@ __metadata: languageName: node linkType: hard +"range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 0a268d4fea508661cf5743dfe3d5f47ce214fd6b7dec1de0da4d669dd4ef3d2144468ebe4179049eff253d9d27e719c88dae55be64f954e80135a0cada804ec9 + languageName: node + linkType: hard + "rangestr@npm:0.0.1": version: 0.0.1 resolution: "rangestr@npm:0.0.1" @@ -14179,18 +13816,6 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:^2.2.0, raw-body@npm:^2.3.3": - version: 2.5.1 - resolution: "raw-body@npm:2.5.1" - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - checksum: 5362adff1575d691bb3f75998803a0ffed8c64eabeaa06e54b4ada25a0cd1b2ae7f4f5ec46565d1bec337e08b5ac90c76eaa0758de6f72a633f025d754dec29e - languageName: node - linkType: hard - "rc@npm:^1.2.7": version: 1.2.8 resolution: "rc@npm:1.2.8" @@ -14253,18 +13878,6 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:1.1.x, readable-stream@npm:~1.1.9": - version: 1.1.14 - resolution: "readable-stream@npm:1.1.14" - dependencies: - core-util-is: ~1.0.0 - inherits: ~2.0.1 - isarray: 0.0.1 - string_decoder: ~0.10.x - checksum: 17dfeae3e909945a4a1abc5613ea92d03269ef54c49288599507fc98ff4615988a1c39a999dcf9aacba70233d9b7040bc11a5f2bfc947e262dedcc0a8b32b5a0 - languageName: node - linkType: hard - "readable-stream@npm:3, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": version: 3.6.0 resolution: "readable-stream@npm:3.6.0" @@ -14291,6 +13904,30 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^4.0.0": + version: 4.2.0 + resolution: "readable-stream@npm:4.2.0" + dependencies: + abort-controller: ^3.0.0 + buffer: ^6.0.3 + events: ^3.3.0 + process: ^0.11.10 + checksum: aa8447f781e6df90af78f6b0b9b9a77da2816dcf6c8220e7021c4de36e04f8129fed7ead81eac0baad2f42098209f9e7d7cd43169e1c156efcd2613828a37439 + languageName: node + linkType: hard + +"readable-stream@npm:~1.1.9": + version: 1.1.14 + resolution: "readable-stream@npm:1.1.14" + dependencies: + core-util-is: ~1.0.0 + inherits: ~2.0.1 + isarray: 0.0.1 + string_decoder: ~0.10.x + checksum: 17dfeae3e909945a4a1abc5613ea92d03269ef54c49288599507fc98ff4615988a1c39a999dcf9aacba70233d9b7040bc11a5f2bfc947e262dedcc0a8b32b5a0 + languageName: node + linkType: hard + "readable-web-to-node-stream@npm:^3.0.2": version: 3.0.2 resolution: "readable-web-to-node-stream@npm:3.0.2" @@ -14318,6 +13955,13 @@ __metadata: languageName: node linkType: hard +"real-require@npm:^0.2.0": + version: 0.2.0 + resolution: "real-require@npm:0.2.0" + checksum: fa060f19f2f447adf678d1376928c76379dce5f72bd334da301685ca6cdcb7b11356813332cc243c88470796bc2e2b1e2917fc10df9143dd93c2ea608694971d + languageName: node + linkType: hard + "rechoir@npm:^0.6.2": version: 0.6.2 resolution: "rechoir@npm:0.6.2" @@ -14348,15 +13992,6 @@ __metadata: languageName: node linkType: hard -"redis-info@npm:^3.0.8": - version: 3.1.0 - resolution: "redis-info@npm:3.1.0" - dependencies: - lodash: ^4.17.11 - checksum: d72ff0584ebb4a2149cfcfcf9142d9a7f9d0b96ae53fbf431f2738f33f1f42add6505ff73b2d640cab345923a34b217d7c728fa706cc81ad8bd8ad4c48987445 - languageName: node - linkType: hard - "redis-lock@npm:0.1.4": version: 0.1.4 resolution: "redis-lock@npm:0.1.4" @@ -14657,16 +14292,6 @@ __metadata: languageName: node linkType: hard -"resolve-path@npm:^1.4.0": - version: 1.4.0 - resolution: "resolve-path@npm:1.4.0" - dependencies: - http-errors: ~1.6.2 - path-is-absolute: 1.0.1 - checksum: 1a39f569ee54dd5f8ee8576ef8671c9724bea65d9f9982fbb5352af9fb4e500e1e459c1bfb1ae3ebfd8d43a709c3a01dfa4f46cf5b831e45e2caed4f1a208300 - languageName: node - linkType: hard - "resolve-url@npm:^0.2.1": version: 0.2.1 resolution: "resolve-url@npm:0.2.1" @@ -14742,6 +14367,13 @@ __metadata: languageName: node linkType: hard +"ret@npm:~0.2.0": + version: 0.2.2 + resolution: "ret@npm:0.2.2" + checksum: 774964bb413a3525e687bca92d81c1cd75555ec33147c32ecca22f3d06409e35df87952cfe3d57afff7650a0f7e42139cf60cb44e94c29dde390243bc1941f16 + languageName: node + linkType: hard + "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -14756,7 +14388,7 @@ __metadata: languageName: node linkType: hard -"rfdc@npm:^1.3.0": +"rfdc@npm:^1.2.0, rfdc@npm:^1.3.0": version: 1.3.0 resolution: "rfdc@npm:1.3.0" checksum: fb2ba8512e43519983b4c61bd3fa77c0f410eff6bae68b08614437bc3f35f91362215f7b4a73cbda6f67330b5746ce07db5dd9850ad3edc91271ad6deea0df32 @@ -14890,6 +14522,15 @@ __metadata: languageName: node linkType: hard +"safe-regex2@npm:^2.0.0": + version: 2.0.0 + resolution: "safe-regex2@npm:2.0.0" + dependencies: + ret: ~0.2.0 + checksum: f5e182fca040dedd50ae052ea0eb035d9903b2db71243d5d8b43299735857288ef2ab52546a368d9c6fd1333b2a0d039297925e78ffc14845354f3f6158af7c2 + languageName: node + linkType: hard + "safe-regex@npm:^1.1.0": version: 1.1.0 resolution: "safe-regex@npm:1.1.0" @@ -14899,6 +14540,13 @@ __metadata: languageName: node linkType: hard +"safe-stable-stringify@npm:^2.3.1": + version: 2.4.1 + resolution: "safe-stable-stringify@npm:2.4.1" + checksum: d8e505c462031301040605a4836ca25b52a1744eff01b0939b4d43136638fb8e88e0cec3d3ab6ab8e26f501086e6ba6bf34b228f57bf2ac56cb8d4061355d723 + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:^2.1.0, safer-buffer@npm:~2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -14967,7 +14615,7 @@ __metadata: languageName: node linkType: hard -"secure-json-parse@npm:^2.4.0": +"secure-json-parse@npm:^2.4.0, secure-json-parse@npm:^2.5.0": version: 2.5.0 resolution: "secure-json-parse@npm:2.5.0" checksum: 84147a32615ce0d93d2fbba60cde85ae362f45cc948ea134e4d6d1e678bb4b7f3a5ce9b9692ed052baefeb2e1c8ba183b34920390e6a089925b97b0d8f7ab064 @@ -14997,7 +14645,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.6.0": +"semver@npm:2 || 3 || 4 || 5": version: 5.7.1 resolution: "semver@npm:5.7.1" bin: @@ -15026,6 +14674,27 @@ __metadata: languageName: node linkType: hard +"send@npm:^0.18.0": + version: 0.18.0 + resolution: "send@npm:0.18.0" + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: ~1.0.2 + escape-html: ~1.0.3 + etag: ~1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: ~1.2.1 + statuses: 2.0.1 + checksum: 74fc07ebb58566b87b078ec63e5a3e41ecd987e4272ba67b7467e86c6ad51bc6b0b0154133b6d8b08a2ddda360464f71382f7ef864700f34844a76c8027817a8 + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -15033,6 +14702,13 @@ __metadata: languageName: node linkType: hard +"set-cookie-parser@npm:^2.4.1": + version: 2.5.1 + resolution: "set-cookie-parser@npm:2.5.1" + checksum: b99c37f976e68ae6eb7c758bf2bbce1e60bb54e3eccedaa25f2da45b77b9cab58d90674cf9edd7aead6fbeac6308f2eb48713320a47ca120d0e838d0194513b6 + languageName: node + linkType: hard + "set-value@npm:^2.0.0, set-value@npm:^2.0.1": version: 2.0.1 resolution: "set-value@npm:2.0.1" @@ -15052,13 +14728,6 @@ __metadata: languageName: node linkType: hard -"setprototypeof@npm:1.1.0": - version: 1.1.0 - resolution: "setprototypeof@npm:1.1.0" - checksum: 27cb44304d6c9e1a23bc6c706af4acaae1a7aa1054d4ec13c05f01a99fd4887109a83a8042b67ad90dbfcd100d43efc171ee036eb080667172079213242ca36e - languageName: node - linkType: hard - "setprototypeof@npm:1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" @@ -15122,13 +14791,6 @@ __metadata: languageName: node linkType: hard -"sigmund@npm:^1.0.1": - version: 1.0.1 - resolution: "sigmund@npm:1.0.1" - checksum: 793f81f8083ad75ff3903ffd93cf35be8d797e872822cf880aea27ce6db522b508d93ea52ae292bccf357ce34dd5c7faa544cc51c2216e70bbf5fcf09b62707c - languageName: node - linkType: hard - "signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -15270,6 +14932,15 @@ __metadata: languageName: node linkType: hard +"sonic-boom@npm:^3.1.0": + version: 3.2.0 + resolution: "sonic-boom@npm:3.2.0" + dependencies: + atomic-sleep: ^1.0.0 + checksum: 526669b78e0ac3bcbe2a53e5ac8960d3b25e61d8e6a46eaed5a0c46d7212c5f638bb136236870babedfcb626063711ba8f81e538f88b79e6a90a5b2ff71943b4 + languageName: node + linkType: hard + "sort-keys@npm:^1.0.0": version: 1.1.2 resolution: "sort-keys@npm:1.1.2" @@ -15413,7 +15084,7 @@ __metadata: languageName: node linkType: hard -"split2@npm:^4.1.0": +"split2@npm:^4.0.0, split2@npm:^4.1.0": version: 4.1.0 resolution: "split2@npm:4.1.0" checksum: ec581597cb74c13cdfb5e2047543dd40cb1e8e9803c7b1e0c29ede05f2b4f049b2d6e7f2788a225d544549375719658b8f38e9366364dec35dc7a12edfda5ee5 @@ -15532,7 +15203,7 @@ __metadata: languageName: node linkType: hard -"statuses@npm:>= 1.4.0 < 2, statuses@npm:>= 1.5.0 < 2, statuses@npm:^1.5.0": +"statuses@npm:>= 1.5.0 < 2, statuses@npm:^1.5.0": version: 1.5.0 resolution: "statuses@npm:1.5.0" checksum: c469b9519de16a4bb19600205cffb39ee471a5f17b82589757ca7bd40a8d92ebb6ed9f98b5a540c5d302ccbc78f15dc03cc0280dd6e00df1335568a5d5758a5c @@ -15571,10 +15242,10 @@ __metadata: languageName: node linkType: hard -"streamsearch@npm:0.1.2": - version: 0.1.2 - resolution: "streamsearch@npm:0.1.2" - checksum: d2db57cbfbf7947ab9c75a7b4c80a8ef8d24850cf0a1a24258bb6956c97317ce1eab7dbcbf9c5aba3e6198611af1053b02411057bbedb99bf9c64b8275248997 +"stream-wormhole@npm:^1.1.0": + version: 1.1.0 + resolution: "stream-wormhole@npm:1.1.0" + checksum: cc19e0235c5d031bd530fa83913c807d9525fa4ba33d51691dd822c0726b8b7ef138b34f289d063a3018cddba67d3ba7fd0ecedaa97242a0f1ed2eed3c6a2ab1 languageName: node linkType: hard @@ -15999,6 +15670,13 @@ __metadata: languageName: node linkType: hard +"text-decoding@npm:^1.0.0": + version: 1.0.0 + resolution: "text-decoding@npm:1.0.0" + checksum: 4b2359d8efdabea72ac470304e991913e9b82a55b1c33ab5204f115d11305ac5900add80aee5f7d22b2bcf0faebaf35b193d28a10b74adf175d9ac9d63604445 + languageName: node + linkType: hard + "text-table@npm:^0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -16038,6 +15716,15 @@ __metadata: languageName: node linkType: hard +"thread-stream@npm:^2.0.0": + version: 2.2.0 + resolution: "thread-stream@npm:2.2.0" + dependencies: + real-require: ^0.2.0 + checksum: b7f0ee166ed17ac54700a0b6fc291967c97785b458ff54efe5431a7281bb52d1163e6ec550a614f2a47f0f02de5b35a342bd5acd215af23030938c64859152b2 + languageName: node + linkType: hard + "three@npm:0.146.0": version: 0.146.0 resolution: "three@npm:0.146.0" @@ -16109,6 +15796,13 @@ __metadata: languageName: node linkType: hard +"tiny-lru@npm:^10.0.0": + version: 10.0.1 + resolution: "tiny-lru@npm:10.0.1" + checksum: 58b5f17a357625335aa3b90ee8c9b3e9abede5c1f46066c73deb129574a205efb112807d6d473909e73f1d874ea99bf14eb5c88223d540eb32ebb5e1ff146689 + languageName: node + linkType: hard + "tinycolor2@npm:1.4.2": version: 1.4.2 resolution: "tinycolor2@npm:1.4.2" @@ -16460,7 +16154,7 @@ __metadata: languageName: node linkType: hard -"type-is@npm:^1.6.14, type-is@npm:^1.6.16, type-is@npm:^1.6.4": +"type-is@npm:^1.6.16": version: 1.6.18 resolution: "type-is@npm:1.6.18" dependencies: @@ -16749,13 +16443,6 @@ __metadata: languageName: node linkType: hard -"unpipe@npm:1.0.0": - version: 1.0.0 - resolution: "unpipe@npm:1.0.0" - checksum: 4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2 - languageName: node - linkType: hard - "unset-value@npm:^1.0.0": version: 1.0.0 resolution: "unset-value@npm:1.0.0" @@ -16981,7 +16668,7 @@ __metadata: languageName: node linkType: hard -"vary@npm:^1.1.2": +"vary@npm:1.1.2, vary@npm:^1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" checksum: ae0123222c6df65b437669d63dfa8c36cee20a504101b2fcd97b8bf76f91259c17f9f2b4d70a1e3c6bbcee7f51b28392833adb6b2770b23b01abec84e369660b @@ -17554,13 +17241,6 @@ __metadata: languageName: node linkType: hard -"yallist@npm:^2.1.2": - version: 2.1.2 - resolution: "yallist@npm:2.1.2" - checksum: 9ba99409209f485b6fcb970330908a6d41fa1c933f75e08250316cce19383179a6b70a7e0721b89672ebb6199cc377bf3e432f55100da6a7d6e11902b0a642cb - languageName: node - linkType: hard - "yallist@npm:^3.0.0, yallist@npm:^3.1.1": version: 3.1.1 resolution: "yallist@npm:3.1.1" -- cgit v1.2.3-freya From 22ccb0fa716a84560c8599781647baaaeb8e80bd Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 4 Dec 2022 10:16:03 +0900 Subject: refactor --- packages/backend/src/core/AccountUpdateService.ts | 6 +- packages/backend/src/core/AntennaService.ts | 2 +- packages/backend/src/core/CaptchaService.ts | 2 +- packages/backend/src/core/CoreModule.ts | 48 +- .../backend/src/core/CreateNotificationService.ts | 4 +- packages/backend/src/core/CustomEmojiService.ts | 4 +- packages/backend/src/core/DriveService.ts | 6 +- .../backend/src/core/FederatedInstanceService.ts | 2 +- .../src/core/FetchInstanceMetadataService.ts | 2 +- packages/backend/src/core/HashtagService.ts | 2 +- packages/backend/src/core/InstanceActorService.ts | 2 +- packages/backend/src/core/MessagingService.ts | 12 +- packages/backend/src/core/NoteCreateService.ts | 16 +- packages/backend/src/core/NoteDeleteService.ts | 8 +- packages/backend/src/core/NotePiningService.ts | 6 +- packages/backend/src/core/NoteReadService.ts | 2 +- packages/backend/src/core/NotificationService.ts | 2 +- packages/backend/src/core/PollService.ts | 6 +- packages/backend/src/core/ProxyAccountService.ts | 2 +- .../backend/src/core/PushNotificationService.ts | 2 +- packages/backend/src/core/QueueModule.ts | 112 ++++ packages/backend/src/core/QueueService.ts | 4 +- packages/backend/src/core/ReactionService.ts | 10 +- packages/backend/src/core/RelayService.ts | 2 +- packages/backend/src/core/RemoteLoggerService.ts | 14 + .../backend/src/core/RemoteUserResolveService.ts | 132 ++++ packages/backend/src/core/S3Service.ts | 2 +- packages/backend/src/core/SignupService.ts | 2 +- packages/backend/src/core/UserBlockingService.ts | 8 +- packages/backend/src/core/UserCacheService.ts | 2 +- packages/backend/src/core/UserFollowingService.ts | 4 +- packages/backend/src/core/UserListService.ts | 4 +- packages/backend/src/core/UserSuspendService.ts | 4 +- packages/backend/src/core/WebfingerService.ts | 48 ++ .../src/core/activitypub/ApAudienceService.ts | 104 +++ .../src/core/activitypub/ApDbResolverService.ts | 179 +++++ .../core/activitypub/ApDeliverManagerService.ts | 199 ++++++ .../backend/src/core/activitypub/ApInboxService.ts | 740 +++++++++++++++++++++ .../src/core/activitypub/ApLoggerService.ts | 14 + .../backend/src/core/activitypub/ApMfmService.ts | 30 + .../src/core/activitypub/ApRendererService.ts | 703 ++++++++++++++++++++ .../src/core/activitypub/ApRequestService.ts | 182 +++++ .../src/core/activitypub/ApResolverService.ts | 195 ++++++ .../src/core/activitypub/LdSignatureService.ts | 147 ++++ .../backend/src/core/activitypub/misc/contexts.ts | 526 +++++++++++++++ .../src/core/activitypub/models/ApImageService.ts | 90 +++ .../core/activitypub/models/ApMentionService.ts | 39 ++ .../src/core/activitypub/models/ApNoteService.ts | 403 +++++++++++ .../src/core/activitypub/models/ApPersonService.ts | 594 +++++++++++++++++ .../core/activitypub/models/ApQuestionService.ts | 109 +++ .../backend/src/core/activitypub/models/icon.ts | 5 + .../src/core/activitypub/models/identifier.ts | 5 + .../backend/src/core/activitypub/models/tag.ts | 19 + packages/backend/src/core/activitypub/type.ts | 296 +++++++++ .../backend/src/core/chart/charts/active-users.ts | 2 +- .../backend/src/core/chart/charts/ap-request.ts | 2 +- packages/backend/src/core/chart/charts/drive.ts | 2 +- .../backend/src/core/chart/charts/federation.ts | 2 +- packages/backend/src/core/chart/charts/hashtag.ts | 2 +- packages/backend/src/core/chart/charts/instance.ts | 2 +- packages/backend/src/core/chart/charts/notes.ts | 2 +- .../src/core/chart/charts/per-user-drive.ts | 2 +- .../src/core/chart/charts/per-user-following.ts | 2 +- .../src/core/chart/charts/per-user-notes.ts | 2 +- .../src/core/chart/charts/per-user-reactions.ts | 2 +- .../backend/src/core/chart/charts/test-grouped.ts | 2 +- .../src/core/chart/charts/test-intersection.ts | 2 +- .../backend/src/core/chart/charts/test-unique.ts | 2 +- packages/backend/src/core/chart/charts/test.ts | 2 +- packages/backend/src/core/chart/charts/users.ts | 2 +- .../src/core/entities/InstanceEntityService.ts | 2 +- packages/backend/src/core/queue/QueueModule.ts | 112 ---- .../backend/src/core/remote/RemoteLoggerService.ts | 14 - .../backend/src/core/remote/ResolveUserService.ts | 132 ---- .../backend/src/core/remote/WebfingerService.ts | 48 -- .../core/remote/activitypub/ApAudienceService.ts | 104 --- .../core/remote/activitypub/ApDbResolverService.ts | 179 ----- .../remote/activitypub/ApDeliverManagerService.ts | 199 ------ .../src/core/remote/activitypub/ApInboxService.ts | 740 --------------------- .../src/core/remote/activitypub/ApLoggerService.ts | 14 - .../src/core/remote/activitypub/ApMfmService.ts | 30 - .../core/remote/activitypub/ApRendererService.ts | 703 -------------------- .../core/remote/activitypub/ApRequestService.ts | 182 ----- .../core/remote/activitypub/ApResolverService.ts | 195 ------ .../core/remote/activitypub/LdSignatureService.ts | 147 ---- .../src/core/remote/activitypub/misc/contexts.ts | 526 --------------- .../remote/activitypub/models/ApImageService.ts | 90 --- .../remote/activitypub/models/ApMentionService.ts | 39 -- .../remote/activitypub/models/ApNoteService.ts | 403 ----------- .../remote/activitypub/models/ApPersonService.ts | 594 ----------------- .../remote/activitypub/models/ApQuestionService.ts | 109 --- .../src/core/remote/activitypub/models/icon.ts | 5 - .../core/remote/activitypub/models/identifier.ts | 5 - .../src/core/remote/activitypub/models/tag.ts | 19 - .../backend/src/core/remote/activitypub/type.ts | 296 --------- .../queue/processors/DeliverProcessorService.ts | 2 +- .../processors/ImportBlockingProcessorService.ts | 6 +- .../processors/ImportFollowingProcessorService.ts | 6 +- .../processors/ImportMutingProcessorService.ts | 6 +- .../processors/ImportUserListsProcessorService.ts | 6 +- .../src/queue/processors/InboxProcessorService.ts | 12 +- packages/backend/src/queue/types.ts | 2 +- .../backend/src/server/ActivityPubServerService.ts | 2 +- .../api/endpoints/admin/queue/deliver-delayed.ts | 2 +- .../api/endpoints/admin/queue/inbox-delayed.ts | 2 +- .../src/server/api/endpoints/admin/queue/stats.ts | 2 +- .../endpoints/admin/resolve-abuse-user-report.ts | 2 +- .../backend/src/server/api/endpoints/ap/get.ts | 2 +- .../backend/src/server/api/endpoints/ap/show.ts | 10 +- .../api/endpoints/federation/update-remote-user.ts | 2 +- .../src/server/api/endpoints/notes/polls/vote.ts | 2 +- .../backend/src/server/api/endpoints/users/show.ts | 6 +- .../backend/src/server/web/ClientServerService.ts | 2 +- packages/backend/test/misc/mock-resolver.ts | 4 +- packages/backend/test/tests/activitypub.ts | 6 +- packages/backend/test/tests/ap-request.ts | 2 +- packages/backend/test/unit/RelayService.ts | 2 +- 117 files changed, 5035 insertions(+), 5035 deletions(-) create mode 100644 packages/backend/src/core/QueueModule.ts create mode 100644 packages/backend/src/core/RemoteLoggerService.ts create mode 100644 packages/backend/src/core/RemoteUserResolveService.ts create mode 100644 packages/backend/src/core/WebfingerService.ts create mode 100644 packages/backend/src/core/activitypub/ApAudienceService.ts create mode 100644 packages/backend/src/core/activitypub/ApDbResolverService.ts create mode 100644 packages/backend/src/core/activitypub/ApDeliverManagerService.ts create mode 100644 packages/backend/src/core/activitypub/ApInboxService.ts create mode 100644 packages/backend/src/core/activitypub/ApLoggerService.ts create mode 100644 packages/backend/src/core/activitypub/ApMfmService.ts create mode 100644 packages/backend/src/core/activitypub/ApRendererService.ts create mode 100644 packages/backend/src/core/activitypub/ApRequestService.ts create mode 100644 packages/backend/src/core/activitypub/ApResolverService.ts create mode 100644 packages/backend/src/core/activitypub/LdSignatureService.ts create mode 100644 packages/backend/src/core/activitypub/misc/contexts.ts create mode 100644 packages/backend/src/core/activitypub/models/ApImageService.ts create mode 100644 packages/backend/src/core/activitypub/models/ApMentionService.ts create mode 100644 packages/backend/src/core/activitypub/models/ApNoteService.ts create mode 100644 packages/backend/src/core/activitypub/models/ApPersonService.ts create mode 100644 packages/backend/src/core/activitypub/models/ApQuestionService.ts create mode 100644 packages/backend/src/core/activitypub/models/icon.ts create mode 100644 packages/backend/src/core/activitypub/models/identifier.ts create mode 100644 packages/backend/src/core/activitypub/models/tag.ts create mode 100644 packages/backend/src/core/activitypub/type.ts delete mode 100644 packages/backend/src/core/queue/QueueModule.ts delete mode 100644 packages/backend/src/core/remote/RemoteLoggerService.ts delete mode 100644 packages/backend/src/core/remote/ResolveUserService.ts delete mode 100644 packages/backend/src/core/remote/WebfingerService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApAudienceService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApDbResolverService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApInboxService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApLoggerService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApMfmService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApRendererService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApRequestService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApResolverService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/LdSignatureService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/misc/contexts.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/ApImageService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/ApMentionService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/ApNoteService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/ApPersonService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/icon.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/identifier.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/tag.ts delete mode 100644 packages/backend/src/core/remote/activitypub/type.ts (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/core/AccountUpdateService.ts b/packages/backend/src/core/AccountUpdateService.ts index 6fe0e05c6d..a5ab4fdfce 100644 --- a/packages/backend/src/core/AccountUpdateService.ts +++ b/packages/backend/src/core/AccountUpdateService.ts @@ -3,10 +3,10 @@ import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type { User } from '@/models/entities/User.js'; -import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { RelayService } from '@/core/RelayService.js'; -import { ApDeliverManagerService } from '@/core/remote/activitypub/ApDeliverManagerService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; +import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; @Injectable() export class AccountUpdateService { diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index af76767f31..8046ba5311 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -11,7 +11,7 @@ import { Cache } from '@/misc/cache.js'; import type { Packed } from '@/misc/schema.js'; import { DI } from '@/di-symbols.js'; import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; -import { UtilityService } from './UtilityService.js'; +import { UtilityService } from '@/core/UtilityService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index b2bc24ac2c..b60271812c 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; -import { HttpRequestService } from './HttpRequestService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; type CaptchaResponse = { success: boolean; diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index da07728d22..085addaa05 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -94,25 +94,25 @@ import { UserEntityService } from './entities/UserEntityService.js'; import { UserGroupEntityService } from './entities/UserGroupEntityService.js'; import { UserGroupInvitationEntityService } from './entities/UserGroupInvitationEntityService.js'; import { UserListEntityService } from './entities/UserListEntityService.js'; -import { ApAudienceService } from './remote/activitypub/ApAudienceService.js'; -import { ApDbResolverService } from './remote/activitypub/ApDbResolverService.js'; -import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js'; -import { ApInboxService } from './remote/activitypub/ApInboxService.js'; -import { ApLoggerService } from './remote/activitypub/ApLoggerService.js'; -import { ApMfmService } from './remote/activitypub/ApMfmService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { ApRequestService } from './remote/activitypub/ApRequestService.js'; -import { ApResolverService } from './remote/activitypub/ApResolverService.js'; -import { LdSignatureService } from './remote/activitypub/LdSignatureService.js'; -import { RemoteLoggerService } from './remote/RemoteLoggerService.js'; -import { ResolveUserService } from './remote/ResolveUserService.js'; -import { WebfingerService } from './remote/WebfingerService.js'; -import { ApImageService } from './remote/activitypub/models/ApImageService.js'; -import { ApMentionService } from './remote/activitypub/models/ApMentionService.js'; -import { ApNoteService } from './remote/activitypub/models/ApNoteService.js'; -import { ApPersonService } from './remote/activitypub/models/ApPersonService.js'; -import { ApQuestionService } from './remote/activitypub/models/ApQuestionService.js'; -import { QueueModule } from './queue/QueueModule.js'; +import { ApAudienceService } from './activitypub/ApAudienceService.js'; +import { ApDbResolverService } from './activitypub/ApDbResolverService.js'; +import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js'; +import { ApInboxService } from './activitypub/ApInboxService.js'; +import { ApLoggerService } from './activitypub/ApLoggerService.js'; +import { ApMfmService } from './activitypub/ApMfmService.js'; +import { ApRendererService } from './activitypub/ApRendererService.js'; +import { ApRequestService } from './activitypub/ApRequestService.js'; +import { ApResolverService } from './activitypub/ApResolverService.js'; +import { LdSignatureService } from './activitypub/LdSignatureService.js'; +import { RemoteLoggerService } from './RemoteLoggerService.js'; +import { RemoteUserResolveService } from './RemoteUserResolveService.js'; +import { WebfingerService } from './WebfingerService.js'; +import { ApImageService } from './activitypub/models/ApImageService.js'; +import { ApMentionService } from './activitypub/models/ApMentionService.js'; +import { ApNoteService } from './activitypub/models/ApNoteService.js'; +import { ApPersonService } from './activitypub/models/ApPersonService.js'; +import { ApQuestionService } from './activitypub/models/ApQuestionService.js'; +import { QueueModule } from './QueueModule.js'; import { QueueService } from './QueueService.js'; import { LoggerService } from './LoggerService.js'; import type { Provider } from '@nestjs/common'; @@ -226,7 +226,7 @@ const $ApRequestService: Provider = { provide: 'ApRequestService', useExisting: const $ApResolverService: Provider = { provide: 'ApResolverService', useExisting: ApResolverService }; const $LdSignatureService: Provider = { provide: 'LdSignatureService', useExisting: LdSignatureService }; const $RemoteLoggerService: Provider = { provide: 'RemoteLoggerService', useExisting: RemoteLoggerService }; -const $ResolveUserService: Provider = { provide: 'ResolveUserService', useExisting: ResolveUserService }; +const $RemoteUserResolveService: Provider = { provide: 'RemoteUserResolveService', useExisting: RemoteUserResolveService }; const $WebfingerService: Provider = { provide: 'WebfingerService', useExisting: WebfingerService }; const $ApImageService: Provider = { provide: 'ApImageService', useExisting: ApImageService }; const $ApMentionService: Provider = { provide: 'ApMentionService', useExisting: ApMentionService }; @@ -346,7 +346,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ApResolverService, LdSignatureService, RemoteLoggerService, - ResolveUserService, + RemoteUserResolveService, WebfingerService, ApImageService, ApMentionService, @@ -462,7 +462,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $ApResolverService, $LdSignatureService, $RemoteLoggerService, - $ResolveUserService, + $RemoteUserResolveService, $WebfingerService, $ApImageService, $ApMentionService, @@ -578,7 +578,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ApResolverService, LdSignatureService, RemoteLoggerService, - ResolveUserService, + RemoteUserResolveService, WebfingerService, ApImageService, ApMentionService, @@ -693,7 +693,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $ApResolverService, $LdSignatureService, $RemoteLoggerService, - $ResolveUserService, + $RemoteUserResolveService, $WebfingerService, $ApImageService, $ApMentionService, diff --git a/packages/backend/src/core/CreateNotificationService.ts b/packages/backend/src/core/CreateNotificationService.ts index feb82dcbf9..504661c3bd 100644 --- a/packages/backend/src/core/CreateNotificationService.ts +++ b/packages/backend/src/core/CreateNotificationService.ts @@ -5,8 +5,8 @@ import type { Notification } from '@/models/entities/Notification.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; -import { NotificationEntityService } from './entities/NotificationEntityService.js'; -import { PushNotificationService } from './PushNotificationService.js'; +import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js'; +import { PushNotificationService } from '@/core/PushNotificationService.js'; @Injectable() export class CreateNotificationService { diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index e1355fff07..3319f3efa8 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -10,8 +10,8 @@ import { Cache } from '@/misc/cache.js'; import { query } from '@/misc/prelude/url.js'; import type { Note } from '@/models/entities/Note.js'; import type { EmojisRepository } from '@/models/index.js'; -import { UtilityService } from './UtilityService.js'; -import { ReactionService } from './ReactionService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { ReactionService } from '@/core/ReactionService.js'; /** * 添付用絵文字情報 diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index e0bdd29c0f..1d2ba5df8c 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -28,9 +28,9 @@ import InstanceChart from '@/core/chart/charts/instance.js'; import { DownloadService } from '@/core/DownloadService.js'; import { S3Service } from '@/core/S3Service.js'; import { InternalStorageService } from '@/core/InternalStorageService.js'; -import { DriveFileEntityService } from './entities/DriveFileEntityService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { FileInfoService } from './FileInfoService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { FileInfoService } from '@/core/FileInfoService.js'; import type S3 from 'aws-sdk/clients/s3.js'; type AddFileArgs = { diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index b98a41f757..a05c95a2ae 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -4,7 +4,7 @@ import type { Instance } from '@/models/entities/Instance.js'; import { Cache } from '@/misc/cache.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; -import { UtilityService } from './UtilityService.js'; +import { UtilityService } from '@/core/UtilityService.js'; @Injectable() export class FederatedInstanceService { diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 184404123c..b92ebe6059 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -9,7 +9,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import type Logger from '@/logger.js'; import { DI } from '@/di-symbols.js'; import { LoggerService } from '@/core/LoggerService.js'; -import { HttpRequestService } from './HttpRequestService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; import type { DOMWindow } from 'jsdom'; type NodeInfo = { diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts index 83950aa890..5ca058e9a4 100644 --- a/packages/backend/src/core/HashtagService.ts +++ b/packages/backend/src/core/HashtagService.ts @@ -6,7 +6,7 @@ import { IdService } from '@/core/IdService.js'; import type { Hashtag } from '@/models/entities/Hashtag.js'; import HashtagChart from '@/core/chart/charts/hashtag.js'; import type { HashtagsRepository, UsersRepository } from '@/models/index.js'; -import { UserEntityService } from './entities/UserEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; @Injectable() export class HashtagService { diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts index fa906df4a2..f35a28147d 100644 --- a/packages/backend/src/core/InstanceActorService.ts +++ b/packages/backend/src/core/InstanceActorService.ts @@ -4,7 +4,7 @@ import type { ILocalUser } from '@/models/entities/User.js'; import type { UsersRepository } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; import { DI } from '@/di-symbols.js'; -import { CreateSystemUserService } from './CreateSystemUserService.js'; +import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; const ACTOR_USERNAME = 'instance.actor' as const; diff --git a/packages/backend/src/core/MessagingService.ts b/packages/backend/src/core/MessagingService.ts index 0603da0651..9de28ad8db 100644 --- a/packages/backend/src/core/MessagingService.ts +++ b/packages/backend/src/core/MessagingService.ts @@ -11,12 +11,12 @@ import { QueueService } from '@/core/QueueService.js'; import { toArray } from '@/misc/prelude/array.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { MessagingMessagesRepository, MutingsRepository, UserGroupJoiningsRepository, UsersRepository } from '@/models/index.js'; -import { IdService } from './IdService.js'; -import { GlobalEventService } from './GlobalEventService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { MessagingMessageEntityService } from './entities/MessagingMessageEntityService.js'; -import { PushNotificationService } from './PushNotificationService.js'; +import { IdService } from '@/core/IdService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; +import { PushNotificationService } from '@/core/PushNotificationService.js'; @Injectable() export class MessagingService { diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index a23e105674..cf1566a5e8 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -34,12 +34,12 @@ import { WebhookService } from '@/core/WebhookService.js'; import { HashtagService } from '@/core/HashtagService.js'; import { AntennaService } from '@/core/AntennaService.js'; import { QueueService } from '@/core/QueueService.js'; -import { NoteEntityService } from './entities/NoteEntityService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { NoteReadService } from './NoteReadService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { ResolveUserService } from './remote/ResolveUserService.js'; -import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; +import { NoteReadService } from '@/core/NoteReadService.js'; +import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5); @@ -179,7 +179,7 @@ export class NoteCreateService { private hashtagService: HashtagService, private antennaService: AntennaService, private webhookService: WebhookService, - private resolveUserService: ResolveUserService, + private remoteUserResolveService: RemoteUserResolveService, private apDeliverManagerService: ApDeliverManagerService, private apRendererService: ApRendererService, private notesChart: NotesChart, @@ -726,7 +726,7 @@ export class NoteCreateService { const mentions = extractMentions(tokens); let mentionedUsers = (await Promise.all(mentions.map(m => - this.resolveUserService.resolveUser(m.username, m.host ?? user.host).catch(() => null), + this.remoteUserResolveService.resolveUser(m.username, m.host ?? user.host).catch(() => null), ))).filter(x => x != null) as User[]; // Drop duplicate users diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index ccc583c5b6..ce6e755a7e 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -11,10 +11,10 @@ import NotesChart from '@/core/chart/charts/notes.js'; import PerUserNotesChart from '@/core/chart/charts/per-user-notes.js'; import InstanceChart from '@/core/chart/charts/instance.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { NoteEntityService } from './entities/NoteEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @Injectable() export class NoteDeleteService { diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts index 8c4a761ba6..a04b52fe4c 100644 --- a/packages/backend/src/core/NotePiningService.ts +++ b/packages/backend/src/core/NotePiningService.ts @@ -8,9 +8,9 @@ import { IdService } from '@/core/IdService.js'; import type { UserNotePining } from '@/models/entities/UserNotePining.js'; import { RelayService } from '@/core/RelayService.js'; import type { Config } from '@/config.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; @Injectable() export class NotePiningService { diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index 2c84e1d4d5..e0feaa957d 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -8,7 +8,7 @@ import type { Note } from '@/models/entities/Note.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { UsersRepository, NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository, FollowingsRepository, ChannelFollowingsRepository, AntennaNotesRepository } from '@/models/index.js'; -import { UserEntityService } from './entities/UserEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NotificationService } from './NotificationService.js'; import { AntennaService } from './AntennaService.js'; diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index 2606ca4de0..8bbc95b02d 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -5,7 +5,7 @@ import type { NotificationsRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import type { Notification } from '@/models/entities/Notification.js'; -import { UserEntityService } from './entities/UserEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalEventService } from './GlobalEventService.js'; import { PushNotificationService } from './PushNotificationService.js'; diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index e3e12b5320..287ce8ada4 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -8,9 +8,9 @@ import type { CacheableUser } from '@/models/entities/User.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { CreateNotificationService } from '@/core/CreateNotificationService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; @Injectable() export class PollService { diff --git a/packages/backend/src/core/ProxyAccountService.ts b/packages/backend/src/core/ProxyAccountService.ts index 07d8d0dbd5..4cbdadd029 100644 --- a/packages/backend/src/core/ProxyAccountService.ts +++ b/packages/backend/src/core/ProxyAccountService.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UsersRepository } from '@/models/index.js'; import type { ILocalUser, User } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; -import { MetaService } from './MetaService.js'; +import { MetaService } from '@/core/MetaService.js'; @Injectable() export class ProxyAccountService { diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index 5eaaed00eb..98e0841799 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -5,7 +5,7 @@ import type { Config } from '@/config.js'; import type { Packed } from '@/misc/schema'; import { getNoteSummary } from '@/misc/get-note-summary.js'; import type { SwSubscriptionsRepository } from '@/models/index.js'; -import { MetaService } from './MetaService.js'; +import { MetaService } from '@/core/MetaService.js'; // Defined also packages/sw/types.ts#L14-L21 type pushNotificationsTypes = { diff --git a/packages/backend/src/core/QueueModule.ts b/packages/backend/src/core/QueueModule.ts new file mode 100644 index 0000000000..edd843977b --- /dev/null +++ b/packages/backend/src/core/QueueModule.ts @@ -0,0 +1,112 @@ +import { Module } from '@nestjs/common'; +import Bull from 'bull'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import type { Provider } from '@nestjs/common'; +import type { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData, WebhookDeliverJobData } from '../queue/types.js'; + +function q(config: Config, name: string, limitPerSec = -1) { + return new Bull(name, { + redis: { + port: config.redis.port, + host: config.redis.host, + family: config.redis.family == null ? 0 : config.redis.family, + password: config.redis.pass, + db: config.redis.db ?? 0, + }, + prefix: config.redis.prefix ? `${config.redis.prefix}:queue` : 'queue', + limiter: limitPerSec > 0 ? { + max: limitPerSec, + duration: 1000, + } : undefined, + settings: { + backoffStrategies: { + apBackoff, + }, + }, + }); +} + +// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019 +function apBackoff(attemptsMade: number, err: Error) { + const baseDelay = 60 * 1000; // 1min + const maxBackoff = 8 * 60 * 60 * 1000; // 8hours + let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay; + backoff = Math.min(backoff, maxBackoff); + backoff += Math.round(backoff * Math.random() * 0.2); + return backoff; +} + +export type SystemQueue = Bull.Queue>; +export type EndedPollNotificationQueue = Bull.Queue; +export type DeliverQueue = Bull.Queue; +export type InboxQueue = Bull.Queue; +export type DbQueue = Bull.Queue; +export type ObjectStorageQueue = Bull.Queue; +export type WebhookDeliverQueue = Bull.Queue; + +const $system: Provider = { + provide: 'queue:system', + useFactory: (config: Config) => q(config, 'system'), + inject: [DI.config], +}; + +const $endedPollNotification: Provider = { + provide: 'queue:endedPollNotification', + useFactory: (config: Config) => q(config, 'endedPollNotification'), + inject: [DI.config], +}; + +const $deliver: Provider = { + provide: 'queue:deliver', + useFactory: (config: Config) => q(config, 'deliver', config.deliverJobPerSec ?? 128), + inject: [DI.config], +}; + +const $inbox: Provider = { + provide: 'queue:inbox', + useFactory: (config: Config) => q(config, 'inbox', config.inboxJobPerSec ?? 16), + inject: [DI.config], +}; + +const $db: Provider = { + provide: 'queue:db', + useFactory: (config: Config) => q(config, 'db'), + inject: [DI.config], +}; + +const $objectStorage: Provider = { + provide: 'queue:objectStorage', + useFactory: (config: Config) => q(config, 'objectStorage'), + inject: [DI.config], +}; + +const $webhookDeliver: Provider = { + provide: 'queue:webhookDeliver', + useFactory: (config: Config) => q(config, 'webhookDeliver', 64), + inject: [DI.config], +}; + +@Module({ + imports: [ + ], + providers: [ + $system, + $endedPollNotification, + $deliver, + $inbox, + $db, + $objectStorage, + $webhookDeliver, + ], + exports: [ + $system, + $endedPollNotification, + $deliver, + $inbox, + $db, + $objectStorage, + $webhookDeliver, + ], +}) +export class QueueModule {} diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index d9ad26747f..a27d68ee19 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -1,11 +1,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { v4 as uuid } from 'uuid'; -import type { IActivity } from '@/core/remote/activitypub/type.js'; +import type { IActivity } from '@/core/activitypub/type.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Webhook, webhookEventTypes } from '@/models/entities/Webhook.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; -import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from './queue/QueueModule.js'; +import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js'; import type { ThinUser } from '../queue/types.js'; import type httpSignature from '@peertube/http-signature'; diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index d5b3c0e799..7a9724e7dd 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -12,11 +12,11 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import PerUserReactionsChart from '@/core/chart/charts/per-user-reactions.js'; import { emojiRegex } from '@/misc/emoji-regex.js'; -import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js'; -import { NoteEntityService } from './entities/NoteEntityService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { MetaService } from './MetaService.js'; +import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { MetaService } from '@/core/MetaService.js'; import { UtilityService } from './UtilityService.js'; const legacies: Record = { diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts index 3c67e0573f..7951edddcb 100644 --- a/packages/backend/src/core/RelayService.ts +++ b/packages/backend/src/core/RelayService.ts @@ -7,7 +7,7 @@ import { Cache } from '@/misc/cache.js'; import type { Relay } from '@/models/entities/Relay.js'; import { QueueService } from '@/core/QueueService.js'; import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; -import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { DI } from '@/di-symbols.js'; import { deepClone } from '@/misc/clone.js'; diff --git a/packages/backend/src/core/RemoteLoggerService.ts b/packages/backend/src/core/RemoteLoggerService.ts new file mode 100644 index 0000000000..68246466c8 --- /dev/null +++ b/packages/backend/src/core/RemoteLoggerService.ts @@ -0,0 +1,14 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type Logger from '@/logger.js'; +import { LoggerService } from '@/core/LoggerService.js'; + +@Injectable() +export class RemoteLoggerService { + public logger: Logger; + + constructor( + private loggerService: LoggerService, + ) { + this.logger = this.loggerService.getLogger('remote', 'cyan'); + } +} diff --git a/packages/backend/src/core/RemoteUserResolveService.ts b/packages/backend/src/core/RemoteUserResolveService.ts new file mode 100644 index 0000000000..809b50f6e9 --- /dev/null +++ b/packages/backend/src/core/RemoteUserResolveService.ts @@ -0,0 +1,132 @@ +import { URL } from 'node:url'; +import { Inject, Injectable } from '@nestjs/common'; +import chalk from 'chalk'; +import { IsNull } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { UsersRepository } from '@/models/index.js'; +import type { IRemoteUser, User } from '@/models/entities/User.js'; +import type { Config } from '@/config.js'; +import type Logger from '@/logger.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { WebfingerService } from '@/core/WebfingerService.js'; +import { RemoteLoggerService } from '@/core/RemoteLoggerService.js'; +import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; + +@Injectable() +export class RemoteUserResolveService { + private logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private utilityService: UtilityService, + private webfingerService: WebfingerService, + private remoteLoggerService: RemoteLoggerService, + private apPersonService: ApPersonService, + ) { + this.logger = this.remoteLoggerService.logger.createSubLogger('resolve-user'); + } + + public async resolveUser(username: string, host: string | null): Promise { + const usernameLower = username.toLowerCase(); + + if (host == null) { + this.logger.info(`return local user: ${usernameLower}`); + return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => { + if (u == null) { + throw new Error('user not found'); + } else { + return u; + } + }); + } + + host = this.utilityService.toPuny(host); + + if (this.config.host === host) { + this.logger.info(`return local user: ${usernameLower}`); + return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => { + if (u == null) { + throw new Error('user not found'); + } else { + return u; + } + }); + } + + const user = await this.usersRepository.findOneBy({ usernameLower, host }) as IRemoteUser | null; + + const acctLower = `${usernameLower}@${host}`; + + if (user == null) { + const self = await this.resolveSelf(acctLower); + + this.logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`); + return await this.apPersonService.createPerson(self.href); + } + + // ユーザー情報が古い場合は、WebFilgerからやりなおして返す + if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + // 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する + await this.usersRepository.update(user.id, { + lastFetchedAt: new Date(), + }); + + this.logger.info(`try resync: ${acctLower}`); + const self = await this.resolveSelf(acctLower); + + if (user.uri !== self.href) { + // if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping. + this.logger.info(`uri missmatch: ${acctLower}`); + this.logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`); + + // validate uri + const uri = new URL(self.href); + if (uri.hostname !== host) { + throw new Error('Invalid uri'); + } + + await this.usersRepository.update({ + usernameLower, + host: host, + }, { + uri: self.href, + }); + } else { + this.logger.info(`uri is fine: ${acctLower}`); + } + + await this.apPersonService.updatePerson(self.href); + + this.logger.info(`return resynced remote user: ${acctLower}`); + return await this.usersRepository.findOneBy({ uri: self.href }).then(u => { + if (u == null) { + throw new Error('user not found'); + } else { + return u; + } + }); + } + + this.logger.info(`return existing remote user: ${acctLower}`); + return user; + } + + private async resolveSelf(acctLower: string) { + this.logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); + const finger = await this.webfingerService.webfinger(acctLower).catch(err => { + this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`); + throw new Error(`Failed to WebFinger for ${acctLower}: ${ err.statusCode ?? err.message }`); + }); + const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self'); + if (!self) { + this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`); + throw new Error('self link not found'); + } + return self; + } +} diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts index 723a79dc59..1374ee06c8 100644 --- a/packages/backend/src/core/S3Service.ts +++ b/packages/backend/src/core/S3Service.ts @@ -4,7 +4,7 @@ import S3 from 'aws-sdk/clients/s3.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import type { Meta } from '@/models/entities/Meta.js'; -import { HttpRequestService } from './HttpRequestService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; @Injectable() export class S3Service { diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 2239d5fd83..1e34d9e4f8 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -12,7 +12,7 @@ import { UserKeypair } from '@/models/entities/UserKeypair.js'; import { UsedUsername } from '@/models/entities/UsedUsername.js'; import generateUserToken from '@/misc/generate-native-user-token.js'; import UsersChart from './chart/charts/users.js'; -import { UserEntityService } from './entities/UserEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UtilityService } from './UtilityService.js'; @Injectable() diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index b7a434684e..3399bb510f 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -10,10 +10,10 @@ import { DI } from '@/di-symbols.js'; import logger from '@/logger.js'; import type { UsersRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; import Logger from '@/logger.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { WebhookService } from './WebhookService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { LoggerService } from './LoggerService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import { WebhookService } from '@/core/WebhookService.js'; @Injectable() export class UserBlockingService { diff --git a/packages/backend/src/core/UserCacheService.ts b/packages/backend/src/core/UserCacheService.ts index b7166010ee..25a600a8da 100644 --- a/packages/backend/src/core/UserCacheService.ts +++ b/packages/backend/src/core/UserCacheService.ts @@ -4,7 +4,7 @@ import type { UsersRepository } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; import type { CacheableLocalUser, CacheableUser, ILocalUser } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; -import { UserEntityService } from './entities/UserEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 31e08c1366..2f51e2a9df 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -14,8 +14,8 @@ import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { DI } from '@/di-symbols.js'; import type { BlockingsRepository, FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import Logger from '../logger.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; const logger = new Logger('following/create'); diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index b1d01a1565..1d1ead5a1f 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -7,8 +7,8 @@ import { IdService } from '@/core/IdService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { ProxyAccountService } from './ProxyAccountService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ProxyAccountService } from '@/core/ProxyAccountService.js'; @Injectable() export class UserListService { diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index 82c2e98236..02f686bab6 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -6,8 +6,8 @@ import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; @Injectable() export class UserSuspendService { diff --git a/packages/backend/src/core/WebfingerService.ts b/packages/backend/src/core/WebfingerService.ts new file mode 100644 index 0000000000..d2a88be583 --- /dev/null +++ b/packages/backend/src/core/WebfingerService.ts @@ -0,0 +1,48 @@ +import { URL } from 'node:url'; +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import { query as urlQuery } from '@/misc/prelude/url.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; + +type ILink = { + href: string; + rel?: string; +}; + +type IWebFinger = { + links: ILink[]; + subject: string; +}; + +@Injectable() +export class WebfingerService { + constructor( + @Inject(DI.config) + private config: Config, + + private httpRequestService: HttpRequestService, + ) { + } + + public async webfinger(query: string): Promise { + const url = this.genUrl(query); + + return await this.httpRequestService.getJson(url, 'application/jrd+json, application/json') as IWebFinger; + } + + private genUrl(query: string): string { + if (query.match(/^https?:\/\//)) { + const u = new URL(query); + return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query }); + } + + const m = query.match(/^([^@]+)@(.*)/); + if (m) { + const hostname = m[2]; + return `https://${hostname}/.well-known/webfinger?` + urlQuery({ resource: `acct:${query}` }); + } + + throw new Error(`Invalid query (${query})`); + } +} diff --git a/packages/backend/src/core/activitypub/ApAudienceService.ts b/packages/backend/src/core/activitypub/ApAudienceService.ts new file mode 100644 index 0000000000..744017aa3a --- /dev/null +++ b/packages/backend/src/core/activitypub/ApAudienceService.ts @@ -0,0 +1,104 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { In } from 'typeorm'; +import promiseLimit from 'promise-limit'; +import { DI } from '@/di-symbols.js'; +import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js'; +import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js'; +import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; +import { ApPersonService } from './models/ApPersonService.js'; +import type { ApObject } from './type.js'; +import type { Resolver } from './ApResolverService.js'; + +type Visibility = 'public' | 'home' | 'followers' | 'specified'; + +type AudienceInfo = { + visibility: Visibility, + mentionedUsers: CacheableUser[], + visibleUsers: CacheableUser[], +}; + +@Injectable() +export class ApAudienceService { + constructor( + private apPersonService: ApPersonService, + ) { + } + + public async parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { + const toGroups = this.groupingAudience(getApIds(to), actor); + const ccGroups = this.groupingAudience(getApIds(cc), actor); + + const others = unique(concat([toGroups.other, ccGroups.other])); + + const limit = promiseLimit(2); + const mentionedUsers = (await Promise.all( + others.map(id => limit(() => this.apPersonService.resolvePerson(id, resolver).catch(() => null))), + )).filter((x): x is CacheableUser => x != null); + + if (toGroups.public.length > 0) { + return { + visibility: 'public', + mentionedUsers, + visibleUsers: [], + }; + } + + if (ccGroups.public.length > 0) { + return { + visibility: 'home', + mentionedUsers, + visibleUsers: [], + }; + } + + if (toGroups.followers.length > 0) { + return { + visibility: 'followers', + mentionedUsers, + visibleUsers: [], + }; + } + + return { + visibility: 'specified', + mentionedUsers, + visibleUsers: mentionedUsers, + }; + } + + private groupingAudience(ids: string[], actor: CacheableRemoteUser) { + const groups = { + public: [] as string[], + followers: [] as string[], + other: [] as string[], + }; + + for (const id of ids) { + if (this.isPublic(id)) { + groups.public.push(id); + } else if (this.isFollowers(id, actor)) { + groups.followers.push(id); + } else { + groups.other.push(id); + } + } + + groups.other = unique(groups.other); + + return groups; + } + + private isPublic(id: string) { + return [ + 'https://www.w3.org/ns/activitystreams#Public', + 'as#Public', + 'Public', + ].includes(id); + } + + private isFollowers(id: string, actor: CacheableRemoteUser) { + return ( + id === (actor.followersUri ?? `${actor.uri}/followers`) + ); + } +} diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts new file mode 100644 index 0000000000..77d200c3c8 --- /dev/null +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -0,0 +1,179 @@ +import { Inject, Injectable } from '@nestjs/common'; +import escapeRegexp from 'escape-regexp'; +import { DI } from '@/di-symbols.js'; +import type { MessagingMessagesRepository, NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js'; +import { Cache } from '@/misc/cache.js'; +import type { UserPublickey } from '@/models/entities/UserPublickey.js'; +import { UserCacheService } from '@/core/UserCacheService.js'; +import type { Note } from '@/models/entities/Note.js'; +import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; +import { getApId } from './type.js'; +import { ApPersonService } from './models/ApPersonService.js'; +import type { IObject } from './type.js'; + +export type UriParseResult = { + /** wether the URI was generated by us */ + local: true; + /** id in DB */ + id: string; + /** hint of type, e.g. "notes", "users" */ + type: string; + /** any remaining text after type and id, not including the slash after id. undefined if empty */ + rest?: string; +} | { + /** wether the URI was generated by us */ + local: false; + /** uri in DB */ + uri: string; +}; + +@Injectable() +export class ApDbResolverService { + private publicKeyCache: Cache; + private publicKeyByUserIdCache: Cache; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.userPublickeysRepository) + private userPublickeysRepository: UserPublickeysRepository, + + private userCacheService: UserCacheService, + private apPersonService: ApPersonService, + ) { + this.publicKeyCache = new Cache(Infinity); + this.publicKeyByUserIdCache = new Cache(Infinity); + } + + public parseUri(value: string | IObject): UriParseResult { + const uri = getApId(value); + + // the host part of a URL is case insensitive, so use the 'i' flag. + const localRegex = new RegExp('^' + escapeRegexp(this.config.url) + '/(\\w+)/(\\w+)(?:\/(.+))?', 'i'); + const matchLocal = uri.match(localRegex); + + if (matchLocal) { + return { + local: true, + type: matchLocal[1], + id: matchLocal[2], + rest: matchLocal[3], + }; + } else { + return { + local: false, + uri, + }; + } + } + + /** + * AP Note => Misskey Note in DB + */ + public async getNoteFromApId(value: string | IObject): Promise { + const parsed = this.parseUri(value); + + if (parsed.local) { + if (parsed.type !== 'notes') return null; + + return await this.notesRepository.findOneBy({ + id: parsed.id, + }); + } else { + return await this.notesRepository.findOneBy({ + uri: parsed.uri, + }); + } + } + + public async getMessageFromApId(value: string | IObject): Promise { + const parsed = this.parseUri(value); + + if (parsed.local) { + if (parsed.type !== 'notes') return null; + + return await this.messagingMessagesRepository.findOneBy({ + id: parsed.id, + }); + } else { + return await this.messagingMessagesRepository.findOneBy({ + uri: parsed.uri, + }); + } + } + + /** + * AP Person => Misskey User in DB + */ + public async getUserFromApId(value: string | IObject): Promise { + const parsed = this.parseUri(value); + + if (parsed.local) { + if (parsed.type !== 'users') return null; + + return await this.userCacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({ + id: parsed.id, + }).then(x => x ?? undefined)) ?? null; + } else { + return await this.userCacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({ + uri: parsed.uri, + })); + } + } + + /** + * AP KeyId => Misskey User and Key + */ + public async getAuthUserFromKeyId(keyId: string): Promise<{ + user: CacheableRemoteUser; + key: UserPublickey; + } | null> { + const key = await this.publicKeyCache.fetch(keyId, async () => { + const key = await this.userPublickeysRepository.findOneBy({ + keyId, + }); + + if (key == null) return null; + + return key; + }, key => key != null); + + if (key == null) return null; + + return { + user: await this.userCacheService.userByIdCache.fetch(key.userId, () => this.usersRepository.findOneByOrFail({ id: key.userId })) as CacheableRemoteUser, + key, + }; + } + + /** + * AP Actor id => Misskey User and Key + */ + public async getAuthUserFromApId(uri: string): Promise<{ + user: CacheableRemoteUser; + key: UserPublickey | null; + } | null> { + const user = await this.apPersonService.resolvePerson(uri) as CacheableRemoteUser; + + if (user == null) return null; + + const key = await this.publicKeyByUserIdCache.fetch(user.id, () => this.userPublickeysRepository.findOneBy({ userId: user.id }), v => v != null); + + return { + user, + key, + }; + } +} diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts new file mode 100644 index 0000000000..6fc75a0397 --- /dev/null +++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts @@ -0,0 +1,199 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { IsNull, Not } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; +import { QueueService } from '@/core/QueueService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; + +interface IRecipe { + type: string; +} + +interface IFollowersRecipe extends IRecipe { + type: 'Followers'; +} + +interface IDirectRecipe extends IRecipe { + type: 'Direct'; + to: IRemoteUser; +} + +const isFollowers = (recipe: any): recipe is IFollowersRecipe => + recipe.type === 'Followers'; + +const isDirect = (recipe: any): recipe is IDirectRecipe => + recipe.type === 'Direct'; + +@Injectable() +export class ApDeliverManagerService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + private queueService: QueueService, + ) { + } + + /** + * Deliver activity to followers + * @param activity Activity + * @param from Followee + */ + public async deliverToFollowers(actor: { id: ILocalUser['id']; host: null; }, activity: any) { + const manager = new DeliverManager( + this.userEntityService, + this.followingsRepository, + this.queueService, + actor, + activity, + ); + manager.addFollowersRecipe(); + await manager.execute(); + } + + /** + * Deliver activity to user + * @param activity Activity + * @param to Target user + */ + public async deliverToUser(actor: { id: ILocalUser['id']; host: null; }, activity: any, to: IRemoteUser) { + const manager = new DeliverManager( + this.userEntityService, + this.followingsRepository, + this.queueService, + actor, + activity, + ); + manager.addDirectRecipe(to); + await manager.execute(); + } + + public createDeliverManager(actor: { id: User['id']; host: null; }, activity: any) { + return new DeliverManager( + this.userEntityService, + this.followingsRepository, + this.queueService, + + actor, + activity, + ); + } +} + +class DeliverManager { + private actor: { id: User['id']; host: null; }; + private activity: any; + private recipes: IRecipe[] = []; + + /** + * Constructor + * @param actor Actor + * @param activity Activity to deliver + */ + constructor( + private userEntityService: UserEntityService, + private followingsRepository: FollowingsRepository, + private queueService: QueueService, + + actor: { id: User['id']; host: null; }, + activity: any, + ) { + this.actor = actor; + this.activity = activity; + } + + /** + * Add recipe for followers deliver + */ + public addFollowersRecipe() { + const deliver = { + type: 'Followers', + } as IFollowersRecipe; + + this.addRecipe(deliver); + } + + /** + * Add recipe for direct deliver + * @param to To + */ + public addDirectRecipe(to: IRemoteUser) { + const recipe = { + type: 'Direct', + to, + } as IDirectRecipe; + + this.addRecipe(recipe); + } + + /** + * Add recipe + * @param recipe Recipe + */ + public addRecipe(recipe: IRecipe) { + this.recipes.push(recipe); + } + + /** + * Execute delivers + */ + public async execute() { + if (!this.userEntityService.isLocalUser(this.actor)) return; + + const inboxes = new Set(); + + /* + build inbox list + + Process follower recipes first to avoid duplication when processing + direct recipes later. + */ + if (this.recipes.some(r => isFollowers(r))) { + // followers deliver + // TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう + // ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう? + const followers = await this.followingsRepository.find({ + where: { + followeeId: this.actor.id, + followerHost: Not(IsNull()), + }, + select: { + followerSharedInbox: true, + followerInbox: true, + }, + }) as { + followerSharedInbox: string | null; + followerInbox: string; + }[]; + + for (const following of followers) { + const inbox = following.followerSharedInbox ?? following.followerInbox; + inboxes.add(inbox); + } + } + + this.recipes.filter((recipe): recipe is IDirectRecipe => + // followers recipes have already been processed + isDirect(recipe) + // check that shared inbox has not been added yet + && !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox)) + // check that they actually have an inbox + && recipe.to.inbox != null, + ) + .forEach(recipe => inboxes.add(recipe.to.inbox!)); + + // deliver + for (const inbox of inboxes) { + this.queueService.deliver(this.actor, this.activity, inbox); + } + } +} diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts new file mode 100644 index 0000000000..3da384ec2d --- /dev/null +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -0,0 +1,740 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { In } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import type { CacheableRemoteUser } from '@/models/entities/User.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { ReactionService } from '@/core/ReactionService.js'; +import { RelayService } from '@/core/RelayService.js'; +import { NotePiningService } from '@/core/NotePiningService.js'; +import { UserBlockingService } from '@/core/UserBlockingService.js'; +import { NoteDeleteService } from '@/core/NoteDeleteService.js'; +import { NoteCreateService } from '@/core/NoteCreateService.js'; +import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js'; +import { AppLockService } from '@/core/AppLockService.js'; +import type Logger from '@/logger.js'; +import { MetaService } from '@/core/MetaService.js'; +import { IdService } from '@/core/IdService.js'; +import { StatusError } from '@/misc/status-error.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { QueueService } from '@/core/QueueService.js'; +import { MessagingService } from '@/core/MessagingService.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, MessagingMessagesRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js'; +import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; +import { ApNoteService } from './models/ApNoteService.js'; +import { ApLoggerService } from './ApLoggerService.js'; +import { ApDbResolverService } from './ApDbResolverService.js'; +import { ApResolverService } from './ApResolverService.js'; +import { ApAudienceService } from './ApAudienceService.js'; +import { ApPersonService } from './models/ApPersonService.js'; +import { ApQuestionService } from './models/ApQuestionService.js'; +import type { Resolver } from './ApResolverService.js'; +import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IRead, IReject, IRemove, IUndo, IUpdate } from './type.js'; + +@Injectable() +export class ApInboxService { + private logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + @Inject(DI.abuseUserReportsRepository) + private abuseUserReportsRepository: AbuseUserReportsRepository, + + @Inject(DI.followRequestsRepository) + private followRequestsRepository: FollowRequestsRepository, + + private userEntityService: UserEntityService, + private noteEntityService: NoteEntityService, + private utilityService: UtilityService, + private idService: IdService, + private metaService: MetaService, + private userFollowingService: UserFollowingService, + private apAudienceService: ApAudienceService, + private reactionService: ReactionService, + private relayService: RelayService, + private notePiningService: NotePiningService, + private userBlockingService: UserBlockingService, + private noteCreateService: NoteCreateService, + private noteDeleteService: NoteDeleteService, + private appLockService: AppLockService, + private apResolverService: ApResolverService, + private apDbResolverService: ApDbResolverService, + private apLoggerService: ApLoggerService, + private apNoteService: ApNoteService, + private apPersonService: ApPersonService, + private apQuestionService: ApQuestionService, + private queueService: QueueService, + private messagingService: MessagingService, + ) { + this.logger = this.apLoggerService.logger; + } + + public async performActivity(actor: CacheableRemoteUser, activity: IObject) { + if (isCollectionOrOrderedCollection(activity)) { + const resolver = this.apResolverService.createResolver(); + for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) { + const act = await resolver.resolve(item); + try { + await this.performOneActivity(actor, act); + } catch (err) { + if (err instanceof Error || typeof err === 'string') { + this.logger.error(err); + } + } + } + } else { + await this.performOneActivity(actor, activity); + } + + // ついでにリモートユーザーの情報が古かったら更新しておく + if (actor.uri) { + if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + setImmediate(() => { + this.apPersonService.updatePerson(actor.uri!); + }); + } + } + } + + public async performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise { + if (actor.isSuspended) return; + + if (isCreate(activity)) { + await this.create(actor, activity); + } else if (isDelete(activity)) { + await this.delete(actor, activity); + } else if (isUpdate(activity)) { + await this.update(actor, activity); + } else if (isRead(activity)) { + await this.read(actor, activity); + } else if (isFollow(activity)) { + await this.follow(actor, activity); + } else if (isAccept(activity)) { + await this.accept(actor, activity); + } else if (isReject(activity)) { + await this.reject(actor, activity); + } else if (isAdd(activity)) { + await this.add(actor, activity).catch(err => this.logger.error(err)); + } else if (isRemove(activity)) { + await this.remove(actor, activity).catch(err => this.logger.error(err)); + } else if (isAnnounce(activity)) { + await this.announce(actor, activity); + } else if (isLike(activity)) { + await this.like(actor, activity); + } else if (isUndo(activity)) { + await this.undo(actor, activity); + } else if (isBlock(activity)) { + await this.block(actor, activity); + } else if (isFlag(activity)) { + await this.flag(actor, activity); + } else { + this.logger.warn(`unrecognized activity type: ${(activity as any).type}`); + } + } + + private async follow(actor: CacheableRemoteUser, activity: IFollow): Promise { + const followee = await this.apDbResolverService.getUserFromApId(activity.object); + + if (followee == null) { + return 'skip: followee not found'; + } + + if (followee.host != null) { + return 'skip: フォローしようとしているユーザーはローカルユーザーではありません'; + } + + await this.userFollowingService.follow(actor, followee, activity.id); + return 'ok'; + } + + private async like(actor: CacheableRemoteUser, activity: ILike): Promise { + const targetUri = getApId(activity.object); + + const note = await this.apNoteService.fetchNote(targetUri); + if (!note) return `skip: target note not found ${targetUri}`; + + await this.apNoteService.extractEmojis(activity.tag ?? [], actor.host).catch(() => null); + + return await this.reactionService.create(actor, note, activity._misskey_reaction ?? activity.content ?? activity.name).catch(err => { + if (err.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') { + return 'skip: already reacted'; + } else { + throw err; + } + }).then(() => 'ok'); + } + + private async read(actor: CacheableRemoteUser, activity: IRead): Promise { + const id = await getApId(activity.object); + + if (!this.utilityService.isSelfHost(this.utilityService.extractDbHost(id))) { + return `skip: Read to foreign host (${id})`; + } + + const messageId = id.split('/').pop(); + + const message = await this.messagingMessagesRepository.findOneBy({ id: messageId }); + if (message == null) { + return 'skip: message not found'; + } + + if (actor.id !== message.recipientId) { + return 'skip: actor is not a message recipient'; + } + + await this.messagingService.readUserMessagingMessage(message.recipientId!, message.userId, [message.id]); + return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`; + } + + private async accept(actor: CacheableRemoteUser, activity: IAccept): Promise { + const uri = activity.id ?? activity; + + this.logger.info(`Accept: ${uri}`); + + const resolver = this.apResolverService.createResolver(); + + const object = await resolver.resolve(activity.object).catch(err => { + this.logger.error(`Resolution failed: ${err}`); + throw err; + }); + + if (isFollow(object)) return await this.acceptFollow(actor, object); + + return `skip: Unknown Accept type: ${getApType(object)}`; + } + + private async acceptFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { + // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある + + const follower = await this.apDbResolverService.getUserFromApId(activity.actor); + + if (follower == null) { + return 'skip: follower not found'; + } + + if (follower.host != null) { + return 'skip: follower is not a local user'; + } + + // relay + const match = activity.id?.match(/follow-relay\/(\w+)/); + if (match) { + return await this.relayService.relayAccepted(match[1]); + } + + await this.userFollowingService.acceptFollowRequest(actor, follower); + return 'ok'; + } + + private async add(actor: CacheableRemoteUser, activity: IAdd): Promise { + if ('actor' in activity && actor.uri !== activity.actor) { + throw new Error('invalid actor'); + } + + if (activity.target == null) { + throw new Error('target is null'); + } + + if (activity.target === actor.featured) { + const note = await this.apNoteService.resolveNote(activity.object); + if (note == null) throw new Error('note not found'); + await this.notePiningService.addPinned(actor, note.id); + return; + } + + throw new Error(`unknown target: ${activity.target}`); + } + + private async announce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { + const uri = getApId(activity); + + this.logger.info(`Announce: ${uri}`); + + const targetUri = getApId(activity.object); + + this.announceNote(actor, activity, targetUri); + } + + private async announceNote(actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { + const uri = getApId(activity); + + if (actor.isSuspended) { + return; + } + + // アナウンス先をブロックしてたら中断 + const meta = await this.metaService.fetch(); + if (meta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) return; + + const unlock = await this.appLockService.getApLock(uri); + + try { + // 既に同じURIを持つものが登録されていないかチェック + const exist = await this.apNoteService.fetchNote(uri); + if (exist) { + return; + } + + // Announce対象をresolve + let renote; + try { + renote = await this.apNoteService.resolveNote(targetUri); + if (renote == null) throw new Error('announce target is null'); + } catch (err) { + // 対象が4xxならスキップ + if (err instanceof StatusError) { + if (err.isClientError) { + this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`); + return; + } + + this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode ?? err}`); + } + throw err; + } + + if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) { + this.logger.warn('skip: invalid actor for this activity'); + return; + } + + this.logger.info(`Creating the (Re)Note: ${uri}`); + + const activityAudience = await this.apAudienceService.parseAudience(actor, activity.to, activity.cc); + + await this.noteCreateService.create(actor, { + createdAt: activity.published ? new Date(activity.published) : null, + renote, + visibility: activityAudience.visibility, + visibleUsers: activityAudience.visibleUsers, + uri, + }); + } finally { + unlock(); + } + } + + private async block(actor: CacheableRemoteUser, activity: IBlock): Promise { + // ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず + + const blockee = await this.apDbResolverService.getUserFromApId(activity.object); + + if (blockee == null) { + return 'skip: blockee not found'; + } + + if (blockee.host != null) { + return 'skip: ブロックしようとしているユーザーはローカルユーザーではありません'; + } + + await this.userBlockingService.block(await this.usersRepository.findOneByOrFail({ id: actor.id }), await this.usersRepository.findOneByOrFail({ id: blockee.id })); + return 'ok'; + } + + private async create(actor: CacheableRemoteUser, activity: ICreate): Promise { + const uri = getApId(activity); + + this.logger.info(`Create: ${uri}`); + + // copy audiences between activity <=> object. + if (typeof activity.object === 'object') { + const to = unique(concat([toArray(activity.to), toArray(activity.object.to)])); + const cc = unique(concat([toArray(activity.cc), toArray(activity.object.cc)])); + + activity.to = to; + activity.cc = cc; + activity.object.to = to; + activity.object.cc = cc; + } + + // If there is no attributedTo, use Activity actor. + if (typeof activity.object === 'object' && !activity.object.attributedTo) { + activity.object.attributedTo = activity.actor; + } + + const resolver = this.apResolverService.createResolver(); + + const object = await resolver.resolve(activity.object).catch(e => { + this.logger.error(`Resolution failed: ${e}`); + throw e; + }); + + if (isPost(object)) { + this.createNote(resolver, actor, object, false, activity); + } else { + this.logger.warn(`Unknown type: ${getApType(object)}`); + } + } + + private async createNote(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { + const uri = getApId(note); + + if (typeof note === 'object') { + if (actor.uri !== note.attributedTo) { + return 'skip: actor.uri !== note.attributedTo'; + } + + if (typeof note.id === 'string') { + if (this.utilityService.extractDbHost(actor.uri) !== this.utilityService.extractDbHost(note.id)) { + return 'skip: host in actor.uri !== note.id'; + } + } + } + + const unlock = await this.appLockService.getApLock(uri); + + try { + const exist = await this.apNoteService.fetchNote(note); + if (exist) return 'skip: note exists'; + + await this.apNoteService.createNote(note, resolver, silent); + return 'ok'; + } catch (err) { + if (err instanceof StatusError && err.isClientError) { + return `skip ${err.statusCode}`; + } else { + throw err; + } + } finally { + unlock(); + } + } + + private async delete(actor: CacheableRemoteUser, activity: IDelete): Promise { + if ('actor' in activity && actor.uri !== activity.actor) { + throw new Error('invalid actor'); + } + + // 削除対象objectのtype + let formerType: string | undefined; + + if (typeof activity.object === 'string') { + // typeが不明だけど、どうせ消えてるのでremote resolveしない + formerType = undefined; + } else { + const object = activity.object as IObject; + if (isTombstone(object)) { + formerType = toSingle(object.formerType); + } else { + formerType = toSingle(object.type); + } + } + + const uri = getApId(activity.object); + + // type不明でもactorとobjectが同じならばそれはPersonに違いない + if (!formerType && actor.uri === uri) { + formerType = 'Person'; + } + + // それでもなかったらおそらくNote + if (!formerType) { + formerType = 'Note'; + } + + if (validPost.includes(formerType)) { + return await this.deleteNote(actor, uri); + } else if (validActor.includes(formerType)) { + return await this.deleteActor(actor, uri); + } else { + return `Unknown type ${formerType}`; + } + } + + private async deleteActor(actor: CacheableRemoteUser, uri: string): Promise { + this.logger.info(`Deleting the Actor: ${uri}`); + + if (actor.uri !== uri) { + return `skip: delete actor ${actor.uri} !== ${uri}`; + } + + const user = await this.usersRepository.findOneByOrFail({ id: actor.id }); + if (user.isDeleted) { + this.logger.info('skip: already deleted'); + } + + const job = await this.queueService.createDeleteAccountJob(actor); + + await this.usersRepository.update(actor.id, { + isDeleted: true, + }); + + return `ok: queued ${job.name} ${job.id}`; + } + + private async deleteNote(actor: CacheableRemoteUser, uri: string): Promise { + this.logger.info(`Deleting the Note: ${uri}`); + + const unlock = await this.appLockService.getApLock(uri); + + try { + const note = await this.apDbResolverService.getNoteFromApId(uri); + + if (note == null) { + const message = await this.apDbResolverService.getMessageFromApId(uri); + if (message == null) return 'message not found'; + + if (message.userId !== actor.id) { + return '投稿を削除しようとしているユーザーは投稿の作成者ではありません'; + } + + await this.messagingService.deleteMessage(message); + + return 'ok: message deleted'; + } + + if (note.userId !== actor.id) { + return '投稿を削除しようとしているユーザーは投稿の作成者ではありません'; + } + + await this.noteDeleteService.delete(actor, note); + return 'ok: note deleted'; + } finally { + unlock(); + } + } + + private async flag(actor: CacheableRemoteUser, activity: IFlag): Promise { + // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので + // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する + const uris = getApIds(activity.object); + + const userIds = uris.filter(uri => uri.startsWith(this.config.url + '/users/')).map(uri => uri.split('/').pop()!); + const users = await this.usersRepository.findBy({ + id: In(userIds), + }); + if (users.length < 1) return 'skip'; + + await this.abuseUserReportsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + targetUserId: users[0].id, + targetUserHost: users[0].host, + reporterId: actor.id, + reporterHost: actor.host, + comment: `${activity.content}\n${JSON.stringify(uris, null, 2)}`, + }); + + return 'ok'; + } + + private async reject(actor: CacheableRemoteUser, activity: IReject): Promise { + const uri = activity.id ?? activity; + + this.logger.info(`Reject: ${uri}`); + + const resolver = this.apResolverService.createResolver(); + + const object = await resolver.resolve(activity.object).catch(e => { + this.logger.error(`Resolution failed: ${e}`); + throw e; + }); + + if (isFollow(object)) return await this.rejectFollow(actor, object); + + return `skip: Unknown Reject type: ${getApType(object)}`; + } + + private async rejectFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { + // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある + + const follower = await this.apDbResolverService.getUserFromApId(activity.actor); + + if (follower == null) { + return 'skip: follower not found'; + } + + if (!this.userEntityService.isLocalUser(follower)) { + return 'skip: follower is not a local user'; + } + + // relay + const match = activity.id?.match(/follow-relay\/(\w+)/); + if (match) { + return await this.relayService.relayRejected(match[1]); + } + + await this.userFollowingService.remoteReject(actor, follower); + return 'ok'; + } + + private async remove(actor: CacheableRemoteUser, activity: IRemove): Promise { + if ('actor' in activity && actor.uri !== activity.actor) { + throw new Error('invalid actor'); + } + + if (activity.target == null) { + throw new Error('target is null'); + } + + if (activity.target === actor.featured) { + const note = await this.apNoteService.resolveNote(activity.object); + if (note == null) throw new Error('note not found'); + await this.notePiningService.removePinned(actor, note.id); + return; + } + + throw new Error(`unknown target: ${activity.target}`); + } + + private async undo(actor: CacheableRemoteUser, activity: IUndo): Promise { + if ('actor' in activity && actor.uri !== activity.actor) { + throw new Error('invalid actor'); + } + + const uri = activity.id ?? activity; + + this.logger.info(`Undo: ${uri}`); + + const resolver = this.apResolverService.createResolver(); + + const object = await resolver.resolve(activity.object).catch(e => { + this.logger.error(`Resolution failed: ${e}`); + throw e; + }); + + if (isFollow(object)) return await this.undoFollow(actor, object); + if (isBlock(object)) return await this.undoBlock(actor, object); + if (isLike(object)) return await this.undoLike(actor, object); + if (isAnnounce(object)) return await this.undoAnnounce(actor, object); + if (isAccept(object)) return await this.undoAccept(actor, object); + + return `skip: unknown object type ${getApType(object)}`; + } + + private async undoAccept(actor: CacheableRemoteUser, activity: IAccept): Promise { + const follower = await this.apDbResolverService.getUserFromApId(activity.object); + if (follower == null) { + return 'skip: follower not found'; + } + + const following = await this.followingsRepository.findOneBy({ + followerId: follower.id, + followeeId: actor.id, + }); + + if (following) { + await this.userFollowingService.unfollow(follower, actor); + return 'ok: unfollowed'; + } + + return 'skip: フォローされていない'; + } + + private async undoAnnounce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { + const uri = getApId(activity); + + const note = await this.notesRepository.findOneBy({ + uri, + userId: actor.id, + }); + + if (!note) return 'skip: no such Announce'; + + await this.noteDeleteService.delete(actor, note); + return 'ok: deleted'; + } + + private async undoBlock(actor: CacheableRemoteUser, activity: IBlock): Promise { + const blockee = await this.apDbResolverService.getUserFromApId(activity.object); + + if (blockee == null) { + return 'skip: blockee not found'; + } + + if (blockee.host != null) { + return 'skip: ブロック解除しようとしているユーザーはローカルユーザーではありません'; + } + + await this.userBlockingService.unblock(await this.usersRepository.findOneByOrFail({ id: actor.id }), blockee); + return 'ok'; + } + + private async undoFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { + const followee = await this.apDbResolverService.getUserFromApId(activity.object); + if (followee == null) { + return 'skip: followee not found'; + } + + if (followee.host != null) { + return 'skip: フォロー解除しようとしているユーザーはローカルユーザーではありません'; + } + + const req = await this.followRequestsRepository.findOneBy({ + followerId: actor.id, + followeeId: followee.id, + }); + + const following = await this.followingsRepository.findOneBy({ + followerId: actor.id, + followeeId: followee.id, + }); + + if (req) { + await this.userFollowingService.cancelFollowRequest(followee, actor); + return 'ok: follow request canceled'; + } + + if (following) { + await this.userFollowingService.unfollow(actor, followee); + return 'ok: unfollowed'; + } + + return 'skip: リクエストもフォローもされていない'; + } + + private async undoLike(actor: CacheableRemoteUser, activity: ILike): Promise { + const targetUri = getApId(activity.object); + + const note = await this.apNoteService.fetchNote(targetUri); + if (!note) return `skip: target note not found ${targetUri}`; + + await this.reactionService.delete(actor, note).catch(e => { + if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') return; + throw e; + }); + + return 'ok'; + } + + private async update(actor: CacheableRemoteUser, activity: IUpdate): Promise { + if ('actor' in activity && actor.uri !== activity.actor) { + return 'skip: invalid actor'; + } + + this.logger.debug('Update'); + + const resolver = this.apResolverService.createResolver(); + + const object = await resolver.resolve(activity.object).catch(e => { + this.logger.error(`Resolution failed: ${e}`); + throw e; + }); + + if (isActor(object)) { + await this.apPersonService.updatePerson(actor.uri!, resolver, object); + return 'ok: Person updated'; + } else if (getApType(object) === 'Question') { + await this.apQuestionService.updateQuestion(object, resolver).catch(err => console.error(err)); + return 'ok: Question updated'; + } else { + return `skip: Unknown type: ${getApType(object)}`; + } + } +} diff --git a/packages/backend/src/core/activitypub/ApLoggerService.ts b/packages/backend/src/core/activitypub/ApLoggerService.ts new file mode 100644 index 0000000000..a742cc42da --- /dev/null +++ b/packages/backend/src/core/activitypub/ApLoggerService.ts @@ -0,0 +1,14 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type Logger from '@/logger.js'; +import { RemoteLoggerService } from '@/core/RemoteLoggerService.js'; + +@Injectable() +export class ApLoggerService { + public logger: Logger; + + constructor( + private remoteLoggerService: RemoteLoggerService, + ) { + this.logger = this.remoteLoggerService.logger.createSubLogger('ap', 'magenta'); + } +} diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts new file mode 100644 index 0000000000..8804fde64a --- /dev/null +++ b/packages/backend/src/core/activitypub/ApMfmService.ts @@ -0,0 +1,30 @@ +import { Inject, Injectable } from '@nestjs/common'; +import * as mfm from 'mfm-js'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import { MfmService } from '@/core/MfmService.js'; +import type { Note } from '@/models/entities/Note.js'; +import { extractApHashtagObjects } from './models/tag.js'; +import type { IObject } from './type.js'; + +@Injectable() +export class ApMfmService { + constructor( + @Inject(DI.config) + private config: Config, + + private mfmService: MfmService, + ) { + } + + public htmlToMfm(html: string, tag?: IObject | IObject[]) { + const hashtagNames = extractApHashtagObjects(tag).map(x => x.name).filter((x): x is string => x != null); + + return this.mfmService.fromHtml(html, hashtagNames); + } + + public getNoteHtml(note: Note) { + if (!note.text) return ''; + return this.mfmService.toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); + } +} diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts new file mode 100644 index 0000000000..38a92567c3 --- /dev/null +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -0,0 +1,703 @@ +import { createPublicKey } from 'node:crypto'; +import { Inject, Injectable } from '@nestjs/common'; +import { In, IsNull } from 'typeorm'; +import { v4 as uuid } from 'uuid'; +import * as mfm from 'mfm-js'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; +import type { IMentionedRemoteUsers, Note } from '@/models/entities/Note.js'; +import type { Blocking } from '@/models/entities/Blocking.js'; +import type { Relay } from '@/models/entities/Relay.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { NoteReaction } from '@/models/entities/NoteReaction.js'; +import type { Emoji } from '@/models/entities/Emoji.js'; +import type { Poll } from '@/models/entities/Poll.js'; +import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; +import type { PollVote } from '@/models/entities/PollVote.js'; +import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; +import { MfmService } from '@/core/MfmService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import type { UserKeypair } from '@/models/entities/UserKeypair.js'; +import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, EmojisRepository, PollsRepository } from '@/models/index.js'; +import { LdSignatureService } from './LdSignatureService.js'; +import { ApMfmService } from './ApMfmService.js'; +import type { IActivity, IObject } from './type.js'; +import type { IIdentifier } from './models/identifier.js'; + +@Injectable() +export class ApRendererService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + private userEntityService: UserEntityService, + private driveFileEntityService: DriveFileEntityService, + private ldSignatureService: LdSignatureService, + private userKeypairStoreService: UserKeypairStoreService, + private apMfmService: ApMfmService, + private mfmService: MfmService, + ) { + } + + public renderAccept(object: any, user: { id: User['id']; host: null }) { + return { + type: 'Accept', + actor: `${this.config.url}/users/${user.id}`, + object, + }; + } + + public renderAdd(user: ILocalUser, target: any, object: any) { + return { + type: 'Add', + actor: `${this.config.url}/users/${user.id}`, + target, + object, + }; + } + + public renderAnnounce(object: any, note: Note) { + const attributedTo = `${this.config.url}/users/${note.userId}`; + + let to: string[] = []; + let cc: string[] = []; + + if (note.visibility === 'public') { + to = ['https://www.w3.org/ns/activitystreams#Public']; + cc = [`${attributedTo}/followers`]; + } else if (note.visibility === 'home') { + to = [`${attributedTo}/followers`]; + cc = ['https://www.w3.org/ns/activitystreams#Public']; + } else { + return null; + } + + return { + id: `${this.config.url}/notes/${note.id}/activity`, + actor: `${this.config.url}/users/${note.userId}`, + type: 'Announce', + published: note.createdAt.toISOString(), + to, + cc, + object, + }; + } + + /** + * Renders a block into its ActivityPub representation. + * + * @param block The block to be rendered. The blockee relation must be loaded. + */ + public renderBlock(block: Blocking) { + if (block.blockee?.uri == null) { + throw new Error('renderBlock: missing blockee uri'); + } + + return { + type: 'Block', + id: `${this.config.url}/blocks/${block.id}`, + actor: `${this.config.url}/users/${block.blockerId}`, + object: block.blockee.uri, + }; + } + + public renderCreate(object: any, note: Note) { + const activity = { + id: `${this.config.url}/notes/${note.id}/activity`, + actor: `${this.config.url}/users/${note.userId}`, + type: 'Create', + published: note.createdAt.toISOString(), + object, + } as any; + + if (object.to) activity.to = object.to; + if (object.cc) activity.cc = object.cc; + + return activity; + } + + public renderDelete(object: any, user: { id: User['id']; host: null }) { + return { + type: 'Delete', + actor: `${this.config.url}/users/${user.id}`, + object, + published: new Date().toISOString(), + }; + } + + public renderDocument(file: DriveFile) { + return { + type: 'Document', + mediaType: file.type, + url: this.driveFileEntityService.getPublicUrl(file), + name: file.comment, + }; + } + + public renderEmoji(emoji: Emoji) { + return { + id: `${this.config.url}/emojis/${emoji.name}`, + type: 'Emoji', + name: `:${emoji.name}:`, + updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : new Date().toISOString, + icon: { + type: 'Image', + mediaType: emoji.type ?? 'image/png', + url: emoji.publicUrl ?? emoji.originalUrl, // ?? emoji.originalUrl してるのは後方互換性のため + }, + }; + } + + // to anonymise reporters, the reporting actor must be a system user + // object has to be a uri or array of uris + public renderFlag(user: ILocalUser, object: [string], content: string) { + return { + type: 'Flag', + actor: `${this.config.url}/users/${user.id}`, + content, + object, + }; + } + + public renderFollowRelay(relay: Relay, relayActor: ILocalUser) { + const follow = { + id: `${this.config.url}/activities/follow-relay/${relay.id}`, + type: 'Follow', + actor: `${this.config.url}/users/${relayActor.id}`, + object: 'https://www.w3.org/ns/activitystreams#Public', + }; + + return follow; + } + + /** + * Convert (local|remote)(Follower|Followee)ID to URL + * @param id Follower|Followee ID + */ + public async renderFollowUser(id: User['id']) { + const user = await this.usersRepository.findOneByOrFail({ id: id }); + return this.userEntityService.isLocalUser(user) ? `${this.config.url}/users/${user.id}` : user.uri; + } + + public renderFollow( + follower: { id: User['id']; host: User['host']; uri: User['host'] }, + followee: { id: User['id']; host: User['host']; uri: User['host'] }, + requestId?: string, + ) { + const follow = { + id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`, + type: 'Follow', + actor: this.userEntityService.isLocalUser(follower) ? `${this.config.url}/users/${follower.id}` : follower.uri, + object: this.userEntityService.isLocalUser(followee) ? `${this.config.url}/users/${followee.id}` : followee.uri, + } as any; + + return follow; + } + + public renderHashtag(tag: string) { + return { + type: 'Hashtag', + href: `${this.config.url}/tags/${encodeURIComponent(tag)}`, + name: `#${tag}`, + }; + } + + public renderImage(file: DriveFile) { + return { + type: 'Image', + url: this.driveFileEntityService.getPublicUrl(file), + sensitive: file.isSensitive, + name: file.comment, + }; + } + + public renderKey(user: ILocalUser, key: UserKeypair, postfix?: string) { + return { + id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`, + type: 'Key', + owner: `${this.config.url}/users/${user.id}`, + publicKeyPem: createPublicKey(key.publicKey).export({ + type: 'spki', + format: 'pem', + }), + }; + } + + public async renderLike(noteReaction: NoteReaction, note: { uri: string | null }) { + const reaction = noteReaction.reaction; + + const object = { + type: 'Like', + id: `${this.config.url}/likes/${noteReaction.id}`, + actor: `${this.config.url}/users/${noteReaction.userId}`, + object: note.uri ? note.uri : `${this.config.url}/notes/${noteReaction.noteId}`, + content: reaction, + _misskey_reaction: reaction, + } as any; + + if (reaction.startsWith(':')) { + const name = reaction.replace(/:/g, ''); + const emoji = await this.emojisRepository.findOneBy({ + name, + host: IsNull(), + }); + + if (emoji) object.tag = [this.renderEmoji(emoji)]; + } + + return object; + } + + public renderMention(mention: User) { + return { + type: 'Mention', + href: this.userEntityService.isRemoteUser(mention) ? mention.uri : `${this.config.url}/users/${(mention as ILocalUser).id}`, + name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as ILocalUser).username}`, + }; + } + + public async renderNote(note: Note, dive = true, isTalk = false): Promise { + const getPromisedFiles = async (ids: string[]) => { + if (!ids || ids.length === 0) return []; + const items = await this.driveFilesRepository.findBy({ id: In(ids) }); + return ids.map(id => items.find(item => item.id === id)).filter(item => item != null) as DriveFile[]; + }; + + let inReplyTo; + let inReplyToNote: Note | null; + + if (note.replyId) { + inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId }); + + if (inReplyToNote != null) { + const inReplyToUser = await this.usersRepository.findOneBy({ id: inReplyToNote.userId }); + + if (inReplyToUser != null) { + if (inReplyToNote.uri) { + inReplyTo = inReplyToNote.uri; + } else { + if (dive) { + inReplyTo = await this.renderNote(inReplyToNote, false); + } else { + inReplyTo = `${this.config.url}/notes/${inReplyToNote.id}`; + } + } + } + } + } else { + inReplyTo = null; + } + + let quote; + + if (note.renoteId) { + const renote = await this.notesRepository.findOneBy({ id: note.renoteId }); + + if (renote) { + quote = renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`; + } + } + + const attributedTo = `${this.config.url}/users/${note.userId}`; + + const mentions = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); + + let to: string[] = []; + let cc: string[] = []; + + if (note.visibility === 'public') { + to = ['https://www.w3.org/ns/activitystreams#Public']; + cc = [`${attributedTo}/followers`].concat(mentions); + } else if (note.visibility === 'home') { + to = [`${attributedTo}/followers`]; + cc = ['https://www.w3.org/ns/activitystreams#Public'].concat(mentions); + } else if (note.visibility === 'followers') { + to = [`${attributedTo}/followers`]; + cc = mentions; + } else { + to = mentions; + } + + const mentionedUsers = note.mentions.length > 0 ? await this.usersRepository.findBy({ + id: In(note.mentions), + }) : []; + + const hashtagTags = (note.tags ?? []).map(tag => this.renderHashtag(tag)); + const mentionTags = mentionedUsers.map(u => this.renderMention(u)); + + const files = await getPromisedFiles(note.fileIds); + + const text = note.text ?? ''; + let poll: Poll | null = null; + + if (note.hasPoll) { + poll = await this.pollsRepository.findOneBy({ noteId: note.id }); + } + + let apText = text; + + if (quote) { + apText += `\n\nRE: ${quote}`; + } + + const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw; + + const content = this.apMfmService.getNoteHtml(Object.assign({}, note, { + text: apText, + })); + + const emojis = await this.getEmojis(note.emojis); + const apemojis = emojis.map(emoji => this.renderEmoji(emoji)); + + const tag = [ + ...hashtagTags, + ...mentionTags, + ...apemojis, + ]; + + const asPoll = poll ? { + type: 'Question', + content: this.apMfmService.getNoteHtml(Object.assign({}, note, { + text: text, + })), + [poll.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt, + [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ + type: 'Note', + name: text, + replies: { + type: 'Collection', + totalItems: poll!.votes[i], + }, + })), + } : {}; + + const asTalk = isTalk ? { + _misskey_talk: true, + } : {}; + + return { + id: `${this.config.url}/notes/${note.id}`, + type: 'Note', + attributedTo, + summary: summary ?? undefined, + content: content ?? undefined, + _misskey_content: text, + source: { + content: text, + mediaType: 'text/x.misskeymarkdown', + }, + _misskey_quote: quote, + quoteUrl: quote, + published: note.createdAt.toISOString(), + to, + cc, + inReplyTo, + attachment: files.map(x => this.renderDocument(x)), + sensitive: note.cw != null || files.some(file => file.isSensitive), + tag, + ...asPoll, + ...asTalk, + }; + } + + public async renderPerson(user: ILocalUser) { + const id = `${this.config.url}/users/${user.id}`; + const isSystem = !!user.username.match(/\./); + + const [avatar, banner, profile] = await Promise.all([ + user.avatarId ? this.driveFilesRepository.findOneBy({ id: user.avatarId }) : Promise.resolve(undefined), + user.bannerId ? this.driveFilesRepository.findOneBy({ id: user.bannerId }) : Promise.resolve(undefined), + this.userProfilesRepository.findOneByOrFail({ userId: user.id }), + ]); + + const attachment: { + type: 'PropertyValue', + name: string, + value: string, + identifier?: IIdentifier, + }[] = []; + + if (profile.fields) { + for (const field of profile.fields) { + attachment.push({ + type: 'PropertyValue', + name: field.name, + value: (field.value != null && field.value.match(/^https?:/)) + ? `${new URL(field.value).href}` + : field.value, + }); + } + } + + const emojis = await this.getEmojis(user.emojis); + const apemojis = emojis.map(emoji => this.renderEmoji(emoji)); + + const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag)); + + const tag = [ + ...apemojis, + ...hashtagTags, + ]; + + const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); + + const person = { + type: isSystem ? 'Application' : user.isBot ? 'Service' : 'Person', + id, + inbox: `${id}/inbox`, + outbox: `${id}/outbox`, + followers: `${id}/followers`, + following: `${id}/following`, + featured: `${id}/collections/featured`, + sharedInbox: `${this.config.url}/inbox`, + endpoints: { sharedInbox: `${this.config.url}/inbox` }, + url: `${this.config.url}/@${user.username}`, + preferredUsername: user.username, + name: user.name, + summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null, + icon: avatar ? this.renderImage(avatar) : null, + image: banner ? this.renderImage(banner) : null, + tag, + manuallyApprovesFollowers: user.isLocked, + discoverable: !!user.isExplorable, + publicKey: this.renderKey(user, keypair, '#main-key'), + isCat: user.isCat, + attachment: attachment.length ? attachment : undefined, + } as any; + + if (profile.birthday) { + person['vcard:bday'] = profile.birthday; + } + + if (profile.location) { + person['vcard:Address'] = profile.location; + } + + return person; + } + + public async renderQuestion(user: { id: User['id'] }, note: Note, poll: Poll) { + const question = { + type: 'Question', + id: `${this.config.url}/questions/${note.id}`, + actor: `${this.config.url}/users/${user.id}`, + content: note.text ?? '', + [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ + name: text, + _misskey_votes: poll.votes[i], + replies: { + type: 'Collection', + totalItems: poll.votes[i], + }, + })), + }; + + return question; + } + + public renderRead(user: { id: User['id'] }, message: MessagingMessage) { + return { + type: 'Read', + actor: `${this.config.url}/users/${user.id}`, + object: message.uri, + }; + } + + public renderReject(object: any, user: { id: User['id'] }) { + return { + type: 'Reject', + actor: `${this.config.url}/users/${user.id}`, + object, + }; + } + + public renderRemove(user: { id: User['id'] }, target: any, object: any) { + return { + type: 'Remove', + actor: `${this.config.url}/users/${user.id}`, + target, + object, + }; + } + + public renderTombstone(id: string) { + return { + id, + type: 'Tombstone', + }; + } + + public renderUndo(object: any, user: { id: User['id'] }) { + if (object == null) return null; + const id = typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined; + + return { + type: 'Undo', + ...(id ? { id } : {}), + actor: `${this.config.url}/users/${user.id}`, + object, + published: new Date().toISOString(), + }; + } + + public renderUpdate(object: any, user: { id: User['id'] }) { + const activity = { + id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`, + actor: `${this.config.url}/users/${user.id}`, + type: 'Update', + to: ['https://www.w3.org/ns/activitystreams#Public'], + object, + published: new Date().toISOString(), + } as any; + + return activity; + } + + public renderVote(user: { id: User['id'] }, vote: PollVote, note: Note, poll: Poll, pollOwner: IRemoteUser) { + return { + id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`, + actor: `${this.config.url}/users/${user.id}`, + type: 'Create', + to: [pollOwner.uri], + published: new Date().toISOString(), + object: { + id: `${this.config.url}/users/${user.id}#votes/${vote.id}`, + type: 'Note', + attributedTo: `${this.config.url}/users/${user.id}`, + to: [pollOwner.uri], + inReplyTo: note.uri, + name: poll.choices[vote.choice], + }, + }; + } + + public renderActivity(x: any): IActivity | null { + if (x == null) return null; + + if (typeof x === 'object' && x.id == null) { + x.id = `${this.config.url}/${uuid()}`; + } + + return Object.assign({ + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', + { + // as non-standards + manuallyApprovesFollowers: 'as:manuallyApprovesFollowers', + sensitive: 'as:sensitive', + Hashtag: 'as:Hashtag', + quoteUrl: 'as:quoteUrl', + // Mastodon + toot: 'http://joinmastodon.org/ns#', + Emoji: 'toot:Emoji', + featured: 'toot:featured', + discoverable: 'toot:discoverable', + // schema + schema: 'http://schema.org#', + PropertyValue: 'schema:PropertyValue', + value: 'schema:value', + // Misskey + misskey: 'https://misskey-hub.net/ns#', + '_misskey_content': 'misskey:_misskey_content', + '_misskey_quote': 'misskey:_misskey_quote', + '_misskey_reaction': 'misskey:_misskey_reaction', + '_misskey_votes': 'misskey:_misskey_votes', + '_misskey_talk': 'misskey:_misskey_talk', + 'isCat': 'misskey:isCat', + // vcard + vcard: 'http://www.w3.org/2006/vcard/ns#', + }, + ], + }, x); + } + + public async attachLdSignature(activity: any, user: { id: User['id']; host: null; }): Promise { + const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); + + const ldSignature = this.ldSignatureService.use(); + ldSignature.debug = false; + activity = await ldSignature.signRsaSignature2017(activity, keypair.privateKey, `${this.config.url}/users/${user.id}#main-key`); + + return activity; + } + + /** + * Render OrderedCollectionPage + * @param id URL of self + * @param totalItems Number of total items + * @param orderedItems Items + * @param partOf URL of base + * @param prev URL of prev page (optional) + * @param next URL of next page (optional) + */ + public renderOrderedCollectionPage(id: string, totalItems: any, orderedItems: any, partOf: string, prev?: string, next?: string) { + const page = { + id, + partOf, + type: 'OrderedCollectionPage', + totalItems, + orderedItems, + } as any; + + if (prev) page.prev = prev; + if (next) page.next = next; + + return page; + } + + /** + * Render OrderedCollection + * @param id URL of self + * @param totalItems Total number of items + * @param first URL of first page (optional) + * @param last URL of last page (optional) + * @param orderedItems attached objects (optional) + */ + public renderOrderedCollection(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: IObject[]) { + const page: any = { + id, + type: 'OrderedCollection', + totalItems, + }; + + if (first) page.first = first; + if (last) page.last = last; + if (orderedItems) page.orderedItems = orderedItems; + + return page; + } + + private async getEmojis(names: string[]): Promise { + if (names == null || names.length === 0) return []; + + const emojis = await Promise.all( + names.map(name => this.emojisRepository.findOneBy({ + name, + host: IsNull(), + })), + ); + + return emojis.filter(emoji => emoji != null) as Emoji[]; + } +} diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts new file mode 100644 index 0000000000..baad46d668 --- /dev/null +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -0,0 +1,182 @@ +import * as crypto from 'node:crypto'; +import { URL } from 'node:url'; +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import type { User } from '@/models/entities/User.js'; +import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; + +type Request = { + url: string; + method: string; + headers: Record; +}; + +type Signed = { + request: Request; + signingString: string; + signature: string; + signatureHeader: string; +}; + +type PrivateKey = { + privateKeyPem: string; + keyId: string; +}; + +@Injectable() +export class ApRequestService { + constructor( + @Inject(DI.config) + private config: Config, + + private userKeypairStoreService: UserKeypairStoreService, + private httpRequestService: HttpRequestService, + ) { + } + + private createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record }): Signed { + const u = new URL(args.url); + const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`; + + const request: Request = { + url: u.href, + method: 'POST', + headers: this.objectAssignWithLcKey({ + 'Date': new Date().toUTCString(), + 'Host': u.hostname, + 'Content-Type': 'application/activity+json', + 'Digest': digestHeader, + }, args.additionalHeaders), + }; + + const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']); + + return { + request, + signingString: result.signingString, + signature: result.signature, + signatureHeader: result.signatureHeader, + }; + } + + private createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record }): Signed { + const u = new URL(args.url); + + const request: Request = { + url: u.href, + method: 'GET', + headers: this.objectAssignWithLcKey({ + 'Accept': 'application/activity+json, application/ld+json', + 'Date': new Date().toUTCString(), + 'Host': new URL(args.url).hostname, + }, args.additionalHeaders), + }; + + const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']); + + return { + request, + signingString: result.signingString, + signature: result.signature, + signatureHeader: result.signatureHeader, + }; + } + + private signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed { + const signingString = this.genSigningString(request, includeHeaders); + const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64'); + const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`; + + request.headers = this.objectAssignWithLcKey(request.headers, { + Signature: signatureHeader, + }); + + return { + request, + signingString, + signature, + signatureHeader, + }; + } + + private genSigningString(request: Request, includeHeaders: string[]): string { + request.headers = this.lcObjectKey(request.headers); + + const results: string[] = []; + + for (const key of includeHeaders.map(x => x.toLowerCase())) { + if (key === '(request-target)') { + results.push(`(request-target): ${request.method.toLowerCase()} ${new URL(request.url).pathname}`); + } else { + results.push(`${key}: ${request.headers[key]}`); + } + } + + return results.join('\n'); + } + + private lcObjectKey(src: Record): Record { + const dst: Record = {}; + for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key]; + return dst; + } + + private objectAssignWithLcKey(a: Record, b: Record): Record { + return Object.assign(this.lcObjectKey(a), this.lcObjectKey(b)); + } + + public async signedPost(user: { id: User['id'] }, url: string, object: any) { + const body = JSON.stringify(object); + + const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); + + const req = this.createSignedPost({ + key: { + privateKeyPem: keypair.privateKey, + keyId: `${this.config.url}/users/${user.id}#main-key`, + }, + url, + body, + additionalHeaders: { + 'User-Agent': this.config.userAgent, + }, + }); + + await this.httpRequestService.getResponse({ + url, + method: req.request.method, + headers: req.request.headers, + body, + }); + } + + /** + * Get AP object with http-signature + * @param user http-signature user + * @param url URL to fetch + */ + public async signedGet(url: string, user: { id: User['id'] }) { + const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); + + const req = this.createSignedGet({ + key: { + privateKeyPem: keypair.privateKey, + keyId: `${this.config.url}/users/${user.id}#main-key`, + }, + url, + additionalHeaders: { + 'User-Agent': this.config.userAgent, + }, + }); + + const res = await this.httpRequestService.getResponse({ + url, + method: req.request.method, + headers: req.request.headers, + }); + + return await res.json(); + } +} diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts new file mode 100644 index 0000000000..bcdb9383d1 --- /dev/null +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -0,0 +1,195 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type { ILocalUser } from '@/models/entities/User.js'; +import { InstanceActorService } from '@/core/InstanceActorService.js'; +import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import { MetaService } from '@/core/MetaService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { DI } from '@/di-symbols.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { isCollectionOrOrderedCollection } from './type.js'; +import { ApDbResolverService } from './ApDbResolverService.js'; +import { ApRendererService } from './ApRendererService.js'; +import { ApRequestService } from './ApRequestService.js'; +import type { IObject, ICollection, IOrderedCollection } from './type.js'; + +@Injectable() +export class ApResolverService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + @Inject(DI.noteReactionsRepository) + private noteReactionsRepository: NoteReactionsRepository, + + private utilityService: UtilityService, + private instanceActorService: InstanceActorService, + private metaService: MetaService, + private apRequestService: ApRequestService, + private httpRequestService: HttpRequestService, + private apRendererService: ApRendererService, + private apDbResolverService: ApDbResolverService, + ) { + } + + public createResolver(): Resolver { + return new Resolver( + this.config, + this.usersRepository, + this.notesRepository, + this.pollsRepository, + this.noteReactionsRepository, + this.utilityService, + this.instanceActorService, + this.metaService, + this.apRequestService, + this.httpRequestService, + this.apRendererService, + this.apDbResolverService, + ); + } +} + +export class Resolver { + private history: Set; + private user?: ILocalUser; + + constructor( + private config: Config, + private usersRepository: UsersRepository, + private notesRepository: NotesRepository, + private pollsRepository: PollsRepository, + private noteReactionsRepository: NoteReactionsRepository, + private utilityService: UtilityService, + private instanceActorService: InstanceActorService, + private metaService: MetaService, + private apRequestService: ApRequestService, + private httpRequestService: HttpRequestService, + private apRendererService: ApRendererService, + private apDbResolverService: ApDbResolverService, + private recursionLimit = 100 + ) { + this.history = new Set(); + } + + public getHistory(): string[] { + return Array.from(this.history); + } + + public async resolveCollection(value: string | IObject): Promise { + const collection = typeof value === 'string' + ? await this.resolve(value) + : value; + + if (isCollectionOrOrderedCollection(collection)) { + return collection; + } else { + throw new Error(`unrecognized collection type: ${collection.type}`); + } + } + + public async resolve(value: string | IObject): Promise { + if (value == null) { + throw new Error('resolvee is null (or undefined)'); + } + + if (typeof value !== 'string') { + return value; + } + + if (value.includes('#')) { + // URLs with fragment parts cannot be resolved correctly because + // the fragment part does not get transmitted over HTTP(S). + // Avoid strange behaviour by not trying to resolve these at all. + throw new Error(`cannot resolve URL with fragment: ${value}`); + } + + if (this.history.has(value)) { + throw new Error('cannot resolve already resolved one'); + } + + if (this.history.size > this.recursionLimit) { + throw new Error(`hit recursion limit: ${this.utilityService.extractDbHost(value)}`); + } + + this.history.add(value); + + const host = this.utilityService.extractDbHost(value); + if (this.utilityService.isSelfHost(host)) { + return await this.resolveLocal(value); + } + + const meta = await this.metaService.fetch(); + if (meta.blockedHosts.includes(host)) { + throw new Error('Instance is blocked'); + } + + if (this.config.signToActivityPubGet && !this.user) { + this.user = await this.instanceActorService.getInstanceActor(); + } + + const object = (this.user + ? await this.apRequestService.signedGet(value, this.user) + : await this.httpRequestService.getJson(value, 'application/activity+json, application/ld+json')) as IObject; + + if (object == null || ( + Array.isArray(object['@context']) ? + !(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') : + object['@context'] !== 'https://www.w3.org/ns/activitystreams' + )) { + throw new Error('invalid response'); + } + + return object; + } + + private resolveLocal(url: string): Promise { + const parsed = this.apDbResolverService.parseUri(url); + if (!parsed.local) throw new Error('resolveLocal: not local'); + + switch (parsed.type) { + case 'notes': + return this.notesRepository.findOneByOrFail({ id: parsed.id }) + .then(note => { + if (parsed.rest === 'activity') { + // this refers to the create activity and not the note itself + return this.apRendererService.renderActivity(this.apRendererService.renderCreate(this.apRendererService.renderNote(note), note)); + } else { + return this.apRendererService.renderNote(note); + } + }); + case 'users': + return this.usersRepository.findOneByOrFail({ id: parsed.id }) + .then(user => this.apRendererService.renderPerson(user as ILocalUser)); + case 'questions': + // Polls are indexed by the note they are attached to. + return Promise.all([ + this.notesRepository.findOneByOrFail({ id: parsed.id }), + this.pollsRepository.findOneByOrFail({ noteId: parsed.id }), + ]) + .then(([note, poll]) => this.apRendererService.renderQuestion({ id: note.userId }, note, poll)); + case 'likes': + return this.noteReactionsRepository.findOneByOrFail({ id: parsed.id }).then(reaction => + this.apRendererService.renderActivity(this.apRendererService.renderLike(reaction, { uri: null }))!); + case 'follows': + // rest should be + if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI'); + + return Promise.all( + [parsed.id, parsed.rest].map(id => this.usersRepository.findOneByOrFail({ id })), + ) + .then(([follower, followee]) => this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee, url))); + default: + throw new Error(`resolveLocal: type ${parsed.type} unhandled`); + } + } +} diff --git a/packages/backend/src/core/activitypub/LdSignatureService.ts b/packages/backend/src/core/activitypub/LdSignatureService.ts new file mode 100644 index 0000000000..ea39f15b2b --- /dev/null +++ b/packages/backend/src/core/activitypub/LdSignatureService.ts @@ -0,0 +1,147 @@ +import * as crypto from 'node:crypto'; +import { Inject, Injectable } from '@nestjs/common'; +import fetch from 'node-fetch'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { CONTEXTS } from './misc/contexts.js'; + +// RsaSignature2017 based from https://github.com/transmute-industries/RsaSignature2017 + +@Injectable() +export class LdSignatureService { + constructor( + private httpRequestService: HttpRequestService, + ) { + } + + public use(): LdSignature { + return new LdSignature(this.httpRequestService); + } +} + +class LdSignature { + public debug = false; + public preLoad = true; + public loderTimeout = 10 * 1000; + + constructor( + private httpRequestService: HttpRequestService, + ) { + } + + public async signRsaSignature2017(data: any, privateKey: string, creator: string, domain?: string, created?: Date): Promise { + const options = { + type: 'RsaSignature2017', + creator, + domain, + nonce: crypto.randomBytes(16).toString('hex'), + created: (created ?? new Date()).toISOString(), + } as { + type: string; + creator: string; + domain?: string; + nonce: string; + created: string; + }; + + if (!domain) { + delete options.domain; + } + + const toBeSigned = await this.createVerifyData(data, options); + + const signer = crypto.createSign('sha256'); + signer.update(toBeSigned); + signer.end(); + + const signature = signer.sign(privateKey); + + return { + ...data, + signature: { + ...options, + signatureValue: signature.toString('base64'), + }, + }; + } + + public async verifyRsaSignature2017(data: any, publicKey: string): Promise { + const toBeSigned = await this.createVerifyData(data, data.signature); + const verifier = crypto.createVerify('sha256'); + verifier.update(toBeSigned); + return verifier.verify(publicKey, data.signature.signatureValue, 'base64'); + } + + public async createVerifyData(data: any, options: any) { + const transformedOptions = { + ...options, + '@context': 'https://w3id.org/identity/v1', + }; + delete transformedOptions['type']; + delete transformedOptions['id']; + delete transformedOptions['signatureValue']; + const canonizedOptions = await this.normalize(transformedOptions); + const optionsHash = this.sha256(canonizedOptions.toString()); + const transformedData = { ...data }; + delete transformedData['signature']; + const cannonidedData = await this.normalize(transformedData); + if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`); + const documentHash = this.sha256(cannonidedData.toString()); + const verifyData = `${optionsHash}${documentHash}`; + return verifyData; + } + + public async normalize(data: any) { + const customLoader = this.getLoader(); + return 42; + } + + private getLoader() { + return async (url: string): Promise => { + if (!url.match('^https?\:\/\/')) throw `Invalid URL ${url}`; + + if (this.preLoad) { + if (url in CONTEXTS) { + if (this.debug) console.debug(`HIT: ${url}`); + return { + contextUrl: null, + document: CONTEXTS[url], + documentUrl: url, + }; + } + } + + if (this.debug) console.debug(`MISS: ${url}`); + const document = await this.fetchDocument(url); + return { + contextUrl: null, + document: document, + documentUrl: url, + }; + }; + } + + private async fetchDocument(url: string) { + const json = await fetch(url, { + headers: { + Accept: 'application/ld+json, application/json', + }, + // TODO + //timeout: this.loderTimeout, + agent: u => u.protocol === 'http:' ? this.httpRequestService.httpAgent : this.httpRequestService.httpsAgent, + }).then(res => { + if (!res.ok) { + throw `${res.status} ${res.statusText}`; + } else { + return res.json(); + } + }); + + return json; + } + + public sha256(data: string): string { + const hash = crypto.createHash('sha256'); + hash.update(data); + return hash.digest('hex'); + } +} diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts new file mode 100644 index 0000000000..aee0d3629c --- /dev/null +++ b/packages/backend/src/core/activitypub/misc/contexts.ts @@ -0,0 +1,526 @@ +/* eslint:disable:quotemark indent */ +const id_v1 = { + '@context': { + 'id': '@id', + 'type': '@type', + + 'cred': 'https://w3id.org/credentials#', + 'dc': 'http://purl.org/dc/terms/', + 'identity': 'https://w3id.org/identity#', + 'perm': 'https://w3id.org/permissions#', + 'ps': 'https://w3id.org/payswarm#', + 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#', + 'sec': 'https://w3id.org/security#', + 'schema': 'http://schema.org/', + 'xsd': 'http://www.w3.org/2001/XMLSchema#', + + 'Group': 'https://www.w3.org/ns/activitystreams#Group', + + 'claim': { '@id': 'cred:claim', '@type': '@id' }, + 'credential': { '@id': 'cred:credential', '@type': '@id' }, + 'issued': { '@id': 'cred:issued', '@type': 'xsd:dateTime' }, + 'issuer': { '@id': 'cred:issuer', '@type': '@id' }, + 'recipient': { '@id': 'cred:recipient', '@type': '@id' }, + 'Credential': 'cred:Credential', + 'CryptographicKeyCredential': 'cred:CryptographicKeyCredential', + + 'about': { '@id': 'schema:about', '@type': '@id' }, + 'address': { '@id': 'schema:address', '@type': '@id' }, + 'addressCountry': 'schema:addressCountry', + 'addressLocality': 'schema:addressLocality', + 'addressRegion': 'schema:addressRegion', + 'comment': 'rdfs:comment', + 'created': { '@id': 'dc:created', '@type': 'xsd:dateTime' }, + 'creator': { '@id': 'dc:creator', '@type': '@id' }, + 'description': 'schema:description', + 'email': 'schema:email', + 'familyName': 'schema:familyName', + 'givenName': 'schema:givenName', + 'image': { '@id': 'schema:image', '@type': '@id' }, + 'label': 'rdfs:label', + 'name': 'schema:name', + 'postalCode': 'schema:postalCode', + 'streetAddress': 'schema:streetAddress', + 'title': 'dc:title', + 'url': { '@id': 'schema:url', '@type': '@id' }, + 'Person': 'schema:Person', + 'PostalAddress': 'schema:PostalAddress', + 'Organization': 'schema:Organization', + + 'identityService': { '@id': 'identity:identityService', '@type': '@id' }, + 'idp': { '@id': 'identity:idp', '@type': '@id' }, + 'Identity': 'identity:Identity', + + 'paymentProcessor': 'ps:processor', + 'preferences': { '@id': 'ps:preferences', '@type': '@vocab' }, + + 'cipherAlgorithm': 'sec:cipherAlgorithm', + 'cipherData': 'sec:cipherData', + 'cipherKey': 'sec:cipherKey', + 'digestAlgorithm': 'sec:digestAlgorithm', + 'digestValue': 'sec:digestValue', + 'domain': 'sec:domain', + 'expires': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, + 'initializationVector': 'sec:initializationVector', + 'member': { '@id': 'schema:member', '@type': '@id' }, + 'memberOf': { '@id': 'schema:memberOf', '@type': '@id' }, + 'nonce': 'sec:nonce', + 'normalizationAlgorithm': 'sec:normalizationAlgorithm', + 'owner': { '@id': 'sec:owner', '@type': '@id' }, + 'password': 'sec:password', + 'privateKey': { '@id': 'sec:privateKey', '@type': '@id' }, + 'privateKeyPem': 'sec:privateKeyPem', + 'publicKey': { '@id': 'sec:publicKey', '@type': '@id' }, + 'publicKeyPem': 'sec:publicKeyPem', + 'publicKeyService': { '@id': 'sec:publicKeyService', '@type': '@id' }, + 'revoked': { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, + 'signature': 'sec:signature', + 'signatureAlgorithm': 'sec:signatureAlgorithm', + 'signatureValue': 'sec:signatureValue', + 'CryptographicKey': 'sec:Key', + 'EncryptedMessage': 'sec:EncryptedMessage', + 'GraphSignature2012': 'sec:GraphSignature2012', + 'LinkedDataSignature2015': 'sec:LinkedDataSignature2015', + + 'accessControl': { '@id': 'perm:accessControl', '@type': '@id' }, + 'writePermission': { '@id': 'perm:writePermission', '@type': '@id' }, + }, +}; + +const security_v1 = { + '@context': { + 'id': '@id', + 'type': '@type', + + 'dc': 'http://purl.org/dc/terms/', + 'sec': 'https://w3id.org/security#', + 'xsd': 'http://www.w3.org/2001/XMLSchema#', + + 'EcdsaKoblitzSignature2016': 'sec:EcdsaKoblitzSignature2016', + 'Ed25519Signature2018': 'sec:Ed25519Signature2018', + 'EncryptedMessage': 'sec:EncryptedMessage', + 'GraphSignature2012': 'sec:GraphSignature2012', + 'LinkedDataSignature2015': 'sec:LinkedDataSignature2015', + 'LinkedDataSignature2016': 'sec:LinkedDataSignature2016', + 'CryptographicKey': 'sec:Key', + + 'authenticationTag': 'sec:authenticationTag', + 'canonicalizationAlgorithm': 'sec:canonicalizationAlgorithm', + 'cipherAlgorithm': 'sec:cipherAlgorithm', + 'cipherData': 'sec:cipherData', + 'cipherKey': 'sec:cipherKey', + 'created': { '@id': 'dc:created', '@type': 'xsd:dateTime' }, + 'creator': { '@id': 'dc:creator', '@type': '@id' }, + 'digestAlgorithm': 'sec:digestAlgorithm', + 'digestValue': 'sec:digestValue', + 'domain': 'sec:domain', + 'encryptionKey': 'sec:encryptionKey', + 'expiration': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, + 'expires': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, + 'initializationVector': 'sec:initializationVector', + 'iterationCount': 'sec:iterationCount', + 'nonce': 'sec:nonce', + 'normalizationAlgorithm': 'sec:normalizationAlgorithm', + 'owner': { '@id': 'sec:owner', '@type': '@id' }, + 'password': 'sec:password', + 'privateKey': { '@id': 'sec:privateKey', '@type': '@id' }, + 'privateKeyPem': 'sec:privateKeyPem', + 'publicKey': { '@id': 'sec:publicKey', '@type': '@id' }, + 'publicKeyBase58': 'sec:publicKeyBase58', + 'publicKeyPem': 'sec:publicKeyPem', + 'publicKeyWif': 'sec:publicKeyWif', + 'publicKeyService': { '@id': 'sec:publicKeyService', '@type': '@id' }, + 'revoked': { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, + 'salt': 'sec:salt', + 'signature': 'sec:signature', + 'signatureAlgorithm': 'sec:signingAlgorithm', + 'signatureValue': 'sec:signatureValue', + }, +}; + +const activitystreams = { + '@context': { + '@vocab': '_:', + 'xsd': 'http://www.w3.org/2001/XMLSchema#', + 'as': 'https://www.w3.org/ns/activitystreams#', + 'ldp': 'http://www.w3.org/ns/ldp#', + 'vcard': 'http://www.w3.org/2006/vcard/ns#', + 'id': '@id', + 'type': '@type', + 'Accept': 'as:Accept', + 'Activity': 'as:Activity', + 'IntransitiveActivity': 'as:IntransitiveActivity', + 'Add': 'as:Add', + 'Announce': 'as:Announce', + 'Application': 'as:Application', + 'Arrive': 'as:Arrive', + 'Article': 'as:Article', + 'Audio': 'as:Audio', + 'Block': 'as:Block', + 'Collection': 'as:Collection', + 'CollectionPage': 'as:CollectionPage', + 'Relationship': 'as:Relationship', + 'Create': 'as:Create', + 'Delete': 'as:Delete', + 'Dislike': 'as:Dislike', + 'Document': 'as:Document', + 'Event': 'as:Event', + 'Follow': 'as:Follow', + 'Flag': 'as:Flag', + 'Group': 'as:Group', + 'Ignore': 'as:Ignore', + 'Image': 'as:Image', + 'Invite': 'as:Invite', + 'Join': 'as:Join', + 'Leave': 'as:Leave', + 'Like': 'as:Like', + 'Link': 'as:Link', + 'Mention': 'as:Mention', + 'Note': 'as:Note', + 'Object': 'as:Object', + 'Offer': 'as:Offer', + 'OrderedCollection': 'as:OrderedCollection', + 'OrderedCollectionPage': 'as:OrderedCollectionPage', + 'Organization': 'as:Organization', + 'Page': 'as:Page', + 'Person': 'as:Person', + 'Place': 'as:Place', + 'Profile': 'as:Profile', + 'Question': 'as:Question', + 'Reject': 'as:Reject', + 'Remove': 'as:Remove', + 'Service': 'as:Service', + 'TentativeAccept': 'as:TentativeAccept', + 'TentativeReject': 'as:TentativeReject', + 'Tombstone': 'as:Tombstone', + 'Undo': 'as:Undo', + 'Update': 'as:Update', + 'Video': 'as:Video', + 'View': 'as:View', + 'Listen': 'as:Listen', + 'Read': 'as:Read', + 'Move': 'as:Move', + 'Travel': 'as:Travel', + 'IsFollowing': 'as:IsFollowing', + 'IsFollowedBy': 'as:IsFollowedBy', + 'IsContact': 'as:IsContact', + 'IsMember': 'as:IsMember', + 'subject': { + '@id': 'as:subject', + '@type': '@id', + }, + 'relationship': { + '@id': 'as:relationship', + '@type': '@id', + }, + 'actor': { + '@id': 'as:actor', + '@type': '@id', + }, + 'attributedTo': { + '@id': 'as:attributedTo', + '@type': '@id', + }, + 'attachment': { + '@id': 'as:attachment', + '@type': '@id', + }, + 'bcc': { + '@id': 'as:bcc', + '@type': '@id', + }, + 'bto': { + '@id': 'as:bto', + '@type': '@id', + }, + 'cc': { + '@id': 'as:cc', + '@type': '@id', + }, + 'context': { + '@id': 'as:context', + '@type': '@id', + }, + 'current': { + '@id': 'as:current', + '@type': '@id', + }, + 'first': { + '@id': 'as:first', + '@type': '@id', + }, + 'generator': { + '@id': 'as:generator', + '@type': '@id', + }, + 'icon': { + '@id': 'as:icon', + '@type': '@id', + }, + 'image': { + '@id': 'as:image', + '@type': '@id', + }, + 'inReplyTo': { + '@id': 'as:inReplyTo', + '@type': '@id', + }, + 'items': { + '@id': 'as:items', + '@type': '@id', + }, + 'instrument': { + '@id': 'as:instrument', + '@type': '@id', + }, + 'orderedItems': { + '@id': 'as:items', + '@type': '@id', + '@container': '@list', + }, + 'last': { + '@id': 'as:last', + '@type': '@id', + }, + 'location': { + '@id': 'as:location', + '@type': '@id', + }, + 'next': { + '@id': 'as:next', + '@type': '@id', + }, + 'object': { + '@id': 'as:object', + '@type': '@id', + }, + 'oneOf': { + '@id': 'as:oneOf', + '@type': '@id', + }, + 'anyOf': { + '@id': 'as:anyOf', + '@type': '@id', + }, + 'closed': { + '@id': 'as:closed', + '@type': 'xsd:dateTime', + }, + 'origin': { + '@id': 'as:origin', + '@type': '@id', + }, + 'accuracy': { + '@id': 'as:accuracy', + '@type': 'xsd:float', + }, + 'prev': { + '@id': 'as:prev', + '@type': '@id', + }, + 'preview': { + '@id': 'as:preview', + '@type': '@id', + }, + 'replies': { + '@id': 'as:replies', + '@type': '@id', + }, + 'result': { + '@id': 'as:result', + '@type': '@id', + }, + 'audience': { + '@id': 'as:audience', + '@type': '@id', + }, + 'partOf': { + '@id': 'as:partOf', + '@type': '@id', + }, + 'tag': { + '@id': 'as:tag', + '@type': '@id', + }, + 'target': { + '@id': 'as:target', + '@type': '@id', + }, + 'to': { + '@id': 'as:to', + '@type': '@id', + }, + 'url': { + '@id': 'as:url', + '@type': '@id', + }, + 'altitude': { + '@id': 'as:altitude', + '@type': 'xsd:float', + }, + 'content': 'as:content', + 'contentMap': { + '@id': 'as:content', + '@container': '@language', + }, + 'name': 'as:name', + 'nameMap': { + '@id': 'as:name', + '@container': '@language', + }, + 'duration': { + '@id': 'as:duration', + '@type': 'xsd:duration', + }, + 'endTime': { + '@id': 'as:endTime', + '@type': 'xsd:dateTime', + }, + 'height': { + '@id': 'as:height', + '@type': 'xsd:nonNegativeInteger', + }, + 'href': { + '@id': 'as:href', + '@type': '@id', + }, + 'hreflang': 'as:hreflang', + 'latitude': { + '@id': 'as:latitude', + '@type': 'xsd:float', + }, + 'longitude': { + '@id': 'as:longitude', + '@type': 'xsd:float', + }, + 'mediaType': 'as:mediaType', + 'published': { + '@id': 'as:published', + '@type': 'xsd:dateTime', + }, + 'radius': { + '@id': 'as:radius', + '@type': 'xsd:float', + }, + 'rel': 'as:rel', + 'startIndex': { + '@id': 'as:startIndex', + '@type': 'xsd:nonNegativeInteger', + }, + 'startTime': { + '@id': 'as:startTime', + '@type': 'xsd:dateTime', + }, + 'summary': 'as:summary', + 'summaryMap': { + '@id': 'as:summary', + '@container': '@language', + }, + 'totalItems': { + '@id': 'as:totalItems', + '@type': 'xsd:nonNegativeInteger', + }, + 'units': 'as:units', + 'updated': { + '@id': 'as:updated', + '@type': 'xsd:dateTime', + }, + 'width': { + '@id': 'as:width', + '@type': 'xsd:nonNegativeInteger', + }, + 'describes': { + '@id': 'as:describes', + '@type': '@id', + }, + 'formerType': { + '@id': 'as:formerType', + '@type': '@id', + }, + 'deleted': { + '@id': 'as:deleted', + '@type': 'xsd:dateTime', + }, + 'inbox': { + '@id': 'ldp:inbox', + '@type': '@id', + }, + 'outbox': { + '@id': 'as:outbox', + '@type': '@id', + }, + 'following': { + '@id': 'as:following', + '@type': '@id', + }, + 'followers': { + '@id': 'as:followers', + '@type': '@id', + }, + 'streams': { + '@id': 'as:streams', + '@type': '@id', + }, + 'preferredUsername': 'as:preferredUsername', + 'endpoints': { + '@id': 'as:endpoints', + '@type': '@id', + }, + 'uploadMedia': { + '@id': 'as:uploadMedia', + '@type': '@id', + }, + 'proxyUrl': { + '@id': 'as:proxyUrl', + '@type': '@id', + }, + 'liked': { + '@id': 'as:liked', + '@type': '@id', + }, + 'oauthAuthorizationEndpoint': { + '@id': 'as:oauthAuthorizationEndpoint', + '@type': '@id', + }, + 'oauthTokenEndpoint': { + '@id': 'as:oauthTokenEndpoint', + '@type': '@id', + }, + 'provideClientKey': { + '@id': 'as:provideClientKey', + '@type': '@id', + }, + 'signClientKey': { + '@id': 'as:signClientKey', + '@type': '@id', + }, + 'sharedInbox': { + '@id': 'as:sharedInbox', + '@type': '@id', + }, + 'Public': { + '@id': 'as:Public', + '@type': '@id', + }, + 'source': 'as:source', + 'likes': { + '@id': 'as:likes', + '@type': '@id', + }, + 'shares': { + '@id': 'as:shares', + '@type': '@id', + }, + 'alsoKnownAs': { + '@id': 'as:alsoKnownAs', + '@type': '@id', + }, + }, +}; + +export const CONTEXTS: Record = { + 'https://w3id.org/identity/v1': id_v1, + 'https://w3id.org/security/v1': security_v1, + 'https://www.w3.org/ns/activitystreams': activitystreams, +}; diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts new file mode 100644 index 0000000000..9bf87f19d4 --- /dev/null +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -0,0 +1,90 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { CacheableRemoteUser } from '@/models/entities/User.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import { MetaService } from '@/core/MetaService.js'; +import { truncate } from '@/misc/truncate.js'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; +import { DriveService } from '@/core/DriveService.js'; +import type Logger from '@/logger.js'; +import { ApResolverService } from '../ApResolverService.js'; +import { ApLoggerService } from '../ApLoggerService.js'; + +@Injectable() +export class ApImageService { + private logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private metaService: MetaService, + private apResolverService: ApResolverService, + private driveService: DriveService, + private apLoggerService: ApLoggerService, + ) { + this.logger = this.apLoggerService.logger; + } + + /** + * Imageを作成します。 + */ + public async createImage(actor: CacheableRemoteUser, value: any): Promise { + // 投稿者が凍結されていたらスキップ + if (actor.isSuspended) { + throw new Error('actor has been suspended'); + } + + const image = await this.apResolverService.createResolver().resolve(value) as any; + + if (image.url == null) { + throw new Error('invalid image: url not privided'); + } + + this.logger.info(`Creating the Image: ${image.url}`); + + const instance = await this.metaService.fetch(); + + let file = await this.driveService.uploadFromUrl({ + url: image.url, + user: actor, + uri: image.url, + sensitive: image.sensitive, + isLink: !instance.cacheRemoteFiles, + comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH), + }); + + if (file.isLink) { + // URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、 + // URLを更新する + if (file.url !== image.url) { + await this.driveFilesRepository.update({ id: file.id }, { + url: image.url, + uri: image.url, + }); + + file = await this.driveFilesRepository.findOneByOrFail({ id: file.id }); + } + } + + return file; + } + + /** + * Imageを解決します。 + * + * Misskeyに対象のImageが登録されていればそれを返し、そうでなければ + * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 + */ + public async resolveImage(actor: CacheableRemoteUser, value: any): Promise { + // TODO + + // リモートサーバーからフェッチしてきて登録 + return await this.createImage(actor, value); + } +} diff --git a/packages/backend/src/core/activitypub/models/ApMentionService.ts b/packages/backend/src/core/activitypub/models/ApMentionService.ts new file mode 100644 index 0000000000..1275e24c62 --- /dev/null +++ b/packages/backend/src/core/activitypub/models/ApMentionService.ts @@ -0,0 +1,39 @@ +import { Inject, Injectable } from '@nestjs/common'; +import promiseLimit from 'promise-limit'; +import { DI } from '@/di-symbols.js'; +import type { UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import { toArray, unique } from '@/misc/prelude/array.js'; +import type { CacheableUser } from '@/models/entities/User.js'; +import { isMention } from '../type.js'; +import { ApResolverService, Resolver } from '../ApResolverService.js'; +import { ApPersonService } from './ApPersonService.js'; +import type { IObject, IApMention } from '../type.js'; + +@Injectable() +export class ApMentionService { + constructor( + @Inject(DI.config) + private config: Config, + + private apResolverService: ApResolverService, + private apPersonService: ApPersonService, + ) { + } + + public async extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) { + const hrefs = unique(this.extractApMentionObjects(tags).map(x => x.href as string)); + + const limit = promiseLimit(2); + const mentionedUsers = (await Promise.all( + hrefs.map(x => limit(() => this.apPersonService.resolvePerson(x, resolver).catch(() => null))), + )).filter((x): x is CacheableUser => x != null); + + return mentionedUsers; + } + + public extractApMentionObjects(tags: IObject | IObject[] | null | undefined): IApMention[] { + if (tags == null) return []; + return toArray(tags).filter(isMention); + } +} diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts new file mode 100644 index 0000000000..7cf6725a38 --- /dev/null +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -0,0 +1,403 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import promiseLimit from 'promise-limit'; +import { DI } from '@/di-symbols.js'; +import type { MessagingMessagesRepository, PollsRepository, EmojisRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { CacheableRemoteUser } from '@/models/entities/User.js'; +import type { Note } from '@/models/entities/Note.js'; +import { toArray, toSingle, unique } from '@/misc/prelude/array.js'; +import type { Emoji } from '@/models/entities/Emoji.js'; +import { MetaService } from '@/core/MetaService.js'; +import { AppLockService } from '@/core/AppLockService.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import { NoteCreateService } from '@/core/NoteCreateService.js'; +import type Logger from '@/logger.js'; +import { IdService } from '@/core/IdService.js'; +import { PollService } from '@/core/PollService.js'; +import { StatusError } from '@/misc/status-error.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { MessagingService } from '@/core/MessagingService.js'; +import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js'; +// eslint-disable-next-line @typescript-eslint/consistent-type-imports +import { ApLoggerService } from '../ApLoggerService.js'; +import { ApMfmService } from '../ApMfmService.js'; +import { ApDbResolverService } from '../ApDbResolverService.js'; +import { ApResolverService } from '../ApResolverService.js'; +import { ApAudienceService } from '../ApAudienceService.js'; +import { ApPersonService } from './ApPersonService.js'; +import { extractApHashtags } from './tag.js'; +import { ApMentionService } from './ApMentionService.js'; +import { ApQuestionService } from './ApQuestionService.js'; +import { ApImageService } from './ApImageService.js'; +import type { Resolver } from '../ApResolverService.js'; +import type { IObject, IPost } from '../type.js'; + +@Injectable() +export class ApNoteService { + private logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + private idService: IdService, + private apMfmService: ApMfmService, + private apResolverService: ApResolverService, + + // 循環参照のため / for circular dependency + @Inject(forwardRef(() => ApPersonService)) + private apPersonService: ApPersonService, + + private utilityService: UtilityService, + private apAudienceService: ApAudienceService, + private apMentionService: ApMentionService, + private apImageService: ApImageService, + private apQuestionService: ApQuestionService, + private metaService: MetaService, + private messagingService: MessagingService, + private appLockService: AppLockService, + private pollService: PollService, + private noteCreateService: NoteCreateService, + private apDbResolverService: ApDbResolverService, + private apLoggerService: ApLoggerService, + ) { + this.logger = this.apLoggerService.logger; + } + + public validateNote(object: any, uri: string) { + const expectHost = this.utilityService.extractDbHost(uri); + + if (object == null) { + return new Error('invalid Note: object is null'); + } + + if (!validPost.includes(getApType(object))) { + return new Error(`invalid Note: invalid object type ${getApType(object)}`); + } + + if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) { + return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`); + } + + if (object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo)) !== expectHost) { + return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.attributedTo)}`); + } + + return null; + } + + /** + * Noteをフェッチします。 + * + * Misskeyに対象のNoteが登録されていればそれを返します。 + */ + public async fetchNote(object: string | IObject): Promise { + return await this.apDbResolverService.getNoteFromApId(object); + } + + /** + * Noteを作成します。 + */ + public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise { + if (resolver == null) resolver = this.apResolverService.createResolver(); + + const object: any = await resolver.resolve(value); + + const entryUri = getApId(value); + const err = this.validateNote(object, entryUri); + if (err) { + this.logger.error(`${err.message}`, { + resolver: { + history: resolver.getHistory(), + }, + value: value, + object: object, + }); + throw new Error('invalid note'); + } + + const note: IPost = object; + + this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); + + this.logger.info(`Creating the Note: ${note.id}`); + + // 投稿者をフェッチ + const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser; + + // 投稿者が凍結されていたらスキップ + if (actor.isSuspended) { + throw new Error('actor has been suspended'); + } + + const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver); + let visibility = noteAudience.visibility; + const visibleUsers = noteAudience.visibleUsers; + + // Audience (to, cc) が指定されてなかった場合 + if (visibility === 'specified' && visibleUsers.length === 0) { + if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している + // こちらから匿名GET出来たものならばpublic + visibility = 'public'; + } + } + + let isMessaging = note._misskey_talk && visibility === 'specified'; + + const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver); + const apHashtags = await extractApHashtags(note.tag); + + // 添付ファイル + // TODO: attachmentは必ずしもImageではない + // TODO: attachmentは必ずしも配列ではない + // Noteがsensitiveなら添付もsensitiveにする + const limit = promiseLimit(2); + + note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : []; + const files = note.attachment + .map(attach => attach.sensitive = note.sensitive) + ? (await Promise.all(note.attachment.map(x => limit(() => this.apImageService.resolveImage(actor, x)) as Promise))) + .filter(image => image != null) + : []; + + // リプライ + const reply: Note | null = note.inReplyTo + ? await this.resolveNote(note.inReplyTo, resolver).then(x => { + if (x == null) { + this.logger.warn('Specified inReplyTo, but nout found'); + throw new Error('inReplyTo not found'); + } else { + return x; + } + }).catch(async err => { + // トークだったらinReplyToのエラーは無視 + const uri = getApId(note.inReplyTo); + if (uri.startsWith(this.config.url + '/')) { + const id = uri.split('/').pop(); + const talk = await this.messagingMessagesRepository.findOneBy({ id }); + if (talk) { + isMessaging = true; + return null; + } + } + + this.logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`); + throw err; + }) + : null; + + // 引用 + let quote: Note | undefined | null; + + if (note._misskey_quote || note.quoteUrl) { + const tryResolveNote = async (uri: string): Promise<{ + status: 'ok'; + res: Note | null; + } | { + status: 'permerror' | 'temperror'; + }> => { + if (typeof uri !== 'string' || !uri.match(/^https?:/)) return { status: 'permerror' }; + try { + const res = await this.resolveNote(uri); + if (res) { + return { + status: 'ok', + res, + }; + } else { + return { + status: 'permerror', + }; + } + } catch (e) { + return { + status: (e instanceof StatusError && e.isClientError) ? 'permerror' : 'temperror', + }; + } + }; + + const uris = unique([note._misskey_quote, note.quoteUrl].filter((x): x is string => typeof x === 'string')); + const results = await Promise.all(uris.map(uri => tryResolveNote(uri))); + + quote = results.filter((x): x is { status: 'ok', res: Note | null } => x.status === 'ok').map(x => x.res).find(x => x); + if (!quote) { + if (results.some(x => x.status === 'temperror')) { + throw 'quote resolve failed'; + } + } + } + + const cw = note.summary === '' ? null : note.summary; + + // テキストのパース + let text: string | null = null; + if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') { + text = note.source.content; + } else if (typeof note._misskey_content !== 'undefined') { + text = note._misskey_content; + } else if (typeof note.content === 'string') { + text = this.apMfmService.htmlToMfm(note.content, note.tag); + } + + // vote + if (reply && reply.hasPoll) { + const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); + + const tryCreateVote = async (name: string, index: number): Promise => { + if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { + this.logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); + } else if (index >= 0) { + this.logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); + await this.pollService.vote(actor, reply, index); + + // リモートフォロワーにUpdate配信 + this.pollService.deliverQuestionUpdate(reply.id); + } + return null; + }; + + if (note.name) { + return await tryCreateVote(note.name, poll.choices.findIndex(x => x === note.name)); + } + } + + const emojis = await this.extractEmojis(note.tag ?? [], actor.host).catch(e => { + this.logger.info(`extractEmojis: ${e}`); + return [] as Emoji[]; + }); + + const apEmojis = emojis.map(emoji => emoji.name); + + const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined); + + if (isMessaging) { + for (const recipient of visibleUsers) { + await this.messagingService.createMessage(actor, recipient, undefined, text ?? undefined, (files && files.length > 0) ? files[0] : null, object.id); + return null; + } + } + + return await this.noteCreateService.create(actor, { + createdAt: note.published ? new Date(note.published) : null, + files, + reply, + renote: quote, + name: note.name, + cw, + text, + localOnly: false, + visibility, + visibleUsers, + apMentions, + apHashtags, + apEmojis, + poll, + uri: note.id, + url: getOneApHrefNullable(note.url), + }, silent); + } + + /** + * Noteを解決します。 + * + * Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ + * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 + */ + public async resolveNote(value: string | IObject, resolver?: Resolver): Promise { + const uri = typeof value === 'string' ? value : value.id; + if (uri == null) throw new Error('missing uri'); + + // ブロックしてたら中断 + const meta = await this.metaService.fetch(); + if (meta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) throw { statusCode: 451 }; + + const unlock = await this.appLockService.getApLock(uri); + + try { + //#region このサーバーに既に登録されていたらそれを返す + const exist = await this.fetchNote(uri); + + if (exist) { + return exist; + } + //#endregion + + if (uri.startsWith(this.config.url)) { + throw new StatusError('cannot resolve local note', 400, 'cannot resolve local note'); + } + + // リモートサーバーからフェッチしてきて登録 + // ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが + // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 + return await this.createNote(uri, resolver, true); + } finally { + unlock(); + } + } + + public async extractEmojis(tags: IObject | IObject[], host: string): Promise { + host = this.utilityService.toPuny(host); + + if (!tags) return []; + + const eomjiTags = toArray(tags).filter(isEmoji); + + return await Promise.all(eomjiTags.map(async tag => { + const name = tag.name!.replace(/^:/, '').replace(/:$/, ''); + tag.icon = toSingle(tag.icon); + + const exists = await this.emojisRepository.findOneBy({ + host, + name, + }); + + 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) + || (tag.icon!.url !== exists.originalUrl) + ) { + await this.emojisRepository.update({ + host, + name, + }, { + uri: tag.id, + originalUrl: tag.icon!.url, + publicUrl: tag.icon!.url, + updatedAt: new Date(), + }); + + return await this.emojisRepository.findOneBy({ + host, + name, + }) as Emoji; + } + + return exists; + } + + this.logger.info(`register emoji host=${host}, name=${name}`); + + return await this.emojisRepository.insert({ + id: this.idService.genId(), + host, + name, + uri: tag.id, + originalUrl: tag.icon!.url, + publicUrl: tag.icon!.url, + updatedAt: new Date(), + aliases: [], + } as Partial).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0])); + })); + } +} diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts new file mode 100644 index 0000000000..f9d6f42ef6 --- /dev/null +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -0,0 +1,594 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import promiseLimit from 'promise-limit'; +import { DataSource } from 'typeorm'; +import { ModuleRef } from '@nestjs/core'; +import { DI } from '@/di-symbols.js'; +import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { CacheableUser, IRemoteUser } from '@/models/entities/User.js'; +import { User } from '@/models/entities/User.js'; +import { truncate } from '@/misc/truncate.js'; +import type { UserCacheService } from '@/core/UserCacheService.js'; +import { normalizeForSearch } from '@/misc/normalize-for-search.js'; +import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; +import type Logger from '@/logger.js'; +import type { Note } from '@/models/entities/Note.js'; +import type { IdService } from '@/core/IdService.js'; +import type { MfmService } from '@/core/MfmService.js'; +import type { Emoji } from '@/models/entities/Emoji.js'; +import { toArray } from '@/misc/prelude/array.js'; +import type { GlobalEventService } from '@/core/GlobalEventService.js'; +import type { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; +import type { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; +import { UserProfile } from '@/models/entities/UserProfile.js'; +import { UserPublickey } from '@/models/entities/UserPublickey.js'; +import type UsersChart from '@/core/chart/charts/users.js'; +import type InstanceChart from '@/core/chart/charts/instance.js'; +import type { HashtagService } from '@/core/HashtagService.js'; +import { UserNotePining } from '@/models/entities/UserNotePining.js'; +import { StatusError } from '@/misc/status-error.js'; +import type { UtilityService } from '@/core/UtilityService.js'; +import type { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js'; +import { extractApHashtags } from './tag.js'; +import type { OnModuleInit } from '@nestjs/common'; +import type { ApNoteService } from './ApNoteService.js'; +import type { ApMfmService } from '../ApMfmService.js'; +import type { ApResolverService, Resolver } from '../ApResolverService.js'; +import type { ApLoggerService } from '../ApLoggerService.js'; +// eslint-disable-next-line @typescript-eslint/consistent-type-imports +import type { ApImageService } from './ApImageService.js'; +import type { IActor, IObject, IApPropertyValue } from '../type.js'; + +const nameLength = 128; +const summaryLength = 2048; + +const services: { + [x: string]: (id: string, username: string) => any +} = { + 'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }), + 'misskey:authentication:github': (id, login) => ({ id, login }), + 'misskey:authentication:discord': (id, name) => $discord(id, name), +}; + +const $discord = (id: string, name: string) => { + if (typeof name !== 'string') { + name = 'unknown#0000'; + } + const [username, discriminator] = name.split('#'); + return { id, username, discriminator }; +}; + +function addService(target: { [x: string]: any }, source: IApPropertyValue) { + const service = services[source.name]; + + if (typeof source.value !== 'string') { + source.value = 'unknown'; + } + + const [id, username] = source.value.split('@'); + + if (service) { + target[source.name.split(':')[2]] = service(id, username); + } +} + +@Injectable() +export class ApPersonService implements OnModuleInit { + private utilityService: UtilityService; + private userEntityService: UserEntityService; + private idService: IdService; + private globalEventService: GlobalEventService; + private federatedInstanceService: FederatedInstanceService; + private fetchInstanceMetadataService: FetchInstanceMetadataService; + private userCacheService: UserCacheService; + private apResolverService: ApResolverService; + private apNoteService: ApNoteService; + private apImageService: ApImageService; + private apMfmService: ApMfmService; + private mfmService: MfmService; + private hashtagService: HashtagService; + private usersChart: UsersChart; + private instanceChart: InstanceChart; + private apLoggerService: ApLoggerService; + private logger: Logger; + + constructor( + private moduleRef: ModuleRef, + + @Inject(DI.config) + private config: Config, + + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.userPublickeysRepository) + private userPublickeysRepository: UserPublickeysRepository, + + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + //private utilityService: UtilityService, + //private userEntityService: UserEntityService, + //private idService: IdService, + //private globalEventService: GlobalEventService, + //private federatedInstanceService: FederatedInstanceService, + //private fetchInstanceMetadataService: FetchInstanceMetadataService, + //private userCacheService: UserCacheService, + //private apResolverService: ApResolverService, + //private apNoteService: ApNoteService, + //private apImageService: ApImageService, + //private apMfmService: ApMfmService, + //private mfmService: MfmService, + //private hashtagService: HashtagService, + //private usersChart: UsersChart, + //private instanceChart: InstanceChart, + //private apLoggerService: ApLoggerService, + ) { + } + + onModuleInit() { + this.utilityService = this.moduleRef.get('UtilityService'); + this.userEntityService = this.moduleRef.get('UserEntityService'); + this.idService = this.moduleRef.get('IdService'); + this.globalEventService = this.moduleRef.get('GlobalEventService'); + this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService'); + this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService'); + this.userCacheService = this.moduleRef.get('UserCacheService'); + this.apResolverService = this.moduleRef.get('ApResolverService'); + this.apNoteService = this.moduleRef.get('ApNoteService'); + this.apImageService = this.moduleRef.get('ApImageService'); + this.apMfmService = this.moduleRef.get('ApMfmService'); + this.mfmService = this.moduleRef.get('MfmService'); + this.hashtagService = this.moduleRef.get('HashtagService'); + this.usersChart = this.moduleRef.get('UsersChart'); + this.instanceChart = this.moduleRef.get('InstanceChart'); + this.apLoggerService = this.moduleRef.get('ApLoggerService'); + this.logger = this.apLoggerService.logger; + } + + /** + * Validate and convert to actor object + * @param x Fetched object + * @param uri Fetch target URI + */ + private validateActor(x: IObject, uri: string): IActor { + const expectHost = this.utilityService.toPuny(new URL(uri).hostname); + + if (x == null) { + throw new Error('invalid Actor: object is null'); + } + + if (!isActor(x)) { + throw new Error(`invalid Actor type '${x.type}'`); + } + + if (!(typeof x.id === 'string' && x.id.length > 0)) { + throw new Error('invalid Actor: wrong id'); + } + + if (!(typeof x.inbox === 'string' && x.inbox.length > 0)) { + throw new Error('invalid Actor: wrong inbox'); + } + + if (!(typeof x.preferredUsername === 'string' && x.preferredUsername.length > 0 && x.preferredUsername.length <= 128 && /^\w([\w-.]*\w)?$/.test(x.preferredUsername))) { + throw new Error('invalid Actor: wrong username'); + } + + // These fields are only informational, and some AP software allows these + // fields to be very long. If they are too long, we cut them off. This way + // we can at least see these users and their activities. + if (x.name) { + if (!(typeof x.name === 'string' && x.name.length > 0)) { + throw new Error('invalid Actor: wrong name'); + } + x.name = truncate(x.name, nameLength); + } + if (x.summary) { + if (!(typeof x.summary === 'string' && x.summary.length > 0)) { + throw new Error('invalid Actor: wrong summary'); + } + x.summary = truncate(x.summary, summaryLength); + } + + const idHost = this.utilityService.toPuny(new URL(x.id!).hostname); + if (idHost !== expectHost) { + throw new Error('invalid Actor: id has different host'); + } + + if (x.publicKey) { + if (typeof x.publicKey.id !== 'string') { + throw new Error('invalid Actor: publicKey.id is not a string'); + } + + const publicKeyIdHost = this.utilityService.toPuny(new URL(x.publicKey.id).hostname); + if (publicKeyIdHost !== expectHost) { + throw new Error('invalid Actor: publicKey.id has different host'); + } + } + + return x; + } + + /** + * Personをフェッチします。 + * + * Misskeyに対象のPersonが登録されていればそれを返します。 + */ + public async fetchPerson(uri: string, resolver?: Resolver): Promise { + if (typeof uri !== 'string') throw new Error('uri is not string'); + + const cached = this.userCacheService.uriPersonCache.get(uri); + if (cached) return cached; + + // URIがこのサーバーを指しているならデータベースからフェッチ + if (uri.startsWith(this.config.url + '/')) { + const id = uri.split('/').pop(); + const u = await this.usersRepository.findOneBy({ id }); + if (u) this.userCacheService.uriPersonCache.set(uri, u); + return u; + } + + //#region このサーバーに既に登録されていたらそれを返す + const exist = await this.usersRepository.findOneBy({ uri }); + + if (exist) { + this.userCacheService.uriPersonCache.set(uri, exist); + return exist; + } + //#endregion + + return null; + } + + /** + * Personを作成します。 + */ + public async createPerson(uri: string, resolver?: Resolver): Promise { + if (typeof uri !== 'string') throw new Error('uri is not string'); + + if (uri.startsWith(this.config.url)) { + throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user'); + } + + if (resolver == null) resolver = this.apResolverService.createResolver(); + + const object = await resolver.resolve(uri) as any; + + const person = this.validateActor(object, uri); + + this.logger.info(`Creating the Person: ${person.id}`); + + const host = this.utilityService.toPuny(new URL(object.id).hostname); + + const { fields } = this.analyzeAttachments(person.attachment ?? []); + + const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32); + + const isBot = getApType(object) === 'Service'; + + const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); + + // Create user + let user: IRemoteUser; + try { + // Start transaction + await this.db.transaction(async transactionalEntityManager => { + user = await transactionalEntityManager.save(new User({ + id: this.idService.genId(), + avatarId: null, + bannerId: null, + createdAt: new Date(), + lastFetchedAt: new Date(), + name: truncate(person.name, nameLength), + isLocked: !!person.manuallyApprovesFollowers, + isExplorable: !!person.discoverable, + username: person.preferredUsername, + usernameLower: person.preferredUsername!.toLowerCase(), + host, + inbox: person.inbox, + sharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), + followersUri: person.followers ? getApId(person.followers) : undefined, + featured: person.featured ? getApId(person.featured) : undefined, + uri: person.id, + tags, + isBot, + isCat: (person as any).isCat === true, + showTimelineReplies: false, + })) as IRemoteUser; + + await transactionalEntityManager.save(new UserProfile({ + userId: user.id, + description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, + url: getOneApHrefNullable(person.url), + fields, + birthday: bday ? bday[0] : null, + location: person['vcard:Address'] ?? null, + userHost: host, + })); + + if (person.publicKey) { + await transactionalEntityManager.save(new UserPublickey({ + userId: user.id, + keyId: person.publicKey.id, + keyPem: person.publicKey.publicKeyPem, + })); + } + }); + } catch (e) { + // duplicate key error + if (isDuplicateKeyValueError(e)) { + // /users/@a => /users/:id のように入力がaliasなときにエラーになることがあるのを対応 + const u = await this.usersRepository.findOneBy({ + uri: person.id, + }); + + if (u) { + user = u as IRemoteUser; + } else { + throw new Error('already registered'); + } + } else { + this.logger.error(e instanceof Error ? e : new Error(e as string)); + throw e; + } + } + + // Register host + this.federatedInstanceService.registerOrFetchInstanceDoc(host).then(i => { + this.instancesRepository.increment({ id: i.id }, 'usersCount', 1); + this.instanceChart.newUser(i.host); + this.fetchInstanceMetadataService.fetchInstanceMetadata(i); + }); + + this.usersChart.update(user!, true); + + // ハッシュタグ更新 + this.hashtagService.updateUsertags(user!, tags); + + //#region アバターとヘッダー画像をフェッチ + const [avatar, banner] = await Promise.all([ + person.icon, + person.image, + ].map(img => + img == null + ? Promise.resolve(null) + : this.apImageService.resolveImage(user!, img).catch(() => null), + )); + + const avatarId = avatar ? avatar.id : null; + const bannerId = banner ? banner.id : null; + + await this.usersRepository.update(user!.id, { + avatarId, + bannerId, + }); + + user!.avatarId = avatarId; + user!.bannerId = bannerId; + //#endregion + + //#region カスタム絵文字取得 + const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host).catch(err => { + this.logger.info(`extractEmojis: ${err}`); + return [] as Emoji[]; + }); + + const emojiNames = emojis.map(emoji => emoji.name); + + await this.usersRepository.update(user!.id, { + emojis: emojiNames, + }); + //#endregion + + await this.updateFeatured(user!.id, resolver).catch(err => this.logger.error(err)); + + return user!; + } + + /** + * Personの情報を更新します。 + * Misskeyに対象のPersonが登録されていなければ無視します。 + * @param uri URI of Person + * @param resolver Resolver + * @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します) + */ + public async updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise { + if (typeof uri !== 'string') throw new Error('uri is not string'); + + // URIがこのサーバーを指しているならスキップ + if (uri.startsWith(this.config.url + '/')) { + return; + } + + //#region このサーバーに既に登録されているか + const exist = await this.usersRepository.findOneBy({ uri }) as IRemoteUser; + + if (exist == null) { + return; + } + //#endregion + + if (resolver == null) resolver = this.apResolverService.createResolver(); + + const object = hint ?? await resolver.resolve(uri); + + const person = this.validateActor(object, uri); + + this.logger.info(`Updating the Person: ${person.id}`); + + // アバターとヘッダー画像をフェッチ + const [avatar, banner] = await Promise.all([ + person.icon, + person.image, + ].map(img => + img == null + ? Promise.resolve(null) + : this.apImageService.resolveImage(exist, img).catch(() => null), + )); + + // カスタム絵文字取得 + const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => { + this.logger.info(`extractEmojis: ${e}`); + return [] as Emoji[]; + }); + + const emojiNames = emojis.map(emoji => emoji.name); + + const { fields } = this.analyzeAttachments(person.attachment ?? []); + + const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32); + + const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); + + const updates = { + lastFetchedAt: new Date(), + inbox: person.inbox, + sharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), + followersUri: person.followers ? getApId(person.followers) : undefined, + featured: person.featured, + emojis: emojiNames, + name: truncate(person.name, nameLength), + tags, + isBot: getApType(object) === 'Service', + isCat: (person as any).isCat === true, + isLocked: !!person.manuallyApprovesFollowers, + isExplorable: !!person.discoverable, + } as Partial; + + if (avatar) { + updates.avatarId = avatar.id; + } + + if (banner) { + updates.bannerId = banner.id; + } + + // Update user + await this.usersRepository.update(exist.id, updates); + + if (person.publicKey) { + await this.userPublickeysRepository.update({ userId: exist.id }, { + keyId: person.publicKey.id, + keyPem: person.publicKey.publicKeyPem, + }); + } + + await this.userProfilesRepository.update({ userId: exist.id }, { + url: getOneApHrefNullable(person.url), + fields, + description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, + birthday: bday ? bday[0] : null, + location: person['vcard:Address'] ?? null, + }); + + this.globalEventService.publishInternalEvent('remoteUserUpdated', { id: exist.id }); + + // ハッシュタグ更新 + this.hashtagService.updateUsertags(exist, tags); + + // 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする + await this.followingsRepository.update({ + followerId: exist.id, + }, { + followerSharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), + }); + + await this.updateFeatured(exist.id, resolver).catch(err => this.logger.error(err)); + } + + /** + * Personを解決します。 + * + * Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ + * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 + */ + public async resolvePerson(uri: string, resolver?: Resolver): Promise { + if (typeof uri !== 'string') throw new Error('uri is not string'); + + //#region このサーバーに既に登録されていたらそれを返す + const exist = await this.fetchPerson(uri); + + if (exist) { + return exist; + } + //#endregion + + // リモートサーバーからフェッチしてきて登録 + if (resolver == null) resolver = this.apResolverService.createResolver(); + return await this.createPerson(uri, resolver); + } + + public analyzeAttachments(attachments: IObject | IObject[] | undefined) { + const fields: { + name: string, + value: string + }[] = []; + 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 { + fields.push({ + name: attachment.name, + value: this.mfmService.fromHtml(attachment.value), + }); + } + } + } + + return { fields, services }; + } + + public async updateFeatured(userId: User['id'], resolver?: Resolver) { + const user = await this.usersRepository.findOneByOrFail({ id: userId }); + if (!this.userEntityService.isRemoteUser(user)) return; + if (!user.featured) return; + + this.logger.info(`Updating the featured: ${user.uri}`); + + if (resolver == null) resolver = this.apResolverService.createResolver(); + + // Resolve to (Ordered)Collection Object + const collection = await resolver.resolveCollection(user.featured); + if (!isCollectionOrOrderedCollection(collection)) throw new Error('Object is not Collection or OrderedCollection'); + + // Resolve to Object(may be Note) arrays + const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems; + const items = await Promise.all(toArray(unresolvedItems).map(x => resolver.resolve(x))); + + // Resolve and regist Notes + const limit = promiseLimit(2); + const featuredNotes = await Promise.all(items + .filter(item => getApType(item) === 'Note') // TODO: Noteでなくてもいいかも + .slice(0, 5) + .map(item => limit(() => this.apNoteService.resolveNote(item, resolver)))); + + await this.db.transaction(async transactionalEntityManager => { + await transactionalEntityManager.delete(UserNotePining, { userId: user.id }); + + // とりあえずidを別の時間で生成して順番を維持 + let td = 0; + for (const note of featuredNotes.filter(note => note != null)) { + td -= 1000; + transactionalEntityManager.insert(UserNotePining, { + id: this.idService.genId(new Date(Date.now() + td)), + createdAt: new Date(), + userId: user.id, + noteId: note!.id, + }); + } + }); + } +} diff --git a/packages/backend/src/core/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/activitypub/models/ApQuestionService.ts new file mode 100644 index 0000000000..5793b98353 --- /dev/null +++ b/packages/backend/src/core/activitypub/models/ApQuestionService.ts @@ -0,0 +1,109 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { NotesRepository, PollsRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { IPoll } from '@/models/entities/Poll.js'; +import type Logger from '@/logger.js'; +import { isQuestion } from '../type.js'; +import { ApLoggerService } from '../ApLoggerService.js'; +import { ApResolverService } from '../ApResolverService.js'; +import type { Resolver } from '../ApResolverService.js'; +import type { IObject, IQuestion } from '../type.js'; + +@Injectable() +export class ApQuestionService { + private logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + private apResolverService: ApResolverService, + private apLoggerService: ApLoggerService, + ) { + this.logger = this.apLoggerService.logger; + } + + public async extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise { + if (resolver == null) resolver = this.apResolverService.createResolver(); + + const question = await resolver.resolve(source); + + if (!isQuestion(question)) { + throw new Error('invalid type'); + } + + const multiple = !question.oneOf; + const expiresAt = question.endTime ? new Date(question.endTime) : question.closed ? new Date(question.closed) : null; + + if (multiple && !question.anyOf) { + throw new Error('invalid question'); + } + + const choices = question[multiple ? 'anyOf' : 'oneOf']! + .map((x, i) => x.name!); + + const votes = question[multiple ? 'anyOf' : 'oneOf']! + .map((x, i) => x.replies && x.replies.totalItems || x._misskey_votes || 0); + + return { + choices, + votes, + multiple, + expiresAt, + }; + } + + /** + * Update votes of Question + * @param uri URI of AP Question object + * @returns true if updated + */ + public async updateQuestion(value: any, resolver?: Resolver) { + const uri = typeof value === 'string' ? value : value.id; + + // URIがこのサーバーを指しているならスキップ + if (uri.startsWith(this.config.url + '/')) throw new Error('uri points local'); + + //#region このサーバーに既に登録されているか + const note = await this.notesRepository.findOneBy({ uri }); + if (note == null) throw new Error('Question is not registed'); + + const poll = await this.pollsRepository.findOneBy({ noteId: note.id }); + if (poll == null) throw new Error('Question is not registed'); + //#endregion + + // resolve new Question object + if (resolver == null) resolver = this.apResolverService.createResolver(); + const question = await resolver.resolve(value) as IQuestion; + this.logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); + + if (question.type !== 'Question') throw new Error('object is not a Question'); + + const apChoices = question.oneOf ?? question.anyOf; + + let changed = false; + + 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; + + if (oldCount !== newCount) { + changed = true; + poll.votes[poll.choices.indexOf(choice)] = newCount; + } + } + + await this.pollsRepository.update({ noteId: note.id }, { + votes: poll.votes, + }); + + return changed; + } +} diff --git a/packages/backend/src/core/activitypub/models/icon.ts b/packages/backend/src/core/activitypub/models/icon.ts new file mode 100644 index 0000000000..50794a937d --- /dev/null +++ b/packages/backend/src/core/activitypub/models/icon.ts @@ -0,0 +1,5 @@ +export type IIcon = { + type: string; + mediaType?: string; + url?: string; +}; diff --git a/packages/backend/src/core/activitypub/models/identifier.ts b/packages/backend/src/core/activitypub/models/identifier.ts new file mode 100644 index 0000000000..f6c3bb8c88 --- /dev/null +++ b/packages/backend/src/core/activitypub/models/identifier.ts @@ -0,0 +1,5 @@ +export type IIdentifier = { + type: string; + name: string; + value: string; +}; diff --git a/packages/backend/src/core/activitypub/models/tag.ts b/packages/backend/src/core/activitypub/models/tag.ts new file mode 100644 index 0000000000..803846a0b0 --- /dev/null +++ b/packages/backend/src/core/activitypub/models/tag.ts @@ -0,0 +1,19 @@ +import { toArray } from '@/misc/prelude/array.js'; +import { isHashtag } from '../type.js'; +import type { IObject, IApHashtag } from '../type.js'; + +export function extractApHashtags(tags: IObject | IObject[] | null | undefined) { + if (tags == null) return []; + + const hashtags = extractApHashtagObjects(tags); + + return hashtags.map(tag => { + const m = tag.name.match(/^#(.+)/); + return m ? m[1] : null; + }).filter((x): x is string => x != null); +} + +export function extractApHashtagObjects(tags: IObject | IObject[] | null | undefined): IApHashtag[] { + if (tags == null) return []; + return toArray(tags).filter(isHashtag); +} diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts new file mode 100644 index 0000000000..dcc5110aa5 --- /dev/null +++ b/packages/backend/src/core/activitypub/type.ts @@ -0,0 +1,296 @@ +export type obj = { [x: string]: any }; +export type ApObject = IObject | string | (IObject | string)[]; + +export interface IObject { + '@context': string | string[] | obj | obj[]; + type: string | string[]; + id?: string; + summary?: string; + published?: string; + cc?: ApObject; + to?: ApObject; + attributedTo: ApObject; + attachment?: any[]; + inReplyTo?: any; + replies?: ICollection; + content?: string; + name?: string; + startTime?: Date; + endTime?: Date; + icon?: any; + image?: any; + url?: ApObject; + href?: string; + tag?: IObject | IObject[]; + sensitive?: boolean; +} + +/** + * Get array of ActivityStreams Objects id + */ +export function getApIds(value: ApObject | undefined): string[] { + if (value == null) return []; + const array = Array.isArray(value) ? value : [value]; + return array.map(x => getApId(x)); +} + +/** + * Get first ActivityStreams Object id + */ +export function getOneApId(value: ApObject): string { + const firstOne = Array.isArray(value) ? value[0] : value; + return getApId(firstOne); +} + +/** + * Get ActivityStreams Object id + */ +export function getApId(value: string | IObject): string { + if (typeof value === 'string') return value; + if (typeof value.id === 'string') return value.id; + throw new Error('cannot detemine id'); +} + +/** + * Get ActivityStreams Object type + */ +export function getApType(value: IObject): string { + if (typeof value.type === 'string') return value.type; + if (Array.isArray(value.type) && typeof value.type[0] === 'string') return value.type[0]; + throw new Error('cannot detect type'); +} + +export function getOneApHrefNullable(value: ApObject | undefined): string | undefined { + const firstOne = Array.isArray(value) ? value[0] : value; + return getApHrefNullable(firstOne); +} + +export function getApHrefNullable(value: string | IObject | undefined): string | undefined { + if (typeof value === 'string') return value; + if (typeof value?.href === 'string') return value.href; + return undefined; +} + +export interface IActivity extends IObject { + //type: 'Activity'; + actor: IObject | string; + object: IObject | string; + target?: IObject | string; + /** LD-Signature */ + signature?: { + type: string; + created: Date; + creator: string; + domain?: string; + nonce?: string; + signatureValue: string; + }; +} + +export interface ICollection extends IObject { + type: 'Collection'; + totalItems: number; + items: ApObject; +} + +export interface IOrderedCollection extends IObject { + type: 'OrderedCollection'; + totalItems: number; + orderedItems: ApObject; +} + +export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video', 'Event']; + +export const isPost = (object: IObject): object is IPost => + validPost.includes(getApType(object)); + +export interface IPost extends IObject { + type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video' | 'Event'; + source?: { + content: string; + mediaType: string; + }; + _misskey_quote?: string; + _misskey_content?: string; + quoteUrl?: string; + _misskey_talk?: boolean; +} + +export interface IQuestion extends IObject { + type: 'Note' | 'Question'; + source?: { + content: string; + mediaType: string; + }; + _misskey_quote?: string; + quoteUrl?: string; + oneOf?: IQuestionChoice[]; + anyOf?: IQuestionChoice[]; + endTime?: Date; + closed?: Date; +} + +export const isQuestion = (object: IObject): object is IQuestion => + getApType(object) === 'Note' || getApType(object) === 'Question'; + +interface IQuestionChoice { + name?: string; + replies?: ICollection; + _misskey_votes?: number; +} +export interface ITombstone extends IObject { + type: 'Tombstone'; + formerType?: string; + deleted?: Date; +} + +export const isTombstone = (object: IObject): object is ITombstone => + getApType(object) === 'Tombstone'; + +export const validActor = ['Person', 'Service', 'Group', 'Organization', 'Application']; + +export const isActor = (object: IObject): object is IActor => + validActor.includes(getApType(object)); + +export interface IActor extends IObject { + type: 'Person' | 'Service' | 'Organization' | 'Group' | 'Application'; + name?: string; + preferredUsername?: string; + manuallyApprovesFollowers?: boolean; + discoverable?: boolean; + inbox: string; + sharedInbox?: string; // 後方互換性のため + publicKey?: { + id: string; + publicKeyPem: string; + }; + followers?: string | ICollection | IOrderedCollection; + following?: string | ICollection | IOrderedCollection; + featured?: string | IOrderedCollection; + outbox: string | IOrderedCollection; + endpoints?: { + sharedInbox?: string; + }; + 'vcard:bday'?: string; + 'vcard:Address'?: string; +} + +export const isCollection = (object: IObject): object is ICollection => + getApType(object) === 'Collection'; + +export const isOrderedCollection = (object: IObject): object is IOrderedCollection => + getApType(object) === 'OrderedCollection'; + +export const isCollectionOrOrderedCollection = (object: IObject): object is ICollection | IOrderedCollection => + isCollection(object) || isOrderedCollection(object); + +export interface IApPropertyValue extends IObject { + type: 'PropertyValue'; + identifier: IApPropertyValue; + name: string; + value: string; +} + +export const isPropertyValue = (object: IObject): object is IApPropertyValue => + object && + getApType(object) === 'PropertyValue' && + typeof object.name === 'string' && + typeof (object as any).value === 'string'; + +export interface IApMention extends IObject { + type: 'Mention'; + href: string; +} + +export const isMention = (object: IObject): object is IApMention => + getApType(object) === 'Mention' && + typeof object.href === 'string'; + +export interface IApHashtag extends IObject { + type: 'Hashtag'; + name: string; +} + +export const isHashtag = (object: IObject): object is IApHashtag => + getApType(object) === 'Hashtag' && + typeof object.name === 'string'; + +export interface IApEmoji extends IObject { + type: 'Emoji'; + updated: Date; +} + +export const isEmoji = (object: IObject): object is IApEmoji => + getApType(object) === 'Emoji' && !Array.isArray(object.icon) && object.icon.url != null; + +export interface ICreate extends IActivity { + type: 'Create'; +} + +export interface IDelete extends IActivity { + type: 'Delete'; +} + +export interface IUpdate extends IActivity { + type: 'Update'; +} + +export interface IRead extends IActivity { + type: 'Read'; +} + +export interface IUndo extends IActivity { + type: 'Undo'; +} + +export interface IFollow extends IActivity { + type: 'Follow'; +} + +export interface IAccept extends IActivity { + type: 'Accept'; +} + +export interface IReject extends IActivity { + type: 'Reject'; +} + +export interface IAdd extends IActivity { + type: 'Add'; +} + +export interface IRemove extends IActivity { + type: 'Remove'; +} + +export interface ILike extends IActivity { + type: 'Like' | 'EmojiReaction' | 'EmojiReact'; + _misskey_reaction?: string; +} + +export interface IAnnounce extends IActivity { + type: 'Announce'; +} + +export interface IBlock extends IActivity { + type: 'Block'; +} + +export interface IFlag extends IActivity { + type: 'Flag'; +} + +export const isCreate = (object: IObject): object is ICreate => getApType(object) === 'Create'; +export const isDelete = (object: IObject): object is IDelete => getApType(object) === 'Delete'; +export const isUpdate = (object: IObject): object is IUpdate => getApType(object) === 'Update'; +export const isRead = (object: IObject): object is IRead => getApType(object) === 'Read'; +export const isUndo = (object: IObject): object is IUndo => getApType(object) === 'Undo'; +export const isFollow = (object: IObject): object is IFollow => getApType(object) === 'Follow'; +export const isAccept = (object: IObject): object is IAccept => getApType(object) === 'Accept'; +export const isReject = (object: IObject): object is IReject => getApType(object) === 'Reject'; +export const isAdd = (object: IObject): object is IAdd => getApType(object) === 'Add'; +export const isRemove = (object: IObject): object is IRemove => getApType(object) === 'Remove'; +export const isLike = (object: IObject): object is ILike => getApType(object) === 'Like' || getApType(object) === 'EmojiReaction' || getApType(object) === 'EmojiReact'; +export const isAnnounce = (object: IObject): object is IAnnounce => getApType(object) === 'Announce'; +export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block'; +export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag'; diff --git a/packages/backend/src/core/chart/charts/active-users.ts b/packages/backend/src/core/chart/charts/active-users.ts index 40c60910ea..6683d76587 100644 --- a/packages/backend/src/core/chart/charts/active-users.ts +++ b/packages/backend/src/core/chart/charts/active-users.ts @@ -5,7 +5,7 @@ import type { User } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/active-users.js'; +import { name, schema } from '@/core/entities/active-users.js'; import type { KVs } from '../core.js'; const week = 1000 * 60 * 60 * 24 * 7; diff --git a/packages/backend/src/core/chart/charts/ap-request.ts b/packages/backend/src/core/chart/charts/ap-request.ts index 4b91fbbf18..1de21a6a16 100644 --- a/packages/backend/src/core/chart/charts/ap-request.ts +++ b/packages/backend/src/core/chart/charts/ap-request.ts @@ -4,7 +4,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/ap-request.js'; +import { name, schema } from '@/core/entities/ap-request.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/drive.ts b/packages/backend/src/core/chart/charts/drive.ts index 494dfbbe57..638e31ac8d 100644 --- a/packages/backend/src/core/chart/charts/drive.ts +++ b/packages/backend/src/core/chart/charts/drive.ts @@ -5,7 +5,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/drive.js'; +import { name, schema } from '@/core/entities/drive.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts index 21e4cedea3..75a565cebc 100644 --- a/packages/backend/src/core/chart/charts/federation.ts +++ b/packages/backend/src/core/chart/charts/federation.ts @@ -6,7 +6,7 @@ import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/federation.js'; +import { name, schema } from '@/core/entities/federation.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/hashtag.ts b/packages/backend/src/core/chart/charts/hashtag.ts index 8b8c795cfd..ff83b8aa5d 100644 --- a/packages/backend/src/core/chart/charts/hashtag.ts +++ b/packages/backend/src/core/chart/charts/hashtag.ts @@ -6,7 +6,7 @@ import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/hashtag.js'; +import { name, schema } from '@/core/entities/hashtag.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/instance.ts b/packages/backend/src/core/chart/charts/instance.ts index 2e0f4c7126..41a35a2123 100644 --- a/packages/backend/src/core/chart/charts/instance.ts +++ b/packages/backend/src/core/chart/charts/instance.ts @@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js'; import { UtilityService } from '@/core/UtilityService.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/instance.js'; +import { name, schema } from '@/core/entities/instance.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/notes.ts b/packages/backend/src/core/chart/charts/notes.ts index 2153cfe4b4..083b0d5519 100644 --- a/packages/backend/src/core/chart/charts/notes.ts +++ b/packages/backend/src/core/chart/charts/notes.ts @@ -6,7 +6,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/notes.js'; +import { name, schema } from '@/core/entities/notes.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/per-user-drive.ts b/packages/backend/src/core/chart/charts/per-user-drive.ts index a44460bb4e..9b2e2d3b5a 100644 --- a/packages/backend/src/core/chart/charts/per-user-drive.ts +++ b/packages/backend/src/core/chart/charts/per-user-drive.ts @@ -7,7 +7,7 @@ import { DI } from '@/di-symbols.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/per-user-drive.js'; +import { name, schema } from '@/core/entities/per-user-drive.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/per-user-following.ts b/packages/backend/src/core/chart/charts/per-user-following.ts index 5ea08a0872..6bd6f1a7dc 100644 --- a/packages/backend/src/core/chart/charts/per-user-following.ts +++ b/packages/backend/src/core/chart/charts/per-user-following.ts @@ -7,7 +7,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { FollowingsRepository } from '@/models/index.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/per-user-following.js'; +import { name, schema } from '@/core/entities/per-user-following.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/per-user-notes.ts b/packages/backend/src/core/chart/charts/per-user-notes.ts index 5c14309d89..53bacd434a 100644 --- a/packages/backend/src/core/chart/charts/per-user-notes.ts +++ b/packages/backend/src/core/chart/charts/per-user-notes.ts @@ -7,7 +7,7 @@ import { DI } from '@/di-symbols.js'; import type { NotesRepository } from '@/models/index.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/per-user-notes.js'; +import { name, schema } from '@/core/entities/per-user-notes.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/per-user-reactions.ts b/packages/backend/src/core/chart/charts/per-user-reactions.ts index 4160219720..78a7be0383 100644 --- a/packages/backend/src/core/chart/charts/per-user-reactions.ts +++ b/packages/backend/src/core/chart/charts/per-user-reactions.ts @@ -7,7 +7,7 @@ import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/per-user-reactions.js'; +import { name, schema } from '@/core/entities/per-user-reactions.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/test-grouped.ts b/packages/backend/src/core/chart/charts/test-grouped.ts index bc215f3942..95585ec93e 100644 --- a/packages/backend/src/core/chart/charts/test-grouped.ts +++ b/packages/backend/src/core/chart/charts/test-grouped.ts @@ -4,7 +4,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; import Chart from '../core.js'; -import { name, schema } from './entities/test-grouped.js'; +import { name, schema } from '@/core/entities/test-grouped.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/test-intersection.ts b/packages/backend/src/core/chart/charts/test-intersection.ts index a074a7dded..c404a211a5 100644 --- a/packages/backend/src/core/chart/charts/test-intersection.ts +++ b/packages/backend/src/core/chart/charts/test-intersection.ts @@ -4,7 +4,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; import Chart from '../core.js'; -import { name, schema } from './entities/test-intersection.js'; +import { name, schema } from '@/core/entities/test-intersection.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/test-unique.ts b/packages/backend/src/core/chart/charts/test-unique.ts index 4d3e2f2403..5430db852b 100644 --- a/packages/backend/src/core/chart/charts/test-unique.ts +++ b/packages/backend/src/core/chart/charts/test-unique.ts @@ -4,7 +4,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; import Chart from '../core.js'; -import { name, schema } from './entities/test-unique.js'; +import { name, schema } from '@/core/entities/test-unique.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/test.ts b/packages/backend/src/core/chart/charts/test.ts index 72caf79e0f..7510b533c2 100644 --- a/packages/backend/src/core/chart/charts/test.ts +++ b/packages/backend/src/core/chart/charts/test.ts @@ -4,7 +4,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; import Chart from '../core.js'; -import { name, schema } from './entities/test.js'; +import { name, schema } from '@/core/entities/test.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/users.ts b/packages/backend/src/core/chart/charts/users.ts index f0359968eb..0731617354 100644 --- a/packages/backend/src/core/chart/charts/users.ts +++ b/packages/backend/src/core/chart/charts/users.ts @@ -7,7 +7,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { UsersRepository } from '@/models/index.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/users.js'; +import { name, schema } from '@/core/entities/users.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index c54285d9df..ebf6116f27 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -6,7 +6,7 @@ import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { Instance } from '@/models/entities/Instance.js'; -import { MetaService } from '../MetaService.js'; +import { MetaService } from '.@/core/MetaService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() diff --git a/packages/backend/src/core/queue/QueueModule.ts b/packages/backend/src/core/queue/QueueModule.ts deleted file mode 100644 index 3a271ea37f..0000000000 --- a/packages/backend/src/core/queue/QueueModule.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { Module } from '@nestjs/common'; -import Bull from 'bull'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import type { Provider } from '@nestjs/common'; -import type { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData, WebhookDeliverJobData } from '../../queue/types.js'; - -function q(config: Config, name: string, limitPerSec = -1) { - return new Bull(name, { - redis: { - port: config.redis.port, - host: config.redis.host, - family: config.redis.family == null ? 0 : config.redis.family, - password: config.redis.pass, - db: config.redis.db ?? 0, - }, - prefix: config.redis.prefix ? `${config.redis.prefix}:queue` : 'queue', - limiter: limitPerSec > 0 ? { - max: limitPerSec, - duration: 1000, - } : undefined, - settings: { - backoffStrategies: { - apBackoff, - }, - }, - }); -} - -// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019 -function apBackoff(attemptsMade: number, err: Error) { - const baseDelay = 60 * 1000; // 1min - const maxBackoff = 8 * 60 * 60 * 1000; // 8hours - let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay; - backoff = Math.min(backoff, maxBackoff); - backoff += Math.round(backoff * Math.random() * 0.2); - return backoff; -} - -export type SystemQueue = Bull.Queue>; -export type EndedPollNotificationQueue = Bull.Queue; -export type DeliverQueue = Bull.Queue; -export type InboxQueue = Bull.Queue; -export type DbQueue = Bull.Queue; -export type ObjectStorageQueue = Bull.Queue; -export type WebhookDeliverQueue = Bull.Queue; - -const $system: Provider = { - provide: 'queue:system', - useFactory: (config: Config) => q(config, 'system'), - inject: [DI.config], -}; - -const $endedPollNotification: Provider = { - provide: 'queue:endedPollNotification', - useFactory: (config: Config) => q(config, 'endedPollNotification'), - inject: [DI.config], -}; - -const $deliver: Provider = { - provide: 'queue:deliver', - useFactory: (config: Config) => q(config, 'deliver', config.deliverJobPerSec ?? 128), - inject: [DI.config], -}; - -const $inbox: Provider = { - provide: 'queue:inbox', - useFactory: (config: Config) => q(config, 'inbox', config.inboxJobPerSec ?? 16), - inject: [DI.config], -}; - -const $db: Provider = { - provide: 'queue:db', - useFactory: (config: Config) => q(config, 'db'), - inject: [DI.config], -}; - -const $objectStorage: Provider = { - provide: 'queue:objectStorage', - useFactory: (config: Config) => q(config, 'objectStorage'), - inject: [DI.config], -}; - -const $webhookDeliver: Provider = { - provide: 'queue:webhookDeliver', - useFactory: (config: Config) => q(config, 'webhookDeliver', 64), - inject: [DI.config], -}; - -@Module({ - imports: [ - ], - providers: [ - $system, - $endedPollNotification, - $deliver, - $inbox, - $db, - $objectStorage, - $webhookDeliver, - ], - exports: [ - $system, - $endedPollNotification, - $deliver, - $inbox, - $db, - $objectStorage, - $webhookDeliver, - ], -}) -export class QueueModule {} diff --git a/packages/backend/src/core/remote/RemoteLoggerService.ts b/packages/backend/src/core/remote/RemoteLoggerService.ts deleted file mode 100644 index 68246466c8..0000000000 --- a/packages/backend/src/core/remote/RemoteLoggerService.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import type Logger from '@/logger.js'; -import { LoggerService } from '@/core/LoggerService.js'; - -@Injectable() -export class RemoteLoggerService { - public logger: Logger; - - constructor( - private loggerService: LoggerService, - ) { - this.logger = this.loggerService.getLogger('remote', 'cyan'); - } -} diff --git a/packages/backend/src/core/remote/ResolveUserService.ts b/packages/backend/src/core/remote/ResolveUserService.ts deleted file mode 100644 index 2fd9e7c378..0000000000 --- a/packages/backend/src/core/remote/ResolveUserService.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { URL } from 'node:url'; -import { Inject, Injectable } from '@nestjs/common'; -import chalk from 'chalk'; -import { IsNull } from 'typeorm'; -import { DI } from '@/di-symbols.js'; -import type { UsersRepository } from '@/models/index.js'; -import type { IRemoteUser, User } from '@/models/entities/User.js'; -import type { Config } from '@/config.js'; -import type Logger from '@/logger.js'; -import { UtilityService } from '../UtilityService.js'; -import { WebfingerService } from './WebfingerService.js'; -import { RemoteLoggerService } from './RemoteLoggerService.js'; -import { ApPersonService } from './activitypub/models/ApPersonService.js'; - -@Injectable() -export class ResolveUserService { - private logger: Logger; - - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - private utilityService: UtilityService, - private webfingerService: WebfingerService, - private remoteLoggerService: RemoteLoggerService, - private apPersonService: ApPersonService, - ) { - this.logger = this.remoteLoggerService.logger.createSubLogger('resolve-user'); - } - - public async resolveUser(username: string, host: string | null): Promise { - const usernameLower = username.toLowerCase(); - - if (host == null) { - this.logger.info(`return local user: ${usernameLower}`); - return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => { - if (u == null) { - throw new Error('user not found'); - } else { - return u; - } - }); - } - - host = this.utilityService.toPuny(host); - - if (this.config.host === host) { - this.logger.info(`return local user: ${usernameLower}`); - return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => { - if (u == null) { - throw new Error('user not found'); - } else { - return u; - } - }); - } - - const user = await this.usersRepository.findOneBy({ usernameLower, host }) as IRemoteUser | null; - - const acctLower = `${usernameLower}@${host}`; - - if (user == null) { - const self = await this.resolveSelf(acctLower); - - this.logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`); - return await this.apPersonService.createPerson(self.href); - } - - // ユーザー情報が古い場合は、WebFilgerからやりなおして返す - if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { - // 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する - await this.usersRepository.update(user.id, { - lastFetchedAt: new Date(), - }); - - this.logger.info(`try resync: ${acctLower}`); - const self = await this.resolveSelf(acctLower); - - if (user.uri !== self.href) { - // if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping. - this.logger.info(`uri missmatch: ${acctLower}`); - this.logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`); - - // validate uri - const uri = new URL(self.href); - if (uri.hostname !== host) { - throw new Error('Invalid uri'); - } - - await this.usersRepository.update({ - usernameLower, - host: host, - }, { - uri: self.href, - }); - } else { - this.logger.info(`uri is fine: ${acctLower}`); - } - - await this.apPersonService.updatePerson(self.href); - - this.logger.info(`return resynced remote user: ${acctLower}`); - return await this.usersRepository.findOneBy({ uri: self.href }).then(u => { - if (u == null) { - throw new Error('user not found'); - } else { - return u; - } - }); - } - - this.logger.info(`return existing remote user: ${acctLower}`); - return user; - } - - private async resolveSelf(acctLower: string) { - this.logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); - const finger = await this.webfingerService.webfinger(acctLower).catch(err => { - this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`); - throw new Error(`Failed to WebFinger for ${acctLower}: ${ err.statusCode ?? err.message }`); - }); - const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self'); - if (!self) { - this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`); - throw new Error('self link not found'); - } - return self; - } -} diff --git a/packages/backend/src/core/remote/WebfingerService.ts b/packages/backend/src/core/remote/WebfingerService.ts deleted file mode 100644 index d2a88be583..0000000000 --- a/packages/backend/src/core/remote/WebfingerService.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { URL } from 'node:url'; -import { Inject, Injectable } from '@nestjs/common'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import { query as urlQuery } from '@/misc/prelude/url.js'; -import { HttpRequestService } from '@/core/HttpRequestService.js'; - -type ILink = { - href: string; - rel?: string; -}; - -type IWebFinger = { - links: ILink[]; - subject: string; -}; - -@Injectable() -export class WebfingerService { - constructor( - @Inject(DI.config) - private config: Config, - - private httpRequestService: HttpRequestService, - ) { - } - - public async webfinger(query: string): Promise { - const url = this.genUrl(query); - - return await this.httpRequestService.getJson(url, 'application/jrd+json, application/json') as IWebFinger; - } - - private genUrl(query: string): string { - if (query.match(/^https?:\/\//)) { - const u = new URL(query); - return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query }); - } - - const m = query.match(/^([^@]+)@(.*)/); - if (m) { - const hostname = m[2]; - return `https://${hostname}/.well-known/webfinger?` + urlQuery({ resource: `acct:${query}` }); - } - - throw new Error(`Invalid query (${query})`); - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApAudienceService.ts b/packages/backend/src/core/remote/activitypub/ApAudienceService.ts deleted file mode 100644 index 744017aa3a..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApAudienceService.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { In } from 'typeorm'; -import promiseLimit from 'promise-limit'; -import { DI } from '@/di-symbols.js'; -import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js'; -import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js'; -import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; -import { ApPersonService } from './models/ApPersonService.js'; -import type { ApObject } from './type.js'; -import type { Resolver } from './ApResolverService.js'; - -type Visibility = 'public' | 'home' | 'followers' | 'specified'; - -type AudienceInfo = { - visibility: Visibility, - mentionedUsers: CacheableUser[], - visibleUsers: CacheableUser[], -}; - -@Injectable() -export class ApAudienceService { - constructor( - private apPersonService: ApPersonService, - ) { - } - - public async parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { - const toGroups = this.groupingAudience(getApIds(to), actor); - const ccGroups = this.groupingAudience(getApIds(cc), actor); - - const others = unique(concat([toGroups.other, ccGroups.other])); - - const limit = promiseLimit(2); - const mentionedUsers = (await Promise.all( - others.map(id => limit(() => this.apPersonService.resolvePerson(id, resolver).catch(() => null))), - )).filter((x): x is CacheableUser => x != null); - - if (toGroups.public.length > 0) { - return { - visibility: 'public', - mentionedUsers, - visibleUsers: [], - }; - } - - if (ccGroups.public.length > 0) { - return { - visibility: 'home', - mentionedUsers, - visibleUsers: [], - }; - } - - if (toGroups.followers.length > 0) { - return { - visibility: 'followers', - mentionedUsers, - visibleUsers: [], - }; - } - - return { - visibility: 'specified', - mentionedUsers, - visibleUsers: mentionedUsers, - }; - } - - private groupingAudience(ids: string[], actor: CacheableRemoteUser) { - const groups = { - public: [] as string[], - followers: [] as string[], - other: [] as string[], - }; - - for (const id of ids) { - if (this.isPublic(id)) { - groups.public.push(id); - } else if (this.isFollowers(id, actor)) { - groups.followers.push(id); - } else { - groups.other.push(id); - } - } - - groups.other = unique(groups.other); - - return groups; - } - - private isPublic(id: string) { - return [ - 'https://www.w3.org/ns/activitystreams#Public', - 'as#Public', - 'Public', - ].includes(id); - } - - private isFollowers(id: string, actor: CacheableRemoteUser) { - return ( - id === (actor.followersUri ?? `${actor.uri}/followers`) - ); - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts b/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts deleted file mode 100644 index 77d200c3c8..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import escapeRegexp from 'escape-regexp'; -import { DI } from '@/di-symbols.js'; -import type { MessagingMessagesRepository, NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js'; -import { Cache } from '@/misc/cache.js'; -import type { UserPublickey } from '@/models/entities/UserPublickey.js'; -import { UserCacheService } from '@/core/UserCacheService.js'; -import type { Note } from '@/models/entities/Note.js'; -import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; -import { getApId } from './type.js'; -import { ApPersonService } from './models/ApPersonService.js'; -import type { IObject } from './type.js'; - -export type UriParseResult = { - /** wether the URI was generated by us */ - local: true; - /** id in DB */ - id: string; - /** hint of type, e.g. "notes", "users" */ - type: string; - /** any remaining text after type and id, not including the slash after id. undefined if empty */ - rest?: string; -} | { - /** wether the URI was generated by us */ - local: false; - /** uri in DB */ - uri: string; -}; - -@Injectable() -export class ApDbResolverService { - private publicKeyCache: Cache; - private publicKeyByUserIdCache: Cache; - - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.userPublickeysRepository) - private userPublickeysRepository: UserPublickeysRepository, - - private userCacheService: UserCacheService, - private apPersonService: ApPersonService, - ) { - this.publicKeyCache = new Cache(Infinity); - this.publicKeyByUserIdCache = new Cache(Infinity); - } - - public parseUri(value: string | IObject): UriParseResult { - const uri = getApId(value); - - // the host part of a URL is case insensitive, so use the 'i' flag. - const localRegex = new RegExp('^' + escapeRegexp(this.config.url) + '/(\\w+)/(\\w+)(?:\/(.+))?', 'i'); - const matchLocal = uri.match(localRegex); - - if (matchLocal) { - return { - local: true, - type: matchLocal[1], - id: matchLocal[2], - rest: matchLocal[3], - }; - } else { - return { - local: false, - uri, - }; - } - } - - /** - * AP Note => Misskey Note in DB - */ - public async getNoteFromApId(value: string | IObject): Promise { - const parsed = this.parseUri(value); - - if (parsed.local) { - if (parsed.type !== 'notes') return null; - - return await this.notesRepository.findOneBy({ - id: parsed.id, - }); - } else { - return await this.notesRepository.findOneBy({ - uri: parsed.uri, - }); - } - } - - public async getMessageFromApId(value: string | IObject): Promise { - const parsed = this.parseUri(value); - - if (parsed.local) { - if (parsed.type !== 'notes') return null; - - return await this.messagingMessagesRepository.findOneBy({ - id: parsed.id, - }); - } else { - return await this.messagingMessagesRepository.findOneBy({ - uri: parsed.uri, - }); - } - } - - /** - * AP Person => Misskey User in DB - */ - public async getUserFromApId(value: string | IObject): Promise { - const parsed = this.parseUri(value); - - if (parsed.local) { - if (parsed.type !== 'users') return null; - - return await this.userCacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({ - id: parsed.id, - }).then(x => x ?? undefined)) ?? null; - } else { - return await this.userCacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({ - uri: parsed.uri, - })); - } - } - - /** - * AP KeyId => Misskey User and Key - */ - public async getAuthUserFromKeyId(keyId: string): Promise<{ - user: CacheableRemoteUser; - key: UserPublickey; - } | null> { - const key = await this.publicKeyCache.fetch(keyId, async () => { - const key = await this.userPublickeysRepository.findOneBy({ - keyId, - }); - - if (key == null) return null; - - return key; - }, key => key != null); - - if (key == null) return null; - - return { - user: await this.userCacheService.userByIdCache.fetch(key.userId, () => this.usersRepository.findOneByOrFail({ id: key.userId })) as CacheableRemoteUser, - key, - }; - } - - /** - * AP Actor id => Misskey User and Key - */ - public async getAuthUserFromApId(uri: string): Promise<{ - user: CacheableRemoteUser; - key: UserPublickey | null; - } | null> { - const user = await this.apPersonService.resolvePerson(uri) as CacheableRemoteUser; - - if (user == null) return null; - - const key = await this.publicKeyByUserIdCache.fetch(user.id, () => this.userPublickeysRepository.findOneBy({ userId: user.id }), v => v != null); - - return { - user, - key, - }; - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts deleted file mode 100644 index 6fc75a0397..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { IsNull, Not } from 'typeorm'; -import { DI } from '@/di-symbols.js'; -import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; -import { QueueService } from '@/core/QueueService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; - -interface IRecipe { - type: string; -} - -interface IFollowersRecipe extends IRecipe { - type: 'Followers'; -} - -interface IDirectRecipe extends IRecipe { - type: 'Direct'; - to: IRemoteUser; -} - -const isFollowers = (recipe: any): recipe is IFollowersRecipe => - recipe.type === 'Followers'; - -const isDirect = (recipe: any): recipe is IDirectRecipe => - recipe.type === 'Direct'; - -@Injectable() -export class ApDeliverManagerService { - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.followingsRepository) - private followingsRepository: FollowingsRepository, - - private userEntityService: UserEntityService, - private queueService: QueueService, - ) { - } - - /** - * Deliver activity to followers - * @param activity Activity - * @param from Followee - */ - public async deliverToFollowers(actor: { id: ILocalUser['id']; host: null; }, activity: any) { - const manager = new DeliverManager( - this.userEntityService, - this.followingsRepository, - this.queueService, - actor, - activity, - ); - manager.addFollowersRecipe(); - await manager.execute(); - } - - /** - * Deliver activity to user - * @param activity Activity - * @param to Target user - */ - public async deliverToUser(actor: { id: ILocalUser['id']; host: null; }, activity: any, to: IRemoteUser) { - const manager = new DeliverManager( - this.userEntityService, - this.followingsRepository, - this.queueService, - actor, - activity, - ); - manager.addDirectRecipe(to); - await manager.execute(); - } - - public createDeliverManager(actor: { id: User['id']; host: null; }, activity: any) { - return new DeliverManager( - this.userEntityService, - this.followingsRepository, - this.queueService, - - actor, - activity, - ); - } -} - -class DeliverManager { - private actor: { id: User['id']; host: null; }; - private activity: any; - private recipes: IRecipe[] = []; - - /** - * Constructor - * @param actor Actor - * @param activity Activity to deliver - */ - constructor( - private userEntityService: UserEntityService, - private followingsRepository: FollowingsRepository, - private queueService: QueueService, - - actor: { id: User['id']; host: null; }, - activity: any, - ) { - this.actor = actor; - this.activity = activity; - } - - /** - * Add recipe for followers deliver - */ - public addFollowersRecipe() { - const deliver = { - type: 'Followers', - } as IFollowersRecipe; - - this.addRecipe(deliver); - } - - /** - * Add recipe for direct deliver - * @param to To - */ - public addDirectRecipe(to: IRemoteUser) { - const recipe = { - type: 'Direct', - to, - } as IDirectRecipe; - - this.addRecipe(recipe); - } - - /** - * Add recipe - * @param recipe Recipe - */ - public addRecipe(recipe: IRecipe) { - this.recipes.push(recipe); - } - - /** - * Execute delivers - */ - public async execute() { - if (!this.userEntityService.isLocalUser(this.actor)) return; - - const inboxes = new Set(); - - /* - build inbox list - - Process follower recipes first to avoid duplication when processing - direct recipes later. - */ - if (this.recipes.some(r => isFollowers(r))) { - // followers deliver - // TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう - // ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう? - const followers = await this.followingsRepository.find({ - where: { - followeeId: this.actor.id, - followerHost: Not(IsNull()), - }, - select: { - followerSharedInbox: true, - followerInbox: true, - }, - }) as { - followerSharedInbox: string | null; - followerInbox: string; - }[]; - - for (const following of followers) { - const inbox = following.followerSharedInbox ?? following.followerInbox; - inboxes.add(inbox); - } - } - - this.recipes.filter((recipe): recipe is IDirectRecipe => - // followers recipes have already been processed - isDirect(recipe) - // check that shared inbox has not been added yet - && !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox)) - // check that they actually have an inbox - && recipe.to.inbox != null, - ) - .forEach(recipe => inboxes.add(recipe.to.inbox!)); - - // deliver - for (const inbox of inboxes) { - this.queueService.deliver(this.actor, this.activity, inbox); - } - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApInboxService.ts b/packages/backend/src/core/remote/activitypub/ApInboxService.ts deleted file mode 100644 index 3da384ec2d..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApInboxService.ts +++ /dev/null @@ -1,740 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { In } from 'typeorm'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import type { CacheableRemoteUser } from '@/models/entities/User.js'; -import { UserFollowingService } from '@/core/UserFollowingService.js'; -import { ReactionService } from '@/core/ReactionService.js'; -import { RelayService } from '@/core/RelayService.js'; -import { NotePiningService } from '@/core/NotePiningService.js'; -import { UserBlockingService } from '@/core/UserBlockingService.js'; -import { NoteDeleteService } from '@/core/NoteDeleteService.js'; -import { NoteCreateService } from '@/core/NoteCreateService.js'; -import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js'; -import { AppLockService } from '@/core/AppLockService.js'; -import type Logger from '@/logger.js'; -import { MetaService } from '@/core/MetaService.js'; -import { IdService } from '@/core/IdService.js'; -import { StatusError } from '@/misc/status-error.js'; -import { UtilityService } from '@/core/UtilityService.js'; -import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { QueueService } from '@/core/QueueService.js'; -import { MessagingService } from '@/core/MessagingService.js'; -import type { UsersRepository, NotesRepository, FollowingsRepository, MessagingMessagesRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js'; -import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; -import { ApNoteService } from './models/ApNoteService.js'; -import { ApLoggerService } from './ApLoggerService.js'; -import { ApDbResolverService } from './ApDbResolverService.js'; -import { ApResolverService } from './ApResolverService.js'; -import { ApAudienceService } from './ApAudienceService.js'; -import { ApPersonService } from './models/ApPersonService.js'; -import { ApQuestionService } from './models/ApQuestionService.js'; -import type { Resolver } from './ApResolverService.js'; -import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IRead, IReject, IRemove, IUndo, IUpdate } from './type.js'; - -@Injectable() -export class ApInboxService { - private logger: Logger; - - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.followingsRepository) - private followingsRepository: FollowingsRepository, - - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - - @Inject(DI.abuseUserReportsRepository) - private abuseUserReportsRepository: AbuseUserReportsRepository, - - @Inject(DI.followRequestsRepository) - private followRequestsRepository: FollowRequestsRepository, - - private userEntityService: UserEntityService, - private noteEntityService: NoteEntityService, - private utilityService: UtilityService, - private idService: IdService, - private metaService: MetaService, - private userFollowingService: UserFollowingService, - private apAudienceService: ApAudienceService, - private reactionService: ReactionService, - private relayService: RelayService, - private notePiningService: NotePiningService, - private userBlockingService: UserBlockingService, - private noteCreateService: NoteCreateService, - private noteDeleteService: NoteDeleteService, - private appLockService: AppLockService, - private apResolverService: ApResolverService, - private apDbResolverService: ApDbResolverService, - private apLoggerService: ApLoggerService, - private apNoteService: ApNoteService, - private apPersonService: ApPersonService, - private apQuestionService: ApQuestionService, - private queueService: QueueService, - private messagingService: MessagingService, - ) { - this.logger = this.apLoggerService.logger; - } - - public async performActivity(actor: CacheableRemoteUser, activity: IObject) { - if (isCollectionOrOrderedCollection(activity)) { - const resolver = this.apResolverService.createResolver(); - for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) { - const act = await resolver.resolve(item); - try { - await this.performOneActivity(actor, act); - } catch (err) { - if (err instanceof Error || typeof err === 'string') { - this.logger.error(err); - } - } - } - } else { - await this.performOneActivity(actor, activity); - } - - // ついでにリモートユーザーの情報が古かったら更新しておく - if (actor.uri) { - if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { - setImmediate(() => { - this.apPersonService.updatePerson(actor.uri!); - }); - } - } - } - - public async performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise { - if (actor.isSuspended) return; - - if (isCreate(activity)) { - await this.create(actor, activity); - } else if (isDelete(activity)) { - await this.delete(actor, activity); - } else if (isUpdate(activity)) { - await this.update(actor, activity); - } else if (isRead(activity)) { - await this.read(actor, activity); - } else if (isFollow(activity)) { - await this.follow(actor, activity); - } else if (isAccept(activity)) { - await this.accept(actor, activity); - } else if (isReject(activity)) { - await this.reject(actor, activity); - } else if (isAdd(activity)) { - await this.add(actor, activity).catch(err => this.logger.error(err)); - } else if (isRemove(activity)) { - await this.remove(actor, activity).catch(err => this.logger.error(err)); - } else if (isAnnounce(activity)) { - await this.announce(actor, activity); - } else if (isLike(activity)) { - await this.like(actor, activity); - } else if (isUndo(activity)) { - await this.undo(actor, activity); - } else if (isBlock(activity)) { - await this.block(actor, activity); - } else if (isFlag(activity)) { - await this.flag(actor, activity); - } else { - this.logger.warn(`unrecognized activity type: ${(activity as any).type}`); - } - } - - private async follow(actor: CacheableRemoteUser, activity: IFollow): Promise { - const followee = await this.apDbResolverService.getUserFromApId(activity.object); - - if (followee == null) { - return 'skip: followee not found'; - } - - if (followee.host != null) { - return 'skip: フォローしようとしているユーザーはローカルユーザーではありません'; - } - - await this.userFollowingService.follow(actor, followee, activity.id); - return 'ok'; - } - - private async like(actor: CacheableRemoteUser, activity: ILike): Promise { - const targetUri = getApId(activity.object); - - const note = await this.apNoteService.fetchNote(targetUri); - if (!note) return `skip: target note not found ${targetUri}`; - - await this.apNoteService.extractEmojis(activity.tag ?? [], actor.host).catch(() => null); - - return await this.reactionService.create(actor, note, activity._misskey_reaction ?? activity.content ?? activity.name).catch(err => { - if (err.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') { - return 'skip: already reacted'; - } else { - throw err; - } - }).then(() => 'ok'); - } - - private async read(actor: CacheableRemoteUser, activity: IRead): Promise { - const id = await getApId(activity.object); - - if (!this.utilityService.isSelfHost(this.utilityService.extractDbHost(id))) { - return `skip: Read to foreign host (${id})`; - } - - const messageId = id.split('/').pop(); - - const message = await this.messagingMessagesRepository.findOneBy({ id: messageId }); - if (message == null) { - return 'skip: message not found'; - } - - if (actor.id !== message.recipientId) { - return 'skip: actor is not a message recipient'; - } - - await this.messagingService.readUserMessagingMessage(message.recipientId!, message.userId, [message.id]); - return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`; - } - - private async accept(actor: CacheableRemoteUser, activity: IAccept): Promise { - const uri = activity.id ?? activity; - - this.logger.info(`Accept: ${uri}`); - - const resolver = this.apResolverService.createResolver(); - - const object = await resolver.resolve(activity.object).catch(err => { - this.logger.error(`Resolution failed: ${err}`); - throw err; - }); - - if (isFollow(object)) return await this.acceptFollow(actor, object); - - return `skip: Unknown Accept type: ${getApType(object)}`; - } - - private async acceptFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { - // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある - - const follower = await this.apDbResolverService.getUserFromApId(activity.actor); - - if (follower == null) { - return 'skip: follower not found'; - } - - if (follower.host != null) { - return 'skip: follower is not a local user'; - } - - // relay - const match = activity.id?.match(/follow-relay\/(\w+)/); - if (match) { - return await this.relayService.relayAccepted(match[1]); - } - - await this.userFollowingService.acceptFollowRequest(actor, follower); - return 'ok'; - } - - private async add(actor: CacheableRemoteUser, activity: IAdd): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { - throw new Error('invalid actor'); - } - - if (activity.target == null) { - throw new Error('target is null'); - } - - if (activity.target === actor.featured) { - const note = await this.apNoteService.resolveNote(activity.object); - if (note == null) throw new Error('note not found'); - await this.notePiningService.addPinned(actor, note.id); - return; - } - - throw new Error(`unknown target: ${activity.target}`); - } - - private async announce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { - const uri = getApId(activity); - - this.logger.info(`Announce: ${uri}`); - - const targetUri = getApId(activity.object); - - this.announceNote(actor, activity, targetUri); - } - - private async announceNote(actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { - const uri = getApId(activity); - - if (actor.isSuspended) { - return; - } - - // アナウンス先をブロックしてたら中断 - const meta = await this.metaService.fetch(); - if (meta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) return; - - const unlock = await this.appLockService.getApLock(uri); - - try { - // 既に同じURIを持つものが登録されていないかチェック - const exist = await this.apNoteService.fetchNote(uri); - if (exist) { - return; - } - - // Announce対象をresolve - let renote; - try { - renote = await this.apNoteService.resolveNote(targetUri); - if (renote == null) throw new Error('announce target is null'); - } catch (err) { - // 対象が4xxならスキップ - if (err instanceof StatusError) { - if (err.isClientError) { - this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`); - return; - } - - this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode ?? err}`); - } - throw err; - } - - if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) { - this.logger.warn('skip: invalid actor for this activity'); - return; - } - - this.logger.info(`Creating the (Re)Note: ${uri}`); - - const activityAudience = await this.apAudienceService.parseAudience(actor, activity.to, activity.cc); - - await this.noteCreateService.create(actor, { - createdAt: activity.published ? new Date(activity.published) : null, - renote, - visibility: activityAudience.visibility, - visibleUsers: activityAudience.visibleUsers, - uri, - }); - } finally { - unlock(); - } - } - - private async block(actor: CacheableRemoteUser, activity: IBlock): Promise { - // ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず - - const blockee = await this.apDbResolverService.getUserFromApId(activity.object); - - if (blockee == null) { - return 'skip: blockee not found'; - } - - if (blockee.host != null) { - return 'skip: ブロックしようとしているユーザーはローカルユーザーではありません'; - } - - await this.userBlockingService.block(await this.usersRepository.findOneByOrFail({ id: actor.id }), await this.usersRepository.findOneByOrFail({ id: blockee.id })); - return 'ok'; - } - - private async create(actor: CacheableRemoteUser, activity: ICreate): Promise { - const uri = getApId(activity); - - this.logger.info(`Create: ${uri}`); - - // copy audiences between activity <=> object. - if (typeof activity.object === 'object') { - const to = unique(concat([toArray(activity.to), toArray(activity.object.to)])); - const cc = unique(concat([toArray(activity.cc), toArray(activity.object.cc)])); - - activity.to = to; - activity.cc = cc; - activity.object.to = to; - activity.object.cc = cc; - } - - // If there is no attributedTo, use Activity actor. - if (typeof activity.object === 'object' && !activity.object.attributedTo) { - activity.object.attributedTo = activity.actor; - } - - const resolver = this.apResolverService.createResolver(); - - const object = await resolver.resolve(activity.object).catch(e => { - this.logger.error(`Resolution failed: ${e}`); - throw e; - }); - - if (isPost(object)) { - this.createNote(resolver, actor, object, false, activity); - } else { - this.logger.warn(`Unknown type: ${getApType(object)}`); - } - } - - private async createNote(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { - const uri = getApId(note); - - if (typeof note === 'object') { - if (actor.uri !== note.attributedTo) { - return 'skip: actor.uri !== note.attributedTo'; - } - - if (typeof note.id === 'string') { - if (this.utilityService.extractDbHost(actor.uri) !== this.utilityService.extractDbHost(note.id)) { - return 'skip: host in actor.uri !== note.id'; - } - } - } - - const unlock = await this.appLockService.getApLock(uri); - - try { - const exist = await this.apNoteService.fetchNote(note); - if (exist) return 'skip: note exists'; - - await this.apNoteService.createNote(note, resolver, silent); - return 'ok'; - } catch (err) { - if (err instanceof StatusError && err.isClientError) { - return `skip ${err.statusCode}`; - } else { - throw err; - } - } finally { - unlock(); - } - } - - private async delete(actor: CacheableRemoteUser, activity: IDelete): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { - throw new Error('invalid actor'); - } - - // 削除対象objectのtype - let formerType: string | undefined; - - if (typeof activity.object === 'string') { - // typeが不明だけど、どうせ消えてるのでremote resolveしない - formerType = undefined; - } else { - const object = activity.object as IObject; - if (isTombstone(object)) { - formerType = toSingle(object.formerType); - } else { - formerType = toSingle(object.type); - } - } - - const uri = getApId(activity.object); - - // type不明でもactorとobjectが同じならばそれはPersonに違いない - if (!formerType && actor.uri === uri) { - formerType = 'Person'; - } - - // それでもなかったらおそらくNote - if (!formerType) { - formerType = 'Note'; - } - - if (validPost.includes(formerType)) { - return await this.deleteNote(actor, uri); - } else if (validActor.includes(formerType)) { - return await this.deleteActor(actor, uri); - } else { - return `Unknown type ${formerType}`; - } - } - - private async deleteActor(actor: CacheableRemoteUser, uri: string): Promise { - this.logger.info(`Deleting the Actor: ${uri}`); - - if (actor.uri !== uri) { - return `skip: delete actor ${actor.uri} !== ${uri}`; - } - - const user = await this.usersRepository.findOneByOrFail({ id: actor.id }); - if (user.isDeleted) { - this.logger.info('skip: already deleted'); - } - - const job = await this.queueService.createDeleteAccountJob(actor); - - await this.usersRepository.update(actor.id, { - isDeleted: true, - }); - - return `ok: queued ${job.name} ${job.id}`; - } - - private async deleteNote(actor: CacheableRemoteUser, uri: string): Promise { - this.logger.info(`Deleting the Note: ${uri}`); - - const unlock = await this.appLockService.getApLock(uri); - - try { - const note = await this.apDbResolverService.getNoteFromApId(uri); - - if (note == null) { - const message = await this.apDbResolverService.getMessageFromApId(uri); - if (message == null) return 'message not found'; - - if (message.userId !== actor.id) { - return '投稿を削除しようとしているユーザーは投稿の作成者ではありません'; - } - - await this.messagingService.deleteMessage(message); - - return 'ok: message deleted'; - } - - if (note.userId !== actor.id) { - return '投稿を削除しようとしているユーザーは投稿の作成者ではありません'; - } - - await this.noteDeleteService.delete(actor, note); - return 'ok: note deleted'; - } finally { - unlock(); - } - } - - private async flag(actor: CacheableRemoteUser, activity: IFlag): Promise { - // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので - // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する - const uris = getApIds(activity.object); - - const userIds = uris.filter(uri => uri.startsWith(this.config.url + '/users/')).map(uri => uri.split('/').pop()!); - const users = await this.usersRepository.findBy({ - id: In(userIds), - }); - if (users.length < 1) return 'skip'; - - await this.abuseUserReportsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), - targetUserId: users[0].id, - targetUserHost: users[0].host, - reporterId: actor.id, - reporterHost: actor.host, - comment: `${activity.content}\n${JSON.stringify(uris, null, 2)}`, - }); - - return 'ok'; - } - - private async reject(actor: CacheableRemoteUser, activity: IReject): Promise { - const uri = activity.id ?? activity; - - this.logger.info(`Reject: ${uri}`); - - const resolver = this.apResolverService.createResolver(); - - const object = await resolver.resolve(activity.object).catch(e => { - this.logger.error(`Resolution failed: ${e}`); - throw e; - }); - - if (isFollow(object)) return await this.rejectFollow(actor, object); - - return `skip: Unknown Reject type: ${getApType(object)}`; - } - - private async rejectFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { - // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある - - const follower = await this.apDbResolverService.getUserFromApId(activity.actor); - - if (follower == null) { - return 'skip: follower not found'; - } - - if (!this.userEntityService.isLocalUser(follower)) { - return 'skip: follower is not a local user'; - } - - // relay - const match = activity.id?.match(/follow-relay\/(\w+)/); - if (match) { - return await this.relayService.relayRejected(match[1]); - } - - await this.userFollowingService.remoteReject(actor, follower); - return 'ok'; - } - - private async remove(actor: CacheableRemoteUser, activity: IRemove): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { - throw new Error('invalid actor'); - } - - if (activity.target == null) { - throw new Error('target is null'); - } - - if (activity.target === actor.featured) { - const note = await this.apNoteService.resolveNote(activity.object); - if (note == null) throw new Error('note not found'); - await this.notePiningService.removePinned(actor, note.id); - return; - } - - throw new Error(`unknown target: ${activity.target}`); - } - - private async undo(actor: CacheableRemoteUser, activity: IUndo): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { - throw new Error('invalid actor'); - } - - const uri = activity.id ?? activity; - - this.logger.info(`Undo: ${uri}`); - - const resolver = this.apResolverService.createResolver(); - - const object = await resolver.resolve(activity.object).catch(e => { - this.logger.error(`Resolution failed: ${e}`); - throw e; - }); - - if (isFollow(object)) return await this.undoFollow(actor, object); - if (isBlock(object)) return await this.undoBlock(actor, object); - if (isLike(object)) return await this.undoLike(actor, object); - if (isAnnounce(object)) return await this.undoAnnounce(actor, object); - if (isAccept(object)) return await this.undoAccept(actor, object); - - return `skip: unknown object type ${getApType(object)}`; - } - - private async undoAccept(actor: CacheableRemoteUser, activity: IAccept): Promise { - const follower = await this.apDbResolverService.getUserFromApId(activity.object); - if (follower == null) { - return 'skip: follower not found'; - } - - const following = await this.followingsRepository.findOneBy({ - followerId: follower.id, - followeeId: actor.id, - }); - - if (following) { - await this.userFollowingService.unfollow(follower, actor); - return 'ok: unfollowed'; - } - - return 'skip: フォローされていない'; - } - - private async undoAnnounce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { - const uri = getApId(activity); - - const note = await this.notesRepository.findOneBy({ - uri, - userId: actor.id, - }); - - if (!note) return 'skip: no such Announce'; - - await this.noteDeleteService.delete(actor, note); - return 'ok: deleted'; - } - - private async undoBlock(actor: CacheableRemoteUser, activity: IBlock): Promise { - const blockee = await this.apDbResolverService.getUserFromApId(activity.object); - - if (blockee == null) { - return 'skip: blockee not found'; - } - - if (blockee.host != null) { - return 'skip: ブロック解除しようとしているユーザーはローカルユーザーではありません'; - } - - await this.userBlockingService.unblock(await this.usersRepository.findOneByOrFail({ id: actor.id }), blockee); - return 'ok'; - } - - private async undoFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { - const followee = await this.apDbResolverService.getUserFromApId(activity.object); - if (followee == null) { - return 'skip: followee not found'; - } - - if (followee.host != null) { - return 'skip: フォロー解除しようとしているユーザーはローカルユーザーではありません'; - } - - const req = await this.followRequestsRepository.findOneBy({ - followerId: actor.id, - followeeId: followee.id, - }); - - const following = await this.followingsRepository.findOneBy({ - followerId: actor.id, - followeeId: followee.id, - }); - - if (req) { - await this.userFollowingService.cancelFollowRequest(followee, actor); - return 'ok: follow request canceled'; - } - - if (following) { - await this.userFollowingService.unfollow(actor, followee); - return 'ok: unfollowed'; - } - - return 'skip: リクエストもフォローもされていない'; - } - - private async undoLike(actor: CacheableRemoteUser, activity: ILike): Promise { - const targetUri = getApId(activity.object); - - const note = await this.apNoteService.fetchNote(targetUri); - if (!note) return `skip: target note not found ${targetUri}`; - - await this.reactionService.delete(actor, note).catch(e => { - if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') return; - throw e; - }); - - return 'ok'; - } - - private async update(actor: CacheableRemoteUser, activity: IUpdate): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { - return 'skip: invalid actor'; - } - - this.logger.debug('Update'); - - const resolver = this.apResolverService.createResolver(); - - const object = await resolver.resolve(activity.object).catch(e => { - this.logger.error(`Resolution failed: ${e}`); - throw e; - }); - - if (isActor(object)) { - await this.apPersonService.updatePerson(actor.uri!, resolver, object); - return 'ok: Person updated'; - } else if (getApType(object) === 'Question') { - await this.apQuestionService.updateQuestion(object, resolver).catch(err => console.error(err)); - return 'ok: Question updated'; - } else { - return `skip: Unknown type: ${getApType(object)}`; - } - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApLoggerService.ts b/packages/backend/src/core/remote/activitypub/ApLoggerService.ts deleted file mode 100644 index 82fd7c5f18..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApLoggerService.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import type Logger from '@/logger.js'; -import { RemoteLoggerService } from '@/core/remote/RemoteLoggerService.js'; - -@Injectable() -export class ApLoggerService { - public logger: Logger; - - constructor( - private remoteLoggerService: RemoteLoggerService, - ) { - this.logger = this.remoteLoggerService.logger.createSubLogger('ap', 'magenta'); - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApMfmService.ts b/packages/backend/src/core/remote/activitypub/ApMfmService.ts deleted file mode 100644 index 8804fde64a..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApMfmService.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import * as mfm from 'mfm-js'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import { MfmService } from '@/core/MfmService.js'; -import type { Note } from '@/models/entities/Note.js'; -import { extractApHashtagObjects } from './models/tag.js'; -import type { IObject } from './type.js'; - -@Injectable() -export class ApMfmService { - constructor( - @Inject(DI.config) - private config: Config, - - private mfmService: MfmService, - ) { - } - - public htmlToMfm(html: string, tag?: IObject | IObject[]) { - const hashtagNames = extractApHashtagObjects(tag).map(x => x.name).filter((x): x is string => x != null); - - return this.mfmService.fromHtml(html, hashtagNames); - } - - public getNoteHtml(note: Note) { - if (!note.text) return ''; - return this.mfmService.toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApRendererService.ts b/packages/backend/src/core/remote/activitypub/ApRendererService.ts deleted file mode 100644 index 38a92567c3..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApRendererService.ts +++ /dev/null @@ -1,703 +0,0 @@ -import { createPublicKey } from 'node:crypto'; -import { Inject, Injectable } from '@nestjs/common'; -import { In, IsNull } from 'typeorm'; -import { v4 as uuid } from 'uuid'; -import * as mfm from 'mfm-js'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; -import type { IMentionedRemoteUsers, Note } from '@/models/entities/Note.js'; -import type { Blocking } from '@/models/entities/Blocking.js'; -import type { Relay } from '@/models/entities/Relay.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import type { NoteReaction } from '@/models/entities/NoteReaction.js'; -import type { Emoji } from '@/models/entities/Emoji.js'; -import type { Poll } from '@/models/entities/Poll.js'; -import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; -import type { PollVote } from '@/models/entities/PollVote.js'; -import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; -import { MfmService } from '@/core/MfmService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; -import type { UserKeypair } from '@/models/entities/UserKeypair.js'; -import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, EmojisRepository, PollsRepository } from '@/models/index.js'; -import { LdSignatureService } from './LdSignatureService.js'; -import { ApMfmService } from './ApMfmService.js'; -import type { IActivity, IObject } from './type.js'; -import type { IIdentifier } from './models/identifier.js'; - -@Injectable() -export class ApRendererService { - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.userProfilesRepository) - private userProfilesRepository: UserProfilesRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.driveFilesRepository) - private driveFilesRepository: DriveFilesRepository, - - @Inject(DI.emojisRepository) - private emojisRepository: EmojisRepository, - - @Inject(DI.pollsRepository) - private pollsRepository: PollsRepository, - - private userEntityService: UserEntityService, - private driveFileEntityService: DriveFileEntityService, - private ldSignatureService: LdSignatureService, - private userKeypairStoreService: UserKeypairStoreService, - private apMfmService: ApMfmService, - private mfmService: MfmService, - ) { - } - - public renderAccept(object: any, user: { id: User['id']; host: null }) { - return { - type: 'Accept', - actor: `${this.config.url}/users/${user.id}`, - object, - }; - } - - public renderAdd(user: ILocalUser, target: any, object: any) { - return { - type: 'Add', - actor: `${this.config.url}/users/${user.id}`, - target, - object, - }; - } - - public renderAnnounce(object: any, note: Note) { - const attributedTo = `${this.config.url}/users/${note.userId}`; - - let to: string[] = []; - let cc: string[] = []; - - if (note.visibility === 'public') { - to = ['https://www.w3.org/ns/activitystreams#Public']; - cc = [`${attributedTo}/followers`]; - } else if (note.visibility === 'home') { - to = [`${attributedTo}/followers`]; - cc = ['https://www.w3.org/ns/activitystreams#Public']; - } else { - return null; - } - - return { - id: `${this.config.url}/notes/${note.id}/activity`, - actor: `${this.config.url}/users/${note.userId}`, - type: 'Announce', - published: note.createdAt.toISOString(), - to, - cc, - object, - }; - } - - /** - * Renders a block into its ActivityPub representation. - * - * @param block The block to be rendered. The blockee relation must be loaded. - */ - public renderBlock(block: Blocking) { - if (block.blockee?.uri == null) { - throw new Error('renderBlock: missing blockee uri'); - } - - return { - type: 'Block', - id: `${this.config.url}/blocks/${block.id}`, - actor: `${this.config.url}/users/${block.blockerId}`, - object: block.blockee.uri, - }; - } - - public renderCreate(object: any, note: Note) { - const activity = { - id: `${this.config.url}/notes/${note.id}/activity`, - actor: `${this.config.url}/users/${note.userId}`, - type: 'Create', - published: note.createdAt.toISOString(), - object, - } as any; - - if (object.to) activity.to = object.to; - if (object.cc) activity.cc = object.cc; - - return activity; - } - - public renderDelete(object: any, user: { id: User['id']; host: null }) { - return { - type: 'Delete', - actor: `${this.config.url}/users/${user.id}`, - object, - published: new Date().toISOString(), - }; - } - - public renderDocument(file: DriveFile) { - return { - type: 'Document', - mediaType: file.type, - url: this.driveFileEntityService.getPublicUrl(file), - name: file.comment, - }; - } - - public renderEmoji(emoji: Emoji) { - return { - id: `${this.config.url}/emojis/${emoji.name}`, - type: 'Emoji', - name: `:${emoji.name}:`, - updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : new Date().toISOString, - icon: { - type: 'Image', - mediaType: emoji.type ?? 'image/png', - url: emoji.publicUrl ?? emoji.originalUrl, // ?? emoji.originalUrl してるのは後方互換性のため - }, - }; - } - - // to anonymise reporters, the reporting actor must be a system user - // object has to be a uri or array of uris - public renderFlag(user: ILocalUser, object: [string], content: string) { - return { - type: 'Flag', - actor: `${this.config.url}/users/${user.id}`, - content, - object, - }; - } - - public renderFollowRelay(relay: Relay, relayActor: ILocalUser) { - const follow = { - id: `${this.config.url}/activities/follow-relay/${relay.id}`, - type: 'Follow', - actor: `${this.config.url}/users/${relayActor.id}`, - object: 'https://www.w3.org/ns/activitystreams#Public', - }; - - return follow; - } - - /** - * Convert (local|remote)(Follower|Followee)ID to URL - * @param id Follower|Followee ID - */ - public async renderFollowUser(id: User['id']) { - const user = await this.usersRepository.findOneByOrFail({ id: id }); - return this.userEntityService.isLocalUser(user) ? `${this.config.url}/users/${user.id}` : user.uri; - } - - public renderFollow( - follower: { id: User['id']; host: User['host']; uri: User['host'] }, - followee: { id: User['id']; host: User['host']; uri: User['host'] }, - requestId?: string, - ) { - const follow = { - id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`, - type: 'Follow', - actor: this.userEntityService.isLocalUser(follower) ? `${this.config.url}/users/${follower.id}` : follower.uri, - object: this.userEntityService.isLocalUser(followee) ? `${this.config.url}/users/${followee.id}` : followee.uri, - } as any; - - return follow; - } - - public renderHashtag(tag: string) { - return { - type: 'Hashtag', - href: `${this.config.url}/tags/${encodeURIComponent(tag)}`, - name: `#${tag}`, - }; - } - - public renderImage(file: DriveFile) { - return { - type: 'Image', - url: this.driveFileEntityService.getPublicUrl(file), - sensitive: file.isSensitive, - name: file.comment, - }; - } - - public renderKey(user: ILocalUser, key: UserKeypair, postfix?: string) { - return { - id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`, - type: 'Key', - owner: `${this.config.url}/users/${user.id}`, - publicKeyPem: createPublicKey(key.publicKey).export({ - type: 'spki', - format: 'pem', - }), - }; - } - - public async renderLike(noteReaction: NoteReaction, note: { uri: string | null }) { - const reaction = noteReaction.reaction; - - const object = { - type: 'Like', - id: `${this.config.url}/likes/${noteReaction.id}`, - actor: `${this.config.url}/users/${noteReaction.userId}`, - object: note.uri ? note.uri : `${this.config.url}/notes/${noteReaction.noteId}`, - content: reaction, - _misskey_reaction: reaction, - } as any; - - if (reaction.startsWith(':')) { - const name = reaction.replace(/:/g, ''); - const emoji = await this.emojisRepository.findOneBy({ - name, - host: IsNull(), - }); - - if (emoji) object.tag = [this.renderEmoji(emoji)]; - } - - return object; - } - - public renderMention(mention: User) { - return { - type: 'Mention', - href: this.userEntityService.isRemoteUser(mention) ? mention.uri : `${this.config.url}/users/${(mention as ILocalUser).id}`, - name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as ILocalUser).username}`, - }; - } - - public async renderNote(note: Note, dive = true, isTalk = false): Promise { - const getPromisedFiles = async (ids: string[]) => { - if (!ids || ids.length === 0) return []; - const items = await this.driveFilesRepository.findBy({ id: In(ids) }); - return ids.map(id => items.find(item => item.id === id)).filter(item => item != null) as DriveFile[]; - }; - - let inReplyTo; - let inReplyToNote: Note | null; - - if (note.replyId) { - inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId }); - - if (inReplyToNote != null) { - const inReplyToUser = await this.usersRepository.findOneBy({ id: inReplyToNote.userId }); - - if (inReplyToUser != null) { - if (inReplyToNote.uri) { - inReplyTo = inReplyToNote.uri; - } else { - if (dive) { - inReplyTo = await this.renderNote(inReplyToNote, false); - } else { - inReplyTo = `${this.config.url}/notes/${inReplyToNote.id}`; - } - } - } - } - } else { - inReplyTo = null; - } - - let quote; - - if (note.renoteId) { - const renote = await this.notesRepository.findOneBy({ id: note.renoteId }); - - if (renote) { - quote = renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`; - } - } - - const attributedTo = `${this.config.url}/users/${note.userId}`; - - const mentions = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); - - let to: string[] = []; - let cc: string[] = []; - - if (note.visibility === 'public') { - to = ['https://www.w3.org/ns/activitystreams#Public']; - cc = [`${attributedTo}/followers`].concat(mentions); - } else if (note.visibility === 'home') { - to = [`${attributedTo}/followers`]; - cc = ['https://www.w3.org/ns/activitystreams#Public'].concat(mentions); - } else if (note.visibility === 'followers') { - to = [`${attributedTo}/followers`]; - cc = mentions; - } else { - to = mentions; - } - - const mentionedUsers = note.mentions.length > 0 ? await this.usersRepository.findBy({ - id: In(note.mentions), - }) : []; - - const hashtagTags = (note.tags ?? []).map(tag => this.renderHashtag(tag)); - const mentionTags = mentionedUsers.map(u => this.renderMention(u)); - - const files = await getPromisedFiles(note.fileIds); - - const text = note.text ?? ''; - let poll: Poll | null = null; - - if (note.hasPoll) { - poll = await this.pollsRepository.findOneBy({ noteId: note.id }); - } - - let apText = text; - - if (quote) { - apText += `\n\nRE: ${quote}`; - } - - const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw; - - const content = this.apMfmService.getNoteHtml(Object.assign({}, note, { - text: apText, - })); - - const emojis = await this.getEmojis(note.emojis); - const apemojis = emojis.map(emoji => this.renderEmoji(emoji)); - - const tag = [ - ...hashtagTags, - ...mentionTags, - ...apemojis, - ]; - - const asPoll = poll ? { - type: 'Question', - content: this.apMfmService.getNoteHtml(Object.assign({}, note, { - text: text, - })), - [poll.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt, - [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ - type: 'Note', - name: text, - replies: { - type: 'Collection', - totalItems: poll!.votes[i], - }, - })), - } : {}; - - const asTalk = isTalk ? { - _misskey_talk: true, - } : {}; - - return { - id: `${this.config.url}/notes/${note.id}`, - type: 'Note', - attributedTo, - summary: summary ?? undefined, - content: content ?? undefined, - _misskey_content: text, - source: { - content: text, - mediaType: 'text/x.misskeymarkdown', - }, - _misskey_quote: quote, - quoteUrl: quote, - published: note.createdAt.toISOString(), - to, - cc, - inReplyTo, - attachment: files.map(x => this.renderDocument(x)), - sensitive: note.cw != null || files.some(file => file.isSensitive), - tag, - ...asPoll, - ...asTalk, - }; - } - - public async renderPerson(user: ILocalUser) { - const id = `${this.config.url}/users/${user.id}`; - const isSystem = !!user.username.match(/\./); - - const [avatar, banner, profile] = await Promise.all([ - user.avatarId ? this.driveFilesRepository.findOneBy({ id: user.avatarId }) : Promise.resolve(undefined), - user.bannerId ? this.driveFilesRepository.findOneBy({ id: user.bannerId }) : Promise.resolve(undefined), - this.userProfilesRepository.findOneByOrFail({ userId: user.id }), - ]); - - const attachment: { - type: 'PropertyValue', - name: string, - value: string, - identifier?: IIdentifier, - }[] = []; - - if (profile.fields) { - for (const field of profile.fields) { - attachment.push({ - type: 'PropertyValue', - name: field.name, - value: (field.value != null && field.value.match(/^https?:/)) - ? `${new URL(field.value).href}` - : field.value, - }); - } - } - - const emojis = await this.getEmojis(user.emojis); - const apemojis = emojis.map(emoji => this.renderEmoji(emoji)); - - const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag)); - - const tag = [ - ...apemojis, - ...hashtagTags, - ]; - - const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); - - const person = { - type: isSystem ? 'Application' : user.isBot ? 'Service' : 'Person', - id, - inbox: `${id}/inbox`, - outbox: `${id}/outbox`, - followers: `${id}/followers`, - following: `${id}/following`, - featured: `${id}/collections/featured`, - sharedInbox: `${this.config.url}/inbox`, - endpoints: { sharedInbox: `${this.config.url}/inbox` }, - url: `${this.config.url}/@${user.username}`, - preferredUsername: user.username, - name: user.name, - summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null, - icon: avatar ? this.renderImage(avatar) : null, - image: banner ? this.renderImage(banner) : null, - tag, - manuallyApprovesFollowers: user.isLocked, - discoverable: !!user.isExplorable, - publicKey: this.renderKey(user, keypair, '#main-key'), - isCat: user.isCat, - attachment: attachment.length ? attachment : undefined, - } as any; - - if (profile.birthday) { - person['vcard:bday'] = profile.birthday; - } - - if (profile.location) { - person['vcard:Address'] = profile.location; - } - - return person; - } - - public async renderQuestion(user: { id: User['id'] }, note: Note, poll: Poll) { - const question = { - type: 'Question', - id: `${this.config.url}/questions/${note.id}`, - actor: `${this.config.url}/users/${user.id}`, - content: note.text ?? '', - [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ - name: text, - _misskey_votes: poll.votes[i], - replies: { - type: 'Collection', - totalItems: poll.votes[i], - }, - })), - }; - - return question; - } - - public renderRead(user: { id: User['id'] }, message: MessagingMessage) { - return { - type: 'Read', - actor: `${this.config.url}/users/${user.id}`, - object: message.uri, - }; - } - - public renderReject(object: any, user: { id: User['id'] }) { - return { - type: 'Reject', - actor: `${this.config.url}/users/${user.id}`, - object, - }; - } - - public renderRemove(user: { id: User['id'] }, target: any, object: any) { - return { - type: 'Remove', - actor: `${this.config.url}/users/${user.id}`, - target, - object, - }; - } - - public renderTombstone(id: string) { - return { - id, - type: 'Tombstone', - }; - } - - public renderUndo(object: any, user: { id: User['id'] }) { - if (object == null) return null; - const id = typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined; - - return { - type: 'Undo', - ...(id ? { id } : {}), - actor: `${this.config.url}/users/${user.id}`, - object, - published: new Date().toISOString(), - }; - } - - public renderUpdate(object: any, user: { id: User['id'] }) { - const activity = { - id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`, - actor: `${this.config.url}/users/${user.id}`, - type: 'Update', - to: ['https://www.w3.org/ns/activitystreams#Public'], - object, - published: new Date().toISOString(), - } as any; - - return activity; - } - - public renderVote(user: { id: User['id'] }, vote: PollVote, note: Note, poll: Poll, pollOwner: IRemoteUser) { - return { - id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`, - actor: `${this.config.url}/users/${user.id}`, - type: 'Create', - to: [pollOwner.uri], - published: new Date().toISOString(), - object: { - id: `${this.config.url}/users/${user.id}#votes/${vote.id}`, - type: 'Note', - attributedTo: `${this.config.url}/users/${user.id}`, - to: [pollOwner.uri], - inReplyTo: note.uri, - name: poll.choices[vote.choice], - }, - }; - } - - public renderActivity(x: any): IActivity | null { - if (x == null) return null; - - if (typeof x === 'object' && x.id == null) { - x.id = `${this.config.url}/${uuid()}`; - } - - return Object.assign({ - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', - { - // as non-standards - manuallyApprovesFollowers: 'as:manuallyApprovesFollowers', - sensitive: 'as:sensitive', - Hashtag: 'as:Hashtag', - quoteUrl: 'as:quoteUrl', - // Mastodon - toot: 'http://joinmastodon.org/ns#', - Emoji: 'toot:Emoji', - featured: 'toot:featured', - discoverable: 'toot:discoverable', - // schema - schema: 'http://schema.org#', - PropertyValue: 'schema:PropertyValue', - value: 'schema:value', - // Misskey - misskey: 'https://misskey-hub.net/ns#', - '_misskey_content': 'misskey:_misskey_content', - '_misskey_quote': 'misskey:_misskey_quote', - '_misskey_reaction': 'misskey:_misskey_reaction', - '_misskey_votes': 'misskey:_misskey_votes', - '_misskey_talk': 'misskey:_misskey_talk', - 'isCat': 'misskey:isCat', - // vcard - vcard: 'http://www.w3.org/2006/vcard/ns#', - }, - ], - }, x); - } - - public async attachLdSignature(activity: any, user: { id: User['id']; host: null; }): Promise { - const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); - - const ldSignature = this.ldSignatureService.use(); - ldSignature.debug = false; - activity = await ldSignature.signRsaSignature2017(activity, keypair.privateKey, `${this.config.url}/users/${user.id}#main-key`); - - return activity; - } - - /** - * Render OrderedCollectionPage - * @param id URL of self - * @param totalItems Number of total items - * @param orderedItems Items - * @param partOf URL of base - * @param prev URL of prev page (optional) - * @param next URL of next page (optional) - */ - public renderOrderedCollectionPage(id: string, totalItems: any, orderedItems: any, partOf: string, prev?: string, next?: string) { - const page = { - id, - partOf, - type: 'OrderedCollectionPage', - totalItems, - orderedItems, - } as any; - - if (prev) page.prev = prev; - if (next) page.next = next; - - return page; - } - - /** - * Render OrderedCollection - * @param id URL of self - * @param totalItems Total number of items - * @param first URL of first page (optional) - * @param last URL of last page (optional) - * @param orderedItems attached objects (optional) - */ - public renderOrderedCollection(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: IObject[]) { - const page: any = { - id, - type: 'OrderedCollection', - totalItems, - }; - - if (first) page.first = first; - if (last) page.last = last; - if (orderedItems) page.orderedItems = orderedItems; - - return page; - } - - private async getEmojis(names: string[]): Promise { - if (names == null || names.length === 0) return []; - - const emojis = await Promise.all( - names.map(name => this.emojisRepository.findOneBy({ - name, - host: IsNull(), - })), - ); - - return emojis.filter(emoji => emoji != null) as Emoji[]; - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApRequestService.ts b/packages/backend/src/core/remote/activitypub/ApRequestService.ts deleted file mode 100644 index baad46d668..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApRequestService.ts +++ /dev/null @@ -1,182 +0,0 @@ -import * as crypto from 'node:crypto'; -import { URL } from 'node:url'; -import { Inject, Injectable } from '@nestjs/common'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import type { User } from '@/models/entities/User.js'; -import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; -import { HttpRequestService } from '@/core/HttpRequestService.js'; - -type Request = { - url: string; - method: string; - headers: Record; -}; - -type Signed = { - request: Request; - signingString: string; - signature: string; - signatureHeader: string; -}; - -type PrivateKey = { - privateKeyPem: string; - keyId: string; -}; - -@Injectable() -export class ApRequestService { - constructor( - @Inject(DI.config) - private config: Config, - - private userKeypairStoreService: UserKeypairStoreService, - private httpRequestService: HttpRequestService, - ) { - } - - private createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record }): Signed { - const u = new URL(args.url); - const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`; - - const request: Request = { - url: u.href, - method: 'POST', - headers: this.objectAssignWithLcKey({ - 'Date': new Date().toUTCString(), - 'Host': u.hostname, - 'Content-Type': 'application/activity+json', - 'Digest': digestHeader, - }, args.additionalHeaders), - }; - - const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']); - - return { - request, - signingString: result.signingString, - signature: result.signature, - signatureHeader: result.signatureHeader, - }; - } - - private createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record }): Signed { - const u = new URL(args.url); - - const request: Request = { - url: u.href, - method: 'GET', - headers: this.objectAssignWithLcKey({ - 'Accept': 'application/activity+json, application/ld+json', - 'Date': new Date().toUTCString(), - 'Host': new URL(args.url).hostname, - }, args.additionalHeaders), - }; - - const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']); - - return { - request, - signingString: result.signingString, - signature: result.signature, - signatureHeader: result.signatureHeader, - }; - } - - private signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed { - const signingString = this.genSigningString(request, includeHeaders); - const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64'); - const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`; - - request.headers = this.objectAssignWithLcKey(request.headers, { - Signature: signatureHeader, - }); - - return { - request, - signingString, - signature, - signatureHeader, - }; - } - - private genSigningString(request: Request, includeHeaders: string[]): string { - request.headers = this.lcObjectKey(request.headers); - - const results: string[] = []; - - for (const key of includeHeaders.map(x => x.toLowerCase())) { - if (key === '(request-target)') { - results.push(`(request-target): ${request.method.toLowerCase()} ${new URL(request.url).pathname}`); - } else { - results.push(`${key}: ${request.headers[key]}`); - } - } - - return results.join('\n'); - } - - private lcObjectKey(src: Record): Record { - const dst: Record = {}; - for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key]; - return dst; - } - - private objectAssignWithLcKey(a: Record, b: Record): Record { - return Object.assign(this.lcObjectKey(a), this.lcObjectKey(b)); - } - - public async signedPost(user: { id: User['id'] }, url: string, object: any) { - const body = JSON.stringify(object); - - const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); - - const req = this.createSignedPost({ - key: { - privateKeyPem: keypair.privateKey, - keyId: `${this.config.url}/users/${user.id}#main-key`, - }, - url, - body, - additionalHeaders: { - 'User-Agent': this.config.userAgent, - }, - }); - - await this.httpRequestService.getResponse({ - url, - method: req.request.method, - headers: req.request.headers, - body, - }); - } - - /** - * Get AP object with http-signature - * @param user http-signature user - * @param url URL to fetch - */ - public async signedGet(url: string, user: { id: User['id'] }) { - const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); - - const req = this.createSignedGet({ - key: { - privateKeyPem: keypair.privateKey, - keyId: `${this.config.url}/users/${user.id}#main-key`, - }, - url, - additionalHeaders: { - 'User-Agent': this.config.userAgent, - }, - }); - - const res = await this.httpRequestService.getResponse({ - url, - method: req.request.method, - headers: req.request.headers, - }); - - return await res.json(); - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApResolverService.ts b/packages/backend/src/core/remote/activitypub/ApResolverService.ts deleted file mode 100644 index bcdb9383d1..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApResolverService.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import type { ILocalUser } from '@/models/entities/User.js'; -import { InstanceActorService } from '@/core/InstanceActorService.js'; -import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import { MetaService } from '@/core/MetaService.js'; -import { HttpRequestService } from '@/core/HttpRequestService.js'; -import { DI } from '@/di-symbols.js'; -import { UtilityService } from '@/core/UtilityService.js'; -import { isCollectionOrOrderedCollection } from './type.js'; -import { ApDbResolverService } from './ApDbResolverService.js'; -import { ApRendererService } from './ApRendererService.js'; -import { ApRequestService } from './ApRequestService.js'; -import type { IObject, ICollection, IOrderedCollection } from './type.js'; - -@Injectable() -export class ApResolverService { - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.pollsRepository) - private pollsRepository: PollsRepository, - - @Inject(DI.noteReactionsRepository) - private noteReactionsRepository: NoteReactionsRepository, - - private utilityService: UtilityService, - private instanceActorService: InstanceActorService, - private metaService: MetaService, - private apRequestService: ApRequestService, - private httpRequestService: HttpRequestService, - private apRendererService: ApRendererService, - private apDbResolverService: ApDbResolverService, - ) { - } - - public createResolver(): Resolver { - return new Resolver( - this.config, - this.usersRepository, - this.notesRepository, - this.pollsRepository, - this.noteReactionsRepository, - this.utilityService, - this.instanceActorService, - this.metaService, - this.apRequestService, - this.httpRequestService, - this.apRendererService, - this.apDbResolverService, - ); - } -} - -export class Resolver { - private history: Set; - private user?: ILocalUser; - - constructor( - private config: Config, - private usersRepository: UsersRepository, - private notesRepository: NotesRepository, - private pollsRepository: PollsRepository, - private noteReactionsRepository: NoteReactionsRepository, - private utilityService: UtilityService, - private instanceActorService: InstanceActorService, - private metaService: MetaService, - private apRequestService: ApRequestService, - private httpRequestService: HttpRequestService, - private apRendererService: ApRendererService, - private apDbResolverService: ApDbResolverService, - private recursionLimit = 100 - ) { - this.history = new Set(); - } - - public getHistory(): string[] { - return Array.from(this.history); - } - - public async resolveCollection(value: string | IObject): Promise { - const collection = typeof value === 'string' - ? await this.resolve(value) - : value; - - if (isCollectionOrOrderedCollection(collection)) { - return collection; - } else { - throw new Error(`unrecognized collection type: ${collection.type}`); - } - } - - public async resolve(value: string | IObject): Promise { - if (value == null) { - throw new Error('resolvee is null (or undefined)'); - } - - if (typeof value !== 'string') { - return value; - } - - if (value.includes('#')) { - // URLs with fragment parts cannot be resolved correctly because - // the fragment part does not get transmitted over HTTP(S). - // Avoid strange behaviour by not trying to resolve these at all. - throw new Error(`cannot resolve URL with fragment: ${value}`); - } - - if (this.history.has(value)) { - throw new Error('cannot resolve already resolved one'); - } - - if (this.history.size > this.recursionLimit) { - throw new Error(`hit recursion limit: ${this.utilityService.extractDbHost(value)}`); - } - - this.history.add(value); - - const host = this.utilityService.extractDbHost(value); - if (this.utilityService.isSelfHost(host)) { - return await this.resolveLocal(value); - } - - const meta = await this.metaService.fetch(); - if (meta.blockedHosts.includes(host)) { - throw new Error('Instance is blocked'); - } - - if (this.config.signToActivityPubGet && !this.user) { - this.user = await this.instanceActorService.getInstanceActor(); - } - - const object = (this.user - ? await this.apRequestService.signedGet(value, this.user) - : await this.httpRequestService.getJson(value, 'application/activity+json, application/ld+json')) as IObject; - - if (object == null || ( - Array.isArray(object['@context']) ? - !(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') : - object['@context'] !== 'https://www.w3.org/ns/activitystreams' - )) { - throw new Error('invalid response'); - } - - return object; - } - - private resolveLocal(url: string): Promise { - const parsed = this.apDbResolverService.parseUri(url); - if (!parsed.local) throw new Error('resolveLocal: not local'); - - switch (parsed.type) { - case 'notes': - return this.notesRepository.findOneByOrFail({ id: parsed.id }) - .then(note => { - if (parsed.rest === 'activity') { - // this refers to the create activity and not the note itself - return this.apRendererService.renderActivity(this.apRendererService.renderCreate(this.apRendererService.renderNote(note), note)); - } else { - return this.apRendererService.renderNote(note); - } - }); - case 'users': - return this.usersRepository.findOneByOrFail({ id: parsed.id }) - .then(user => this.apRendererService.renderPerson(user as ILocalUser)); - case 'questions': - // Polls are indexed by the note they are attached to. - return Promise.all([ - this.notesRepository.findOneByOrFail({ id: parsed.id }), - this.pollsRepository.findOneByOrFail({ noteId: parsed.id }), - ]) - .then(([note, poll]) => this.apRendererService.renderQuestion({ id: note.userId }, note, poll)); - case 'likes': - return this.noteReactionsRepository.findOneByOrFail({ id: parsed.id }).then(reaction => - this.apRendererService.renderActivity(this.apRendererService.renderLike(reaction, { uri: null }))!); - case 'follows': - // rest should be - if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI'); - - return Promise.all( - [parsed.id, parsed.rest].map(id => this.usersRepository.findOneByOrFail({ id })), - ) - .then(([follower, followee]) => this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee, url))); - default: - throw new Error(`resolveLocal: type ${parsed.type} unhandled`); - } - } -} diff --git a/packages/backend/src/core/remote/activitypub/LdSignatureService.ts b/packages/backend/src/core/remote/activitypub/LdSignatureService.ts deleted file mode 100644 index ea39f15b2b..0000000000 --- a/packages/backend/src/core/remote/activitypub/LdSignatureService.ts +++ /dev/null @@ -1,147 +0,0 @@ -import * as crypto from 'node:crypto'; -import { Inject, Injectable } from '@nestjs/common'; -import fetch from 'node-fetch'; -import { HttpRequestService } from '@/core/HttpRequestService.js'; -import { CONTEXTS } from './misc/contexts.js'; - -// RsaSignature2017 based from https://github.com/transmute-industries/RsaSignature2017 - -@Injectable() -export class LdSignatureService { - constructor( - private httpRequestService: HttpRequestService, - ) { - } - - public use(): LdSignature { - return new LdSignature(this.httpRequestService); - } -} - -class LdSignature { - public debug = false; - public preLoad = true; - public loderTimeout = 10 * 1000; - - constructor( - private httpRequestService: HttpRequestService, - ) { - } - - public async signRsaSignature2017(data: any, privateKey: string, creator: string, domain?: string, created?: Date): Promise { - const options = { - type: 'RsaSignature2017', - creator, - domain, - nonce: crypto.randomBytes(16).toString('hex'), - created: (created ?? new Date()).toISOString(), - } as { - type: string; - creator: string; - domain?: string; - nonce: string; - created: string; - }; - - if (!domain) { - delete options.domain; - } - - const toBeSigned = await this.createVerifyData(data, options); - - const signer = crypto.createSign('sha256'); - signer.update(toBeSigned); - signer.end(); - - const signature = signer.sign(privateKey); - - return { - ...data, - signature: { - ...options, - signatureValue: signature.toString('base64'), - }, - }; - } - - public async verifyRsaSignature2017(data: any, publicKey: string): Promise { - const toBeSigned = await this.createVerifyData(data, data.signature); - const verifier = crypto.createVerify('sha256'); - verifier.update(toBeSigned); - return verifier.verify(publicKey, data.signature.signatureValue, 'base64'); - } - - public async createVerifyData(data: any, options: any) { - const transformedOptions = { - ...options, - '@context': 'https://w3id.org/identity/v1', - }; - delete transformedOptions['type']; - delete transformedOptions['id']; - delete transformedOptions['signatureValue']; - const canonizedOptions = await this.normalize(transformedOptions); - const optionsHash = this.sha256(canonizedOptions.toString()); - const transformedData = { ...data }; - delete transformedData['signature']; - const cannonidedData = await this.normalize(transformedData); - if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`); - const documentHash = this.sha256(cannonidedData.toString()); - const verifyData = `${optionsHash}${documentHash}`; - return verifyData; - } - - public async normalize(data: any) { - const customLoader = this.getLoader(); - return 42; - } - - private getLoader() { - return async (url: string): Promise => { - if (!url.match('^https?\:\/\/')) throw `Invalid URL ${url}`; - - if (this.preLoad) { - if (url in CONTEXTS) { - if (this.debug) console.debug(`HIT: ${url}`); - return { - contextUrl: null, - document: CONTEXTS[url], - documentUrl: url, - }; - } - } - - if (this.debug) console.debug(`MISS: ${url}`); - const document = await this.fetchDocument(url); - return { - contextUrl: null, - document: document, - documentUrl: url, - }; - }; - } - - private async fetchDocument(url: string) { - const json = await fetch(url, { - headers: { - Accept: 'application/ld+json, application/json', - }, - // TODO - //timeout: this.loderTimeout, - agent: u => u.protocol === 'http:' ? this.httpRequestService.httpAgent : this.httpRequestService.httpsAgent, - }).then(res => { - if (!res.ok) { - throw `${res.status} ${res.statusText}`; - } else { - return res.json(); - } - }); - - return json; - } - - public sha256(data: string): string { - const hash = crypto.createHash('sha256'); - hash.update(data); - return hash.digest('hex'); - } -} diff --git a/packages/backend/src/core/remote/activitypub/misc/contexts.ts b/packages/backend/src/core/remote/activitypub/misc/contexts.ts deleted file mode 100644 index aee0d3629c..0000000000 --- a/packages/backend/src/core/remote/activitypub/misc/contexts.ts +++ /dev/null @@ -1,526 +0,0 @@ -/* eslint:disable:quotemark indent */ -const id_v1 = { - '@context': { - 'id': '@id', - 'type': '@type', - - 'cred': 'https://w3id.org/credentials#', - 'dc': 'http://purl.org/dc/terms/', - 'identity': 'https://w3id.org/identity#', - 'perm': 'https://w3id.org/permissions#', - 'ps': 'https://w3id.org/payswarm#', - 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', - 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#', - 'sec': 'https://w3id.org/security#', - 'schema': 'http://schema.org/', - 'xsd': 'http://www.w3.org/2001/XMLSchema#', - - 'Group': 'https://www.w3.org/ns/activitystreams#Group', - - 'claim': { '@id': 'cred:claim', '@type': '@id' }, - 'credential': { '@id': 'cred:credential', '@type': '@id' }, - 'issued': { '@id': 'cred:issued', '@type': 'xsd:dateTime' }, - 'issuer': { '@id': 'cred:issuer', '@type': '@id' }, - 'recipient': { '@id': 'cred:recipient', '@type': '@id' }, - 'Credential': 'cred:Credential', - 'CryptographicKeyCredential': 'cred:CryptographicKeyCredential', - - 'about': { '@id': 'schema:about', '@type': '@id' }, - 'address': { '@id': 'schema:address', '@type': '@id' }, - 'addressCountry': 'schema:addressCountry', - 'addressLocality': 'schema:addressLocality', - 'addressRegion': 'schema:addressRegion', - 'comment': 'rdfs:comment', - 'created': { '@id': 'dc:created', '@type': 'xsd:dateTime' }, - 'creator': { '@id': 'dc:creator', '@type': '@id' }, - 'description': 'schema:description', - 'email': 'schema:email', - 'familyName': 'schema:familyName', - 'givenName': 'schema:givenName', - 'image': { '@id': 'schema:image', '@type': '@id' }, - 'label': 'rdfs:label', - 'name': 'schema:name', - 'postalCode': 'schema:postalCode', - 'streetAddress': 'schema:streetAddress', - 'title': 'dc:title', - 'url': { '@id': 'schema:url', '@type': '@id' }, - 'Person': 'schema:Person', - 'PostalAddress': 'schema:PostalAddress', - 'Organization': 'schema:Organization', - - 'identityService': { '@id': 'identity:identityService', '@type': '@id' }, - 'idp': { '@id': 'identity:idp', '@type': '@id' }, - 'Identity': 'identity:Identity', - - 'paymentProcessor': 'ps:processor', - 'preferences': { '@id': 'ps:preferences', '@type': '@vocab' }, - - 'cipherAlgorithm': 'sec:cipherAlgorithm', - 'cipherData': 'sec:cipherData', - 'cipherKey': 'sec:cipherKey', - 'digestAlgorithm': 'sec:digestAlgorithm', - 'digestValue': 'sec:digestValue', - 'domain': 'sec:domain', - 'expires': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, - 'initializationVector': 'sec:initializationVector', - 'member': { '@id': 'schema:member', '@type': '@id' }, - 'memberOf': { '@id': 'schema:memberOf', '@type': '@id' }, - 'nonce': 'sec:nonce', - 'normalizationAlgorithm': 'sec:normalizationAlgorithm', - 'owner': { '@id': 'sec:owner', '@type': '@id' }, - 'password': 'sec:password', - 'privateKey': { '@id': 'sec:privateKey', '@type': '@id' }, - 'privateKeyPem': 'sec:privateKeyPem', - 'publicKey': { '@id': 'sec:publicKey', '@type': '@id' }, - 'publicKeyPem': 'sec:publicKeyPem', - 'publicKeyService': { '@id': 'sec:publicKeyService', '@type': '@id' }, - 'revoked': { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, - 'signature': 'sec:signature', - 'signatureAlgorithm': 'sec:signatureAlgorithm', - 'signatureValue': 'sec:signatureValue', - 'CryptographicKey': 'sec:Key', - 'EncryptedMessage': 'sec:EncryptedMessage', - 'GraphSignature2012': 'sec:GraphSignature2012', - 'LinkedDataSignature2015': 'sec:LinkedDataSignature2015', - - 'accessControl': { '@id': 'perm:accessControl', '@type': '@id' }, - 'writePermission': { '@id': 'perm:writePermission', '@type': '@id' }, - }, -}; - -const security_v1 = { - '@context': { - 'id': '@id', - 'type': '@type', - - 'dc': 'http://purl.org/dc/terms/', - 'sec': 'https://w3id.org/security#', - 'xsd': 'http://www.w3.org/2001/XMLSchema#', - - 'EcdsaKoblitzSignature2016': 'sec:EcdsaKoblitzSignature2016', - 'Ed25519Signature2018': 'sec:Ed25519Signature2018', - 'EncryptedMessage': 'sec:EncryptedMessage', - 'GraphSignature2012': 'sec:GraphSignature2012', - 'LinkedDataSignature2015': 'sec:LinkedDataSignature2015', - 'LinkedDataSignature2016': 'sec:LinkedDataSignature2016', - 'CryptographicKey': 'sec:Key', - - 'authenticationTag': 'sec:authenticationTag', - 'canonicalizationAlgorithm': 'sec:canonicalizationAlgorithm', - 'cipherAlgorithm': 'sec:cipherAlgorithm', - 'cipherData': 'sec:cipherData', - 'cipherKey': 'sec:cipherKey', - 'created': { '@id': 'dc:created', '@type': 'xsd:dateTime' }, - 'creator': { '@id': 'dc:creator', '@type': '@id' }, - 'digestAlgorithm': 'sec:digestAlgorithm', - 'digestValue': 'sec:digestValue', - 'domain': 'sec:domain', - 'encryptionKey': 'sec:encryptionKey', - 'expiration': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, - 'expires': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, - 'initializationVector': 'sec:initializationVector', - 'iterationCount': 'sec:iterationCount', - 'nonce': 'sec:nonce', - 'normalizationAlgorithm': 'sec:normalizationAlgorithm', - 'owner': { '@id': 'sec:owner', '@type': '@id' }, - 'password': 'sec:password', - 'privateKey': { '@id': 'sec:privateKey', '@type': '@id' }, - 'privateKeyPem': 'sec:privateKeyPem', - 'publicKey': { '@id': 'sec:publicKey', '@type': '@id' }, - 'publicKeyBase58': 'sec:publicKeyBase58', - 'publicKeyPem': 'sec:publicKeyPem', - 'publicKeyWif': 'sec:publicKeyWif', - 'publicKeyService': { '@id': 'sec:publicKeyService', '@type': '@id' }, - 'revoked': { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, - 'salt': 'sec:salt', - 'signature': 'sec:signature', - 'signatureAlgorithm': 'sec:signingAlgorithm', - 'signatureValue': 'sec:signatureValue', - }, -}; - -const activitystreams = { - '@context': { - '@vocab': '_:', - 'xsd': 'http://www.w3.org/2001/XMLSchema#', - 'as': 'https://www.w3.org/ns/activitystreams#', - 'ldp': 'http://www.w3.org/ns/ldp#', - 'vcard': 'http://www.w3.org/2006/vcard/ns#', - 'id': '@id', - 'type': '@type', - 'Accept': 'as:Accept', - 'Activity': 'as:Activity', - 'IntransitiveActivity': 'as:IntransitiveActivity', - 'Add': 'as:Add', - 'Announce': 'as:Announce', - 'Application': 'as:Application', - 'Arrive': 'as:Arrive', - 'Article': 'as:Article', - 'Audio': 'as:Audio', - 'Block': 'as:Block', - 'Collection': 'as:Collection', - 'CollectionPage': 'as:CollectionPage', - 'Relationship': 'as:Relationship', - 'Create': 'as:Create', - 'Delete': 'as:Delete', - 'Dislike': 'as:Dislike', - 'Document': 'as:Document', - 'Event': 'as:Event', - 'Follow': 'as:Follow', - 'Flag': 'as:Flag', - 'Group': 'as:Group', - 'Ignore': 'as:Ignore', - 'Image': 'as:Image', - 'Invite': 'as:Invite', - 'Join': 'as:Join', - 'Leave': 'as:Leave', - 'Like': 'as:Like', - 'Link': 'as:Link', - 'Mention': 'as:Mention', - 'Note': 'as:Note', - 'Object': 'as:Object', - 'Offer': 'as:Offer', - 'OrderedCollection': 'as:OrderedCollection', - 'OrderedCollectionPage': 'as:OrderedCollectionPage', - 'Organization': 'as:Organization', - 'Page': 'as:Page', - 'Person': 'as:Person', - 'Place': 'as:Place', - 'Profile': 'as:Profile', - 'Question': 'as:Question', - 'Reject': 'as:Reject', - 'Remove': 'as:Remove', - 'Service': 'as:Service', - 'TentativeAccept': 'as:TentativeAccept', - 'TentativeReject': 'as:TentativeReject', - 'Tombstone': 'as:Tombstone', - 'Undo': 'as:Undo', - 'Update': 'as:Update', - 'Video': 'as:Video', - 'View': 'as:View', - 'Listen': 'as:Listen', - 'Read': 'as:Read', - 'Move': 'as:Move', - 'Travel': 'as:Travel', - 'IsFollowing': 'as:IsFollowing', - 'IsFollowedBy': 'as:IsFollowedBy', - 'IsContact': 'as:IsContact', - 'IsMember': 'as:IsMember', - 'subject': { - '@id': 'as:subject', - '@type': '@id', - }, - 'relationship': { - '@id': 'as:relationship', - '@type': '@id', - }, - 'actor': { - '@id': 'as:actor', - '@type': '@id', - }, - 'attributedTo': { - '@id': 'as:attributedTo', - '@type': '@id', - }, - 'attachment': { - '@id': 'as:attachment', - '@type': '@id', - }, - 'bcc': { - '@id': 'as:bcc', - '@type': '@id', - }, - 'bto': { - '@id': 'as:bto', - '@type': '@id', - }, - 'cc': { - '@id': 'as:cc', - '@type': '@id', - }, - 'context': { - '@id': 'as:context', - '@type': '@id', - }, - 'current': { - '@id': 'as:current', - '@type': '@id', - }, - 'first': { - '@id': 'as:first', - '@type': '@id', - }, - 'generator': { - '@id': 'as:generator', - '@type': '@id', - }, - 'icon': { - '@id': 'as:icon', - '@type': '@id', - }, - 'image': { - '@id': 'as:image', - '@type': '@id', - }, - 'inReplyTo': { - '@id': 'as:inReplyTo', - '@type': '@id', - }, - 'items': { - '@id': 'as:items', - '@type': '@id', - }, - 'instrument': { - '@id': 'as:instrument', - '@type': '@id', - }, - 'orderedItems': { - '@id': 'as:items', - '@type': '@id', - '@container': '@list', - }, - 'last': { - '@id': 'as:last', - '@type': '@id', - }, - 'location': { - '@id': 'as:location', - '@type': '@id', - }, - 'next': { - '@id': 'as:next', - '@type': '@id', - }, - 'object': { - '@id': 'as:object', - '@type': '@id', - }, - 'oneOf': { - '@id': 'as:oneOf', - '@type': '@id', - }, - 'anyOf': { - '@id': 'as:anyOf', - '@type': '@id', - }, - 'closed': { - '@id': 'as:closed', - '@type': 'xsd:dateTime', - }, - 'origin': { - '@id': 'as:origin', - '@type': '@id', - }, - 'accuracy': { - '@id': 'as:accuracy', - '@type': 'xsd:float', - }, - 'prev': { - '@id': 'as:prev', - '@type': '@id', - }, - 'preview': { - '@id': 'as:preview', - '@type': '@id', - }, - 'replies': { - '@id': 'as:replies', - '@type': '@id', - }, - 'result': { - '@id': 'as:result', - '@type': '@id', - }, - 'audience': { - '@id': 'as:audience', - '@type': '@id', - }, - 'partOf': { - '@id': 'as:partOf', - '@type': '@id', - }, - 'tag': { - '@id': 'as:tag', - '@type': '@id', - }, - 'target': { - '@id': 'as:target', - '@type': '@id', - }, - 'to': { - '@id': 'as:to', - '@type': '@id', - }, - 'url': { - '@id': 'as:url', - '@type': '@id', - }, - 'altitude': { - '@id': 'as:altitude', - '@type': 'xsd:float', - }, - 'content': 'as:content', - 'contentMap': { - '@id': 'as:content', - '@container': '@language', - }, - 'name': 'as:name', - 'nameMap': { - '@id': 'as:name', - '@container': '@language', - }, - 'duration': { - '@id': 'as:duration', - '@type': 'xsd:duration', - }, - 'endTime': { - '@id': 'as:endTime', - '@type': 'xsd:dateTime', - }, - 'height': { - '@id': 'as:height', - '@type': 'xsd:nonNegativeInteger', - }, - 'href': { - '@id': 'as:href', - '@type': '@id', - }, - 'hreflang': 'as:hreflang', - 'latitude': { - '@id': 'as:latitude', - '@type': 'xsd:float', - }, - 'longitude': { - '@id': 'as:longitude', - '@type': 'xsd:float', - }, - 'mediaType': 'as:mediaType', - 'published': { - '@id': 'as:published', - '@type': 'xsd:dateTime', - }, - 'radius': { - '@id': 'as:radius', - '@type': 'xsd:float', - }, - 'rel': 'as:rel', - 'startIndex': { - '@id': 'as:startIndex', - '@type': 'xsd:nonNegativeInteger', - }, - 'startTime': { - '@id': 'as:startTime', - '@type': 'xsd:dateTime', - }, - 'summary': 'as:summary', - 'summaryMap': { - '@id': 'as:summary', - '@container': '@language', - }, - 'totalItems': { - '@id': 'as:totalItems', - '@type': 'xsd:nonNegativeInteger', - }, - 'units': 'as:units', - 'updated': { - '@id': 'as:updated', - '@type': 'xsd:dateTime', - }, - 'width': { - '@id': 'as:width', - '@type': 'xsd:nonNegativeInteger', - }, - 'describes': { - '@id': 'as:describes', - '@type': '@id', - }, - 'formerType': { - '@id': 'as:formerType', - '@type': '@id', - }, - 'deleted': { - '@id': 'as:deleted', - '@type': 'xsd:dateTime', - }, - 'inbox': { - '@id': 'ldp:inbox', - '@type': '@id', - }, - 'outbox': { - '@id': 'as:outbox', - '@type': '@id', - }, - 'following': { - '@id': 'as:following', - '@type': '@id', - }, - 'followers': { - '@id': 'as:followers', - '@type': '@id', - }, - 'streams': { - '@id': 'as:streams', - '@type': '@id', - }, - 'preferredUsername': 'as:preferredUsername', - 'endpoints': { - '@id': 'as:endpoints', - '@type': '@id', - }, - 'uploadMedia': { - '@id': 'as:uploadMedia', - '@type': '@id', - }, - 'proxyUrl': { - '@id': 'as:proxyUrl', - '@type': '@id', - }, - 'liked': { - '@id': 'as:liked', - '@type': '@id', - }, - 'oauthAuthorizationEndpoint': { - '@id': 'as:oauthAuthorizationEndpoint', - '@type': '@id', - }, - 'oauthTokenEndpoint': { - '@id': 'as:oauthTokenEndpoint', - '@type': '@id', - }, - 'provideClientKey': { - '@id': 'as:provideClientKey', - '@type': '@id', - }, - 'signClientKey': { - '@id': 'as:signClientKey', - '@type': '@id', - }, - 'sharedInbox': { - '@id': 'as:sharedInbox', - '@type': '@id', - }, - 'Public': { - '@id': 'as:Public', - '@type': '@id', - }, - 'source': 'as:source', - 'likes': { - '@id': 'as:likes', - '@type': '@id', - }, - 'shares': { - '@id': 'as:shares', - '@type': '@id', - }, - 'alsoKnownAs': { - '@id': 'as:alsoKnownAs', - '@type': '@id', - }, - }, -}; - -export const CONTEXTS: Record = { - 'https://w3id.org/identity/v1': id_v1, - 'https://w3id.org/security/v1': security_v1, - 'https://www.w3.org/ns/activitystreams': activitystreams, -}; diff --git a/packages/backend/src/core/remote/activitypub/models/ApImageService.ts b/packages/backend/src/core/remote/activitypub/models/ApImageService.ts deleted file mode 100644 index 9bf87f19d4..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/ApImageService.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import type { CacheableRemoteUser } from '@/models/entities/User.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import { MetaService } from '@/core/MetaService.js'; -import { truncate } from '@/misc/truncate.js'; -import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; -import { DriveService } from '@/core/DriveService.js'; -import type Logger from '@/logger.js'; -import { ApResolverService } from '../ApResolverService.js'; -import { ApLoggerService } from '../ApLoggerService.js'; - -@Injectable() -export class ApImageService { - private logger: Logger; - - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.driveFilesRepository) - private driveFilesRepository: DriveFilesRepository, - - private metaService: MetaService, - private apResolverService: ApResolverService, - private driveService: DriveService, - private apLoggerService: ApLoggerService, - ) { - this.logger = this.apLoggerService.logger; - } - - /** - * Imageを作成します。 - */ - public async createImage(actor: CacheableRemoteUser, value: any): Promise { - // 投稿者が凍結されていたらスキップ - if (actor.isSuspended) { - throw new Error('actor has been suspended'); - } - - const image = await this.apResolverService.createResolver().resolve(value) as any; - - if (image.url == null) { - throw new Error('invalid image: url not privided'); - } - - this.logger.info(`Creating the Image: ${image.url}`); - - const instance = await this.metaService.fetch(); - - let file = await this.driveService.uploadFromUrl({ - url: image.url, - user: actor, - uri: image.url, - sensitive: image.sensitive, - isLink: !instance.cacheRemoteFiles, - comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH), - }); - - if (file.isLink) { - // URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、 - // URLを更新する - if (file.url !== image.url) { - await this.driveFilesRepository.update({ id: file.id }, { - url: image.url, - uri: image.url, - }); - - file = await this.driveFilesRepository.findOneByOrFail({ id: file.id }); - } - } - - return file; - } - - /** - * Imageを解決します。 - * - * Misskeyに対象のImageが登録されていればそれを返し、そうでなければ - * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 - */ - public async resolveImage(actor: CacheableRemoteUser, value: any): Promise { - // TODO - - // リモートサーバーからフェッチしてきて登録 - return await this.createImage(actor, value); - } -} diff --git a/packages/backend/src/core/remote/activitypub/models/ApMentionService.ts b/packages/backend/src/core/remote/activitypub/models/ApMentionService.ts deleted file mode 100644 index 1275e24c62..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/ApMentionService.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import promiseLimit from 'promise-limit'; -import { DI } from '@/di-symbols.js'; -import type { UsersRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import { toArray, unique } from '@/misc/prelude/array.js'; -import type { CacheableUser } from '@/models/entities/User.js'; -import { isMention } from '../type.js'; -import { ApResolverService, Resolver } from '../ApResolverService.js'; -import { ApPersonService } from './ApPersonService.js'; -import type { IObject, IApMention } from '../type.js'; - -@Injectable() -export class ApMentionService { - constructor( - @Inject(DI.config) - private config: Config, - - private apResolverService: ApResolverService, - private apPersonService: ApPersonService, - ) { - } - - public async extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) { - const hrefs = unique(this.extractApMentionObjects(tags).map(x => x.href as string)); - - const limit = promiseLimit(2); - const mentionedUsers = (await Promise.all( - hrefs.map(x => limit(() => this.apPersonService.resolvePerson(x, resolver).catch(() => null))), - )).filter((x): x is CacheableUser => x != null); - - return mentionedUsers; - } - - public extractApMentionObjects(tags: IObject | IObject[] | null | undefined): IApMention[] { - if (tags == null) return []; - return toArray(tags).filter(isMention); - } -} diff --git a/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts b/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts deleted file mode 100644 index 7cf6725a38..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts +++ /dev/null @@ -1,403 +0,0 @@ -import { forwardRef, Inject, Injectable } from '@nestjs/common'; -import promiseLimit from 'promise-limit'; -import { DI } from '@/di-symbols.js'; -import type { MessagingMessagesRepository, PollsRepository, EmojisRepository } from '@/models/index.js'; -import type { UsersRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import type { CacheableRemoteUser } from '@/models/entities/User.js'; -import type { Note } from '@/models/entities/Note.js'; -import { toArray, toSingle, unique } from '@/misc/prelude/array.js'; -import type { Emoji } from '@/models/entities/Emoji.js'; -import { MetaService } from '@/core/MetaService.js'; -import { AppLockService } from '@/core/AppLockService.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import { NoteCreateService } from '@/core/NoteCreateService.js'; -import type Logger from '@/logger.js'; -import { IdService } from '@/core/IdService.js'; -import { PollService } from '@/core/PollService.js'; -import { StatusError } from '@/misc/status-error.js'; -import { UtilityService } from '@/core/UtilityService.js'; -import { MessagingService } from '@/core/MessagingService.js'; -import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js'; -// eslint-disable-next-line @typescript-eslint/consistent-type-imports -import { ApLoggerService } from '../ApLoggerService.js'; -import { ApMfmService } from '../ApMfmService.js'; -import { ApDbResolverService } from '../ApDbResolverService.js'; -import { ApResolverService } from '../ApResolverService.js'; -import { ApAudienceService } from '../ApAudienceService.js'; -import { ApPersonService } from './ApPersonService.js'; -import { extractApHashtags } from './tag.js'; -import { ApMentionService } from './ApMentionService.js'; -import { ApQuestionService } from './ApQuestionService.js'; -import { ApImageService } from './ApImageService.js'; -import type { Resolver } from '../ApResolverService.js'; -import type { IObject, IPost } from '../type.js'; - -@Injectable() -export class ApNoteService { - private logger: Logger; - - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.pollsRepository) - private pollsRepository: PollsRepository, - - @Inject(DI.emojisRepository) - private emojisRepository: EmojisRepository, - - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - - private idService: IdService, - private apMfmService: ApMfmService, - private apResolverService: ApResolverService, - - // 循環参照のため / for circular dependency - @Inject(forwardRef(() => ApPersonService)) - private apPersonService: ApPersonService, - - private utilityService: UtilityService, - private apAudienceService: ApAudienceService, - private apMentionService: ApMentionService, - private apImageService: ApImageService, - private apQuestionService: ApQuestionService, - private metaService: MetaService, - private messagingService: MessagingService, - private appLockService: AppLockService, - private pollService: PollService, - private noteCreateService: NoteCreateService, - private apDbResolverService: ApDbResolverService, - private apLoggerService: ApLoggerService, - ) { - this.logger = this.apLoggerService.logger; - } - - public validateNote(object: any, uri: string) { - const expectHost = this.utilityService.extractDbHost(uri); - - if (object == null) { - return new Error('invalid Note: object is null'); - } - - if (!validPost.includes(getApType(object))) { - return new Error(`invalid Note: invalid object type ${getApType(object)}`); - } - - if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) { - return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`); - } - - if (object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo)) !== expectHost) { - return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.attributedTo)}`); - } - - return null; - } - - /** - * Noteをフェッチします。 - * - * Misskeyに対象のNoteが登録されていればそれを返します。 - */ - public async fetchNote(object: string | IObject): Promise { - return await this.apDbResolverService.getNoteFromApId(object); - } - - /** - * Noteを作成します。 - */ - public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise { - if (resolver == null) resolver = this.apResolverService.createResolver(); - - const object: any = await resolver.resolve(value); - - const entryUri = getApId(value); - const err = this.validateNote(object, entryUri); - if (err) { - this.logger.error(`${err.message}`, { - resolver: { - history: resolver.getHistory(), - }, - value: value, - object: object, - }); - throw new Error('invalid note'); - } - - const note: IPost = object; - - this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); - - this.logger.info(`Creating the Note: ${note.id}`); - - // 投稿者をフェッチ - const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser; - - // 投稿者が凍結されていたらスキップ - if (actor.isSuspended) { - throw new Error('actor has been suspended'); - } - - const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver); - let visibility = noteAudience.visibility; - const visibleUsers = noteAudience.visibleUsers; - - // Audience (to, cc) が指定されてなかった場合 - if (visibility === 'specified' && visibleUsers.length === 0) { - if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している - // こちらから匿名GET出来たものならばpublic - visibility = 'public'; - } - } - - let isMessaging = note._misskey_talk && visibility === 'specified'; - - const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver); - const apHashtags = await extractApHashtags(note.tag); - - // 添付ファイル - // TODO: attachmentは必ずしもImageではない - // TODO: attachmentは必ずしも配列ではない - // Noteがsensitiveなら添付もsensitiveにする - const limit = promiseLimit(2); - - note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : []; - const files = note.attachment - .map(attach => attach.sensitive = note.sensitive) - ? (await Promise.all(note.attachment.map(x => limit(() => this.apImageService.resolveImage(actor, x)) as Promise))) - .filter(image => image != null) - : []; - - // リプライ - const reply: Note | null = note.inReplyTo - ? await this.resolveNote(note.inReplyTo, resolver).then(x => { - if (x == null) { - this.logger.warn('Specified inReplyTo, but nout found'); - throw new Error('inReplyTo not found'); - } else { - return x; - } - }).catch(async err => { - // トークだったらinReplyToのエラーは無視 - const uri = getApId(note.inReplyTo); - if (uri.startsWith(this.config.url + '/')) { - const id = uri.split('/').pop(); - const talk = await this.messagingMessagesRepository.findOneBy({ id }); - if (talk) { - isMessaging = true; - return null; - } - } - - this.logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`); - throw err; - }) - : null; - - // 引用 - let quote: Note | undefined | null; - - if (note._misskey_quote || note.quoteUrl) { - const tryResolveNote = async (uri: string): Promise<{ - status: 'ok'; - res: Note | null; - } | { - status: 'permerror' | 'temperror'; - }> => { - if (typeof uri !== 'string' || !uri.match(/^https?:/)) return { status: 'permerror' }; - try { - const res = await this.resolveNote(uri); - if (res) { - return { - status: 'ok', - res, - }; - } else { - return { - status: 'permerror', - }; - } - } catch (e) { - return { - status: (e instanceof StatusError && e.isClientError) ? 'permerror' : 'temperror', - }; - } - }; - - const uris = unique([note._misskey_quote, note.quoteUrl].filter((x): x is string => typeof x === 'string')); - const results = await Promise.all(uris.map(uri => tryResolveNote(uri))); - - quote = results.filter((x): x is { status: 'ok', res: Note | null } => x.status === 'ok').map(x => x.res).find(x => x); - if (!quote) { - if (results.some(x => x.status === 'temperror')) { - throw 'quote resolve failed'; - } - } - } - - const cw = note.summary === '' ? null : note.summary; - - // テキストのパース - let text: string | null = null; - if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') { - text = note.source.content; - } else if (typeof note._misskey_content !== 'undefined') { - text = note._misskey_content; - } else if (typeof note.content === 'string') { - text = this.apMfmService.htmlToMfm(note.content, note.tag); - } - - // vote - if (reply && reply.hasPoll) { - const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); - - const tryCreateVote = async (name: string, index: number): Promise => { - if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { - this.logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); - } else if (index >= 0) { - this.logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); - await this.pollService.vote(actor, reply, index); - - // リモートフォロワーにUpdate配信 - this.pollService.deliverQuestionUpdate(reply.id); - } - return null; - }; - - if (note.name) { - return await tryCreateVote(note.name, poll.choices.findIndex(x => x === note.name)); - } - } - - const emojis = await this.extractEmojis(note.tag ?? [], actor.host).catch(e => { - this.logger.info(`extractEmojis: ${e}`); - return [] as Emoji[]; - }); - - const apEmojis = emojis.map(emoji => emoji.name); - - const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined); - - if (isMessaging) { - for (const recipient of visibleUsers) { - await this.messagingService.createMessage(actor, recipient, undefined, text ?? undefined, (files && files.length > 0) ? files[0] : null, object.id); - return null; - } - } - - return await this.noteCreateService.create(actor, { - createdAt: note.published ? new Date(note.published) : null, - files, - reply, - renote: quote, - name: note.name, - cw, - text, - localOnly: false, - visibility, - visibleUsers, - apMentions, - apHashtags, - apEmojis, - poll, - uri: note.id, - url: getOneApHrefNullable(note.url), - }, silent); - } - - /** - * Noteを解決します。 - * - * Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ - * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 - */ - public async resolveNote(value: string | IObject, resolver?: Resolver): Promise { - const uri = typeof value === 'string' ? value : value.id; - if (uri == null) throw new Error('missing uri'); - - // ブロックしてたら中断 - const meta = await this.metaService.fetch(); - if (meta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) throw { statusCode: 451 }; - - const unlock = await this.appLockService.getApLock(uri); - - try { - //#region このサーバーに既に登録されていたらそれを返す - const exist = await this.fetchNote(uri); - - if (exist) { - return exist; - } - //#endregion - - if (uri.startsWith(this.config.url)) { - throw new StatusError('cannot resolve local note', 400, 'cannot resolve local note'); - } - - // リモートサーバーからフェッチしてきて登録 - // ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが - // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 - return await this.createNote(uri, resolver, true); - } finally { - unlock(); - } - } - - public async extractEmojis(tags: IObject | IObject[], host: string): Promise { - host = this.utilityService.toPuny(host); - - if (!tags) return []; - - const eomjiTags = toArray(tags).filter(isEmoji); - - return await Promise.all(eomjiTags.map(async tag => { - const name = tag.name!.replace(/^:/, '').replace(/:$/, ''); - tag.icon = toSingle(tag.icon); - - const exists = await this.emojisRepository.findOneBy({ - host, - name, - }); - - 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) - || (tag.icon!.url !== exists.originalUrl) - ) { - await this.emojisRepository.update({ - host, - name, - }, { - uri: tag.id, - originalUrl: tag.icon!.url, - publicUrl: tag.icon!.url, - updatedAt: new Date(), - }); - - return await this.emojisRepository.findOneBy({ - host, - name, - }) as Emoji; - } - - return exists; - } - - this.logger.info(`register emoji host=${host}, name=${name}`); - - return await this.emojisRepository.insert({ - id: this.idService.genId(), - host, - name, - uri: tag.id, - originalUrl: tag.icon!.url, - publicUrl: tag.icon!.url, - updatedAt: new Date(), - aliases: [], - } as Partial).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0])); - })); - } -} diff --git a/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts b/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts deleted file mode 100644 index f9d6f42ef6..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts +++ /dev/null @@ -1,594 +0,0 @@ -import { forwardRef, Inject, Injectable } from '@nestjs/common'; -import promiseLimit from 'promise-limit'; -import { DataSource } from 'typeorm'; -import { ModuleRef } from '@nestjs/core'; -import { DI } from '@/di-symbols.js'; -import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import type { CacheableUser, IRemoteUser } from '@/models/entities/User.js'; -import { User } from '@/models/entities/User.js'; -import { truncate } from '@/misc/truncate.js'; -import type { UserCacheService } from '@/core/UserCacheService.js'; -import { normalizeForSearch } from '@/misc/normalize-for-search.js'; -import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; -import type Logger from '@/logger.js'; -import type { Note } from '@/models/entities/Note.js'; -import type { IdService } from '@/core/IdService.js'; -import type { MfmService } from '@/core/MfmService.js'; -import type { Emoji } from '@/models/entities/Emoji.js'; -import { toArray } from '@/misc/prelude/array.js'; -import type { GlobalEventService } from '@/core/GlobalEventService.js'; -import type { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; -import type { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; -import { UserProfile } from '@/models/entities/UserProfile.js'; -import { UserPublickey } from '@/models/entities/UserPublickey.js'; -import type UsersChart from '@/core/chart/charts/users.js'; -import type InstanceChart from '@/core/chart/charts/instance.js'; -import type { HashtagService } from '@/core/HashtagService.js'; -import { UserNotePining } from '@/models/entities/UserNotePining.js'; -import { StatusError } from '@/misc/status-error.js'; -import type { UtilityService } from '@/core/UtilityService.js'; -import type { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js'; -import { extractApHashtags } from './tag.js'; -import type { OnModuleInit } from '@nestjs/common'; -import type { ApNoteService } from './ApNoteService.js'; -import type { ApMfmService } from '../ApMfmService.js'; -import type { ApResolverService, Resolver } from '../ApResolverService.js'; -import type { ApLoggerService } from '../ApLoggerService.js'; -// eslint-disable-next-line @typescript-eslint/consistent-type-imports -import type { ApImageService } from './ApImageService.js'; -import type { IActor, IObject, IApPropertyValue } from '../type.js'; - -const nameLength = 128; -const summaryLength = 2048; - -const services: { - [x: string]: (id: string, username: string) => any -} = { - 'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }), - 'misskey:authentication:github': (id, login) => ({ id, login }), - 'misskey:authentication:discord': (id, name) => $discord(id, name), -}; - -const $discord = (id: string, name: string) => { - if (typeof name !== 'string') { - name = 'unknown#0000'; - } - const [username, discriminator] = name.split('#'); - return { id, username, discriminator }; -}; - -function addService(target: { [x: string]: any }, source: IApPropertyValue) { - const service = services[source.name]; - - if (typeof source.value !== 'string') { - source.value = 'unknown'; - } - - const [id, username] = source.value.split('@'); - - if (service) { - target[source.name.split(':')[2]] = service(id, username); - } -} - -@Injectable() -export class ApPersonService implements OnModuleInit { - private utilityService: UtilityService; - private userEntityService: UserEntityService; - private idService: IdService; - private globalEventService: GlobalEventService; - private federatedInstanceService: FederatedInstanceService; - private fetchInstanceMetadataService: FetchInstanceMetadataService; - private userCacheService: UserCacheService; - private apResolverService: ApResolverService; - private apNoteService: ApNoteService; - private apImageService: ApImageService; - private apMfmService: ApMfmService; - private mfmService: MfmService; - private hashtagService: HashtagService; - private usersChart: UsersChart; - private instanceChart: InstanceChart; - private apLoggerService: ApLoggerService; - private logger: Logger; - - constructor( - private moduleRef: ModuleRef, - - @Inject(DI.config) - private config: Config, - - @Inject(DI.db) - private db: DataSource, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.userProfilesRepository) - private userProfilesRepository: UserProfilesRepository, - - @Inject(DI.userPublickeysRepository) - private userPublickeysRepository: UserPublickeysRepository, - - @Inject(DI.instancesRepository) - private instancesRepository: InstancesRepository, - - @Inject(DI.followingsRepository) - private followingsRepository: FollowingsRepository, - - //private utilityService: UtilityService, - //private userEntityService: UserEntityService, - //private idService: IdService, - //private globalEventService: GlobalEventService, - //private federatedInstanceService: FederatedInstanceService, - //private fetchInstanceMetadataService: FetchInstanceMetadataService, - //private userCacheService: UserCacheService, - //private apResolverService: ApResolverService, - //private apNoteService: ApNoteService, - //private apImageService: ApImageService, - //private apMfmService: ApMfmService, - //private mfmService: MfmService, - //private hashtagService: HashtagService, - //private usersChart: UsersChart, - //private instanceChart: InstanceChart, - //private apLoggerService: ApLoggerService, - ) { - } - - onModuleInit() { - this.utilityService = this.moduleRef.get('UtilityService'); - this.userEntityService = this.moduleRef.get('UserEntityService'); - this.idService = this.moduleRef.get('IdService'); - this.globalEventService = this.moduleRef.get('GlobalEventService'); - this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService'); - this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService'); - this.userCacheService = this.moduleRef.get('UserCacheService'); - this.apResolverService = this.moduleRef.get('ApResolverService'); - this.apNoteService = this.moduleRef.get('ApNoteService'); - this.apImageService = this.moduleRef.get('ApImageService'); - this.apMfmService = this.moduleRef.get('ApMfmService'); - this.mfmService = this.moduleRef.get('MfmService'); - this.hashtagService = this.moduleRef.get('HashtagService'); - this.usersChart = this.moduleRef.get('UsersChart'); - this.instanceChart = this.moduleRef.get('InstanceChart'); - this.apLoggerService = this.moduleRef.get('ApLoggerService'); - this.logger = this.apLoggerService.logger; - } - - /** - * Validate and convert to actor object - * @param x Fetched object - * @param uri Fetch target URI - */ - private validateActor(x: IObject, uri: string): IActor { - const expectHost = this.utilityService.toPuny(new URL(uri).hostname); - - if (x == null) { - throw new Error('invalid Actor: object is null'); - } - - if (!isActor(x)) { - throw new Error(`invalid Actor type '${x.type}'`); - } - - if (!(typeof x.id === 'string' && x.id.length > 0)) { - throw new Error('invalid Actor: wrong id'); - } - - if (!(typeof x.inbox === 'string' && x.inbox.length > 0)) { - throw new Error('invalid Actor: wrong inbox'); - } - - if (!(typeof x.preferredUsername === 'string' && x.preferredUsername.length > 0 && x.preferredUsername.length <= 128 && /^\w([\w-.]*\w)?$/.test(x.preferredUsername))) { - throw new Error('invalid Actor: wrong username'); - } - - // These fields are only informational, and some AP software allows these - // fields to be very long. If they are too long, we cut them off. This way - // we can at least see these users and their activities. - if (x.name) { - if (!(typeof x.name === 'string' && x.name.length > 0)) { - throw new Error('invalid Actor: wrong name'); - } - x.name = truncate(x.name, nameLength); - } - if (x.summary) { - if (!(typeof x.summary === 'string' && x.summary.length > 0)) { - throw new Error('invalid Actor: wrong summary'); - } - x.summary = truncate(x.summary, summaryLength); - } - - const idHost = this.utilityService.toPuny(new URL(x.id!).hostname); - if (idHost !== expectHost) { - throw new Error('invalid Actor: id has different host'); - } - - if (x.publicKey) { - if (typeof x.publicKey.id !== 'string') { - throw new Error('invalid Actor: publicKey.id is not a string'); - } - - const publicKeyIdHost = this.utilityService.toPuny(new URL(x.publicKey.id).hostname); - if (publicKeyIdHost !== expectHost) { - throw new Error('invalid Actor: publicKey.id has different host'); - } - } - - return x; - } - - /** - * Personをフェッチします。 - * - * Misskeyに対象のPersonが登録されていればそれを返します。 - */ - public async fetchPerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw new Error('uri is not string'); - - const cached = this.userCacheService.uriPersonCache.get(uri); - if (cached) return cached; - - // URIがこのサーバーを指しているならデータベースからフェッチ - if (uri.startsWith(this.config.url + '/')) { - const id = uri.split('/').pop(); - const u = await this.usersRepository.findOneBy({ id }); - if (u) this.userCacheService.uriPersonCache.set(uri, u); - return u; - } - - //#region このサーバーに既に登録されていたらそれを返す - const exist = await this.usersRepository.findOneBy({ uri }); - - if (exist) { - this.userCacheService.uriPersonCache.set(uri, exist); - return exist; - } - //#endregion - - return null; - } - - /** - * Personを作成します。 - */ - public async createPerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw new Error('uri is not string'); - - if (uri.startsWith(this.config.url)) { - throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user'); - } - - if (resolver == null) resolver = this.apResolverService.createResolver(); - - const object = await resolver.resolve(uri) as any; - - const person = this.validateActor(object, uri); - - this.logger.info(`Creating the Person: ${person.id}`); - - const host = this.utilityService.toPuny(new URL(object.id).hostname); - - const { fields } = this.analyzeAttachments(person.attachment ?? []); - - const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32); - - const isBot = getApType(object) === 'Service'; - - const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); - - // Create user - let user: IRemoteUser; - try { - // Start transaction - await this.db.transaction(async transactionalEntityManager => { - user = await transactionalEntityManager.save(new User({ - id: this.idService.genId(), - avatarId: null, - bannerId: null, - createdAt: new Date(), - lastFetchedAt: new Date(), - name: truncate(person.name, nameLength), - isLocked: !!person.manuallyApprovesFollowers, - isExplorable: !!person.discoverable, - username: person.preferredUsername, - usernameLower: person.preferredUsername!.toLowerCase(), - host, - inbox: person.inbox, - sharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), - followersUri: person.followers ? getApId(person.followers) : undefined, - featured: person.featured ? getApId(person.featured) : undefined, - uri: person.id, - tags, - isBot, - isCat: (person as any).isCat === true, - showTimelineReplies: false, - })) as IRemoteUser; - - await transactionalEntityManager.save(new UserProfile({ - userId: user.id, - description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, - url: getOneApHrefNullable(person.url), - fields, - birthday: bday ? bday[0] : null, - location: person['vcard:Address'] ?? null, - userHost: host, - })); - - if (person.publicKey) { - await transactionalEntityManager.save(new UserPublickey({ - userId: user.id, - keyId: person.publicKey.id, - keyPem: person.publicKey.publicKeyPem, - })); - } - }); - } catch (e) { - // duplicate key error - if (isDuplicateKeyValueError(e)) { - // /users/@a => /users/:id のように入力がaliasなときにエラーになることがあるのを対応 - const u = await this.usersRepository.findOneBy({ - uri: person.id, - }); - - if (u) { - user = u as IRemoteUser; - } else { - throw new Error('already registered'); - } - } else { - this.logger.error(e instanceof Error ? e : new Error(e as string)); - throw e; - } - } - - // Register host - this.federatedInstanceService.registerOrFetchInstanceDoc(host).then(i => { - this.instancesRepository.increment({ id: i.id }, 'usersCount', 1); - this.instanceChart.newUser(i.host); - this.fetchInstanceMetadataService.fetchInstanceMetadata(i); - }); - - this.usersChart.update(user!, true); - - // ハッシュタグ更新 - this.hashtagService.updateUsertags(user!, tags); - - //#region アバターとヘッダー画像をフェッチ - const [avatar, banner] = await Promise.all([ - person.icon, - person.image, - ].map(img => - img == null - ? Promise.resolve(null) - : this.apImageService.resolveImage(user!, img).catch(() => null), - )); - - const avatarId = avatar ? avatar.id : null; - const bannerId = banner ? banner.id : null; - - await this.usersRepository.update(user!.id, { - avatarId, - bannerId, - }); - - user!.avatarId = avatarId; - user!.bannerId = bannerId; - //#endregion - - //#region カスタム絵文字取得 - const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host).catch(err => { - this.logger.info(`extractEmojis: ${err}`); - return [] as Emoji[]; - }); - - const emojiNames = emojis.map(emoji => emoji.name); - - await this.usersRepository.update(user!.id, { - emojis: emojiNames, - }); - //#endregion - - await this.updateFeatured(user!.id, resolver).catch(err => this.logger.error(err)); - - return user!; - } - - /** - * Personの情報を更新します。 - * Misskeyに対象のPersonが登録されていなければ無視します。 - * @param uri URI of Person - * @param resolver Resolver - * @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します) - */ - public async updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise { - if (typeof uri !== 'string') throw new Error('uri is not string'); - - // URIがこのサーバーを指しているならスキップ - if (uri.startsWith(this.config.url + '/')) { - return; - } - - //#region このサーバーに既に登録されているか - const exist = await this.usersRepository.findOneBy({ uri }) as IRemoteUser; - - if (exist == null) { - return; - } - //#endregion - - if (resolver == null) resolver = this.apResolverService.createResolver(); - - const object = hint ?? await resolver.resolve(uri); - - const person = this.validateActor(object, uri); - - this.logger.info(`Updating the Person: ${person.id}`); - - // アバターとヘッダー画像をフェッチ - const [avatar, banner] = await Promise.all([ - person.icon, - person.image, - ].map(img => - img == null - ? Promise.resolve(null) - : this.apImageService.resolveImage(exist, img).catch(() => null), - )); - - // カスタム絵文字取得 - const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => { - this.logger.info(`extractEmojis: ${e}`); - return [] as Emoji[]; - }); - - const emojiNames = emojis.map(emoji => emoji.name); - - const { fields } = this.analyzeAttachments(person.attachment ?? []); - - const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32); - - const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); - - const updates = { - lastFetchedAt: new Date(), - inbox: person.inbox, - sharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), - followersUri: person.followers ? getApId(person.followers) : undefined, - featured: person.featured, - emojis: emojiNames, - name: truncate(person.name, nameLength), - tags, - isBot: getApType(object) === 'Service', - isCat: (person as any).isCat === true, - isLocked: !!person.manuallyApprovesFollowers, - isExplorable: !!person.discoverable, - } as Partial; - - if (avatar) { - updates.avatarId = avatar.id; - } - - if (banner) { - updates.bannerId = banner.id; - } - - // Update user - await this.usersRepository.update(exist.id, updates); - - if (person.publicKey) { - await this.userPublickeysRepository.update({ userId: exist.id }, { - keyId: person.publicKey.id, - keyPem: person.publicKey.publicKeyPem, - }); - } - - await this.userProfilesRepository.update({ userId: exist.id }, { - url: getOneApHrefNullable(person.url), - fields, - description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, - birthday: bday ? bday[0] : null, - location: person['vcard:Address'] ?? null, - }); - - this.globalEventService.publishInternalEvent('remoteUserUpdated', { id: exist.id }); - - // ハッシュタグ更新 - this.hashtagService.updateUsertags(exist, tags); - - // 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする - await this.followingsRepository.update({ - followerId: exist.id, - }, { - followerSharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), - }); - - await this.updateFeatured(exist.id, resolver).catch(err => this.logger.error(err)); - } - - /** - * Personを解決します。 - * - * Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ - * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 - */ - public async resolvePerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw new Error('uri is not string'); - - //#region このサーバーに既に登録されていたらそれを返す - const exist = await this.fetchPerson(uri); - - if (exist) { - return exist; - } - //#endregion - - // リモートサーバーからフェッチしてきて登録 - if (resolver == null) resolver = this.apResolverService.createResolver(); - return await this.createPerson(uri, resolver); - } - - public analyzeAttachments(attachments: IObject | IObject[] | undefined) { - const fields: { - name: string, - value: string - }[] = []; - 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 { - fields.push({ - name: attachment.name, - value: this.mfmService.fromHtml(attachment.value), - }); - } - } - } - - return { fields, services }; - } - - public async updateFeatured(userId: User['id'], resolver?: Resolver) { - const user = await this.usersRepository.findOneByOrFail({ id: userId }); - if (!this.userEntityService.isRemoteUser(user)) return; - if (!user.featured) return; - - this.logger.info(`Updating the featured: ${user.uri}`); - - if (resolver == null) resolver = this.apResolverService.createResolver(); - - // Resolve to (Ordered)Collection Object - const collection = await resolver.resolveCollection(user.featured); - if (!isCollectionOrOrderedCollection(collection)) throw new Error('Object is not Collection or OrderedCollection'); - - // Resolve to Object(may be Note) arrays - const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems; - const items = await Promise.all(toArray(unresolvedItems).map(x => resolver.resolve(x))); - - // Resolve and regist Notes - const limit = promiseLimit(2); - const featuredNotes = await Promise.all(items - .filter(item => getApType(item) === 'Note') // TODO: Noteでなくてもいいかも - .slice(0, 5) - .map(item => limit(() => this.apNoteService.resolveNote(item, resolver)))); - - await this.db.transaction(async transactionalEntityManager => { - await transactionalEntityManager.delete(UserNotePining, { userId: user.id }); - - // とりあえずidを別の時間で生成して順番を維持 - let td = 0; - for (const note of featuredNotes.filter(note => note != null)) { - td -= 1000; - transactionalEntityManager.insert(UserNotePining, { - id: this.idService.genId(new Date(Date.now() + td)), - createdAt: new Date(), - userId: user.id, - noteId: note!.id, - }); - } - }); - } -} diff --git a/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts deleted file mode 100644 index 5793b98353..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { DI } from '@/di-symbols.js'; -import type { NotesRepository, PollsRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import type { IPoll } from '@/models/entities/Poll.js'; -import type Logger from '@/logger.js'; -import { isQuestion } from '../type.js'; -import { ApLoggerService } from '../ApLoggerService.js'; -import { ApResolverService } from '../ApResolverService.js'; -import type { Resolver } from '../ApResolverService.js'; -import type { IObject, IQuestion } from '../type.js'; - -@Injectable() -export class ApQuestionService { - private logger: Logger; - - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.pollsRepository) - private pollsRepository: PollsRepository, - - private apResolverService: ApResolverService, - private apLoggerService: ApLoggerService, - ) { - this.logger = this.apLoggerService.logger; - } - - public async extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise { - if (resolver == null) resolver = this.apResolverService.createResolver(); - - const question = await resolver.resolve(source); - - if (!isQuestion(question)) { - throw new Error('invalid type'); - } - - const multiple = !question.oneOf; - const expiresAt = question.endTime ? new Date(question.endTime) : question.closed ? new Date(question.closed) : null; - - if (multiple && !question.anyOf) { - throw new Error('invalid question'); - } - - const choices = question[multiple ? 'anyOf' : 'oneOf']! - .map((x, i) => x.name!); - - const votes = question[multiple ? 'anyOf' : 'oneOf']! - .map((x, i) => x.replies && x.replies.totalItems || x._misskey_votes || 0); - - return { - choices, - votes, - multiple, - expiresAt, - }; - } - - /** - * Update votes of Question - * @param uri URI of AP Question object - * @returns true if updated - */ - public async updateQuestion(value: any, resolver?: Resolver) { - const uri = typeof value === 'string' ? value : value.id; - - // URIがこのサーバーを指しているならスキップ - if (uri.startsWith(this.config.url + '/')) throw new Error('uri points local'); - - //#region このサーバーに既に登録されているか - const note = await this.notesRepository.findOneBy({ uri }); - if (note == null) throw new Error('Question is not registed'); - - const poll = await this.pollsRepository.findOneBy({ noteId: note.id }); - if (poll == null) throw new Error('Question is not registed'); - //#endregion - - // resolve new Question object - if (resolver == null) resolver = this.apResolverService.createResolver(); - const question = await resolver.resolve(value) as IQuestion; - this.logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); - - if (question.type !== 'Question') throw new Error('object is not a Question'); - - const apChoices = question.oneOf ?? question.anyOf; - - let changed = false; - - 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; - - if (oldCount !== newCount) { - changed = true; - poll.votes[poll.choices.indexOf(choice)] = newCount; - } - } - - await this.pollsRepository.update({ noteId: note.id }, { - votes: poll.votes, - }); - - return changed; - } -} diff --git a/packages/backend/src/core/remote/activitypub/models/icon.ts b/packages/backend/src/core/remote/activitypub/models/icon.ts deleted file mode 100644 index 50794a937d..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/icon.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type IIcon = { - type: string; - mediaType?: string; - url?: string; -}; diff --git a/packages/backend/src/core/remote/activitypub/models/identifier.ts b/packages/backend/src/core/remote/activitypub/models/identifier.ts deleted file mode 100644 index f6c3bb8c88..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/identifier.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type IIdentifier = { - type: string; - name: string; - value: string; -}; diff --git a/packages/backend/src/core/remote/activitypub/models/tag.ts b/packages/backend/src/core/remote/activitypub/models/tag.ts deleted file mode 100644 index 803846a0b0..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/tag.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { toArray } from '@/misc/prelude/array.js'; -import { isHashtag } from '../type.js'; -import type { IObject, IApHashtag } from '../type.js'; - -export function extractApHashtags(tags: IObject | IObject[] | null | undefined) { - if (tags == null) return []; - - const hashtags = extractApHashtagObjects(tags); - - return hashtags.map(tag => { - const m = tag.name.match(/^#(.+)/); - return m ? m[1] : null; - }).filter((x): x is string => x != null); -} - -export function extractApHashtagObjects(tags: IObject | IObject[] | null | undefined): IApHashtag[] { - if (tags == null) return []; - return toArray(tags).filter(isHashtag); -} diff --git a/packages/backend/src/core/remote/activitypub/type.ts b/packages/backend/src/core/remote/activitypub/type.ts deleted file mode 100644 index dcc5110aa5..0000000000 --- a/packages/backend/src/core/remote/activitypub/type.ts +++ /dev/null @@ -1,296 +0,0 @@ -export type obj = { [x: string]: any }; -export type ApObject = IObject | string | (IObject | string)[]; - -export interface IObject { - '@context': string | string[] | obj | obj[]; - type: string | string[]; - id?: string; - summary?: string; - published?: string; - cc?: ApObject; - to?: ApObject; - attributedTo: ApObject; - attachment?: any[]; - inReplyTo?: any; - replies?: ICollection; - content?: string; - name?: string; - startTime?: Date; - endTime?: Date; - icon?: any; - image?: any; - url?: ApObject; - href?: string; - tag?: IObject | IObject[]; - sensitive?: boolean; -} - -/** - * Get array of ActivityStreams Objects id - */ -export function getApIds(value: ApObject | undefined): string[] { - if (value == null) return []; - const array = Array.isArray(value) ? value : [value]; - return array.map(x => getApId(x)); -} - -/** - * Get first ActivityStreams Object id - */ -export function getOneApId(value: ApObject): string { - const firstOne = Array.isArray(value) ? value[0] : value; - return getApId(firstOne); -} - -/** - * Get ActivityStreams Object id - */ -export function getApId(value: string | IObject): string { - if (typeof value === 'string') return value; - if (typeof value.id === 'string') return value.id; - throw new Error('cannot detemine id'); -} - -/** - * Get ActivityStreams Object type - */ -export function getApType(value: IObject): string { - if (typeof value.type === 'string') return value.type; - if (Array.isArray(value.type) && typeof value.type[0] === 'string') return value.type[0]; - throw new Error('cannot detect type'); -} - -export function getOneApHrefNullable(value: ApObject | undefined): string | undefined { - const firstOne = Array.isArray(value) ? value[0] : value; - return getApHrefNullable(firstOne); -} - -export function getApHrefNullable(value: string | IObject | undefined): string | undefined { - if (typeof value === 'string') return value; - if (typeof value?.href === 'string') return value.href; - return undefined; -} - -export interface IActivity extends IObject { - //type: 'Activity'; - actor: IObject | string; - object: IObject | string; - target?: IObject | string; - /** LD-Signature */ - signature?: { - type: string; - created: Date; - creator: string; - domain?: string; - nonce?: string; - signatureValue: string; - }; -} - -export interface ICollection extends IObject { - type: 'Collection'; - totalItems: number; - items: ApObject; -} - -export interface IOrderedCollection extends IObject { - type: 'OrderedCollection'; - totalItems: number; - orderedItems: ApObject; -} - -export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video', 'Event']; - -export const isPost = (object: IObject): object is IPost => - validPost.includes(getApType(object)); - -export interface IPost extends IObject { - type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video' | 'Event'; - source?: { - content: string; - mediaType: string; - }; - _misskey_quote?: string; - _misskey_content?: string; - quoteUrl?: string; - _misskey_talk?: boolean; -} - -export interface IQuestion extends IObject { - type: 'Note' | 'Question'; - source?: { - content: string; - mediaType: string; - }; - _misskey_quote?: string; - quoteUrl?: string; - oneOf?: IQuestionChoice[]; - anyOf?: IQuestionChoice[]; - endTime?: Date; - closed?: Date; -} - -export const isQuestion = (object: IObject): object is IQuestion => - getApType(object) === 'Note' || getApType(object) === 'Question'; - -interface IQuestionChoice { - name?: string; - replies?: ICollection; - _misskey_votes?: number; -} -export interface ITombstone extends IObject { - type: 'Tombstone'; - formerType?: string; - deleted?: Date; -} - -export const isTombstone = (object: IObject): object is ITombstone => - getApType(object) === 'Tombstone'; - -export const validActor = ['Person', 'Service', 'Group', 'Organization', 'Application']; - -export const isActor = (object: IObject): object is IActor => - validActor.includes(getApType(object)); - -export interface IActor extends IObject { - type: 'Person' | 'Service' | 'Organization' | 'Group' | 'Application'; - name?: string; - preferredUsername?: string; - manuallyApprovesFollowers?: boolean; - discoverable?: boolean; - inbox: string; - sharedInbox?: string; // 後方互換性のため - publicKey?: { - id: string; - publicKeyPem: string; - }; - followers?: string | ICollection | IOrderedCollection; - following?: string | ICollection | IOrderedCollection; - featured?: string | IOrderedCollection; - outbox: string | IOrderedCollection; - endpoints?: { - sharedInbox?: string; - }; - 'vcard:bday'?: string; - 'vcard:Address'?: string; -} - -export const isCollection = (object: IObject): object is ICollection => - getApType(object) === 'Collection'; - -export const isOrderedCollection = (object: IObject): object is IOrderedCollection => - getApType(object) === 'OrderedCollection'; - -export const isCollectionOrOrderedCollection = (object: IObject): object is ICollection | IOrderedCollection => - isCollection(object) || isOrderedCollection(object); - -export interface IApPropertyValue extends IObject { - type: 'PropertyValue'; - identifier: IApPropertyValue; - name: string; - value: string; -} - -export const isPropertyValue = (object: IObject): object is IApPropertyValue => - object && - getApType(object) === 'PropertyValue' && - typeof object.name === 'string' && - typeof (object as any).value === 'string'; - -export interface IApMention extends IObject { - type: 'Mention'; - href: string; -} - -export const isMention = (object: IObject): object is IApMention => - getApType(object) === 'Mention' && - typeof object.href === 'string'; - -export interface IApHashtag extends IObject { - type: 'Hashtag'; - name: string; -} - -export const isHashtag = (object: IObject): object is IApHashtag => - getApType(object) === 'Hashtag' && - typeof object.name === 'string'; - -export interface IApEmoji extends IObject { - type: 'Emoji'; - updated: Date; -} - -export const isEmoji = (object: IObject): object is IApEmoji => - getApType(object) === 'Emoji' && !Array.isArray(object.icon) && object.icon.url != null; - -export interface ICreate extends IActivity { - type: 'Create'; -} - -export interface IDelete extends IActivity { - type: 'Delete'; -} - -export interface IUpdate extends IActivity { - type: 'Update'; -} - -export interface IRead extends IActivity { - type: 'Read'; -} - -export interface IUndo extends IActivity { - type: 'Undo'; -} - -export interface IFollow extends IActivity { - type: 'Follow'; -} - -export interface IAccept extends IActivity { - type: 'Accept'; -} - -export interface IReject extends IActivity { - type: 'Reject'; -} - -export interface IAdd extends IActivity { - type: 'Add'; -} - -export interface IRemove extends IActivity { - type: 'Remove'; -} - -export interface ILike extends IActivity { - type: 'Like' | 'EmojiReaction' | 'EmojiReact'; - _misskey_reaction?: string; -} - -export interface IAnnounce extends IActivity { - type: 'Announce'; -} - -export interface IBlock extends IActivity { - type: 'Block'; -} - -export interface IFlag extends IActivity { - type: 'Flag'; -} - -export const isCreate = (object: IObject): object is ICreate => getApType(object) === 'Create'; -export const isDelete = (object: IObject): object is IDelete => getApType(object) === 'Delete'; -export const isUpdate = (object: IObject): object is IUpdate => getApType(object) === 'Update'; -export const isRead = (object: IObject): object is IRead => getApType(object) === 'Read'; -export const isUndo = (object: IObject): object is IUndo => getApType(object) === 'Undo'; -export const isFollow = (object: IObject): object is IFollow => getApType(object) === 'Follow'; -export const isAccept = (object: IObject): object is IAccept => getApType(object) === 'Accept'; -export const isReject = (object: IObject): object is IReject => getApType(object) === 'Reject'; -export const isAdd = (object: IObject): object is IAdd => getApType(object) === 'Add'; -export const isRemove = (object: IObject): object is IRemove => getApType(object) === 'Remove'; -export const isLike = (object: IObject): object is ILike => getApType(object) === 'Like' || getApType(object) === 'EmojiReaction' || getApType(object) === 'EmojiReact'; -export const isAnnounce = (object: IObject): object is IAnnounce => getApType(object) === 'Announce'; -export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block'; -export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag'; diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 9042a21d2c..2a4b201a7d 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -5,7 +5,7 @@ import type { DriveFilesRepository, InstancesRepository } from '@/models/index.j import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { MetaService } from '@/core/MetaService.js'; -import { ApRequestService } from '@/core/remote/activitypub/ApRequestService.js'; +import { ApRequestService } from '@/core/activitypub/ApRequestService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; import { Cache } from '@/misc/cache.js'; diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index 6c6789cff4..9442a60d8d 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -5,7 +5,7 @@ import type { UsersRepository, BlockingsRepository, DriveFilesRepository } from import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; -import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; +import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -32,7 +32,7 @@ export class ImportBlockingProcessorService { private utilityService: UtilityService, private userBlockingService: UserBlockingService, - private resolveUserService: ResolveUserService, + private remoteUserResolveService: RemoteUserResolveService, private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { @@ -78,7 +78,7 @@ export class ImportBlockingProcessorService { if (host == null && target == null) continue; if (target == null) { - target = await this.resolveUserService.resolveUser(username, host); + target = await this.remoteUserResolveService.resolveUser(username, host); } if (target == null) { diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index 9f2e5e801a..667f7279fb 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -5,7 +5,7 @@ import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; -import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; +import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -29,7 +29,7 @@ export class ImportFollowingProcessorService { private utilityService: UtilityService, private userFollowingService: UserFollowingService, - private resolveUserService: ResolveUserService, + private remoteUserResolveService: RemoteUserResolveService, private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { @@ -75,7 +75,7 @@ export class ImportFollowingProcessorService { if (host == null && target == null) continue; if (target == null) { - target = await this.resolveUserService.resolveUser(username, host); + target = await this.remoteUserResolveService.resolveUser(username, host); } if (target == null) { diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index 62ad3b5c88..f3c16e73d5 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -5,7 +5,7 @@ import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; -import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; +import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -29,7 +29,7 @@ export class ImportMutingProcessorService { private utilityService: UtilityService, private userMutingService: UserMutingService, - private resolveUserService: ResolveUserService, + private remoteUserResolveService: RemoteUserResolveService, private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { @@ -75,7 +75,7 @@ export class ImportMutingProcessorService { if (host == null && target == null) continue; if (target == null) { - target = await this.resolveUserService.resolveUser(username, host); + target = await this.remoteUserResolveService.resolveUser(username, host); } if (target == null) { diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index f214d59e1c..1519877c5f 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -5,7 +5,7 @@ import type { UsersRepository, DriveFilesRepository, UserListJoiningsRepository, import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; -import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; +import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UserListService } from '@/core/UserListService.js'; import { IdService } from '@/core/IdService.js'; @@ -37,7 +37,7 @@ export class ImportUserListsProcessorService { private utilityService: UtilityService, private idService: IdService, private userListService: UserListService, - private resolveUserService: ResolveUserService, + private remoteUserResolveService: RemoteUserResolveService, private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { @@ -95,7 +95,7 @@ export class ImportUserListsProcessorService { }); if (target == null) { - target = await this.resolveUserService.resolveUser(username, host); + target = await this.remoteUserResolveService.resolveUser(username, host); } if (await this.userListJoiningsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue; diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index e8cd7dcaca..8f1c474020 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -7,7 +7,7 @@ import type { InstancesRepository, DriveFilesRepository } from '@/models/index.j import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { MetaService } from '@/core/MetaService.js'; -import { ApRequestService } from '@/core/remote/activitypub/ApRequestService.js'; +import { ApRequestService } from '@/core/activitypub/ApRequestService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; import { Cache } from '@/misc/cache.js'; @@ -15,15 +15,15 @@ import type { Instance } from '@/models/entities/Instance.js'; import InstanceChart from '@/core/chart/charts/instance.js'; import ApRequestChart from '@/core/chart/charts/ap-request.js'; import FederationChart from '@/core/chart/charts/federation.js'; -import { getApId } from '@/core/remote/activitypub/type.js'; +import { getApId } from '@/core/activitypub/type.js'; import type { CacheableRemoteUser } from '@/models/entities/User.js'; import type { UserPublickey } from '@/models/entities/UserPublickey.js'; -import { ApDbResolverService } from '@/core/remote/activitypub/ApDbResolverService.js'; +import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js'; import { StatusError } from '@/misc/status-error.js'; import { UtilityService } from '@/core/UtilityService.js'; -import { ApPersonService } from '@/core/remote/activitypub/models/ApPersonService.js'; -import { LdSignatureService } from '@/core/remote/activitypub/LdSignatureService.js'; -import { ApInboxService } from '@/core/remote/activitypub/ApInboxService.js'; +import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; +import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js'; +import { ApInboxService } from '@/core/activitypub/ApInboxService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DeliverJobData, InboxJobData } from '../types.js'; diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index 18ec997a1b..1214c9eb95 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -2,7 +2,7 @@ import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Note } from '@/models/entities/Note.js'; import type { User } from '@/models/entities/User.js'; import type { Webhook } from '@/models/entities/Webhook.js'; -import type { IActivity } from '@/core/remote/activitypub/type.js'; +import type { IActivity } from '@/core/activitypub/type.js'; import type httpSignature from '@peertube/http-signature'; export type DeliverJobData = { diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 9689a623fd..0ce2c9cd97 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js'; import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; import * as url from '@/misc/prelude/url.js'; import type { Config } from '@/config.js'; -import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { QueueService } from '@/core/QueueService.js'; import type { ILocalUser, User } from '@/models/entities/User.js'; import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts index 526efa9f9d..9442bda5eb 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts @@ -1,7 +1,7 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DeliverQueue } from '@/core/queue/QueueModule.js'; +import type { DeliverQueue } from '@/core/QueueModule.js'; export const meta = { tags: ['admin'], diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts index b8934428c3..55a3410d49 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts @@ -1,7 +1,7 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { InboxQueue } from '@/core/queue/QueueModule.js'; +import type { InboxQueue } from '@/core/QueueModule.js'; export const meta = { tags: ['admin'], diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts index 605ea3d042..7f3732c970 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/queue/QueueModule.js'; +import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/QueueModule.js'; export const meta = { tags: ['admin'], diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index a6e59276fb..cdaec13a3f 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -3,7 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; import { QueueService } from '@/core/QueueService.js'; -import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts index 3d4c85e50b..8bafb3b122 100644 --- a/packages/backend/src/server/api/endpoints/ap/get.ts +++ b/packages/backend/src/server/api/endpoints/ap/get.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ApResolverService } from '@/core/remote/activitypub/ApResolverService.js'; +import { ApResolverService } from '@/core/activitypub/ApResolverService.js'; import { ApiError } from '../../error.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 0a5fc31751..c218ec4642 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -4,13 +4,13 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, NotesRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import type { CacheableLocalUser, User } from '@/models/entities/User.js'; -import { isActor, isPost, getApId } from '@/core/remote/activitypub/type.js'; +import { isActor, isPost, getApId } from '@/core/activitypub/type.js'; import type { SchemaType } from '@/misc/schema.js'; -import { ApResolverService } from '@/core/remote/activitypub/ApResolverService.js'; -import { ApDbResolverService } from '@/core/remote/activitypub/ApDbResolverService.js'; +import { ApResolverService } from '@/core/activitypub/ApResolverService.js'; +import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js'; import { MetaService } from '@/core/MetaService.js'; -import { ApPersonService } from '@/core/remote/activitypub/models/ApPersonService.js'; -import { ApNoteService } from '@/core/remote/activitypub/models/ApNoteService.js'; +import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; +import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts index 30e77aab45..c19252f198 100644 --- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts +++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ApPersonService } from '@/core/remote/activitypub/models/ApPersonService.js'; +import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { GetterService } from '@/server/api/GetterService.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 6b3b062c15..793d7c5408 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -7,7 +7,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; import { QueueService } from '@/core/QueueService.js'; import { PollService } from '@/core/PollService.js'; -import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 1e15025bf4..7932d5cd12 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -4,7 +4,7 @@ import type { UsersRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; +import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; import { ApiLoggerService } from '../../ApiLoggerService.js'; @@ -89,7 +89,7 @@ export default class extends Endpoint { private usersRepository: UsersRepository, private userEntityService: UserEntityService, - private resolveUserService: ResolveUserService, + private remoteUserResolveService: RemoteUserResolveService, private apiLoggerService: ApiLoggerService, ) { super(meta, paramDef, async (ps, me) => { @@ -121,7 +121,7 @@ export default class extends Endpoint { } else { // Lookup user if (typeof ps.host === 'string' && typeof ps.username === 'string') { - user = await this.resolveUserService.resolveUser(ps.username, ps.host).catch(err => { + user = await this.remoteUserResolveService.resolveUser(ps.username, ps.host).catch(err => { this.apiLoggerService.logger.warn(`failed to resolve remote user: ${err}`); throw new ApiError(meta.errors.failedToResolveRemoteUser); }); diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 4c3f2bfd36..3fcf8b7c0d 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -14,7 +14,7 @@ import { getNoteSummary } from '@/misc/get-note-summary.js'; import { DI } from '@/di-symbols.js'; import * as Acct from '@/misc/acct.js'; import { MetaService } from '@/core/MetaService.js'; -import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/queue/QueueModule.js'; +import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/QueueModule.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts index ba89ac329a..cad4d8af6c 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.ts @@ -1,5 +1,5 @@ -import Resolver from '../../src/remote/activitypub/resolver.js'; -import { IObject } from '../../src/remote/activitypub/type.js'; +import Resolver from '../../src/activitypub/resolver.js'; +import { IObject } from '../../src/activitypub/type.js'; type MockResponse = { type: string; diff --git a/packages/backend/test/tests/activitypub.ts b/packages/backend/test/tests/activitypub.ts index 6f549ca9c2..08ec0a59ea 100644 --- a/packages/backend/test/tests/activitypub.ts +++ b/packages/backend/test/tests/activitypub.ts @@ -29,7 +29,7 @@ describe('ActivityPub', () => { it('Minimum Actor', async () => { const { MockResolver } = await import('../misc/mock-resolver.js'); - const { createPerson } = await import('../../src/remote/activitypub/models/person.js'); + const { createPerson } = await import('../../src/activitypub/models/person.js'); const resolver = new MockResolver(); resolver._register(actor.id, actor); @@ -43,7 +43,7 @@ describe('ActivityPub', () => { it('Minimum Note', async () => { const { MockResolver } = await import('../misc/mock-resolver.js'); - const { createNote } = await import('../../src/remote/activitypub/models/note.js'); + const { createNote } = await import('../../src/activitypub/models/note.js'); const resolver = new MockResolver(); resolver._register(actor.id, actor); @@ -76,7 +76,7 @@ describe('ActivityPub', () => { it('Actor', async () => { const { MockResolver } = await import('../misc/mock-resolver.js'); - const { createPerson } = await import('../../src/remote/activitypub/models/person.js'); + const { createPerson } = await import('../../src/activitypub/models/person.js'); const resolver = new MockResolver(); resolver._register(actor.id, actor); diff --git a/packages/backend/test/tests/ap-request.ts b/packages/backend/test/tests/ap-request.ts index 299df9be32..d628f03f44 100644 --- a/packages/backend/test/tests/ap-request.ts +++ b/packages/backend/test/tests/ap-request.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import httpSignature from '@peertube/http-signature'; import { genRsaKeyPair } from '../../src/misc/gen-key-pair.js'; -import { createSignedPost, createSignedGet } from '../../src/remote/activitypub/ap-request.js'; +import { createSignedPost, createSignedGet } from '../../src/activitypub/ap-request.js'; export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => { return { diff --git a/packages/backend/test/unit/RelayService.ts b/packages/backend/test/unit/RelayService.ts index bb555648e9..5f87fea7aa 100644 --- a/packages/backend/test/unit/RelayService.ts +++ b/packages/backend/test/unit/RelayService.ts @@ -5,7 +5,7 @@ import { ModuleMocker } from 'jest-mock'; import { Test } from '@nestjs/testing'; import { GlobalModule } from '@/GlobalModule.js'; import { RelayService } from '@/core/RelayService.js'; -import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; import { QueueService } from '@/core/QueueService.js'; import { IdService } from '@/core/IdService.js'; -- cgit v1.2.3-freya From 8950114f7d494c02f6d2ff9fe11a5539cdfcfd66 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 4 Dec 2022 10:32:12 +0900 Subject: refactor: remove needless const notation --- packages/backend/src/server/ActivityPubServerService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 0ce2c9cd97..015c8f2b4c 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -433,7 +433,7 @@ export class ActivityPubServerService { fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { const note = await this.notesRepository.findOneBy({ id: request.params.note, - visibility: In(['public' as const, 'home' as const]), + visibility: In(['public', 'home']), localOnly: false, }); @@ -462,7 +462,7 @@ export class ActivityPubServerService { const note = await this.notesRepository.findOneBy({ id: request.params.note, userHost: IsNull(), - visibility: In(['public' as const, 'home' as const]), + visibility: In(['public', 'home']), localOnly: false, }); -- cgit v1.2.3-freya From bbb49457f9fb5d46402e913c92ebf77722cad6ff Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 4 Dec 2022 15:03:09 +0900 Subject: refactor: introduce bindThis decorator to bind this automaticaly --- packages/backend/src/core/AccountUpdateService.ts | 2 + packages/backend/src/core/AiService.ts | 3 + packages/backend/src/core/AntennaService.ts | 6 ++ packages/backend/src/core/AppLockService.ts | 4 + packages/backend/src/core/CaptchaService.ts | 5 ++ .../backend/src/core/CreateNotificationService.ts | 4 + .../backend/src/core/CreateSystemUserService.ts | 2 + packages/backend/src/core/CustomEmojiService.ts | 8 ++ packages/backend/src/core/DeleteAccountService.ts | 2 + packages/backend/src/core/DownloadService.ts | 4 + packages/backend/src/core/DriveService.ts | 11 +++ packages/backend/src/core/EmailService.ts | 3 + .../backend/src/core/FederatedInstanceService.ts | 2 + .../src/core/FetchInstanceMetadataService.ts | 10 +++ packages/backend/src/core/FileInfoService.ts | 11 +++ packages/backend/src/core/GlobalEventService.ts | 3 + packages/backend/src/core/HashtagService.ts | 4 + packages/backend/src/core/HttpRequestService.ts | 5 ++ packages/backend/src/core/IdService.ts | 2 + .../backend/src/core/ImageProcessingService.ts | 7 ++ packages/backend/src/core/InstanceActorService.ts | 2 + .../backend/src/core/InternalStorageService.ts | 6 ++ packages/backend/src/core/LoggerService.ts | 2 + packages/backend/src/core/MessagingService.ts | 7 ++ packages/backend/src/core/MetaService.ts | 7 +- packages/backend/src/core/MfmService.ts | 3 + packages/backend/src/core/ModerationLogService.ts | 2 + packages/backend/src/core/NoteCreateService.ts | 13 +++ packages/backend/src/core/NoteDeleteService.ts | 4 + packages/backend/src/core/NotePiningService.ts | 4 + packages/backend/src/core/NoteReadService.ts | 3 + packages/backend/src/core/NotificationService.ts | 5 ++ packages/backend/src/core/PollService.ts | 3 + packages/backend/src/core/ProxyAccountService.ts | 2 + .../backend/src/core/PushNotificationService.ts | 1 + packages/backend/src/core/QueryService.ts | 10 +++ packages/backend/src/core/QueueService.ts | 20 +++++ packages/backend/src/core/ReactionService.ts | 8 ++ packages/backend/src/core/RelayService.ts | 8 ++ packages/backend/src/core/RemoteLoggerService.ts | 1 + .../backend/src/core/RemoteUserResolveService.ts | 3 + packages/backend/src/core/S3Service.ts | 2 + packages/backend/src/core/SignupService.ts | 2 + .../src/core/TwoFactorAuthenticationService.ts | 4 + packages/backend/src/core/UserBlockingService.ts | 6 ++ packages/backend/src/core/UserCacheService.ts | 5 +- packages/backend/src/core/UserFollowingService.ts | 16 ++++ .../backend/src/core/UserKeypairStoreService.ts | 2 + packages/backend/src/core/UserListService.ts | 2 + packages/backend/src/core/UserMutingService.ts | 2 + packages/backend/src/core/UserSuspendService.ts | 3 + packages/backend/src/core/UtilityService.ts | 6 ++ .../backend/src/core/VideoProcessingService.ts | 2 + packages/backend/src/core/WebfingerService.ts | 3 + packages/backend/src/core/WebhookService.ts | 6 +- .../src/core/activitypub/ApAudienceService.ts | 5 ++ .../src/core/activitypub/ApDbResolverService.ts | 7 ++ .../core/activitypub/ApDeliverManagerService.ts | 8 ++ .../backend/src/core/activitypub/ApInboxService.ts | 28 ++++++ .../src/core/activitypub/ApLoggerService.ts | 1 + .../backend/src/core/activitypub/ApMfmService.ts | 3 + .../src/core/activitypub/ApRendererService.ts | 33 +++++++ .../src/core/activitypub/ApRequestService.ts | 9 ++ .../src/core/activitypub/ApResolverService.ts | 100 +++++++++++---------- .../src/core/activitypub/LdSignatureService.ts | 33 ++++--- .../src/core/activitypub/models/ApImageService.ts | 3 + .../core/activitypub/models/ApMentionService.ts | 3 + .../src/core/activitypub/models/ApNoteService.ts | 6 ++ .../src/core/activitypub/models/ApPersonService.ts | 8 ++ .../core/activitypub/models/ApQuestionService.ts | 3 + .../backend/src/core/chart/ChartLoggerService.ts | 1 + .../src/core/chart/ChartManagementService.ts | 2 + .../backend/src/core/chart/charts/active-users.ts | 3 + .../backend/src/core/chart/charts/ap-request.ts | 4 + packages/backend/src/core/chart/charts/drive.ts | 2 + .../backend/src/core/chart/charts/federation.ts | 3 + packages/backend/src/core/chart/charts/hashtag.ts | 2 + packages/backend/src/core/chart/charts/instance.ts | 8 ++ packages/backend/src/core/chart/charts/notes.ts | 2 + .../src/core/chart/charts/per-user-drive.ts | 2 + .../src/core/chart/charts/per-user-following.ts | 2 + .../src/core/chart/charts/per-user-notes.ts | 2 + .../src/core/chart/charts/per-user-reactions.ts | 2 + .../backend/src/core/chart/charts/test-grouped.ts | 2 + .../src/core/chart/charts/test-intersection.ts | 3 + .../backend/src/core/chart/charts/test-unique.ts | 2 + packages/backend/src/core/chart/charts/test.ts | 3 + packages/backend/src/core/chart/charts/users.ts | 2 + packages/backend/src/core/chart/core.ts | 11 +++ .../core/entities/AbuseUserReportEntityService.ts | 3 + .../src/core/entities/AntennaEntityService.ts | 2 + .../backend/src/core/entities/AppEntityService.ts | 2 + .../src/core/entities/AuthSessionEntityService.ts | 2 + .../src/core/entities/BlockingEntityService.ts | 3 + .../src/core/entities/ChannelEntityService.ts | 2 + .../backend/src/core/entities/ClipEntityService.ts | 3 + .../src/core/entities/DriveFileEntityService.ts | 11 +++ .../src/core/entities/DriveFolderEntityService.ts | 2 + .../src/core/entities/EmojiEntityService.ts | 3 + .../core/entities/FollowRequestEntityService.ts | 2 + .../src/core/entities/FollowingEntityService.ts | 7 ++ .../src/core/entities/GalleryLikeEntityService.ts | 3 + .../src/core/entities/GalleryPostEntityService.ts | 3 + .../src/core/entities/HashtagEntityService.ts | 3 + .../src/core/entities/InstanceEntityService.ts | 3 + .../core/entities/MessagingMessageEntityService.ts | 2 + .../core/entities/ModerationLogEntityService.ts | 3 + .../src/core/entities/MutingEntityService.ts | 3 + .../backend/src/core/entities/NoteEntityService.ts | 8 ++ .../src/core/entities/NoteFavoriteEntityService.ts | 3 + .../src/core/entities/NoteReactionEntityService.ts | 2 + .../src/core/entities/NotificationEntityService.ts | 3 + .../backend/src/core/entities/PageEntityService.ts | 3 + .../src/core/entities/PageLikeEntityService.ts | 3 + .../src/core/entities/SigninEntityService.ts | 2 + .../backend/src/core/entities/UserEntityService.ts | 12 +++ .../src/core/entities/UserGroupEntityService.ts | 2 + .../entities/UserGroupInvitationEntityService.ts | 3 + .../src/core/entities/UserListEntityService.ts | 2 + packages/backend/src/daemons/JanitorService.ts | 3 + packages/backend/src/daemons/QueueStatsService.ts | 3 + packages/backend/src/daemons/ServerStatsService.ts | 3 + packages/backend/src/decorators.ts | 41 +++++++++ packages/backend/src/logger.ts | 8 ++ packages/backend/src/misc/cache.ts | 7 ++ packages/backend/src/misc/i18n.ts | 3 +- packages/backend/src/postgre.ts | 8 ++ .../backend/src/queue/DbQueueProcessorsService.ts | 2 + .../queue/ObjectStorageQueueProcessorsService.ts | 2 + packages/backend/src/queue/QueueLoggerService.ts | 1 + .../backend/src/queue/QueueProcessorService.ts | 2 + .../src/queue/SystemQueueProcessorsService.ts | 2 + .../CheckExpiredMutingsProcessorService.ts | 2 + .../processors/CleanChartsProcessorService.ts | 2 + .../src/queue/processors/CleanProcessorService.ts | 2 + .../processors/CleanRemoteFilesProcessorService.ts | 2 + .../processors/DeleteAccountProcessorService.ts | 2 + .../processors/DeleteDriveFilesProcessorService.ts | 2 + .../queue/processors/DeleteFileProcessorService.ts | 2 + .../queue/processors/DeliverProcessorService.ts | 2 + .../EndedPollNotificationProcessorService.ts | 2 + .../processors/ExportBlockingProcessorService.ts | 2 + .../ExportCustomEmojisProcessorService.ts | 2 + .../processors/ExportFollowingProcessorService.ts | 2 + .../processors/ExportMutingProcessorService.ts | 2 + .../processors/ExportNotesProcessorService.ts | 2 + .../processors/ExportUserListsProcessorService.ts | 2 + .../processors/ImportBlockingProcessorService.ts | 2 + .../ImportCustomEmojisProcessorService.ts | 2 + .../processors/ImportFollowingProcessorService.ts | 2 + .../processors/ImportMutingProcessorService.ts | 2 + .../processors/ImportUserListsProcessorService.ts | 2 + .../src/queue/processors/InboxProcessorService.ts | 2 + .../processors/ResyncChartsProcessorService.ts | 2 + .../queue/processors/TickChartsProcessorService.ts | 2 + .../processors/WebhookDeliverProcessorService.ts | 2 + .../backend/src/server/ActivityPubServerService.ts | 12 ++- packages/backend/src/server/FileServerService.ts | 6 +- .../backend/src/server/MediaProxyServerService.ts | 5 +- .../backend/src/server/NodeinfoServerService.ts | 5 +- packages/backend/src/server/ServerService.ts | 2 + .../backend/src/server/WellKnownServerService.ts | 4 +- packages/backend/src/server/api/ApiCallService.ts | 7 ++ .../backend/src/server/api/ApiLoggerService.ts | 1 + .../backend/src/server/api/ApiServerService.ts | 4 +- .../backend/src/server/api/AuthenticateService.ts | 2 + packages/backend/src/server/api/GetterService.ts | 5 ++ .../backend/src/server/api/RateLimiterService.ts | 2 + .../backend/src/server/api/SigninApiService.ts | 2 + packages/backend/src/server/api/SigninService.ts | 2 + .../backend/src/server/api/SignupApiService.ts | 3 + .../src/server/api/StreamingApiServerService.ts | 2 + .../src/server/api/endpoints/admin/suspend-user.ts | 3 + .../backend/src/server/api/endpoints/ap/show.ts | 3 + .../server/api/integration/DiscordServerService.ts | 6 +- .../server/api/integration/GithubServerService.ts | 6 +- .../server/api/integration/TwitterServerService.ts | 6 +- .../src/server/api/stream/ChannelsService.ts | 2 + packages/backend/src/server/api/stream/channel.ts | 2 + .../src/server/api/stream/channels/admin.ts | 3 + .../src/server/api/stream/channels/antenna.ts | 7 +- .../src/server/api/stream/channels/channel.ts | 11 ++- .../src/server/api/stream/channels/drive.ts | 3 + .../server/api/stream/channels/global-timeline.ts | 7 +- .../src/server/api/stream/channels/hashtag.ts | 7 +- .../server/api/stream/channels/home-timeline.ts | 7 +- .../server/api/stream/channels/hybrid-timeline.ts | 7 +- .../server/api/stream/channels/local-timeline.ts | 7 +- .../backend/src/server/api/stream/channels/main.ts | 3 + .../server/api/stream/channels/messaging-index.ts | 3 + .../src/server/api/stream/channels/messaging.ts | 13 ++- .../src/server/api/stream/channels/queue-stats.ts | 10 ++- .../src/server/api/stream/channels/server-stats.ts | 10 ++- .../src/server/api/stream/channels/user-list.ts | 13 ++- packages/backend/src/server/api/stream/index.ts | 32 ++++++- .../backend/src/server/web/ClientServerService.ts | 5 +- packages/backend/src/server/web/FeedService.ts | 2 + .../backend/src/server/web/UrlPreviewService.ts | 3 + packages/backend/test/misc/mock-resolver.ts | 1 + 199 files changed, 969 insertions(+), 96 deletions(-) create mode 100644 packages/backend/src/decorators.ts (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/core/AccountUpdateService.ts b/packages/backend/src/core/AccountUpdateService.ts index a5ab4fdfce..5f6dfca0ca 100644 --- a/packages/backend/src/core/AccountUpdateService.ts +++ b/packages/backend/src/core/AccountUpdateService.ts @@ -7,6 +7,7 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { RelayService } from '@/core/RelayService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class AccountUpdateService { @@ -24,6 +25,7 @@ export class AccountUpdateService { ) { } + @bindThis public async publishToFollowers(userId: User['id']) { const user = await this.usersRepository.findOneBy({ id: userId }); if (user == null) throw new Error('user not found'); diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index 15084b8ff1..aee9504c85 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -12,6 +12,7 @@ const _dirname = dirname(_filename); const REQUIRED_CPU_FLAGS = ['avx2', 'fma']; let isSupportedCpu: undefined | boolean = undefined; +import { bindThis } from '@/decorators.js'; @Injectable() export class AiService { @@ -23,6 +24,7 @@ export class AiService { ) { } + @bindThis public async detectSensitive(path: string): Promise { try { if (isSupportedCpu === undefined) { @@ -53,6 +55,7 @@ export class AiService { } } + @bindThis private async getCpuFlags(): Promise { const str = await si.cpuFlags(); return str.split(/\s+/); diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 8046ba5311..9c120c993d 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -13,6 +13,7 @@ import { DI } from '@/di-symbols.js'; import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; import { UtilityService } from '@/core/UtilityService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; @Injectable() export class AntennaService implements OnApplicationShutdown { @@ -56,10 +57,12 @@ export class AntennaService implements OnApplicationShutdown { this.redisSubscriber.on('message', this.onRedisMessage); } + @bindThis public onApplicationShutdown(signal?: string | undefined) { this.redisSubscriber.off('message', this.onRedisMessage); } + @bindThis private async onRedisMessage(_: string, data: string): Promise { const obj = JSON.parse(data); @@ -81,6 +84,7 @@ export class AntennaService implements OnApplicationShutdown { } } + @bindThis public async addNoteToAntenna(antenna: Antenna, note: Note, noteUser: { id: User['id']; }): Promise { // 通知しない設定になっているか、自分自身の投稿なら既読にする const read = !antenna.notify || (antenna.userId === noteUser.id); @@ -133,6 +137,7 @@ export class AntennaService implements OnApplicationShutdown { /** * noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい */ + @bindThis public async checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise { if (note.visibility === 'specified') return false; @@ -217,6 +222,7 @@ export class AntennaService implements OnApplicationShutdown { return true; } + @bindThis public async getAntennas() { if (!this.antennasFetched) { this.antennas = await this.antennasRepository.find(); diff --git a/packages/backend/src/core/AppLockService.ts b/packages/backend/src/core/AppLockService.ts index 04b3d8b112..1f512b5790 100644 --- a/packages/backend/src/core/AppLockService.ts +++ b/packages/backend/src/core/AppLockService.ts @@ -8,6 +8,7 @@ import { DI } from '@/di-symbols.js'; * Retry delay (ms) for lock acquisition */ const retryDelay = 100; +import { bindThis } from '@/decorators.js'; @Injectable() export class AppLockService { @@ -26,14 +27,17 @@ export class AppLockService { * @param timeout Lock timeout (ms), The timeout releases previous lock. * @returns Unlock function */ + @bindThis public getApLock(uri: string, timeout = 30 * 1000): Promise<() => void> { return this.lock(`ap-object:${uri}`, timeout); } + @bindThis public getFetchInstanceMetadataLock(host: string, timeout = 30 * 1000): Promise<() => void> { return this.lock(`instance:${host}`, timeout); } + @bindThis public getChartInsertLock(lockKey: string, timeout = 30 * 1000): Promise<() => void> { return this.lock(`chart-insert:${lockKey}`, timeout); } diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index b60271812c..0207cf58a0 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -3,6 +3,7 @@ import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { bindThis } from '@/decorators.js'; type CaptchaResponse = { success: boolean; @@ -19,6 +20,7 @@ export class CaptchaService { ) { } + @bindThis private async getCaptchaResponse(url: string, secret: string, response: string): Promise { const params = new URLSearchParams({ secret, @@ -45,6 +47,7 @@ export class CaptchaService { return await res.json() as CaptchaResponse; } + @bindThis public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise { if (response == null) { throw 'recaptcha-failed: no response provided'; @@ -60,6 +63,7 @@ export class CaptchaService { } } + @bindThis public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise { if (response == null) { throw 'hcaptcha-failed: no response provided'; @@ -75,6 +79,7 @@ export class CaptchaService { } } + @bindThis public async verifyTurnstile(secret: string, response: string | null | undefined): Promise { if (response == null) { throw 'turnstile-failed: no response provided'; diff --git a/packages/backend/src/core/CreateNotificationService.ts b/packages/backend/src/core/CreateNotificationService.ts index 504661c3bd..f376b7b9cf 100644 --- a/packages/backend/src/core/CreateNotificationService.ts +++ b/packages/backend/src/core/CreateNotificationService.ts @@ -7,6 +7,7 @@ import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js'; import { PushNotificationService } from '@/core/PushNotificationService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class CreateNotificationService { @@ -30,6 +31,7 @@ export class CreateNotificationService { ) { } + @bindThis public async createNotification( notifieeId: User['id'], type: Notification['type'], @@ -90,6 +92,7 @@ export class CreateNotificationService { // TODO: locale ファイルをクライアント用とサーバー用で分けたい + @bindThis private async emailNotificationFollow(userId: User['id'], follower: User) { /* const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); @@ -101,6 +104,7 @@ export class CreateNotificationService { */ } + @bindThis private async emailNotificationReceiveFollowRequest(userId: User['id'], follower: User) { /* const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); diff --git a/packages/backend/src/core/CreateSystemUserService.ts b/packages/backend/src/core/CreateSystemUserService.ts index 71f50d7cb3..1e753f65cc 100644 --- a/packages/backend/src/core/CreateSystemUserService.ts +++ b/packages/backend/src/core/CreateSystemUserService.ts @@ -10,6 +10,7 @@ import { UserKeypair } from '@/models/entities/UserKeypair.js'; import { UsedUsername } from '@/models/entities/UsedUsername.js'; import { DI } from '@/di-symbols.js'; import generateNativeUserToken from '@/misc/generate-native-user-token.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class CreateSystemUserService { @@ -21,6 +22,7 @@ export class CreateSystemUserService { ) { } + @bindThis public async createSystemUser(username: string): Promise { const password = uuid(); diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 3319f3efa8..36f88fd743 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -20,6 +20,7 @@ type PopulatedEmoji = { name: string; url: string; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class CustomEmojiService { @@ -43,6 +44,7 @@ export class CustomEmojiService { this.cache = new Cache(1000 * 60 * 60 * 12); } + @bindThis public async add(data: { driveFile: DriveFile; name: string; @@ -67,6 +69,7 @@ export class CustomEmojiService { return emoji; } + @bindThis private normalizeHost(src: string | undefined, noteUserHost: string | null): string | null { // クエリに使うホスト let host = src === '.' ? null // .はローカルホスト (ここがマッチするのはリアクションのみ) @@ -79,6 +82,7 @@ export class CustomEmojiService { return host; } + @bindThis private parseEmojiStr(emojiName: string, noteUserHost: string | null) { const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/); if (!match) return { name: null, host: null }; @@ -97,6 +101,7 @@ export class CustomEmojiService { * @param noteUserHost ノートやユーザープロフィールの所有者のホスト * @returns 絵文字情報, nullは未マッチを意味する */ + @bindThis public async populateEmoji(emojiName: string, noteUserHost: string | null): Promise { const { name, host } = this.parseEmojiStr(emojiName, noteUserHost); if (name == null) return null; @@ -123,11 +128,13 @@ export class CustomEmojiService { /** * 複数の添付用絵文字情報を解決する (キャシュ付き, 存在しないものは結果から除外される) */ + @bindThis public async populateEmojis(emojiNames: string[], noteUserHost: string | null): Promise { const emojis = await Promise.all(emojiNames.map(x => this.populateEmoji(x, noteUserHost))); return emojis.filter((x): x is PopulatedEmoji => x != null); } + @bindThis public aggregateNoteEmojis(notes: Note[]) { let emojis: { name: string | null; host: string | null; }[] = []; for (const note of notes) { @@ -154,6 +161,7 @@ export class CustomEmojiService { /** * 与えられた絵文字のリストをデータベースから取得し、キャッシュに追加します */ + @bindThis public async prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise { const notCachedEmojis = emojis.filter(emoji => this.cache.get(`${emoji.name} ${emoji.host}`) == null); const emojisQuery: any[] = []; diff --git a/packages/backend/src/core/DeleteAccountService.ts b/packages/backend/src/core/DeleteAccountService.ts index 53d48c450b..e42c738707 100644 --- a/packages/backend/src/core/DeleteAccountService.ts +++ b/packages/backend/src/core/DeleteAccountService.ts @@ -4,6 +4,7 @@ import { QueueService } from '@/core/QueueService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DeleteAccountService { @@ -17,6 +18,7 @@ export class DeleteAccountService { ) { } + @bindThis public async deleteAccount(user: { id: string; host: string | null; diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts index 25965b7ac4..9097bb08e0 100644 --- a/packages/backend/src/core/DownloadService.ts +++ b/packages/backend/src/core/DownloadService.ts @@ -15,6 +15,7 @@ import { LoggerService } from '@/core/LoggerService.js'; import type Logger from '@/logger.js'; const pipeline = util.promisify(stream.pipeline); +import { bindThis } from '@/decorators.js'; @Injectable() export class DownloadService { @@ -30,6 +31,7 @@ export class DownloadService { this.logger = this.loggerService.getLogger('download'); } + @bindThis public async downloadUrl(url: string, path: string): Promise { this.logger.info(`Downloading ${chalk.cyan(url)} ...`); @@ -94,6 +96,7 @@ export class DownloadService { this.logger.succ(`Download finished: ${chalk.cyan(url)}`); } + @bindThis public async downloadTextFile(url: string): Promise { // Create temp file const [path, cleanup] = await createTemp(); @@ -112,6 +115,7 @@ export class DownloadService { } } + @bindThis private isPrivateIp(ip: string): boolean { for (const net of this.config.allowedPrivateNetworks ?? []) { const cidr = new IPCIDR(net); diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 1d2ba5df8c..b83047dbc2 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -71,6 +71,7 @@ type UploadFromUrlArgs = { requestIp?: string | null; requestHeaders?: Record | null; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class DriveService { @@ -122,6 +123,7 @@ export class DriveService { * @param hash Hash for original * @param size Size for original */ + @bindThis private async save(file: DriveFile, path: string, name: string, type: string, hash: string, size: number): Promise { // thunbnail, webpublic を必要なら生成 const alts = await this.generateAlts(path, type, !file.uri); @@ -242,6 +244,7 @@ export class DriveService { * @param type Content-Type for original * @param generateWeb Generate webpublic or not */ + @bindThis public async generateAlts(path: string, type: string, generateWeb: boolean) { if (type.startsWith('video/')) { try { @@ -345,6 +348,7 @@ export class DriveService { /** * Upload to ObjectStorage */ + @bindThis private async upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { if (type === 'image/apng') type = 'image/png'; if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream'; @@ -372,6 +376,7 @@ export class DriveService { if (result) this.registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`); } + @bindThis private async deleteOldFile(user: IRemoteUser) { const q = this.driveFilesRepository.createQueryBuilder('file') .where('file.userId = :userId', { userId: user.id }) @@ -398,6 +403,7 @@ export class DriveService { * Add file to drive * */ + @bindThis public async addFile({ user, path, @@ -601,6 +607,7 @@ export class DriveService { return file; } + @bindThis public async deleteFile(file: DriveFile, isExpired = false) { if (file.storedInternal) { this.internalStorageService.del(file.accessKey!); @@ -627,6 +634,7 @@ export class DriveService { this.deletePostProcess(file, isExpired); } + @bindThis public async deleteFileSync(file: DriveFile, isExpired = false) { if (file.storedInternal) { this.internalStorageService.del(file.accessKey!); @@ -657,6 +665,7 @@ export class DriveService { this.deletePostProcess(file, isExpired); } + @bindThis private async deletePostProcess(file: DriveFile, isExpired = false) { // リモートファイル期限切れ削除後は直リンクにする if (isExpired && file.userHost !== null && file.uri != null) { @@ -683,6 +692,7 @@ export class DriveService { } } + @bindThis public async deleteObjectStorageFile(key: string) { const meta = await this.metaService.fetch(); @@ -694,6 +704,7 @@ export class DriveService { }).promise(); } + @bindThis public async uploadFromUrl({ url, user, diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 019b9087cd..59932a5b88 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -7,6 +7,7 @@ import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import type { UserProfilesRepository } from '@/models/index.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class EmailService { @@ -25,6 +26,7 @@ export class EmailService { this.logger = this.loggerService.getLogger('email'); } + @bindThis public async sendEmail(to: string, subject: string, html: string, text: string) { const meta = await this.metaService.fetch(true); @@ -141,6 +143,7 @@ export class EmailService { } } + @bindThis public async validateEmailForAccount(emailAddress: string): Promise<{ available: boolean; reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp'; diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index a05c95a2ae..97745a1168 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -5,6 +5,7 @@ import { Cache } from '@/misc/cache.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class FederatedInstanceService { @@ -20,6 +21,7 @@ export class FederatedInstanceService { this.cache = new Cache(1000 * 60 * 60); } + @bindThis public async registerOrFetchInstanceDoc(host: string): Promise { host = this.utilityService.toPuny(host); diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index b92ebe6059..4d4945ca7f 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -30,6 +30,7 @@ type NodeInfo = { themeColor?: unknown; }; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class FetchInstanceMetadataService { @@ -46,6 +47,7 @@ export class FetchInstanceMetadataService { this.logger = this.loggerService.getLogger('metadata', 'cyan'); } + @bindThis public async fetchInstanceMetadata(instance: Instance, force = false): Promise { const unlock = await this.appLockService.getFetchInstanceMetadataLock(instance.host); @@ -105,6 +107,7 @@ export class FetchInstanceMetadataService { } } + @bindThis private async fetchNodeinfo(instance: Instance): Promise { this.logger.info(`Fetching nodeinfo of ${instance.host} ...`); @@ -148,6 +151,7 @@ export class FetchInstanceMetadataService { } } + @bindThis private async fetchDom(instance: Instance): Promise { this.logger.info(`Fetching HTML of ${instance.host} ...`); @@ -161,6 +165,7 @@ export class FetchInstanceMetadataService { return doc; } + @bindThis private async fetchManifest(instance: Instance): Promise | null> { const url = 'https://' + instance.host; @@ -171,6 +176,7 @@ export class FetchInstanceMetadataService { return manifest; } + @bindThis private async fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] | null): Promise { const url = 'https://' + instance.host; @@ -198,6 +204,7 @@ export class FetchInstanceMetadataService { return null; } + @bindThis private async fetchIconUrl(instance: Instance, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) { const url = 'https://' + instance.host; @@ -226,6 +233,7 @@ export class FetchInstanceMetadataService { return null; } + @bindThis private async getThemeColor(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { const themeColor = info?.metadata?.themeColor ?? doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') ?? manifest?.theme_color; @@ -237,6 +245,7 @@ export class FetchInstanceMetadataService { return null; } + @bindThis private async getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (info && info.metadata) { if (typeof info.metadata.nodeName === 'string') { @@ -261,6 +270,7 @@ export class FetchInstanceMetadataService { return null; } + @bindThis private async getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (info && info.metadata) { if (typeof info.metadata.nodeDescription === 'string') { diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts index fd8a4fdd3a..bea1b3402e 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -14,6 +14,7 @@ import sharp from 'sharp'; import { encode } from 'blurhash'; import { createTempDir } from '@/misc/create-temp.js'; import { AiService } from '@/core/AiService.js'; +import { bindThis } from '@/decorators.js'; const pipeline = util.promisify(stream.pipeline); @@ -42,6 +43,7 @@ const TYPE_SVG = { mime: 'image/svg+xml', ext: 'svg', }; + @Injectable() export class FileInfoService { constructor( @@ -52,6 +54,7 @@ export class FileInfoService { /** * Get file information */ + @bindThis public async getFileInfo(path: string, opts: { skipSensitiveDetection: boolean; sensitiveThreshold?: number; @@ -135,6 +138,7 @@ export class FileInfoService { }; } + @bindThis private async detectSensitivity(source: string, mime: string, sensitiveThreshold: number, sensitiveThresholdForPorn: number, analyzeVideo: boolean): Promise<[sensitive: boolean, porn: boolean]> { let sensitive = false; let porn = false; @@ -269,6 +273,7 @@ export class FileInfoService { } } + @bindThis private exists(path: string): Promise { return fs.promises.access(path).then(() => true, () => false); } @@ -276,6 +281,7 @@ export class FileInfoService { /** * Detect MIME Type and extension */ + @bindThis public async detectType(path: string): Promise<{ mime: string; ext: string | null; @@ -312,6 +318,7 @@ export class FileInfoService { /** * Check the file is SVG or not */ + @bindThis public async checkSvg(path: string) { try { const size = await this.getFileSize(path); @@ -325,6 +332,7 @@ export class FileInfoService { /** * Get file size */ + @bindThis public async getFileSize(path: string): Promise { const getStat = util.promisify(fs.stat); return (await getStat(path)).size; @@ -333,6 +341,7 @@ export class FileInfoService { /** * Calculate MD5 hash */ + @bindThis private async calcHash(path: string): Promise { const hash = crypto.createHash('md5').setEncoding('hex'); await pipeline(fs.createReadStream(path), hash); @@ -342,6 +351,7 @@ export class FileInfoService { /** * Detect dimensions of image */ + @bindThis private async detectImageSize(path: string): Promise<{ width: number; height: number; @@ -358,6 +368,7 @@ export class FileInfoService { /** * Calculate average color of image */ + @bindThis private getBlurhash(path: string): Promise { return new Promise((resolve, reject) => { sharp(path) diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index df0c9b5ccb..90f911027e 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -25,6 +25,7 @@ import type { import type { Packed } from '@/misc/schema.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class GlobalEventService { @@ -37,6 +38,7 @@ export class GlobalEventService { ) { } + @bindThis private publish(channel: StreamChannels, type: string | null, value?: any): void { const message = type == null ? value : value == null ? { type: type, body: null } : @@ -99,6 +101,7 @@ export class GlobalEventService { this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value); } + @bindThis public publishNotesStream(note: Packed<'Note'>): void { this.publish('notesStream', null, note); } diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts index 5ca058e9a4..309cfe8c3f 100644 --- a/packages/backend/src/core/HashtagService.ts +++ b/packages/backend/src/core/HashtagService.ts @@ -7,6 +7,7 @@ import type { Hashtag } from '@/models/entities/Hashtag.js'; import HashtagChart from '@/core/chart/charts/hashtag.js'; import type { HashtagsRepository, UsersRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class HashtagService { @@ -23,12 +24,14 @@ export class HashtagService { ) { } + @bindThis public async updateHashtags(user: { id: User['id']; host: User['host']; }, tags: string[]) { for (const tag of tags) { await this.updateHashtag(user, tag); } } + @bindThis public async updateUsertags(user: User, tags: string[]) { for (const tag of tags) { await this.updateHashtag(user, tag, true, true); @@ -39,6 +42,7 @@ export class HashtagService { } } + @bindThis public async updateHashtag(user: { id: User['id']; host: User['host']; }, tag: string, isUserAttached = false, inc = true) { tag = normalizeForSearch(tag); diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index 396fefad1c..d998307973 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { StatusError } from '@/misc/status-error.js'; +import { bindThis } from '@/decorators.js'; import type { Response } from 'node-fetch'; import type { URL } from 'node:url'; @@ -84,6 +85,7 @@ export class HttpRequestService { * @param url URL * @param bypassProxy Allways bypass proxy */ + @bindThis public getAgentByUrl(url: URL, bypassProxy = false): http.Agent | https.Agent { if (bypassProxy || (this.config.proxyBypassHosts || []).includes(url.hostname)) { return url.protocol === 'http:' ? this.http : this.https; @@ -92,6 +94,7 @@ export class HttpRequestService { } } + @bindThis public async getJson(url: string, accept = 'application/json, */*', timeout = 10000, headers?: Record): Promise { const res = await this.getResponse({ url, @@ -106,6 +109,7 @@ export class HttpRequestService { return await res.json(); } + @bindThis public async getHtml(url: string, accept = 'text/html, */*', timeout = 10000, headers?: Record): Promise { const res = await this.getResponse({ url, @@ -120,6 +124,7 @@ export class HttpRequestService { return await res.text(); } + @bindThis public async getResponse(args: { url: string, method: string, diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts index 997be17937..0e8a7b13ad 100644 --- a/packages/backend/src/core/IdService.ts +++ b/packages/backend/src/core/IdService.ts @@ -6,6 +6,7 @@ import { genAid } from '@/misc/id/aid.js'; import { genMeid } from '@/misc/id/meid.js'; import { genMeidg } from '@/misc/id/meidg.js'; import { genObjectId } from '@/misc/id/object-id.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class IdService { @@ -18,6 +19,7 @@ export class IdService { this.metohd = config.id.toLowerCase(); } + @bindThis public genId(date?: Date): string { if (!date || (date > new Date())) date = new Date(); diff --git a/packages/backend/src/core/ImageProcessingService.ts b/packages/backend/src/core/ImageProcessingService.ts index 3a50361a42..3a61873044 100644 --- a/packages/backend/src/core/ImageProcessingService.ts +++ b/packages/backend/src/core/ImageProcessingService.ts @@ -8,6 +8,7 @@ export type IImage = { ext: string | null; type: string; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class ImageProcessingService { @@ -21,10 +22,12 @@ export class ImageProcessingService { * Convert to JPEG * with resize, remove metadata, resolve orientation, stop animation */ + @bindThis public async convertToJpeg(path: string, width: number, height: number): Promise { return this.convertSharpToJpeg(await sharp(path), width, height); } + @bindThis public async convertSharpToJpeg(sharp: sharp.Sharp, width: number, height: number): Promise { const data = await sharp .resize(width, height, { @@ -49,10 +52,12 @@ export class ImageProcessingService { * Convert to WebP * with resize, remove metadata, resolve orientation, stop animation */ + @bindThis public async convertToWebp(path: string, width: number, height: number, quality = 85): Promise { return this.convertSharpToWebp(await sharp(path), width, height, quality); } + @bindThis public async convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number, quality = 85): Promise { const data = await sharp .resize(width, height, { @@ -76,10 +81,12 @@ export class ImageProcessingService { * Convert to PNG * with resize, remove metadata, resolve orientation, stop animation */ + @bindThis public async convertToPng(path: string, width: number, height: number): Promise { return this.convertSharpToPng(await sharp(path), width, height); } + @bindThis public async convertSharpToPng(sharp: sharp.Sharp, width: number, height: number): Promise { const data = await sharp .resize(width, height, { diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts index f35a28147d..abd6685d61 100644 --- a/packages/backend/src/core/InstanceActorService.ts +++ b/packages/backend/src/core/InstanceActorService.ts @@ -7,6 +7,7 @@ import { DI } from '@/di-symbols.js'; import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; const ACTOR_USERNAME = 'instance.actor' as const; +import { bindThis } from '@/decorators.js'; @Injectable() export class InstanceActorService { @@ -21,6 +22,7 @@ export class InstanceActorService { this.cache = new Cache(Infinity); } + @bindThis public async getInstanceActor(): Promise { const cached = this.cache.get(null); if (cached) return cached; diff --git a/packages/backend/src/core/InternalStorageService.ts b/packages/backend/src/core/InternalStorageService.ts index 6d2a9b2db6..e32cbf645c 100644 --- a/packages/backend/src/core/InternalStorageService.ts +++ b/packages/backend/src/core/InternalStorageService.ts @@ -10,6 +10,7 @@ const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); const path = Path.resolve(_dirname, '../../../../files'); +import { bindThis } from '@/decorators.js'; @Injectable() export class InternalStorageService { @@ -19,26 +20,31 @@ export class InternalStorageService { ) { } + @bindThis public resolvePath(key: string) { return Path.resolve(path, key); } + @bindThis public read(key: string) { return fs.createReadStream(this.resolvePath(key)); } + @bindThis public saveFromPath(key: string, srcPath: string) { fs.mkdirSync(path, { recursive: true }); fs.copyFileSync(srcPath, this.resolvePath(key)); return `${this.config.url}/files/${key}`; } + @bindThis public saveFromBuffer(key: string, data: Buffer) { fs.mkdirSync(path, { recursive: true }); fs.writeFileSync(this.resolvePath(key), data); return `${this.config.url}/files/${key}`; } + @bindThis public del(key: string) { fs.unlink(this.resolvePath(key), () => {}); } diff --git a/packages/backend/src/core/LoggerService.ts b/packages/backend/src/core/LoggerService.ts index a3192c0262..4303f3ae22 100644 --- a/packages/backend/src/core/LoggerService.ts +++ b/packages/backend/src/core/LoggerService.ts @@ -3,6 +3,7 @@ import * as SyslogPro from 'syslog-pro'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class LoggerService { @@ -27,6 +28,7 @@ export class LoggerService { } } + @bindThis public getLogger(domain: string, color?: string | undefined, store?: boolean) { return new Logger(domain, color, store, this.syslogClient); } diff --git a/packages/backend/src/core/MessagingService.ts b/packages/backend/src/core/MessagingService.ts index 9de28ad8db..f4a1090658 100644 --- a/packages/backend/src/core/MessagingService.ts +++ b/packages/backend/src/core/MessagingService.ts @@ -17,6 +17,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; import { PushNotificationService } from '@/core/PushNotificationService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class MessagingService { @@ -46,6 +47,7 @@ export class MessagingService { ) { } + @bindThis public async createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: CacheableUser | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) { const message = { id: this.idService.genId(), @@ -140,11 +142,13 @@ export class MessagingService { return messageObj; } + @bindThis public async deleteMessage(message: MessagingMessage) { await this.messagingMessagesRepository.delete(message.id); this.postDeleteMessage(message); } + @bindThis private async postDeleteMessage(message: MessagingMessage) { if (message.recipientId) { const user = await this.usersRepository.findOneByOrFail({ id: message.userId }); @@ -165,6 +169,7 @@ export class MessagingService { /** * Mark messages as read */ + @bindThis public async readUserMessagingMessage( userId: User['id'], otherpartyId: User['id'], @@ -220,6 +225,7 @@ export class MessagingService { /** * Mark messages as read */ + @bindThis public async readGroupMessagingMessage( userId: User['id'], groupId: UserGroup['id'], @@ -284,6 +290,7 @@ export class MessagingService { } } + @bindThis public async deliverReadActivity(user: { id: User['id']; host: null; }, recipient: IRemoteUser, messages: MessagingMessage | MessagingMessage[]) { messages = toArray(messages).filter(x => x.uri); const contents = messages.map(x => this.apRendererService.renderRead(user, x)); diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index c3d41bfccb..ff05779aee 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -5,6 +5,7 @@ import { DI } from '@/di-symbols.js'; import { Meta } from '@/models/entities/Meta.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; @Injectable() export class MetaService implements OnApplicationShutdown { @@ -20,7 +21,7 @@ export class MetaService implements OnApplicationShutdown { private globalEventService: GlobalEventService, ) { - this.onMessage = this.onMessage.bind(this); + //this.onMessage = this.onMessage.bind(this); if (process.env.NODE_ENV !== 'test') { this.intervalId = setInterval(() => { @@ -34,6 +35,7 @@ export class MetaService implements OnApplicationShutdown { this.redisSubscriber.on('message', this.onMessage); } + @bindThis private async onMessage(_: string, data: string): Promise { const obj = JSON.parse(data); @@ -50,6 +52,7 @@ export class MetaService implements OnApplicationShutdown { } } + @bindThis public async fetch(noCache = false): Promise { if (!noCache && this.cache) return this.cache; @@ -84,6 +87,7 @@ export class MetaService implements OnApplicationShutdown { }); } + @bindThis public async update(data: Partial): Promise { const updated = await this.db.transaction(async transactionalEntityManager => { const metas = await transactionalEntityManager.find(Meta, { @@ -114,6 +118,7 @@ export class MetaService implements OnApplicationShutdown { return updated; } + @bindThis public onApplicationShutdown(signal?: string | undefined) { clearInterval(this.intervalId); this.redisSubscriber.off('message', this.onMessage); diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index 2e03bf3cc0..d53623baaa 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -14,6 +14,7 @@ const treeAdapter = TreeAdapter.defaultTreeAdapter; const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/; const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/; +import { bindThis } from '@/decorators.js'; @Injectable() export class MfmService { @@ -23,6 +24,7 @@ export class MfmService { ) { } + @bindThis public fromHtml(html: string, hashtagNames?: string[]): string { // some AP servers like Pixelfed use br tags as well as newlines html = html.replace(/\r?\n/gi, '\n'); @@ -228,6 +230,7 @@ export class MfmService { } } + @bindThis public toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = []) { if (nodes == null) { return null; diff --git a/packages/backend/src/core/ModerationLogService.ts b/packages/backend/src/core/ModerationLogService.ts index 81ae322b95..80e8cb9e52 100644 --- a/packages/backend/src/core/ModerationLogService.ts +++ b/packages/backend/src/core/ModerationLogService.ts @@ -3,6 +3,7 @@ import { DI } from '@/di-symbols.js'; import type { ModerationLogsRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { IdService } from '@/core/IdService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ModerationLogService { @@ -14,6 +15,7 @@ export class ModerationLogService { ) { } + @bindThis public async insertModerationLog(moderator: { id: User['id'] }, type: string, info?: Record) { await this.moderationLogsRepository.insert({ id: this.idService.genId(), diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index cf1566a5e8..a41df28050 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -64,6 +64,7 @@ class NotificationManager { this.queue = []; } + @bindThis public push(notifiee: ILocalUser['id'], reason: NotificationType) { // 自分自身へは通知しない if (this.notifier.id === notifiee) return; @@ -83,6 +84,7 @@ class NotificationManager { } } + @bindThis public async deliver() { for (const x of this.queue) { // ミュート情報を取得 @@ -130,6 +132,7 @@ type Option = { url?: string | null; app?: App | null; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class NoteCreateService { @@ -188,6 +191,7 @@ export class NoteCreateService { private instanceChart: InstanceChart, ) {} + @bindThis public async create(user: { id: User['id']; username: User['username']; @@ -307,6 +311,7 @@ export class NoteCreateService { return note; } + @bindThis private async insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) { const insert = new Note({ id: this.idService.genId(data.createdAt!), @@ -403,6 +408,7 @@ export class NoteCreateService { } } + @bindThis private async postNoteCreated(note: Note, user: { id: User['id']; username: User['username']; @@ -644,6 +650,7 @@ export class NoteCreateService { this.index(note); } + @bindThis private incRenoteCount(renote: Note) { this.notesRepository.createQueryBuilder().update() .set({ @@ -654,6 +661,7 @@ export class NoteCreateService { .execute(); } + @bindThis private async createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) { for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) { const threadMuted = await this.noteThreadMutingsRepository.findOneBy({ @@ -683,10 +691,12 @@ export class NoteCreateService { } } + @bindThis private saveReply(reply: Note, note: Note) { this.notesRepository.increment({ id: reply.id }, 'repliesCount', 1); } + @bindThis private async renderNoteOrRenoteActivity(data: Option, note: Note) { if (data.localOnly) return null; @@ -697,6 +707,7 @@ export class NoteCreateService { return this.apRendererService.renderActivity(content); } + @bindThis private index(note: Note) { if (note.text == null || this.config.elasticsearch == null) return; /* @@ -711,6 +722,7 @@ export class NoteCreateService { });*/ } + @bindThis private incNotesCountOfUser(user: { id: User['id']; }) { this.usersRepository.createQueryBuilder().update() .set({ @@ -721,6 +733,7 @@ export class NoteCreateService { .execute(); } + @bindThis private async extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.MfmNode[]): Promise { if (tokens == null) return []; diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index ce6e755a7e..7331d03552 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -15,6 +15,7 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NoteDeleteService { @@ -112,6 +113,7 @@ export class NoteDeleteService { }); } + @bindThis private async findCascadingNotes(note: Note) { const cascadingNotes: Note[] = []; @@ -134,6 +136,7 @@ export class NoteDeleteService { return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users } + @bindThis private async getMentionedRemoteUsers(note: Note) { const where = [] as any[]; @@ -159,6 +162,7 @@ export class NoteDeleteService { }) as IRemoteUser[]; } + @bindThis private async deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) { this.apDeliverManagerService.deliverToFollowers(user, content); this.relayService.deliverToRelays(user, content); diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts index a04b52fe4c..f8997574a7 100644 --- a/packages/backend/src/core/NotePiningService.ts +++ b/packages/backend/src/core/NotePiningService.ts @@ -11,6 +11,7 @@ import type { Config } from '@/config.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NotePiningService { @@ -40,6 +41,7 @@ export class NotePiningService { * @param user * @param noteId */ + @bindThis public async addPinned(user: { id: User['id']; host: User['host']; }, noteId: Note['id']) { // Fetch pinee const note = await this.notesRepository.findOneBy({ @@ -79,6 +81,7 @@ export class NotePiningService { * @param user * @param noteId */ + @bindThis public async removePinned(user: { id: User['id']; host: User['host']; }, noteId: Note['id']) { // Fetch unpinee const note = await this.notesRepository.findOneBy({ @@ -101,6 +104,7 @@ export class NotePiningService { } } + @bindThis public async deliverPinnedChange(userId: User['id'], noteId: Note['id'], isAddition: boolean) { const user = await this.usersRepository.findOneBy({ id: userId }); if (user == null) throw new Error('user not found'); diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index e0feaa957d..f70495ff30 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -11,6 +11,7 @@ import type { UsersRepository, NoteUnreadsRepository, MutingsRepository, NoteThr import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NotificationService } from './NotificationService.js'; import { AntennaService } from './AntennaService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NoteReadService { @@ -44,6 +45,7 @@ export class NoteReadService { ) { } + @bindThis public async insertNoteUnread(userId: User['id'], note: Note, params: { // NOTE: isSpecifiedがtrueならisMentionedは必ずfalse isSpecified: boolean; @@ -94,6 +96,7 @@ export class NoteReadService { }, 2000); } + @bindThis public async read( userId: User['id'], notes: (Note | Packed<'Note'>)[], diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index 8bbc95b02d..9fef36dd2c 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -8,6 +8,7 @@ import type { Notification } from '@/models/entities/Notification.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalEventService } from './GlobalEventService.js'; import { PushNotificationService } from './PushNotificationService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NotificationService { @@ -21,6 +22,7 @@ export class NotificationService { ) { } + @bindThis public async readNotification( userId: User['id'], notificationIds: Notification['id'][], @@ -42,6 +44,7 @@ export class NotificationService { else return this.postReadNotifications(userId, notificationIds); } + @bindThis public async readNotificationByQuery( userId: User['id'], query: Record, @@ -55,11 +58,13 @@ export class NotificationService { return this.readNotification(userId, notificationIds); } + @bindThis private postReadAllNotifications(userId: User['id']) { this.globalEventService.publishMainStream(userId, 'readAllNotifications'); return this.pushNotificationService.pushNotification(userId, 'readAllNotifications', undefined); } + @bindThis private postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) { this.globalEventService.publishMainStream(userId, 'readNotifications', notificationIds); return this.pushNotificationService.pushNotification(userId, 'readNotifications', { notificationIds }); diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index 287ce8ada4..3cc9b0cc9b 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -11,6 +11,7 @@ import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class PollService { @@ -40,6 +41,7 @@ export class PollService { ) { } + @bindThis public async vote(user: CacheableUser, note: Note, choice: number) { const poll = await this.pollsRepository.findOneBy({ noteId: note.id }); @@ -99,6 +101,7 @@ export class PollService { }); } + @bindThis public async deliverQuestionUpdate(noteId: Note['id']) { const note = await this.notesRepository.findOneBy({ id: noteId }); if (note == null) throw new Error('note not found'); diff --git a/packages/backend/src/core/ProxyAccountService.ts b/packages/backend/src/core/ProxyAccountService.ts index 4cbdadd029..55b70bfc94 100644 --- a/packages/backend/src/core/ProxyAccountService.ts +++ b/packages/backend/src/core/ProxyAccountService.ts @@ -3,6 +3,7 @@ import type { UsersRepository } from '@/models/index.js'; import type { ILocalUser, User } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ProxyAccountService { @@ -14,6 +15,7 @@ export class ProxyAccountService { ) { } + @bindThis public async fetch(): Promise { const meta = await this.metaService.fetch(); if (meta.proxyAccountId == null) return null; diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index 98e0841799..bffb24cf4c 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -37,6 +37,7 @@ function truncateNotification(notification: Packed<'Notification'>): any { return notification; } +import { bindThis } from '@/decorators.js'; @Injectable() export class PushNotificationService { diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index 771adeaed5..4cc844ccea 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -4,6 +4,7 @@ import { DI } from '@/di-symbols.js'; import type { User } from '@/models/entities/User.js'; import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository } from '@/models/index.js'; import type { SelectQueryBuilder } from 'typeorm'; +import { bindThis } from '@/decorators.js'; @Injectable() export class QueryService { @@ -59,6 +60,7 @@ export class QueryService { } // ここでいうBlockedは被Blockedの意 + @bindThis public generateBlockedUserQuery(q: SelectQueryBuilder, me: { id: User['id'] }): void { const blockingQuery = this.blockingsRepository.createQueryBuilder('blocking') .select('blocking.blockerId') @@ -81,6 +83,7 @@ export class QueryService { q.setParameters(blockingQuery.getParameters()); } + @bindThis public generateBlockQueryForUsers(q: SelectQueryBuilder, me: { id: User['id'] }): void { const blockingQuery = this.blockingsRepository.createQueryBuilder('blocking') .select('blocking.blockeeId') @@ -97,6 +100,7 @@ export class QueryService { q.setParameters(blockedQuery.getParameters()); } + @bindThis public generateChannelQuery(q: SelectQueryBuilder, me?: { id: User['id'] } | null): void { if (me == null) { q.andWhere('note.channelId IS NULL'); @@ -118,6 +122,7 @@ export class QueryService { } } + @bindThis public generateMutedNoteQuery(q: SelectQueryBuilder, me: { id: User['id'] }): void { const mutedQuery = this.mutedNotesRepository.createQueryBuilder('muted') .select('muted.noteId') @@ -128,6 +133,7 @@ export class QueryService { q.setParameters(mutedQuery.getParameters()); } + @bindThis public generateMutedNoteThreadQuery(q: SelectQueryBuilder, me: { id: User['id'] }): void { const mutedQuery = this.noteThreadMutingsRepository.createQueryBuilder('threadMuted') .select('threadMuted.threadId') @@ -142,6 +148,7 @@ export class QueryService { q.setParameters(mutedQuery.getParameters()); } + @bindThis public generateMutedUserQuery(q: SelectQueryBuilder, me: { id: User['id'] }, exclude?: User): void { const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') .select('muting.muteeId') @@ -186,6 +193,7 @@ export class QueryService { q.setParameters(mutingInstanceQuery.getParameters()); } + @bindThis public generateMutedUserQueryForUsers(q: SelectQueryBuilder, me: { id: User['id'] }): void { const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') .select('muting.muteeId') @@ -196,6 +204,7 @@ export class QueryService { q.setParameters(mutingQuery.getParameters()); } + @bindThis public generateRepliesQuery(q: SelectQueryBuilder, me?: Pick | null): void { if (me == null) { q.andWhere(new Brackets(qb => { qb @@ -221,6 +230,7 @@ export class QueryService { } } + @bindThis public generateVisibilityQuery(q: SelectQueryBuilder, me?: { id: User['id'] } | null): void { // This code must always be synchronized with the checks in Notes.isVisibleForMe. if (me == null) { diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index a27d68ee19..7956a3a8f9 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -8,6 +8,7 @@ import { DI } from '@/di-symbols.js'; import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js'; import type { ThinUser } from '../queue/types.js'; import type httpSignature from '@peertube/http-signature'; +import { bindThis } from '@/decorators.js'; @Injectable() export class QueueService { @@ -24,6 +25,7 @@ export class QueueService { @Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue, ) {} + @bindThis public deliver(user: ThinUser, content: IActivity | null, to: string | null) { if (content == null) return null; if (to == null) return null; @@ -47,6 +49,7 @@ export class QueueService { }); } + @bindThis public inbox(activity: IActivity, signature: httpSignature.IParsedSignature) { const data = { activity: activity, @@ -64,6 +67,7 @@ export class QueueService { }); } + @bindThis public createDeleteDriveFilesJob(user: ThinUser) { return this.dbQueue.add('deleteDriveFiles', { user: user, @@ -73,6 +77,7 @@ export class QueueService { }); } + @bindThis public createExportCustomEmojisJob(user: ThinUser) { return this.dbQueue.add('exportCustomEmojis', { user: user, @@ -82,6 +87,7 @@ export class QueueService { }); } + @bindThis public createExportNotesJob(user: ThinUser) { return this.dbQueue.add('exportNotes', { user: user, @@ -91,6 +97,7 @@ export class QueueService { }); } + @bindThis public createExportFollowingJob(user: ThinUser, excludeMuting = false, excludeInactive = false) { return this.dbQueue.add('exportFollowing', { user: user, @@ -102,6 +109,7 @@ export class QueueService { }); } + @bindThis public createExportMuteJob(user: ThinUser) { return this.dbQueue.add('exportMuting', { user: user, @@ -111,6 +119,7 @@ export class QueueService { }); } + @bindThis public createExportBlockingJob(user: ThinUser) { return this.dbQueue.add('exportBlocking', { user: user, @@ -120,6 +129,7 @@ export class QueueService { }); } + @bindThis public createExportUserListsJob(user: ThinUser) { return this.dbQueue.add('exportUserLists', { user: user, @@ -129,6 +139,7 @@ export class QueueService { }); } + @bindThis public createImportFollowingJob(user: ThinUser, fileId: DriveFile['id']) { return this.dbQueue.add('importFollowing', { user: user, @@ -139,6 +150,7 @@ export class QueueService { }); } + @bindThis public createImportMutingJob(user: ThinUser, fileId: DriveFile['id']) { return this.dbQueue.add('importMuting', { user: user, @@ -149,6 +161,7 @@ export class QueueService { }); } + @bindThis public createImportBlockingJob(user: ThinUser, fileId: DriveFile['id']) { return this.dbQueue.add('importBlocking', { user: user, @@ -159,6 +172,7 @@ export class QueueService { }); } + @bindThis public createImportUserListsJob(user: ThinUser, fileId: DriveFile['id']) { return this.dbQueue.add('importUserLists', { user: user, @@ -169,6 +183,7 @@ export class QueueService { }); } + @bindThis public createImportCustomEmojisJob(user: ThinUser, fileId: DriveFile['id']) { return this.dbQueue.add('importCustomEmojis', { user: user, @@ -179,6 +194,7 @@ export class QueueService { }); } + @bindThis public createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean; } = {}) { return this.dbQueue.add('deleteAccount', { user: user, @@ -189,6 +205,7 @@ export class QueueService { }); } + @bindThis public createDeleteObjectStorageFileJob(key: string) { return this.objectStorageQueue.add('deleteFile', { key: key, @@ -198,6 +215,7 @@ export class QueueService { }); } + @bindThis public createCleanRemoteFilesJob() { return this.objectStorageQueue.add('cleanRemoteFiles', {}, { removeOnComplete: true, @@ -205,6 +223,7 @@ export class QueueService { }); } + @bindThis public webhookDeliver(webhook: Webhook, type: typeof webhookEventTypes[number], content: unknown) { const data = { type, @@ -228,6 +247,7 @@ export class QueueService { }); } + @bindThis public destroy() { this.deliverQueue.once('cleaned', (jobs, status) => { //deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`); diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 7a9724e7dd..b02c990566 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -49,6 +49,7 @@ type DecodedReaction = { */ host?: string | null; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class ReactionService { @@ -81,6 +82,7 @@ export class ReactionService { ) { } + @bindThis public async create(user: { id: User['id']; host: User['host']; }, note: Note, reaction?: string) { // Check blocking if (note.userId !== user.id) { @@ -196,6 +198,7 @@ export class ReactionService { //#endregion } + @bindThis public async delete(user: { id: User['id']; host: User['host']; }, note: Note) { // if already unreacted const exist = await this.noteReactionsRepository.findOneBy({ @@ -244,11 +247,13 @@ export class ReactionService { //#endregion } + @bindThis public async getFallbackReaction(): Promise { const meta = await this.metaService.fetch(); return meta.useStarForReactionFallback ? '⭐' : '👍'; } + @bindThis public convertLegacyReactions(reactions: Record) { const _reactions = {} as Record; @@ -279,6 +284,7 @@ export class ReactionService { return _reactions2; } + @bindThis public async toDbReaction(reaction?: string | null, reacterHost?: string | null): Promise { if (reaction == null) return await this.getFallbackReaction(); @@ -311,6 +317,7 @@ export class ReactionService { return await this.getFallbackReaction(); } + @bindThis public decodeReaction(str: string): DecodedReaction { const custom = str.match(/^:([\w+-]+)(?:@([\w.-]+))?:$/); @@ -332,6 +339,7 @@ export class ReactionService { }; } + @bindThis public convertLegacyReaction(reaction: string): string { reaction = this.decodeReaction(reaction).reaction; if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts index 7951edddcb..5fb853f25a 100644 --- a/packages/backend/src/core/RelayService.ts +++ b/packages/backend/src/core/RelayService.ts @@ -12,6 +12,7 @@ import { DI } from '@/di-symbols.js'; import { deepClone } from '@/misc/clone.js'; const ACTOR_USERNAME = 'relay.actor' as const; +import { bindThis } from '@/decorators.js'; @Injectable() export class RelayService { @@ -32,6 +33,7 @@ export class RelayService { this.relaysCache = new Cache(1000 * 60 * 10); } + @bindThis private async getRelayActor(): Promise { const user = await this.usersRepository.findOneBy({ host: IsNull(), @@ -44,6 +46,7 @@ export class RelayService { return created as ILocalUser; } + @bindThis public async addRelay(inbox: string): Promise { const relay = await this.relaysRepository.insert({ id: this.idService.genId(), @@ -59,6 +62,7 @@ export class RelayService { return relay; } + @bindThis public async removeRelay(inbox: string): Promise { const relay = await this.relaysRepository.findOneBy({ inbox, @@ -77,11 +81,13 @@ export class RelayService { await this.relaysRepository.delete(relay.id); } + @bindThis public async listRelay(): Promise { const relays = await this.relaysRepository.find(); return relays; } + @bindThis public async relayAccepted(id: string): Promise { const result = await this.relaysRepository.update(id, { status: 'accepted', @@ -90,6 +96,7 @@ export class RelayService { return JSON.stringify(result); } + @bindThis public async relayRejected(id: string): Promise { const result = await this.relaysRepository.update(id, { status: 'rejected', @@ -98,6 +105,7 @@ export class RelayService { return JSON.stringify(result); } + @bindThis public async deliverToRelays(user: { id: User['id']; host: null; }, activity: any): Promise { if (activity == null) return; diff --git a/packages/backend/src/core/RemoteLoggerService.ts b/packages/backend/src/core/RemoteLoggerService.ts index 68246466c8..0ea5d7b42f 100644 --- a/packages/backend/src/core/RemoteLoggerService.ts +++ b/packages/backend/src/core/RemoteLoggerService.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class RemoteLoggerService { diff --git a/packages/backend/src/core/RemoteUserResolveService.ts b/packages/backend/src/core/RemoteUserResolveService.ts index 809b50f6e9..dde4098624 100644 --- a/packages/backend/src/core/RemoteUserResolveService.ts +++ b/packages/backend/src/core/RemoteUserResolveService.ts @@ -11,6 +11,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { WebfingerService } from '@/core/WebfingerService.js'; import { RemoteLoggerService } from '@/core/RemoteLoggerService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class RemoteUserResolveService { @@ -31,6 +32,7 @@ export class RemoteUserResolveService { this.logger = this.remoteLoggerService.logger.createSubLogger('resolve-user'); } + @bindThis public async resolveUser(username: string, host: string | null): Promise { const usernameLower = username.toLowerCase(); @@ -116,6 +118,7 @@ export class RemoteUserResolveService { return user; } + @bindThis private async resolveSelf(acctLower: string) { this.logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); const finger = await this.webfingerService.webfinger(acctLower).catch(err => { diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts index 1374ee06c8..0ce69aaa74 100644 --- a/packages/backend/src/core/S3Service.ts +++ b/packages/backend/src/core/S3Service.ts @@ -5,6 +5,7 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import type { Meta } from '@/models/entities/Meta.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class S3Service { @@ -16,6 +17,7 @@ export class S3Service { ) { } + @bindThis public getS3(meta: Meta) { const u = meta.objectStorageEndpoint != null ? `${meta.objectStorageUseSSL ? 'https://' : 'http://'}${meta.objectStorageEndpoint}` diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 1e34d9e4f8..9cf203566d 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -14,6 +14,7 @@ import generateUserToken from '@/misc/generate-native-user-token.js'; import UsersChart from './chart/charts/users.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UtilityService } from './UtilityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class SignupService { @@ -37,6 +38,7 @@ export class SignupService { ) { } + @bindThis public async signup(opts: { username: User['username']; password?: string | null; diff --git a/packages/backend/src/core/TwoFactorAuthenticationService.ts b/packages/backend/src/core/TwoFactorAuthenticationService.ts index 54e87ec36f..150047fd22 100644 --- a/packages/backend/src/core/TwoFactorAuthenticationService.ts +++ b/packages/backend/src/core/TwoFactorAuthenticationService.ts @@ -103,6 +103,7 @@ function PEMString(pemBuffer: Buffer, type = 'CERTIFICATE') { `\n-----END ${type}-----\n` ); } +import { bindThis } from '@/decorators.js'; @Injectable() export class TwoFactorAuthenticationService { @@ -115,6 +116,7 @@ export class TwoFactorAuthenticationService { ) { } + @bindThis public hash(data: Buffer) { return crypto .createHash('sha256') @@ -122,6 +124,7 @@ export class TwoFactorAuthenticationService { .digest(); } + @bindThis public verifySignin({ publicKey, authenticatorData, @@ -159,6 +162,7 @@ export class TwoFactorAuthenticationService { .verify(PEMString(publicKey), signature); } + @bindThis public getProcedures() { return { none: { diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 3399bb510f..d411768dc7 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -14,6 +14,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { WebhookService } from '@/core/WebhookService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserBlockingService { @@ -50,6 +51,7 @@ export class UserBlockingService { this.logger = this.loggerService.getLogger('user-block'); } + @bindThis public async block(blocker: User, blockee: User) { await Promise.all([ this.cancelRequest(blocker, blockee), @@ -76,6 +78,7 @@ export class UserBlockingService { } } + @bindThis private async cancelRequest(follower: User, followee: User) { const request = await this.followRequestsRepository.findOneBy({ followeeId: followee.id, @@ -126,6 +129,7 @@ export class UserBlockingService { } } + @bindThis private async unFollow(follower: User, followee: User) { const following = await this.followingsRepository.findOneBy({ followerId: follower.id, @@ -167,6 +171,7 @@ export class UserBlockingService { } } + @bindThis private async removeFromList(listOwner: User, user: User) { const userLists = await this.userListsRepository.findBy({ userId: listOwner.id, @@ -180,6 +185,7 @@ export class UserBlockingService { } } + @bindThis public async unblock(blocker: CacheableUser, blockee: CacheableUser) { const blocking = await this.blockingsRepository.findOneBy({ blockerId: blocker.id, diff --git a/packages/backend/src/core/UserCacheService.ts b/packages/backend/src/core/UserCacheService.ts index 25a600a8da..423c8993e3 100644 --- a/packages/backend/src/core/UserCacheService.ts +++ b/packages/backend/src/core/UserCacheService.ts @@ -6,6 +6,7 @@ import type { CacheableLocalUser, CacheableUser, ILocalUser } from '@/models/ent import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserCacheService implements OnApplicationShutdown { @@ -23,7 +24,7 @@ export class UserCacheService implements OnApplicationShutdown { private userEntityService: UserEntityService, ) { - this.onMessage = this.onMessage.bind(this); + //this.onMessage = this.onMessage.bind(this); this.userByIdCache = new Cache(Infinity); this.localUserByNativeTokenCache = new Cache(Infinity); @@ -33,6 +34,7 @@ export class UserCacheService implements OnApplicationShutdown { this.redisSubscriber.on('message', this.onMessage); } + @bindThis private async onMessage(_: string, data: string): Promise { const obj = JSON.parse(data); @@ -68,6 +70,7 @@ export class UserCacheService implements OnApplicationShutdown { } } + @bindThis public onApplicationShutdown(signal?: string | undefined) { this.redisSubscriber.off('message', this.onMessage); } diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 2f51e2a9df..a6cb042c7d 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -31,6 +31,7 @@ type Remote = IRemoteUser | { inbox: IRemoteUser['inbox']; }; type Both = Local | Remote; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserFollowingService { @@ -66,6 +67,7 @@ export class UserFollowingService { ) { } + @bindThis public async follow(_follower: { id: User['id'] }, _followee: { id: User['id'] }, requestId?: string): Promise { const [follower, followee] = await Promise.all([ this.usersRepository.findOneByOrFail({ id: _follower.id }), @@ -140,6 +142,7 @@ export class UserFollowingService { } } + @bindThis private async insertFollowingDoc( followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox'] @@ -253,6 +256,7 @@ export class UserFollowingService { } } + @bindThis public async unfollow( follower: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; @@ -305,6 +309,7 @@ export class UserFollowingService { } } + @bindThis private async decrementFollowing( follower: {id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }, @@ -333,6 +338,7 @@ export class UserFollowingService { this.perUserFollowingChart.update(follower, followee, false); } + @bindThis public async createFollowRequest( follower: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; @@ -396,6 +402,7 @@ export class UserFollowingService { } } + @bindThis public async cancelFollowRequest( followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox'] @@ -431,6 +438,7 @@ export class UserFollowingService { }).then(packed => this.globalEventServie.publishMainStream(followee.id, 'meUpdated', packed)); } + @bindThis public async acceptFollowRequest( followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; @@ -458,6 +466,7 @@ export class UserFollowingService { }).then(packed => this.globalEventServie.publishMainStream(followee.id, 'meUpdated', packed)); } + @bindThis public async acceptAllFollowRequests( user: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; @@ -476,6 +485,7 @@ export class UserFollowingService { /** * API following/request/reject */ + @bindThis public async rejectFollowRequest(user: Local, follower: Both): Promise { if (this.userEntityService.isRemoteUser(follower)) { this.deliverReject(user, follower); @@ -491,6 +501,7 @@ export class UserFollowingService { /** * API following/reject */ + @bindThis public async rejectFollow(user: Local, follower: Both): Promise { if (this.userEntityService.isRemoteUser(follower)) { this.deliverReject(user, follower); @@ -506,6 +517,7 @@ export class UserFollowingService { /** * AP Reject/Follow */ + @bindThis public async remoteReject(actor: Remote, follower: Local): Promise { await this.removeFollowRequest(actor, follower); await this.removeFollow(actor, follower); @@ -515,6 +527,7 @@ export class UserFollowingService { /** * Remove follow request record */ + @bindThis private async removeFollowRequest(followee: Both, follower: Both): Promise { const request = await this.followRequestsRepository.findOneBy({ followeeId: followee.id, @@ -529,6 +542,7 @@ export class UserFollowingService { /** * Remove follow record */ + @bindThis private async removeFollow(followee: Both, follower: Both): Promise { const following = await this.followingsRepository.findOneBy({ followeeId: followee.id, @@ -544,6 +558,7 @@ export class UserFollowingService { /** * Deliver Reject to remote */ + @bindThis private async deliverReject(followee: Local, follower: Remote): Promise { const request = await this.followRequestsRepository.findOneBy({ followeeId: followee.id, @@ -557,6 +572,7 @@ export class UserFollowingService { /** * Publish unfollow to local */ + @bindThis private async publishUnfollow(followee: Both, follower: Local): Promise { const packedFollowee = await this.userEntityService.pack(followee.id, follower, { detail: true, diff --git a/packages/backend/src/core/UserKeypairStoreService.ts b/packages/backend/src/core/UserKeypairStoreService.ts index 8eca03a10b..1d3cc87c8d 100644 --- a/packages/backend/src/core/UserKeypairStoreService.ts +++ b/packages/backend/src/core/UserKeypairStoreService.ts @@ -4,6 +4,7 @@ import type { UserKeypairsRepository } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; import type { UserKeypair } from '@/models/entities/UserKeypair.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserKeypairStoreService { @@ -16,6 +17,7 @@ export class UserKeypairStoreService { this.cache = new Cache(Infinity); } + @bindThis public async getUserKeypair(userId: User['id']): Promise { return await this.cache.fetch(userId, () => this.userKeypairsRepository.findOneByOrFail({ userId: userId })); } diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 1d1ead5a1f..054387ff8e 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -9,6 +9,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ProxyAccountService } from '@/core/ProxyAccountService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserListService { @@ -27,6 +28,7 @@ export class UserListService { ) { } + @bindThis public async push(target: User, list: UserList) { await this.userListJoiningsRepository.insert({ id: this.idService.genId(), diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts index 4c09e450c8..3029d02c00 100644 --- a/packages/backend/src/core/UserMutingService.ts +++ b/packages/backend/src/core/UserMutingService.ts @@ -5,6 +5,7 @@ import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { User } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserMutingService { @@ -21,6 +22,7 @@ export class UserMutingService { ) { } + @bindThis public async mute(user: User, target: User): Promise { await this.mutingsRepository.insert({ id: this.idService.genId(), diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index 02f686bab6..df1664942f 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -8,6 +8,7 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserSuspendService { @@ -28,6 +29,7 @@ export class UserSuspendService { ) { } + @bindThis public async doPostSuspend(user: { id: User['id']; host: User['host'] }): Promise { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); @@ -57,6 +59,7 @@ export class UserSuspendService { } } + @bindThis public async doPostUnsuspend(user: User): Promise { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false }); diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index 15dd684286..1412e6e9aa 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -3,6 +3,7 @@ import { toASCII } from 'punycode'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UtilityService { @@ -12,24 +13,29 @@ export class UtilityService { ) { } + @bindThis public getFullApAccount(username: string, host: string | null): string { return host ? `${username}@${this.toPuny(host)}` : `${username}@${this.toPuny(this.config.host)}`; } + @bindThis public isSelfHost(host: string | null): boolean { if (host == null) return true; return this.toPuny(this.config.host) === this.toPuny(host); } + @bindThis public extractDbHost(uri: string): string { const url = new URL(uri); return this.toPuny(url.hostname); } + @bindThis public toPuny(host: string): string { return toASCII(host.toLowerCase()); } + @bindThis public toPunyNullable(host: string | null | undefined): string | null { if (host == null) return null; return toASCII(host.toLowerCase()); diff --git a/packages/backend/src/core/VideoProcessingService.ts b/packages/backend/src/core/VideoProcessingService.ts index af4036a291..2807960cb7 100644 --- a/packages/backend/src/core/VideoProcessingService.ts +++ b/packages/backend/src/core/VideoProcessingService.ts @@ -5,6 +5,7 @@ import type { Config } from '@/config.js'; import { ImageProcessingService } from '@/core/ImageProcessingService.js'; import type { IImage } from '@/core/ImageProcessingService.js'; import { createTempDir } from '@/misc/create-temp.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class VideoProcessingService { @@ -16,6 +17,7 @@ export class VideoProcessingService { ) { } + @bindThis public async generateVideoThumbnail(source: string): Promise { const [dir, cleanup] = await createTempDir(); diff --git a/packages/backend/src/core/WebfingerService.ts b/packages/backend/src/core/WebfingerService.ts index d2a88be583..2d7afe5c88 100644 --- a/packages/backend/src/core/WebfingerService.ts +++ b/packages/backend/src/core/WebfingerService.ts @@ -14,6 +14,7 @@ type IWebFinger = { links: ILink[]; subject: string; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class WebfingerService { @@ -25,12 +26,14 @@ export class WebfingerService { ) { } + @bindThis public async webfinger(query: string): Promise { const url = this.genUrl(query); return await this.httpRequestService.getJson(url, 'application/jrd+json, application/json') as IWebFinger; } + @bindThis private genUrl(query: string): string { if (query.match(/^https?:\/\//)) { const u = new URL(query); diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index 1a690314f8..91a39f1359 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -4,6 +4,7 @@ import type { WebhooksRepository } from '@/models/index.js'; import type { Webhook } from '@/models/entities/Webhook.js'; import { DI } from '@/di-symbols.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; @Injectable() export class WebhookService implements OnApplicationShutdown { @@ -17,10 +18,11 @@ export class WebhookService implements OnApplicationShutdown { @Inject(DI.webhooksRepository) private webhooksRepository: WebhooksRepository, ) { - this.onMessage = this.onMessage.bind(this); + //this.onMessage = this.onMessage.bind(this); this.redisSubscriber.on('message', this.onMessage); } + @bindThis public async getActiveWebhooks() { if (!this.webhooksFetched) { this.webhooks = await this.webhooksRepository.findBy({ @@ -32,6 +34,7 @@ export class WebhookService implements OnApplicationShutdown { return this.webhooks; } + @bindThis private async onMessage(_: string, data: string): Promise { const obj = JSON.parse(data); @@ -64,6 +67,7 @@ export class WebhookService implements OnApplicationShutdown { } } + @bindThis public onApplicationShutdown(signal?: string | undefined) { this.redisSubscriber.off('message', this.onMessage); } diff --git a/packages/backend/src/core/activitypub/ApAudienceService.ts b/packages/backend/src/core/activitypub/ApAudienceService.ts index 744017aa3a..64f01644a7 100644 --- a/packages/backend/src/core/activitypub/ApAudienceService.ts +++ b/packages/backend/src/core/activitypub/ApAudienceService.ts @@ -4,6 +4,7 @@ import promiseLimit from 'promise-limit'; import { DI } from '@/di-symbols.js'; import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js'; import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js'; +import { bindThis } from '@/decorators.js'; import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; import { ApPersonService } from './models/ApPersonService.js'; import type { ApObject } from './type.js'; @@ -24,6 +25,7 @@ export class ApAudienceService { ) { } + @bindThis public async parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { const toGroups = this.groupingAudience(getApIds(to), actor); const ccGroups = this.groupingAudience(getApIds(cc), actor); @@ -66,6 +68,7 @@ export class ApAudienceService { }; } + @bindThis private groupingAudience(ids: string[], actor: CacheableRemoteUser) { const groups = { public: [] as string[], @@ -88,6 +91,7 @@ export class ApAudienceService { return groups; } + @bindThis private isPublic(id: string) { return [ 'https://www.w3.org/ns/activitystreams#Public', @@ -96,6 +100,7 @@ export class ApAudienceService { ].includes(id); } + @bindThis private isFollowers(id: string, actor: CacheableRemoteUser) { return ( id === (actor.followersUri ?? `${actor.uri}/followers`) diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index 77d200c3c8..1f28fb3a07 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -9,6 +9,7 @@ import type { UserPublickey } from '@/models/entities/UserPublickey.js'; import { UserCacheService } from '@/core/UserCacheService.js'; import type { Note } from '@/models/entities/Note.js'; import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; +import { bindThis } from '@/decorators.js'; import { getApId } from './type.js'; import { ApPersonService } from './models/ApPersonService.js'; import type { IObject } from './type.js'; @@ -57,6 +58,7 @@ export class ApDbResolverService { this.publicKeyByUserIdCache = new Cache(Infinity); } + @bindThis public parseUri(value: string | IObject): UriParseResult { const uri = getApId(value); @@ -82,6 +84,7 @@ export class ApDbResolverService { /** * AP Note => Misskey Note in DB */ + @bindThis public async getNoteFromApId(value: string | IObject): Promise { const parsed = this.parseUri(value); @@ -98,6 +101,7 @@ export class ApDbResolverService { } } + @bindThis public async getMessageFromApId(value: string | IObject): Promise { const parsed = this.parseUri(value); @@ -117,6 +121,7 @@ export class ApDbResolverService { /** * AP Person => Misskey User in DB */ + @bindThis public async getUserFromApId(value: string | IObject): Promise { const parsed = this.parseUri(value); @@ -136,6 +141,7 @@ export class ApDbResolverService { /** * AP KeyId => Misskey User and Key */ + @bindThis public async getAuthUserFromKeyId(keyId: string): Promise<{ user: CacheableRemoteUser; key: UserPublickey; @@ -161,6 +167,7 @@ export class ApDbResolverService { /** * AP Actor id => Misskey User and Key */ + @bindThis public async getAuthUserFromApId(uri: string): Promise<{ user: CacheableRemoteUser; key: UserPublickey | null; diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts index 6fc75a0397..256cf12651 100644 --- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts +++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts @@ -6,6 +6,7 @@ import type { Config } from '@/config.js'; import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; import { QueueService } from '@/core/QueueService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; interface IRecipe { type: string; @@ -48,6 +49,7 @@ export class ApDeliverManagerService { * @param activity Activity * @param from Followee */ + @bindThis public async deliverToFollowers(actor: { id: ILocalUser['id']; host: null; }, activity: any) { const manager = new DeliverManager( this.userEntityService, @@ -65,6 +67,7 @@ export class ApDeliverManagerService { * @param activity Activity * @param to Target user */ + @bindThis public async deliverToUser(actor: { id: ILocalUser['id']; host: null; }, activity: any, to: IRemoteUser) { const manager = new DeliverManager( this.userEntityService, @@ -77,6 +80,7 @@ export class ApDeliverManagerService { await manager.execute(); } + @bindThis public createDeliverManager(actor: { id: User['id']; host: null; }, activity: any) { return new DeliverManager( this.userEntityService, @@ -114,6 +118,7 @@ class DeliverManager { /** * Add recipe for followers deliver */ + @bindThis public addFollowersRecipe() { const deliver = { type: 'Followers', @@ -126,6 +131,7 @@ class DeliverManager { * Add recipe for direct deliver * @param to To */ + @bindThis public addDirectRecipe(to: IRemoteUser) { const recipe = { type: 'Direct', @@ -139,6 +145,7 @@ class DeliverManager { * Add recipe * @param recipe Recipe */ + @bindThis public addRecipe(recipe: IRecipe) { this.recipes.push(recipe); } @@ -146,6 +153,7 @@ class DeliverManager { /** * Execute delivers */ + @bindThis public async execute() { if (!this.userEntityService.isLocalUser(this.actor)) return; diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 3da384ec2d..79a917426a 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -32,6 +32,7 @@ import { ApPersonService } from './models/ApPersonService.js'; import { ApQuestionService } from './models/ApQuestionService.js'; import type { Resolver } from './ApResolverService.js'; import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IRead, IReject, IRemove, IUndo, IUpdate } from './type.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApInboxService { @@ -85,6 +86,7 @@ export class ApInboxService { this.logger = this.apLoggerService.logger; } + @bindThis public async performActivity(actor: CacheableRemoteUser, activity: IObject) { if (isCollectionOrOrderedCollection(activity)) { const resolver = this.apResolverService.createResolver(); @@ -112,6 +114,7 @@ export class ApInboxService { } } + @bindThis public async performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise { if (actor.isSuspended) return; @@ -148,6 +151,7 @@ export class ApInboxService { } } + @bindThis private async follow(actor: CacheableRemoteUser, activity: IFollow): Promise { const followee = await this.apDbResolverService.getUserFromApId(activity.object); @@ -163,6 +167,7 @@ export class ApInboxService { return 'ok'; } + @bindThis private async like(actor: CacheableRemoteUser, activity: ILike): Promise { const targetUri = getApId(activity.object); @@ -180,6 +185,7 @@ export class ApInboxService { }).then(() => 'ok'); } + @bindThis private async read(actor: CacheableRemoteUser, activity: IRead): Promise { const id = await getApId(activity.object); @@ -202,6 +208,7 @@ export class ApInboxService { return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`; } + @bindThis private async accept(actor: CacheableRemoteUser, activity: IAccept): Promise { const uri = activity.id ?? activity; @@ -219,6 +226,7 @@ export class ApInboxService { return `skip: Unknown Accept type: ${getApType(object)}`; } + @bindThis private async acceptFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある @@ -242,6 +250,7 @@ export class ApInboxService { return 'ok'; } + @bindThis private async add(actor: CacheableRemoteUser, activity: IAdd): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); @@ -261,6 +270,7 @@ export class ApInboxService { throw new Error(`unknown target: ${activity.target}`); } + @bindThis private async announce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { const uri = getApId(activity); @@ -271,6 +281,7 @@ export class ApInboxService { this.announceNote(actor, activity, targetUri); } + @bindThis private async announceNote(actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { const uri = getApId(activity); @@ -330,6 +341,7 @@ export class ApInboxService { } } + @bindThis private async block(actor: CacheableRemoteUser, activity: IBlock): Promise { // ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず @@ -347,6 +359,7 @@ export class ApInboxService { return 'ok'; } + @bindThis private async create(actor: CacheableRemoteUser, activity: ICreate): Promise { const uri = getApId(activity); @@ -382,6 +395,7 @@ export class ApInboxService { } } + @bindThis private async createNote(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { const uri = getApId(note); @@ -416,6 +430,7 @@ export class ApInboxService { } } + @bindThis private async delete(actor: CacheableRemoteUser, activity: IDelete): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); @@ -457,6 +472,7 @@ export class ApInboxService { } } + @bindThis private async deleteActor(actor: CacheableRemoteUser, uri: string): Promise { this.logger.info(`Deleting the Actor: ${uri}`); @@ -478,6 +494,7 @@ export class ApInboxService { return `ok: queued ${job.name} ${job.id}`; } + @bindThis private async deleteNote(actor: CacheableRemoteUser, uri: string): Promise { this.logger.info(`Deleting the Note: ${uri}`); @@ -510,6 +527,7 @@ export class ApInboxService { } } + @bindThis private async flag(actor: CacheableRemoteUser, activity: IFlag): Promise { // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する @@ -534,6 +552,7 @@ export class ApInboxService { return 'ok'; } + @bindThis private async reject(actor: CacheableRemoteUser, activity: IReject): Promise { const uri = activity.id ?? activity; @@ -551,6 +570,7 @@ export class ApInboxService { return `skip: Unknown Reject type: ${getApType(object)}`; } + @bindThis private async rejectFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある @@ -574,6 +594,7 @@ export class ApInboxService { return 'ok'; } + @bindThis private async remove(actor: CacheableRemoteUser, activity: IRemove): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); @@ -593,6 +614,7 @@ export class ApInboxService { throw new Error(`unknown target: ${activity.target}`); } + @bindThis private async undo(actor: CacheableRemoteUser, activity: IUndo): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); @@ -618,6 +640,7 @@ export class ApInboxService { return `skip: unknown object type ${getApType(object)}`; } + @bindThis private async undoAccept(actor: CacheableRemoteUser, activity: IAccept): Promise { const follower = await this.apDbResolverService.getUserFromApId(activity.object); if (follower == null) { @@ -637,6 +660,7 @@ export class ApInboxService { return 'skip: フォローされていない'; } + @bindThis private async undoAnnounce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { const uri = getApId(activity); @@ -651,6 +675,7 @@ export class ApInboxService { return 'ok: deleted'; } + @bindThis private async undoBlock(actor: CacheableRemoteUser, activity: IBlock): Promise { const blockee = await this.apDbResolverService.getUserFromApId(activity.object); @@ -666,6 +691,7 @@ export class ApInboxService { return 'ok'; } + @bindThis private async undoFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { const followee = await this.apDbResolverService.getUserFromApId(activity.object); if (followee == null) { @@ -699,6 +725,7 @@ export class ApInboxService { return 'skip: リクエストもフォローもされていない'; } + @bindThis private async undoLike(actor: CacheableRemoteUser, activity: ILike): Promise { const targetUri = getApId(activity.object); @@ -713,6 +740,7 @@ export class ApInboxService { return 'ok'; } + @bindThis private async update(actor: CacheableRemoteUser, activity: IUpdate): Promise { if ('actor' in activity && actor.uri !== activity.actor) { return 'skip: invalid actor'; diff --git a/packages/backend/src/core/activitypub/ApLoggerService.ts b/packages/backend/src/core/activitypub/ApLoggerService.ts index a742cc42da..b9bf1e4054 100644 --- a/packages/backend/src/core/activitypub/ApLoggerService.ts +++ b/packages/backend/src/core/activitypub/ApLoggerService.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type Logger from '@/logger.js'; import { RemoteLoggerService } from '@/core/RemoteLoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApLoggerService { diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts index 8804fde64a..6116822f7a 100644 --- a/packages/backend/src/core/activitypub/ApMfmService.ts +++ b/packages/backend/src/core/activitypub/ApMfmService.ts @@ -6,6 +6,7 @@ import { MfmService } from '@/core/MfmService.js'; import type { Note } from '@/models/entities/Note.js'; import { extractApHashtagObjects } from './models/tag.js'; import type { IObject } from './type.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApMfmService { @@ -17,12 +18,14 @@ export class ApMfmService { ) { } + @bindThis public htmlToMfm(html: string, tag?: IObject | IObject[]) { const hashtagNames = extractApHashtagObjects(tag).map(x => x.name).filter((x): x is string => x != null); return this.mfmService.fromHtml(html, hashtagNames); } + @bindThis public getNoteHtml(note: Note) { if (!note.text) return ''; return this.mfmService.toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 38a92567c3..1800840ee9 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -25,6 +25,7 @@ import { LdSignatureService } from './LdSignatureService.js'; import { ApMfmService } from './ApMfmService.js'; import type { IActivity, IObject } from './type.js'; import type { IIdentifier } from './models/identifier.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApRendererService { @@ -59,6 +60,7 @@ export class ApRendererService { ) { } + @bindThis public renderAccept(object: any, user: { id: User['id']; host: null }) { return { type: 'Accept', @@ -67,6 +69,7 @@ export class ApRendererService { }; } + @bindThis public renderAdd(user: ILocalUser, target: any, object: any) { return { type: 'Add', @@ -76,6 +79,7 @@ export class ApRendererService { }; } + @bindThis public renderAnnounce(object: any, note: Note) { const attributedTo = `${this.config.url}/users/${note.userId}`; @@ -108,6 +112,7 @@ export class ApRendererService { * * @param block The block to be rendered. The blockee relation must be loaded. */ + @bindThis public renderBlock(block: Blocking) { if (block.blockee?.uri == null) { throw new Error('renderBlock: missing blockee uri'); @@ -121,6 +126,7 @@ export class ApRendererService { }; } + @bindThis public renderCreate(object: any, note: Note) { const activity = { id: `${this.config.url}/notes/${note.id}/activity`, @@ -136,6 +142,7 @@ export class ApRendererService { return activity; } + @bindThis public renderDelete(object: any, user: { id: User['id']; host: null }) { return { type: 'Delete', @@ -145,6 +152,7 @@ export class ApRendererService { }; } + @bindThis public renderDocument(file: DriveFile) { return { type: 'Document', @@ -154,6 +162,7 @@ export class ApRendererService { }; } + @bindThis public renderEmoji(emoji: Emoji) { return { id: `${this.config.url}/emojis/${emoji.name}`, @@ -170,6 +179,7 @@ export class ApRendererService { // to anonymise reporters, the reporting actor must be a system user // object has to be a uri or array of uris + @bindThis public renderFlag(user: ILocalUser, object: [string], content: string) { return { type: 'Flag', @@ -179,6 +189,7 @@ export class ApRendererService { }; } + @bindThis public renderFollowRelay(relay: Relay, relayActor: ILocalUser) { const follow = { id: `${this.config.url}/activities/follow-relay/${relay.id}`, @@ -194,11 +205,13 @@ export class ApRendererService { * Convert (local|remote)(Follower|Followee)ID to URL * @param id Follower|Followee ID */ + @bindThis public async renderFollowUser(id: User['id']) { const user = await this.usersRepository.findOneByOrFail({ id: id }); return this.userEntityService.isLocalUser(user) ? `${this.config.url}/users/${user.id}` : user.uri; } + @bindThis public renderFollow( follower: { id: User['id']; host: User['host']; uri: User['host'] }, followee: { id: User['id']; host: User['host']; uri: User['host'] }, @@ -214,6 +227,7 @@ export class ApRendererService { return follow; } + @bindThis public renderHashtag(tag: string) { return { type: 'Hashtag', @@ -222,6 +236,7 @@ export class ApRendererService { }; } + @bindThis public renderImage(file: DriveFile) { return { type: 'Image', @@ -231,6 +246,7 @@ export class ApRendererService { }; } + @bindThis public renderKey(user: ILocalUser, key: UserKeypair, postfix?: string) { return { id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`, @@ -243,6 +259,7 @@ export class ApRendererService { }; } + @bindThis public async renderLike(noteReaction: NoteReaction, note: { uri: string | null }) { const reaction = noteReaction.reaction; @@ -268,6 +285,7 @@ export class ApRendererService { return object; } + @bindThis public renderMention(mention: User) { return { type: 'Mention', @@ -276,6 +294,7 @@ export class ApRendererService { }; } + @bindThis public async renderNote(note: Note, dive = true, isTalk = false): Promise { const getPromisedFiles = async (ids: string[]) => { if (!ids || ids.length === 0) return []; @@ -420,6 +439,7 @@ export class ApRendererService { }; } + @bindThis public async renderPerson(user: ILocalUser) { const id = `${this.config.url}/users/${user.id}`; const isSystem = !!user.username.match(/\./); @@ -496,6 +516,7 @@ export class ApRendererService { return person; } + @bindThis public async renderQuestion(user: { id: User['id'] }, note: Note, poll: Poll) { const question = { type: 'Question', @@ -515,6 +536,7 @@ export class ApRendererService { return question; } + @bindThis public renderRead(user: { id: User['id'] }, message: MessagingMessage) { return { type: 'Read', @@ -523,6 +545,7 @@ export class ApRendererService { }; } + @bindThis public renderReject(object: any, user: { id: User['id'] }) { return { type: 'Reject', @@ -531,6 +554,7 @@ export class ApRendererService { }; } + @bindThis public renderRemove(user: { id: User['id'] }, target: any, object: any) { return { type: 'Remove', @@ -540,6 +564,7 @@ export class ApRendererService { }; } + @bindThis public renderTombstone(id: string) { return { id, @@ -547,6 +572,7 @@ export class ApRendererService { }; } + @bindThis public renderUndo(object: any, user: { id: User['id'] }) { if (object == null) return null; const id = typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined; @@ -560,6 +586,7 @@ export class ApRendererService { }; } + @bindThis public renderUpdate(object: any, user: { id: User['id'] }) { const activity = { id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`, @@ -573,6 +600,7 @@ export class ApRendererService { return activity; } + @bindThis public renderVote(user: { id: User['id'] }, vote: PollVote, note: Note, poll: Poll, pollOwner: IRemoteUser) { return { id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`, @@ -591,6 +619,7 @@ export class ApRendererService { }; } + @bindThis public renderActivity(x: any): IActivity | null { if (x == null) return null; @@ -632,6 +661,7 @@ export class ApRendererService { }, x); } + @bindThis public async attachLdSignature(activity: any, user: { id: User['id']; host: null; }): Promise { const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); @@ -651,6 +681,7 @@ export class ApRendererService { * @param prev URL of prev page (optional) * @param next URL of next page (optional) */ + @bindThis public renderOrderedCollectionPage(id: string, totalItems: any, orderedItems: any, partOf: string, prev?: string, next?: string) { const page = { id, @@ -674,6 +705,7 @@ export class ApRendererService { * @param last URL of last page (optional) * @param orderedItems attached objects (optional) */ + @bindThis public renderOrderedCollection(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: IObject[]) { const page: any = { id, @@ -688,6 +720,7 @@ export class ApRendererService { return page; } + @bindThis private async getEmojis(names: string[]): Promise { if (names == null || names.length === 0) return []; diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index baad46d668..d1edd579fa 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -6,6 +6,7 @@ import type { Config } from '@/config.js'; import type { User } from '@/models/entities/User.js'; import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { bindThis } from '@/decorators.js'; type Request = { url: string; @@ -36,6 +37,7 @@ export class ApRequestService { ) { } + @bindThis private createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record }): Signed { const u = new URL(args.url); const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`; @@ -61,6 +63,7 @@ export class ApRequestService { }; } + @bindThis private createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record }): Signed { const u = new URL(args.url); @@ -84,6 +87,7 @@ export class ApRequestService { }; } + @bindThis private signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed { const signingString = this.genSigningString(request, includeHeaders); const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64'); @@ -101,6 +105,7 @@ export class ApRequestService { }; } + @bindThis private genSigningString(request: Request, includeHeaders: string[]): string { request.headers = this.lcObjectKey(request.headers); @@ -117,16 +122,19 @@ export class ApRequestService { return results.join('\n'); } + @bindThis private lcObjectKey(src: Record): Record { const dst: Record = {}; for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key]; return dst; } + @bindThis private objectAssignWithLcKey(a: Record, b: Record): Record { return Object.assign(this.lcObjectKey(a), this.lcObjectKey(b)); } + @bindThis public async signedPost(user: { id: User['id'] }, url: string, object: any) { const body = JSON.stringify(object); @@ -157,6 +165,7 @@ export class ApRequestService { * @param user http-signature user * @param url URL to fetch */ + @bindThis public async signedGet(url: string, user: { id: User['id'] }) { const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index bcdb9383d1..e96c84f148 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -7,58 +7,13 @@ import { MetaService } from '@/core/MetaService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { DI } from '@/di-symbols.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { bindThis } from '@/decorators.js'; import { isCollectionOrOrderedCollection } from './type.js'; import { ApDbResolverService } from './ApDbResolverService.js'; import { ApRendererService } from './ApRendererService.js'; import { ApRequestService } from './ApRequestService.js'; import type { IObject, ICollection, IOrderedCollection } from './type.js'; -@Injectable() -export class ApResolverService { - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.pollsRepository) - private pollsRepository: PollsRepository, - - @Inject(DI.noteReactionsRepository) - private noteReactionsRepository: NoteReactionsRepository, - - private utilityService: UtilityService, - private instanceActorService: InstanceActorService, - private metaService: MetaService, - private apRequestService: ApRequestService, - private httpRequestService: HttpRequestService, - private apRendererService: ApRendererService, - private apDbResolverService: ApDbResolverService, - ) { - } - - public createResolver(): Resolver { - return new Resolver( - this.config, - this.usersRepository, - this.notesRepository, - this.pollsRepository, - this.noteReactionsRepository, - this.utilityService, - this.instanceActorService, - this.metaService, - this.apRequestService, - this.httpRequestService, - this.apRendererService, - this.apDbResolverService, - ); - } -} - export class Resolver { private history: Set; private user?: ILocalUser; @@ -76,15 +31,17 @@ export class Resolver { private httpRequestService: HttpRequestService, private apRendererService: ApRendererService, private apDbResolverService: ApDbResolverService, - private recursionLimit = 100 + private recursionLimit = 100, ) { this.history = new Set(); } + @bindThis public getHistory(): string[] { return Array.from(this.history); } + @bindThis public async resolveCollection(value: string | IObject): Promise { const collection = typeof value === 'string' ? await this.resolve(value) @@ -97,6 +54,7 @@ export class Resolver { } } + @bindThis public async resolve(value: string | IObject): Promise { if (value == null) { throw new Error('resolvee is null (or undefined)'); @@ -152,6 +110,7 @@ export class Resolver { return object; } + @bindThis private resolveLocal(url: string): Promise { const parsed = this.apDbResolverService.parseUri(url); if (!parsed.local) throw new Error('resolveLocal: not local'); @@ -193,3 +152,50 @@ export class Resolver { } } } + +@Injectable() +export class ApResolverService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + @Inject(DI.noteReactionsRepository) + private noteReactionsRepository: NoteReactionsRepository, + + private utilityService: UtilityService, + private instanceActorService: InstanceActorService, + private metaService: MetaService, + private apRequestService: ApRequestService, + private httpRequestService: HttpRequestService, + private apRendererService: ApRendererService, + private apDbResolverService: ApDbResolverService, + ) { + } + + @bindThis + public createResolver(): Resolver { + return new Resolver( + this.config, + this.usersRepository, + this.notesRepository, + this.pollsRepository, + this.noteReactionsRepository, + this.utilityService, + this.instanceActorService, + this.metaService, + this.apRequestService, + this.httpRequestService, + this.apRendererService, + this.apDbResolverService, + ); + } +} diff --git a/packages/backend/src/core/activitypub/LdSignatureService.ts b/packages/backend/src/core/activitypub/LdSignatureService.ts index ea39f15b2b..b71320ed0b 100644 --- a/packages/backend/src/core/activitypub/LdSignatureService.ts +++ b/packages/backend/src/core/activitypub/LdSignatureService.ts @@ -2,22 +2,11 @@ import * as crypto from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import fetch from 'node-fetch'; import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { bindThis } from '@/decorators.js'; import { CONTEXTS } from './misc/contexts.js'; // RsaSignature2017 based from https://github.com/transmute-industries/RsaSignature2017 -@Injectable() -export class LdSignatureService { - constructor( - private httpRequestService: HttpRequestService, - ) { - } - - public use(): LdSignature { - return new LdSignature(this.httpRequestService); - } -} - class LdSignature { public debug = false; public preLoad = true; @@ -28,6 +17,7 @@ class LdSignature { ) { } + @bindThis public async signRsaSignature2017(data: any, privateKey: string, creator: string, domain?: string, created?: Date): Promise { const options = { type: 'RsaSignature2017', @@ -64,6 +54,7 @@ class LdSignature { }; } + @bindThis public async verifyRsaSignature2017(data: any, publicKey: string): Promise { const toBeSigned = await this.createVerifyData(data, data.signature); const verifier = crypto.createVerify('sha256'); @@ -71,6 +62,7 @@ class LdSignature { return verifier.verify(publicKey, data.signature.signatureValue, 'base64'); } + @bindThis public async createVerifyData(data: any, options: any) { const transformedOptions = { ...options, @@ -90,11 +82,13 @@ class LdSignature { return verifyData; } + @bindThis public async normalize(data: any) { const customLoader = this.getLoader(); return 42; } + @bindThis private getLoader() { return async (url: string): Promise => { if (!url.match('^https?\:\/\/')) throw `Invalid URL ${url}`; @@ -120,6 +114,7 @@ class LdSignature { }; } + @bindThis private async fetchDocument(url: string) { const json = await fetch(url, { headers: { @@ -139,9 +134,23 @@ class LdSignature { return json; } + @bindThis public sha256(data: string): string { const hash = crypto.createHash('sha256'); hash.update(data); return hash.digest('hex'); } } + +@Injectable() +export class LdSignatureService { + constructor( + private httpRequestService: HttpRequestService, + ) { + } + + @bindThis + public use(): LdSignature { + return new LdSignature(this.httpRequestService); + } +} diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index 9bf87f19d4..58fcc8cb53 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -11,6 +11,7 @@ import { DriveService } from '@/core/DriveService.js'; import type Logger from '@/logger.js'; import { ApResolverService } from '../ApResolverService.js'; import { ApLoggerService } from '../ApLoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApImageService { @@ -34,6 +35,7 @@ export class ApImageService { /** * Imageを作成します。 */ + @bindThis public async createImage(actor: CacheableRemoteUser, value: any): Promise { // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { @@ -81,6 +83,7 @@ export class ApImageService { * Misskeyに対象のImageが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ + @bindThis public async resolveImage(actor: CacheableRemoteUser, value: any): Promise { // TODO diff --git a/packages/backend/src/core/activitypub/models/ApMentionService.ts b/packages/backend/src/core/activitypub/models/ApMentionService.ts index 1275e24c62..41e6c6b14f 100644 --- a/packages/backend/src/core/activitypub/models/ApMentionService.ts +++ b/packages/backend/src/core/activitypub/models/ApMentionService.ts @@ -9,6 +9,7 @@ import { isMention } from '../type.js'; import { ApResolverService, Resolver } from '../ApResolverService.js'; import { ApPersonService } from './ApPersonService.js'; import type { IObject, IApMention } from '../type.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApMentionService { @@ -21,6 +22,7 @@ export class ApMentionService { ) { } + @bindThis public async extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) { const hrefs = unique(this.extractApMentionObjects(tags).map(x => x.href as string)); @@ -32,6 +34,7 @@ export class ApMentionService { return mentionedUsers; } + @bindThis public extractApMentionObjects(tags: IObject | IObject[] | null | undefined): IApMention[] { if (tags == null) return []; return toArray(tags).filter(isMention); diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 7cf6725a38..e1d93a08b0 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -32,6 +32,7 @@ import { ApQuestionService } from './ApQuestionService.js'; import { ApImageService } from './ApImageService.js'; import type { Resolver } from '../ApResolverService.js'; import type { IObject, IPost } from '../type.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApNoteService { @@ -74,6 +75,7 @@ export class ApNoteService { this.logger = this.apLoggerService.logger; } + @bindThis public validateNote(object: any, uri: string) { const expectHost = this.utilityService.extractDbHost(uri); @@ -101,6 +103,7 @@ export class ApNoteService { * * Misskeyに対象のNoteが登録されていればそれを返します。 */ + @bindThis public async fetchNote(object: string | IObject): Promise { return await this.apDbResolverService.getNoteFromApId(object); } @@ -108,6 +111,7 @@ export class ApNoteService { /** * Noteを作成します。 */ + @bindThis public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise { if (resolver == null) resolver = this.apResolverService.createResolver(); @@ -313,6 +317,7 @@ export class ApNoteService { * Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ + @bindThis public async resolveNote(value: string | IObject, resolver?: Resolver): Promise { const uri = typeof value === 'string' ? value : value.id; if (uri == null) throw new Error('missing uri'); @@ -345,6 +350,7 @@ export class ApNoteService { } } + @bindThis public async extractEmojis(tags: IObject | IObject[], host: string): Promise { host = this.utilityService.toPuny(host); diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index f9d6f42ef6..d5faf37df2 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -72,6 +72,7 @@ function addService(target: { [x: string]: any }, source: IApPropertyValue) { target[source.name.split(':')[2]] = service(id, username); } } +import { bindThis } from '@/decorators.js'; @Injectable() export class ApPersonService implements OnModuleInit { @@ -161,6 +162,7 @@ export class ApPersonService implements OnModuleInit { * @param x Fetched object * @param uri Fetch target URI */ + @bindThis private validateActor(x: IObject, uri: string): IActor { const expectHost = this.utilityService.toPuny(new URL(uri).hostname); @@ -224,6 +226,7 @@ export class ApPersonService implements OnModuleInit { * * Misskeyに対象のPersonが登録されていればそれを返します。 */ + @bindThis public async fetchPerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); @@ -253,6 +256,7 @@ export class ApPersonService implements OnModuleInit { /** * Personを作成します。 */ + @bindThis public async createPerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); @@ -402,6 +406,7 @@ export class ApPersonService implements OnModuleInit { * @param resolver Resolver * @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します) */ + @bindThis public async updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); @@ -512,6 +517,7 @@ export class ApPersonService implements OnModuleInit { * Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ + @bindThis public async resolvePerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); @@ -528,6 +534,7 @@ export class ApPersonService implements OnModuleInit { return await this.createPerson(uri, resolver); } + @bindThis public analyzeAttachments(attachments: IObject | IObject[] | undefined) { const fields: { name: string, @@ -551,6 +558,7 @@ export class ApPersonService implements OnModuleInit { return { fields, services }; } + @bindThis public async updateFeatured(userId: User['id'], resolver?: Resolver) { const user = await this.usersRepository.findOneByOrFail({ id: userId }); if (!this.userEntityService.isRemoteUser(user)) return; diff --git a/packages/backend/src/core/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/activitypub/models/ApQuestionService.ts index 5793b98353..13a2f0fa5c 100644 --- a/packages/backend/src/core/activitypub/models/ApQuestionService.ts +++ b/packages/backend/src/core/activitypub/models/ApQuestionService.ts @@ -9,6 +9,7 @@ import { ApLoggerService } from '../ApLoggerService.js'; import { ApResolverService } from '../ApResolverService.js'; import type { Resolver } from '../ApResolverService.js'; import type { IObject, IQuestion } from '../type.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApQuestionService { @@ -30,6 +31,7 @@ export class ApQuestionService { this.logger = this.apLoggerService.logger; } + @bindThis public async extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise { if (resolver == null) resolver = this.apResolverService.createResolver(); @@ -65,6 +67,7 @@ export class ApQuestionService { * @param uri URI of AP Question object * @returns true if updated */ + @bindThis public async updateQuestion(value: any, resolver?: Resolver) { const uri = typeof value === 'string' ? value : value.id; diff --git a/packages/backend/src/core/chart/ChartLoggerService.ts b/packages/backend/src/core/chart/ChartLoggerService.ts index 544a006ac9..d392c6d595 100644 --- a/packages/backend/src/core/chart/ChartLoggerService.ts +++ b/packages/backend/src/core/chart/ChartLoggerService.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ChartLoggerService { diff --git a/packages/backend/src/core/chart/ChartManagementService.ts b/packages/backend/src/core/chart/ChartManagementService.ts index 6476cd6843..13ee06c6c5 100644 --- a/packages/backend/src/core/chart/ChartManagementService.ts +++ b/packages/backend/src/core/chart/ChartManagementService.ts @@ -13,6 +13,7 @@ import PerUserFollowingChart from './charts/per-user-following.js'; import PerUserDriveChart from './charts/per-user-drive.js'; import ApRequestChart from './charts/ap-request.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ChartManagementService implements OnApplicationShutdown { @@ -49,6 +50,7 @@ export class ChartManagementService implements OnApplicationShutdown { ]; } + @bindThis public async run() { // 20分おきにメモリ情報をDBに書き込み this.saveIntervalId = setInterval(() => { diff --git a/packages/backend/src/core/chart/charts/active-users.ts b/packages/backend/src/core/chart/charts/active-users.ts index 40c60910ea..bc0ba25cbb 100644 --- a/packages/backend/src/core/chart/charts/active-users.ts +++ b/packages/backend/src/core/chart/charts/active-users.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import type { User } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/active-users.js'; @@ -36,6 +37,7 @@ export default class ActiveUsersChart extends Chart { return {}; } + @bindThis public async read(user: { id: User['id'], host: null, createdAt: User['createdAt'] }): Promise { await this.commit({ 'read': [user.id], @@ -48,6 +50,7 @@ export default class ActiveUsersChart extends Chart { }); } + @bindThis public async write(user: { id: User['id'], host: null, createdAt: User['createdAt'] }): Promise { await this.commit({ 'write': [user.id], diff --git a/packages/backend/src/core/chart/charts/ap-request.ts b/packages/backend/src/core/chart/charts/ap-request.ts index 4b91fbbf18..ce377460c8 100644 --- a/packages/backend/src/core/chart/charts/ap-request.ts +++ b/packages/backend/src/core/chart/charts/ap-request.ts @@ -2,6 +2,7 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/ap-request.js'; @@ -31,18 +32,21 @@ export default class ApRequestChart extends Chart { return {}; } + @bindThis public async deliverSucc(): Promise { await this.commit({ 'deliverSucceeded': 1, }); } + @bindThis public async deliverFail(): Promise { await this.commit({ 'deliverFailed': 1, }); } + @bindThis public async inbox(): Promise { await this.commit({ 'inboxReceived': 1, diff --git a/packages/backend/src/core/chart/charts/drive.ts b/packages/backend/src/core/chart/charts/drive.ts index 494dfbbe57..da36b944f5 100644 --- a/packages/backend/src/core/chart/charts/drive.ts +++ b/packages/backend/src/core/chart/charts/drive.ts @@ -3,6 +3,7 @@ import { Not, IsNull, DataSource } from 'typeorm'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/drive.js'; @@ -32,6 +33,7 @@ export default class DriveChart extends Chart { return {}; } + @bindThis public async update(file: DriveFile, isAdditional: boolean): Promise { const fileSizeKb = file.size / 1000; await this.commit(file.userHost === null ? { diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts index 21e4cedea3..d9234e8028 100644 --- a/packages/backend/src/core/chart/charts/federation.ts +++ b/packages/backend/src/core/chart/charts/federation.ts @@ -4,6 +4,7 @@ import type { FollowingsRepository, InstancesRepository } from '@/models/index.j import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/federation.js'; @@ -107,6 +108,7 @@ export default class FederationChart extends Chart { }; } + @bindThis public async deliverd(host: string, succeeded: boolean): Promise { await this.commit(succeeded ? { 'deliveredInstances': [host], @@ -115,6 +117,7 @@ export default class FederationChart extends Chart { }); } + @bindThis public async inbox(host: string): Promise { await this.commit({ 'inboxInstances': [host], diff --git a/packages/backend/src/core/chart/charts/hashtag.ts b/packages/backend/src/core/chart/charts/hashtag.ts index 8b8c795cfd..3899b41363 100644 --- a/packages/backend/src/core/chart/charts/hashtag.ts +++ b/packages/backend/src/core/chart/charts/hashtag.ts @@ -4,6 +4,7 @@ import type { User } from '@/models/entities/User.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/hashtag.js'; @@ -34,6 +35,7 @@ export default class HashtagChart extends Chart { return {}; } + @bindThis public async update(hashtag: string, user: { id: User['id'], host: User['host'] }): Promise { await this.commit({ 'local.users': this.userEntityService.isLocalUser(user) ? [user.id] : [], diff --git a/packages/backend/src/core/chart/charts/instance.ts b/packages/backend/src/core/chart/charts/instance.ts index 2e0f4c7126..8ca88d80e3 100644 --- a/packages/backend/src/core/chart/charts/instance.ts +++ b/packages/backend/src/core/chart/charts/instance.ts @@ -6,6 +6,7 @@ import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/instance.js'; @@ -68,12 +69,14 @@ export default class InstanceChart extends Chart { return {}; } + @bindThis public async requestReceived(host: string): Promise { await this.commit({ 'requests.received': 1, }, this.utilityService.toPuny(host)); } + @bindThis public async requestSent(host: string, isSucceeded: boolean): Promise { await this.commit({ 'requests.succeeded': isSucceeded ? 1 : 0, @@ -81,6 +84,7 @@ export default class InstanceChart extends Chart { }, this.utilityService.toPuny(host)); } + @bindThis public async newUser(host: string): Promise { await this.commit({ 'users.total': 1, @@ -88,6 +92,7 @@ export default class InstanceChart extends Chart { }, this.utilityService.toPuny(host)); } + @bindThis public async updateNote(host: string, note: Note, isAdditional: boolean): Promise { await this.commit({ 'notes.total': isAdditional ? 1 : -1, @@ -100,6 +105,7 @@ export default class InstanceChart extends Chart { }, this.utilityService.toPuny(host)); } + @bindThis public async updateFollowing(host: string, isAdditional: boolean): Promise { await this.commit({ 'following.total': isAdditional ? 1 : -1, @@ -108,6 +114,7 @@ export default class InstanceChart extends Chart { }, this.utilityService.toPuny(host)); } + @bindThis public async updateFollowers(host: string, isAdditional: boolean): Promise { await this.commit({ 'followers.total': isAdditional ? 1 : -1, @@ -116,6 +123,7 @@ export default class InstanceChart extends Chart { }, this.utilityService.toPuny(host)); } + @bindThis public async updateDrive(file: DriveFile, isAdditional: boolean): Promise { const fileSizeKb = file.size / 1000; await this.commit({ diff --git a/packages/backend/src/core/chart/charts/notes.ts b/packages/backend/src/core/chart/charts/notes.ts index 2153cfe4b4..23dc248fec 100644 --- a/packages/backend/src/core/chart/charts/notes.ts +++ b/packages/backend/src/core/chart/charts/notes.ts @@ -4,6 +4,7 @@ import type { NotesRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/notes.js'; @@ -44,6 +45,7 @@ export default class NotesChart extends Chart { return {}; } + @bindThis public async update(note: Note, isAdditional: boolean): Promise { const prefix = note.userHost === null ? 'local' : 'remote'; diff --git a/packages/backend/src/core/chart/charts/per-user-drive.ts b/packages/backend/src/core/chart/charts/per-user-drive.ts index a44460bb4e..ffba04b041 100644 --- a/packages/backend/src/core/chart/charts/per-user-drive.ts +++ b/packages/backend/src/core/chart/charts/per-user-drive.ts @@ -5,6 +5,7 @@ import type { DriveFile } from '@/models/entities/DriveFile.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-drive.js'; @@ -46,6 +47,7 @@ export default class PerUserDriveChart extends Chart { return {}; } + @bindThis public async update(file: DriveFile, isAdditional: boolean): Promise { const fileSizeKb = file.size / 1000; await this.commit({ diff --git a/packages/backend/src/core/chart/charts/per-user-following.ts b/packages/backend/src/core/chart/charts/per-user-following.ts index 5ea08a0872..aea6d44a9a 100644 --- a/packages/backend/src/core/chart/charts/per-user-following.ts +++ b/packages/backend/src/core/chart/charts/per-user-following.ts @@ -5,6 +5,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { FollowingsRepository } from '@/models/index.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-following.js'; @@ -55,6 +56,7 @@ export default class PerUserFollowingChart extends Chart { return {}; } + @bindThis public async update(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }, isFollow: boolean): Promise { const prefixFollower = this.userEntityService.isLocalUser(follower) ? 'local' : 'remote'; const prefixFollowee = this.userEntityService.isLocalUser(followee) ? 'local' : 'remote'; diff --git a/packages/backend/src/core/chart/charts/per-user-notes.ts b/packages/backend/src/core/chart/charts/per-user-notes.ts index 5c14309d89..1e2a579dfa 100644 --- a/packages/backend/src/core/chart/charts/per-user-notes.ts +++ b/packages/backend/src/core/chart/charts/per-user-notes.ts @@ -5,6 +5,7 @@ import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import type { NotesRepository } from '@/models/index.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-notes.js'; @@ -43,6 +44,7 @@ export default class PerUserNotesChart extends Chart { return {}; } + @bindThis public async update(user: { id: User['id'] }, note: Note, isAdditional: boolean): Promise { await this.commit({ 'total': isAdditional ? 1 : -1, diff --git a/packages/backend/src/core/chart/charts/per-user-reactions.ts b/packages/backend/src/core/chart/charts/per-user-reactions.ts index 4160219720..7bc6d4b521 100644 --- a/packages/backend/src/core/chart/charts/per-user-reactions.ts +++ b/packages/backend/src/core/chart/charts/per-user-reactions.ts @@ -5,6 +5,7 @@ import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-reactions.js'; @@ -35,6 +36,7 @@ export default class PerUserReactionsChart extends Chart { return {}; } + @bindThis public async update(user: { id: User['id'], host: User['host'] }, note: Note): Promise { const prefix = this.userEntityService.isLocalUser(user) ? 'local' : 'remote'; this.commit({ diff --git a/packages/backend/src/core/chart/charts/test-grouped.ts b/packages/backend/src/core/chart/charts/test-grouped.ts index bc215f3942..128967bc65 100644 --- a/packages/backend/src/core/chart/charts/test-grouped.ts +++ b/packages/backend/src/core/chart/charts/test-grouped.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { name, schema } from './entities/test-grouped.js'; import type { KVs } from '../core.js'; @@ -35,6 +36,7 @@ export default class TestGroupedChart extends Chart { return {}; } + @bindThis public async increment(group: string): Promise { if (this.total[group] == null) this.total[group] = 0; diff --git a/packages/backend/src/core/chart/charts/test-intersection.ts b/packages/backend/src/core/chart/charts/test-intersection.ts index a074a7dded..6b4eed9062 100644 --- a/packages/backend/src/core/chart/charts/test-intersection.ts +++ b/packages/backend/src/core/chart/charts/test-intersection.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { name, schema } from './entities/test-intersection.js'; import type { KVs } from '../core.js'; @@ -31,12 +32,14 @@ export default class TestIntersectionChart extends Chart { return {}; } + @bindThis public async addA(key: string): Promise { await this.commit({ a: [key], }); } + @bindThis public async addB(key: string): Promise { await this.commit({ b: [key], diff --git a/packages/backend/src/core/chart/charts/test-unique.ts b/packages/backend/src/core/chart/charts/test-unique.ts index 4d3e2f2403..5d2b3f8ab1 100644 --- a/packages/backend/src/core/chart/charts/test-unique.ts +++ b/packages/backend/src/core/chart/charts/test-unique.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { name, schema } from './entities/test-unique.js'; import type { KVs } from '../core.js'; @@ -31,6 +32,7 @@ export default class TestUniqueChart extends Chart { return {}; } + @bindThis public async uniqueIncrement(key: string): Promise { await this.commit({ foo: [key], diff --git a/packages/backend/src/core/chart/charts/test.ts b/packages/backend/src/core/chart/charts/test.ts index 72caf79e0f..238351d8b3 100644 --- a/packages/backend/src/core/chart/charts/test.ts +++ b/packages/backend/src/core/chart/charts/test.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { name, schema } from './entities/test.js'; import type { KVs } from '../core.js'; @@ -35,6 +36,7 @@ export default class TestChart extends Chart { return {}; } + @bindThis public async increment(): Promise { this.total++; @@ -44,6 +46,7 @@ export default class TestChart extends Chart { }); } + @bindThis public async decrement(): Promise { this.total--; diff --git a/packages/backend/src/core/chart/charts/users.ts b/packages/backend/src/core/chart/charts/users.ts index f0359968eb..7bc3602439 100644 --- a/packages/backend/src/core/chart/charts/users.ts +++ b/packages/backend/src/core/chart/charts/users.ts @@ -5,6 +5,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { UsersRepository } from '@/models/index.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/users.js'; @@ -46,6 +47,7 @@ export default class UsersChart extends Chart { return {}; } + @bindThis public async update(user: { id: User['id'], host: User['host'] }, isAdditional: boolean): Promise { const prefix = this.userEntityService.isLocalUser(user) ? 'local' : 'remote'; diff --git a/packages/backend/src/core/chart/core.ts b/packages/backend/src/core/chart/core.ts index cf5aa48884..2092b13b7e 100644 --- a/packages/backend/src/core/chart/core.ts +++ b/packages/backend/src/core/chart/core.ts @@ -8,6 +8,7 @@ import * as nestedProperty from 'nested-property'; import { EntitySchema, LessThan, Between } from 'typeorm'; import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '@/misc/prelude/time.js'; import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; import type { Repository, DataSource } from 'typeorm'; const columnPrefix = '___' as const; @@ -249,6 +250,7 @@ export default abstract class Chart { this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day); } + @bindThis private convertRawRecord(x: RawRecord): KVs { const kvs = {} as Record; for (const k of Object.keys(x).filter((k) => k.startsWith(columnPrefix)) as (keyof Columns)[]) { @@ -257,6 +259,7 @@ export default abstract class Chart { return kvs as KVs; } + @bindThis private getNewLog(latest: KVs | null): KVs { const log = {} as Record; for (const [k, v] of Object.entries(this.schema) as ([keyof typeof this['schema'], this['schema'][string]])[]) { @@ -269,6 +272,7 @@ export default abstract class Chart { return log as KVs; } + @bindThis private getLatestLog(group: string | null, span: 'hour' | 'day'): Promise | null> { const repository = span === 'hour' ? this.repositoryForHour : @@ -288,6 +292,7 @@ export default abstract class Chart { /** * 現在(=今のHour or Day)のログをデータベースから探して、あればそれを返し、なければ作成して返します。 */ + @bindThis private async claimCurrentLog(group: string | null, span: 'hour' | 'day'): Promise> { const [y, m, d, h] = Chart.getCurrentDate(); @@ -380,6 +385,7 @@ export default abstract class Chart { }); } + @bindThis public async save(): Promise { if (this.buffer.length === 0) { this.logger.info(`${this.name}: Write skipped`); @@ -498,6 +504,7 @@ export default abstract class Chart { update(logHour, logDay)))); } + @bindThis public async tick(major: boolean, group: string | null = null): Promise { const data = major ? await this.tickMajor(group) : await this.tickMinor(group); @@ -533,10 +540,12 @@ export default abstract class Chart { update(logHour, logDay)); } + @bindThis public resync(group: string | null = null): Promise { return this.tick(true, group); } + @bindThis public async clean(): Promise { const current = dateUTC(Chart.getCurrentDate()); @@ -572,6 +581,7 @@ export default abstract class Chart { ]); } + @bindThis public async getChartRaw(span: 'hour' | 'day', amount: number, cursor: Date | null, group: string | null = null): Promise> { const [y, m, d, h, _m, _s, _ms] = cursor ? Chart.parseDate(subtractTime(addTime(cursor, 1, span), 1)) : Chart.getCurrentDate(); const [y2, m2, d2, h2] = cursor ? Chart.parseDate(addTime(cursor, 1, span)) : [] as never; @@ -676,6 +686,7 @@ export default abstract class Chart { return res; } + @bindThis public async getChart(span: 'hour' | 'day', amount: number, cursor: Date | null, group: string | null = null): Promise>> { const result = await this.getChartRaw(span, amount, cursor, group); const object = {}; diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts index 1660894571..7f8240b8b2 100644 --- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts +++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts @@ -4,6 +4,7 @@ import type { AbuseUserReportsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { AbuseUserReport } from '@/models/entities/AbuseUserReport.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class AbuseUserReportEntityService { @@ -15,6 +16,7 @@ export class AbuseUserReportEntityService { ) { } + @bindThis public async pack( src: AbuseUserReport['id'] | AbuseUserReport, ) { @@ -41,6 +43,7 @@ export class AbuseUserReportEntityService { }); } + @bindThis public packMany( reports: any[], ) { diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts index 44110e7364..bc79ce26aa 100644 --- a/packages/backend/src/core/entities/AntennaEntityService.ts +++ b/packages/backend/src/core/entities/AntennaEntityService.ts @@ -4,6 +4,7 @@ import type { AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepos import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { Antenna } from '@/models/entities/Antenna.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class AntennaEntityService { @@ -19,6 +20,7 @@ export class AntennaEntityService { ) { } + @bindThis public async pack( src: Antenna['id'] | Antenna, ): Promise> { diff --git a/packages/backend/src/core/entities/AppEntityService.ts b/packages/backend/src/core/entities/AppEntityService.ts index 1cc7ca11dc..781cbdcc6b 100644 --- a/packages/backend/src/core/entities/AppEntityService.ts +++ b/packages/backend/src/core/entities/AppEntityService.ts @@ -6,6 +6,7 @@ import type { Packed } from '@/misc/schema.js'; import type { App } from '@/models/entities/App.js'; import type { User } from '@/models/entities/User.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class AppEntityService { @@ -18,6 +19,7 @@ export class AppEntityService { ) { } + @bindThis public async pack( src: App['id'] | App, me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/AuthSessionEntityService.ts b/packages/backend/src/core/entities/AuthSessionEntityService.ts index bf8efa5f78..4a74f9c2f6 100644 --- a/packages/backend/src/core/entities/AuthSessionEntityService.ts +++ b/packages/backend/src/core/entities/AuthSessionEntityService.ts @@ -7,6 +7,7 @@ import type { AuthSession } from '@/models/entities/AuthSession.js'; import type { User } from '@/models/entities/User.js'; import { UserEntityService } from './UserEntityService.js'; import { AppEntityService } from './AppEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class AuthSessionEntityService { @@ -18,6 +19,7 @@ export class AuthSessionEntityService { ) { } + @bindThis public async pack( src: AuthSession['id'] | AuthSession, me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/BlockingEntityService.ts b/packages/backend/src/core/entities/BlockingEntityService.ts index 49a96037ca..c9e15207b9 100644 --- a/packages/backend/src/core/entities/BlockingEntityService.ts +++ b/packages/backend/src/core/entities/BlockingEntityService.ts @@ -6,6 +6,7 @@ import type { Packed } from '@/misc/schema.js'; import type { Blocking } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class BlockingEntityService { @@ -17,6 +18,7 @@ export class BlockingEntityService { ) { } + @bindThis public async pack( src: Blocking['id'] | Blocking, me?: { id: User['id'] } | null | undefined, @@ -33,6 +35,7 @@ export class BlockingEntityService { }); } + @bindThis public packMany( blockings: any[], me: { id: User['id'] }, diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts index 860967443e..5e2f019a12 100644 --- a/packages/backend/src/core/entities/ChannelEntityService.ts +++ b/packages/backend/src/core/entities/ChannelEntityService.ts @@ -8,6 +8,7 @@ import type { User } from '@/models/entities/User.js'; import type { Channel } from '@/models/entities/Channel.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ChannelEntityService { @@ -29,6 +30,7 @@ export class ChannelEntityService { ) { } + @bindThis public async pack( src: Channel['id'] | Channel, me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts index 7a5d2f7f0a..1e794391e9 100644 --- a/packages/backend/src/core/entities/ClipEntityService.ts +++ b/packages/backend/src/core/entities/ClipEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { Clip } from '@/models/entities/Clip.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ClipEntityService { @@ -18,6 +19,7 @@ export class ClipEntityService { ) { } + @bindThis public async pack( src: Clip['id'] | Clip, ): Promise> { @@ -34,6 +36,7 @@ export class ClipEntityService { }); } + @bindThis public packMany( clips: Clip[], ) { diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index e0aeb70dfc..706c8c1186 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -19,6 +19,7 @@ type PackOptions = { self?: boolean, withUser?: boolean, }; +import { bindThis } from '@/decorators.js'; @Injectable() export class DriveFileEntityService { @@ -44,6 +45,7 @@ export class DriveFileEntityService { ) { } + @bindThis public validateFileName(name: string): boolean { return ( (name.trim().length > 0) && @@ -54,6 +56,7 @@ export class DriveFileEntityService { ); } + @bindThis public getPublicProperties(file: DriveFile): DriveFile['properties'] { if (file.properties.orientation != null) { const properties = deepClone(file.properties); @@ -67,6 +70,7 @@ export class DriveFileEntityService { return file.properties; } + @bindThis public getPublicUrl(file: DriveFile, thumbnail = false): string | null { // リモートかつメディアプロキシ if (file.uri != null && file.userHost != null && this.config.mediaProxy != null) { @@ -90,6 +94,7 @@ export class DriveFileEntityService { return thumbnail ? (file.thumbnailUrl ?? (isImage ? (file.webpublicUrl ?? file.url) : null)) : (file.webpublicUrl ?? file.url); } + @bindThis public async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise { const id = typeof user === 'object' ? user.id : user; @@ -103,6 +108,7 @@ export class DriveFileEntityService { return parseInt(sum, 10) ?? 0; } + @bindThis public async calcDriveUsageOfHost(host: string): Promise { const { sum } = await this.driveFilesRepository .createQueryBuilder('file') @@ -114,6 +120,7 @@ export class DriveFileEntityService { return parseInt(sum, 10) ?? 0; } + @bindThis public async calcDriveUsageOfLocal(): Promise { const { sum } = await this.driveFilesRepository .createQueryBuilder('file') @@ -125,6 +132,7 @@ export class DriveFileEntityService { return parseInt(sum, 10) ?? 0; } + @bindThis public async calcDriveUsageOfRemote(): Promise { const { sum } = await this.driveFilesRepository .createQueryBuilder('file') @@ -136,6 +144,7 @@ export class DriveFileEntityService { return parseInt(sum, 10) ?? 0; } + @bindThis public async pack( src: DriveFile['id'] | DriveFile, options?: PackOptions, @@ -169,6 +178,7 @@ export class DriveFileEntityService { }); } + @bindThis public async packNullable( src: DriveFile['id'] | DriveFile, options?: PackOptions, @@ -203,6 +213,7 @@ export class DriveFileEntityService { }); } + @bindThis public async packMany( files: (DriveFile['id'] | DriveFile)[], options?: PackOptions, diff --git a/packages/backend/src/core/entities/DriveFolderEntityService.ts b/packages/backend/src/core/entities/DriveFolderEntityService.ts index 5761fa37bc..0bb0f1754e 100644 --- a/packages/backend/src/core/entities/DriveFolderEntityService.ts +++ b/packages/backend/src/core/entities/DriveFolderEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { DriveFolder } from '@/models/entities/DriveFolder.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DriveFolderEntityService { @@ -19,6 +20,7 @@ export class DriveFolderEntityService { ) { } + @bindThis public async pack( src: DriveFolder['id'] | DriveFolder, options?: { diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts index fc09b5a2c7..08d83a2753 100644 --- a/packages/backend/src/core/entities/EmojiEntityService.ts +++ b/packages/backend/src/core/entities/EmojiEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { Emoji } from '@/models/entities/Emoji.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class EmojiEntityService { @@ -18,6 +19,7 @@ export class EmojiEntityService { ) { } + @bindThis public async pack( src: Emoji['id'] | Emoji, ): Promise> { @@ -34,6 +36,7 @@ export class EmojiEntityService { }; } + @bindThis public packMany( emojis: any[], ) { diff --git a/packages/backend/src/core/entities/FollowRequestEntityService.ts b/packages/backend/src/core/entities/FollowRequestEntityService.ts index 4a60c1263f..88c91d0f21 100644 --- a/packages/backend/src/core/entities/FollowRequestEntityService.ts +++ b/packages/backend/src/core/entities/FollowRequestEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { FollowRequest } from '@/models/entities/FollowRequest.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class FollowRequestEntityService { @@ -18,6 +19,7 @@ export class FollowRequestEntityService { ) { } + @bindThis public async pack( src: FollowRequest['id'] | FollowRequest, me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/FollowingEntityService.ts b/packages/backend/src/core/entities/FollowingEntityService.ts index c7e040a57b..a833ae719b 100644 --- a/packages/backend/src/core/entities/FollowingEntityService.ts +++ b/packages/backend/src/core/entities/FollowingEntityService.ts @@ -31,6 +31,7 @@ type RemoteFolloweeFollowing = Following & { followeeInbox: string; followeeSharedInbox: string; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class FollowingEntityService { @@ -42,22 +43,27 @@ export class FollowingEntityService { ) { } + @bindThis public isLocalFollower(following: Following): following is LocalFollowerFollowing { return following.followerHost == null; } + @bindThis public isRemoteFollower(following: Following): following is RemoteFollowerFollowing { return following.followerHost != null; } + @bindThis public isLocalFollowee(following: Following): following is LocalFolloweeFollowing { return following.followeeHost == null; } + @bindThis public isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing { return following.followeeHost != null; } + @bindThis public async pack( src: Following['id'] | Following, me?: { id: User['id'] } | null | undefined, @@ -84,6 +90,7 @@ export class FollowingEntityService { }); } + @bindThis public packMany( followings: any[], me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/GalleryLikeEntityService.ts b/packages/backend/src/core/entities/GalleryLikeEntityService.ts index 7e599113cc..8b15ffc2bb 100644 --- a/packages/backend/src/core/entities/GalleryLikeEntityService.ts +++ b/packages/backend/src/core/entities/GalleryLikeEntityService.ts @@ -8,6 +8,7 @@ import type { User } from '@/models/entities/User.js'; import type { GalleryLike } from '@/models/entities/GalleryLike.js'; import { UserEntityService } from './UserEntityService.js'; import { GalleryPostEntityService } from './GalleryPostEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class GalleryLikeEntityService { @@ -19,6 +20,7 @@ export class GalleryLikeEntityService { ) { } + @bindThis public async pack( src: GalleryLike['id'] | GalleryLike, me?: any, @@ -31,6 +33,7 @@ export class GalleryLikeEntityService { }; } + @bindThis public packMany( likes: any[], me: any, diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts index ca98687d7b..ab29e7dba1 100644 --- a/packages/backend/src/core/entities/GalleryPostEntityService.ts +++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts @@ -8,6 +8,7 @@ import type { User } from '@/models/entities/User.js'; import type { GalleryPost } from '@/models/entities/GalleryPost.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class GalleryPostEntityService { @@ -23,6 +24,7 @@ export class GalleryPostEntityService { ) { } + @bindThis public async pack( src: GalleryPost['id'] | GalleryPost, me?: { id: User['id'] } | null | undefined, @@ -47,6 +49,7 @@ export class GalleryPostEntityService { }); } + @bindThis public packMany( posts: GalleryPost[], me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/HashtagEntityService.ts b/packages/backend/src/core/entities/HashtagEntityService.ts index 511992c44f..f79b821222 100644 --- a/packages/backend/src/core/entities/HashtagEntityService.ts +++ b/packages/backend/src/core/entities/HashtagEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { Hashtag } from '@/models/entities/Hashtag.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class HashtagEntityService { @@ -18,6 +19,7 @@ export class HashtagEntityService { ) { } + @bindThis public async pack( src: Hashtag, ): Promise> { @@ -32,6 +34,7 @@ export class HashtagEntityService { }; } + @bindThis public packMany( hashtags: Hashtag[], ) { diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index f4fe9a17d3..5a7ceb89a3 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -8,6 +8,7 @@ import type { User } from '@/models/entities/User.js'; import type { Instance } from '@/models/entities/Instance.js'; import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class InstanceEntityService { @@ -19,6 +20,7 @@ export class InstanceEntityService { ) { } + @bindThis public async pack( instance: Instance, ): Promise> { @@ -50,6 +52,7 @@ export class InstanceEntityService { }; } + @bindThis public packMany( instances: Instance[], ) { diff --git a/packages/backend/src/core/entities/MessagingMessageEntityService.ts b/packages/backend/src/core/entities/MessagingMessageEntityService.ts index b7c42a5760..cdb752dd81 100644 --- a/packages/backend/src/core/entities/MessagingMessageEntityService.ts +++ b/packages/backend/src/core/entities/MessagingMessageEntityService.ts @@ -9,6 +9,7 @@ import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; import { UserGroupEntityService } from './UserGroupEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class MessagingMessageEntityService { @@ -22,6 +23,7 @@ export class MessagingMessageEntityService { ) { } + @bindThis public async pack( src: MessagingMessage['id'] | MessagingMessage, me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/ModerationLogEntityService.ts b/packages/backend/src/core/entities/ModerationLogEntityService.ts index 2f508710b8..ab61797910 100644 --- a/packages/backend/src/core/entities/ModerationLogEntityService.ts +++ b/packages/backend/src/core/entities/ModerationLogEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { ModerationLog } from '@/models/entities/ModerationLog.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ModerationLogEntityService { @@ -18,6 +19,7 @@ export class ModerationLogEntityService { ) { } + @bindThis public async pack( src: ModerationLog['id'] | ModerationLog, ) { @@ -35,6 +37,7 @@ export class ModerationLogEntityService { }); } + @bindThis public packMany( reports: any[], ) { diff --git a/packages/backend/src/core/entities/MutingEntityService.ts b/packages/backend/src/core/entities/MutingEntityService.ts index 862be009da..4f02ef4087 100644 --- a/packages/backend/src/core/entities/MutingEntityService.ts +++ b/packages/backend/src/core/entities/MutingEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { Muting } from '@/models/entities/Muting.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class MutingEntityService { @@ -18,6 +19,7 @@ export class MutingEntityService { ) { } + @bindThis public async pack( src: Muting['id'] | Muting, me?: { id: User['id'] } | null | undefined, @@ -35,6 +37,7 @@ export class MutingEntityService { }); } + @bindThis public packMany( mutings: any[], me: { id: User['id'] }, diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 5605cf8ce6..73d3184957 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -16,6 +16,7 @@ import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { ReactionService } from '../ReactionService.js'; import type { UserEntityService } from './UserEntityService.js'; import type { DriveFileEntityService } from './DriveFileEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NoteEntityService implements OnModuleInit { @@ -68,6 +69,7 @@ export class NoteEntityService implements OnModuleInit { this.reactionService = this.moduleRef.get('ReactionService'); } + @bindThis private async hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) { // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) let hide = false; @@ -128,6 +130,7 @@ export class NoteEntityService implements OnModuleInit { } } + @bindThis private async populatePoll(note: Note, meId: User['id'] | null) { const poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id }); const choices = poll.choices.map(c => ({ @@ -166,6 +169,7 @@ export class NoteEntityService implements OnModuleInit { }; } + @bindThis private async populateMyReaction(note: Note, meId: User['id'], _hint_?: { myReactions: Map; }) { @@ -191,6 +195,7 @@ export class NoteEntityService implements OnModuleInit { return undefined; } + @bindThis public async isVisibleForMe(note: Note, meId: User['id'] | null): Promise { // This code must always be synchronized with the checks in generateVisibilityQuery. // visibility が specified かつ自分が指定されていなかったら非表示 @@ -244,6 +249,7 @@ export class NoteEntityService implements OnModuleInit { return true; } + @bindThis public async pack( src: Note['id'] | Note, me?: { id: User['id'] } | null | undefined, @@ -353,6 +359,7 @@ export class NoteEntityService implements OnModuleInit { return packed; } + @bindThis public async packMany( notes: Note[], me?: { id: User['id'] } | null | undefined, @@ -388,6 +395,7 @@ export class NoteEntityService implements OnModuleInit { }))); } + @bindThis public async countSameRenotes(userId: string, renoteId: string, excludeNoteId: string | undefined): Promise { // 指定したユーザーの指定したノートのリノートがいくつあるか数える const query = this.notesRepository.createQueryBuilder('note') diff --git a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts index 1a68a5c628..aa5c354b6d 100644 --- a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts +++ b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts @@ -8,6 +8,7 @@ import type { User } from '@/models/entities/User.js'; import type { NoteFavorite } from '@/models/entities/NoteFavorite.js'; import { UserEntityService } from './UserEntityService.js'; import { NoteEntityService } from './NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NoteFavoriteEntityService { @@ -19,6 +20,7 @@ export class NoteFavoriteEntityService { ) { } + @bindThis public async pack( src: NoteFavorite['id'] | NoteFavorite, me?: { id: User['id'] } | null | undefined, @@ -33,6 +35,7 @@ export class NoteFavoriteEntityService { }; } + @bindThis public packMany( favorites: any[], me: { id: User['id'] }, diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index 47008ee08e..eba6f9d908 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -11,6 +11,7 @@ import type { ReactionService } from '../ReactionService.js'; import type { UserEntityService } from './UserEntityService.js'; import type { NoteEntityService } from './NoteEntityService.js'; import { ModuleRef } from '@nestjs/core'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NoteReactionEntityService implements OnModuleInit { @@ -36,6 +37,7 @@ export class NoteReactionEntityService implements OnModuleInit { this.reactionService = this.moduleRef.get('ReactionService'); } + @bindThis public async pack( src: NoteReaction['id'] | NoteReaction, me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index c415599fea..346faae6b0 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -13,6 +13,7 @@ import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { UserEntityService } from './UserEntityService.js'; import type { NoteEntityService } from './NoteEntityService.js'; import type { UserGroupInvitationEntityService } from './UserGroupInvitationEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NotificationEntityService implements OnModuleInit { @@ -47,6 +48,7 @@ export class NotificationEntityService implements OnModuleInit { this.customEmojiService = this.moduleRef.get('CustomEmojiService'); } + @bindThis public async pack( src: Notification['id'] | Notification, options: { @@ -120,6 +122,7 @@ export class NotificationEntityService implements OnModuleInit { }); } + @bindThis public async packMany( notifications: Notification[], meId: User['id'], diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts index 004443759b..48e45dd019 100644 --- a/packages/backend/src/core/entities/PageEntityService.ts +++ b/packages/backend/src/core/entities/PageEntityService.ts @@ -9,6 +9,7 @@ import type { Page } from '@/models/entities/Page.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class PageEntityService { @@ -27,6 +28,7 @@ export class PageEntityService { ) { } + @bindThis public async pack( src: Page['id'] | Page, me?: { id: User['id'] } | null | undefined, @@ -99,6 +101,7 @@ export class PageEntityService { }); } + @bindThis public packMany( pages: Page[], me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/PageLikeEntityService.ts b/packages/backend/src/core/entities/PageLikeEntityService.ts index 62d9c82ca6..d3e45783dd 100644 --- a/packages/backend/src/core/entities/PageLikeEntityService.ts +++ b/packages/backend/src/core/entities/PageLikeEntityService.ts @@ -8,6 +8,7 @@ import type { User } from '@/models/entities/User.js'; import type { PageLike } from '@/models/entities/PageLike.js'; import { UserEntityService } from './UserEntityService.js'; import { PageEntityService } from './PageEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class PageLikeEntityService { @@ -19,6 +20,7 @@ export class PageLikeEntityService { ) { } + @bindThis public async pack( src: PageLike['id'] | PageLike, me?: { id: User['id'] } | null | undefined, @@ -31,6 +33,7 @@ export class PageLikeEntityService { }; } + @bindThis public packMany( likes: any[], me: { id: User['id'] }, diff --git a/packages/backend/src/core/entities/SigninEntityService.ts b/packages/backend/src/core/entities/SigninEntityService.ts index fd89662f7d..c402644742 100644 --- a/packages/backend/src/core/entities/SigninEntityService.ts +++ b/packages/backend/src/core/entities/SigninEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { Signin } from '@/models/entities/Signin.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class SigninEntityService { @@ -18,6 +19,7 @@ export class SigninEntityService { ) { } + @bindThis public async pack( src: Signin, ) { diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index c691eaebdf..4a027d1de6 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -41,6 +41,7 @@ function isRemoteUser(user: T): user is T & { function isRemoteUser(user: User | { host: User['host'] }): boolean { return !isLocalUser(user); } +import { bindThis } from '@/decorators.js'; @Injectable() export class UserEntityService implements OnModuleInit { @@ -143,6 +144,7 @@ export class UserEntityService implements OnModuleInit { public isLocalUser = isLocalUser; public isRemoteUser = isRemoteUser; + @bindThis public async getRelation(me: User['id'], target: User['id']) { return awaitAll({ id: target, @@ -198,6 +200,7 @@ export class UserEntityService implements OnModuleInit { }); } + @bindThis public async getHasUnreadMessagingMessage(userId: User['id']): Promise { const mute = await this.mutingsRepository.findBy({ muterId: userId, @@ -227,6 +230,7 @@ export class UserEntityService implements OnModuleInit { return withUser || withGroups.some(x => x); } + @bindThis public async getHasUnreadAnnouncement(userId: User['id']): Promise { const reads = await this.announcementReadsRepository.findBy({ userId: userId, @@ -239,6 +243,7 @@ export class UserEntityService implements OnModuleInit { return count > 0; } + @bindThis public async getHasUnreadAntenna(userId: User['id']): Promise { const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId); @@ -250,6 +255,7 @@ export class UserEntityService implements OnModuleInit { return unread != null; } + @bindThis public async getHasUnreadChannel(userId: User['id']): Promise { const channels = await this.channelFollowingsRepository.findBy({ followerId: userId }); @@ -261,6 +267,7 @@ export class UserEntityService implements OnModuleInit { return unread != null; } + @bindThis public async getHasUnreadNotification(userId: User['id']): Promise { const mute = await this.mutingsRepository.findBy({ muterId: userId, @@ -279,6 +286,7 @@ export class UserEntityService implements OnModuleInit { return count > 0; } + @bindThis public async getHasPendingReceivedFollowRequest(userId: User['id']): Promise { const count = await this.followRequestsRepository.countBy({ followeeId: userId, @@ -287,6 +295,7 @@ export class UserEntityService implements OnModuleInit { return count > 0; } + @bindThis public getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' { if (user.hideOnlineStatus) return 'unknown'; if (user.lastActiveDate == null) return 'unknown'; @@ -298,6 +307,7 @@ export class UserEntityService implements OnModuleInit { ); } + @bindThis public async getAvatarUrl(user: User): Promise { if (user.avatar) { return this.driveFileEntityService.getPublicUrl(user.avatar, true) ?? this.getIdenticonUrl(user.id); @@ -309,6 +319,7 @@ export class UserEntityService implements OnModuleInit { } } + @bindThis public getAvatarUrlSync(user: User): string { if (user.avatar) { return this.driveFileEntityService.getPublicUrl(user.avatar, true) ?? this.getIdenticonUrl(user.id); @@ -317,6 +328,7 @@ export class UserEntityService implements OnModuleInit { } } + @bindThis public getIdenticonUrl(userId: User['id']): string { return `${this.config.url}/identicon/${userId}`; } diff --git a/packages/backend/src/core/entities/UserGroupEntityService.ts b/packages/backend/src/core/entities/UserGroupEntityService.ts index e399197618..0674a76723 100644 --- a/packages/backend/src/core/entities/UserGroupEntityService.ts +++ b/packages/backend/src/core/entities/UserGroupEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { UserGroup } from '@/models/entities/UserGroup.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserGroupEntityService { @@ -21,6 +22,7 @@ export class UserGroupEntityService { ) { } + @bindThis public async pack( src: UserGroup['id'] | UserGroup, ): Promise> { diff --git a/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts b/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts index f5c9be3475..0fba1426f4 100644 --- a/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts +++ b/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts @@ -8,6 +8,7 @@ import type { User } from '@/models/entities/User.js'; import type { UserGroupInvitation } from '@/models/entities/UserGroupInvitation.js'; import { UserEntityService } from './UserEntityService.js'; import { UserGroupEntityService } from './UserGroupEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserGroupInvitationEntityService { @@ -19,6 +20,7 @@ export class UserGroupInvitationEntityService { ) { } + @bindThis public async pack( src: UserGroupInvitation['id'] | UserGroupInvitation, ) { @@ -30,6 +32,7 @@ export class UserGroupInvitationEntityService { }; } + @bindThis public packMany( invitations: any[], ) { diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts index e2b0814914..f2e0426928 100644 --- a/packages/backend/src/core/entities/UserListEntityService.ts +++ b/packages/backend/src/core/entities/UserListEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { UserList } from '@/models/entities/UserList.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserListEntityService { @@ -21,6 +22,7 @@ export class UserListEntityService { ) { } + @bindThis public async pack( src: UserList['id'] | UserList, ): Promise> { diff --git a/packages/backend/src/daemons/JanitorService.ts b/packages/backend/src/daemons/JanitorService.ts index dbad576abe..8cdfb703f1 100644 --- a/packages/backend/src/daemons/JanitorService.ts +++ b/packages/backend/src/daemons/JanitorService.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { LessThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { AttestationChallengesRepository } from '@/models/index.js'; +import { bindThis } from '@/decorators.js'; import type { OnApplicationShutdown } from '@nestjs/common'; const interval = 30 * 60 * 1000; @@ -19,6 +20,7 @@ export class JanitorService implements OnApplicationShutdown { /** * Clean up database occasionally */ + @bindThis public start(): void { const tick = async () => { await this.attestationChallengesRepository.delete({ @@ -31,6 +33,7 @@ export class JanitorService implements OnApplicationShutdown { this.intervalId = setInterval(tick, interval); } + @bindThis public onApplicationShutdown(signal?: string | undefined) { clearInterval(this.intervalId); } diff --git a/packages/backend/src/daemons/QueueStatsService.ts b/packages/backend/src/daemons/QueueStatsService.ts index 931de19067..7b47d78a17 100644 --- a/packages/backend/src/daemons/QueueStatsService.ts +++ b/packages/backend/src/daemons/QueueStatsService.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import Xev from 'xev'; import { DI } from '@/di-symbols.js'; import { QueueService } from '@/core/QueueService.js'; +import { bindThis } from '@/decorators.js'; import type { OnApplicationShutdown } from '@nestjs/common'; const ev = new Xev(); @@ -20,6 +21,7 @@ export class QueueStatsService implements OnApplicationShutdown { /** * Report queue stats regularly */ + @bindThis public start(): void { const log = [] as any[]; @@ -71,6 +73,7 @@ export class QueueStatsService implements OnApplicationShutdown { this.intervalId = setInterval(tick, interval); } + @bindThis public onApplicationShutdown(signal?: string | undefined) { clearInterval(this.intervalId); } diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts index e40912442d..7971f9e810 100644 --- a/packages/backend/src/daemons/ServerStatsService.ts +++ b/packages/backend/src/daemons/ServerStatsService.ts @@ -3,6 +3,7 @@ import si from 'systeminformation'; import Xev from 'xev'; import * as osUtils from 'os-utils'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import type { OnApplicationShutdown } from '@nestjs/common'; const ev = new Xev(); @@ -23,6 +24,7 @@ export class ServerStatsService implements OnApplicationShutdown { /** * Report server stats regularly */ + @bindThis public start(): void { const log = [] as any[]; @@ -61,6 +63,7 @@ export class ServerStatsService implements OnApplicationShutdown { this.intervalId = setInterval(tick, interval); } + @bindThis public onApplicationShutdown(signal?: string | undefined) { clearInterval(this.intervalId); } diff --git a/packages/backend/src/decorators.ts b/packages/backend/src/decorators.ts new file mode 100644 index 0000000000..94b1c4be8c --- /dev/null +++ b/packages/backend/src/decorators.ts @@ -0,0 +1,41 @@ +// https://github.com/andreypopp/autobind-decorator + +/** + * Return a descriptor removing the value and returning a getter + * The getter will return a .bind version of the function + * and memoize the result against a symbol on the instance + */ +export function bindThis(target, key, descriptor) { + let fn = descriptor.value; + + if (typeof fn !== 'function') { + throw new TypeError(`@bindThis decorator can only be applied to methods not: ${typeof fn}`); + } + + return { + configurable: true, + get() { + // eslint-disable-next-line no-prototype-builtins + if (this === target.prototype || this.hasOwnProperty(key) || + typeof fn !== 'function') { + return fn; + } + + const boundFn = fn.bind(this); + Object.defineProperty(this, key, { + configurable: true, + get() { + return boundFn; + }, + set(value) { + fn = value; + delete this[key]; + }, + }); + return boundFn; + }, + set(value) { + fn = value; + }, + }; +} diff --git a/packages/backend/src/logger.ts b/packages/backend/src/logger.ts index 6722220681..d09b479c42 100644 --- a/packages/backend/src/logger.ts +++ b/packages/backend/src/logger.ts @@ -2,6 +2,7 @@ import cluster from 'node:cluster'; import chalk from 'chalk'; import { default as convertColor } from 'color-convert'; import { format as dateFormat } from 'date-fns'; +import { bindThis } from '@/decorators.js'; import { envOption } from './env.js'; type Domain = { @@ -26,12 +27,14 @@ export default class Logger { this.syslogClient = syslogClient; } + @bindThis public createSubLogger(domain: string, color?: string, store = true): Logger { const logger = new Logger(domain, color, store); logger.parentLogger = this; return logger; } + @bindThis private log(level: Level, message: string, data?: Record | null, important = false, subDomains: Domain[] = [], store = true): void { if (envOption.quiet) return; if (!this.store) store = false; @@ -80,6 +83,7 @@ export default class Logger { } } + @bindThis public error(x: string | Error, data?: Record | null, important = false): void { // 実行を継続できない状況で使う if (x instanceof Error) { data = data ?? {}; @@ -92,20 +96,24 @@ export default class Logger { } } + @bindThis public warn(message: string, data?: Record | null, important = false): void { // 実行を継続できるが改善すべき状況で使う this.log('warning', message, data, important); } + @bindThis public succ(message: string, data?: Record | null, important = false): void { // 何かに成功した状況で使う this.log('success', message, data, important); } + @bindThis public debug(message: string, data?: Record | null, important = false): void { // デバッグ用に使う(開発者に必要だが利用者に不要な情報) if (process.env.NODE_ENV !== 'production' || envOption.verbose) { this.log('debug', message, data, important); } } + @bindThis public info(message: string, data?: Record | null, important = false): void { // それ以外 this.log('info', message, data, important); } diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index e5b911ed32..69512498f8 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -1,3 +1,5 @@ +import { bindThis } from '@/decorators.js'; + export class Cache { public cache: Map; private lifetime: number; @@ -7,6 +9,7 @@ export class Cache { this.lifetime = lifetime; } + @bindThis public set(key: string | null, value: T): void { this.cache.set(key, { date: Date.now(), @@ -14,6 +17,7 @@ export class Cache { }); } + @bindThis public get(key: string | null): T | undefined { const cached = this.cache.get(key); if (cached == null) return undefined; @@ -24,6 +28,7 @@ export class Cache { return cached.value; } + @bindThis public delete(key: string | null) { this.cache.delete(key); } @@ -32,6 +37,7 @@ export class Cache { * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします */ + @bindThis public async fetch(key: string | null, fetcher: () => Promise, validator?: (cachedValue: T) => boolean): Promise { const cachedValue = this.get(key); if (cachedValue !== undefined) { @@ -56,6 +62,7 @@ export class Cache { * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします */ + @bindThis public async fetchMaybe(key: string | null, fetcher: () => Promise, validator?: (cachedValue: T) => boolean): Promise { const cachedValue = this.get(key); if (cachedValue !== undefined) { diff --git a/packages/backend/src/misc/i18n.ts b/packages/backend/src/misc/i18n.ts index 4fa398763a..e304a8adac 100644 --- a/packages/backend/src/misc/i18n.ts +++ b/packages/backend/src/misc/i18n.ts @@ -5,12 +5,13 @@ export class I18n> { this.locale = locale; //#region BIND - this.t = this.t.bind(this); + //this.t = this.t.bind(this); //#endregion } // string にしているのは、ドット区切りでのパス指定を許可するため // なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも + @bindThis public t(key: string, args?: Record): string { try { let str = key.split('.').reduce((o, i) => o[i], this.locale) as string; diff --git a/packages/backend/src/postgre.ts b/packages/backend/src/postgre.ts index 2beb31e24e..0f9f7a6a96 100644 --- a/packages/backend/src/postgre.ts +++ b/packages/backend/src/postgre.ts @@ -72,6 +72,7 @@ import { Channel } from '@/models/entities/Channel.js'; import { Config } from '@/config.js'; import MisskeyLogger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; import { envOption } from './env.js'; export const dbLogger = new MisskeyLogger('db'); @@ -79,32 +80,39 @@ export const dbLogger = new MisskeyLogger('db'); const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false); class MyCustomLogger implements Logger { + @bindThis private highlight(sql: string) { return highlight.highlight(sql, { language: 'sql', ignoreIllegals: true, }); } + @bindThis public logQuery(query: string, parameters?: any[]) { sqlLogger.info(this.highlight(query).substring(0, 100)); } + @bindThis public logQueryError(error: string, query: string, parameters?: any[]) { sqlLogger.error(this.highlight(query)); } + @bindThis public logQuerySlow(time: number, query: string, parameters?: any[]) { sqlLogger.warn(this.highlight(query)); } + @bindThis public logSchemaBuild(message: string) { sqlLogger.info(message); } + @bindThis public log(message: string) { sqlLogger.info(message); } + @bindThis public logMigration(message: string) { sqlLogger.info(message); } diff --git a/packages/backend/src/queue/DbQueueProcessorsService.ts b/packages/backend/src/queue/DbQueueProcessorsService.ts index 58384c4d1b..e5568ab9bf 100644 --- a/packages/backend/src/queue/DbQueueProcessorsService.ts +++ b/packages/backend/src/queue/DbQueueProcessorsService.ts @@ -16,6 +16,7 @@ import { ImportUserListsProcessorService } from './processors/ImportUserListsPro import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js'; import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DbQueueProcessorsService { @@ -39,6 +40,7 @@ export class DbQueueProcessorsService { ) { } + @bindThis public start(q: Bull.Queue): void { q.process('deleteDriveFiles', (job, done) => this.deleteDriveFilesProcessorService.process(job, done)); q.process('exportCustomEmojis', (job, done) => this.exportCustomEmojisProcessorService.process(job, done)); diff --git a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts index 3ff3dd090c..c95e1c1ba6 100644 --- a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts +++ b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts @@ -5,6 +5,7 @@ import type { Config } from '@/config.js'; import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js'; import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ObjectStorageQueueProcessorsService { @@ -17,6 +18,7 @@ export class ObjectStorageQueueProcessorsService { ) { } + @bindThis public start(q: Bull.Queue): void { q.process('deleteFile', 16, (job) => this.deleteFileProcessorService.process(job)); q.process('cleanRemoteFiles', 16, (job, done) => this.cleanRemoteFilesProcessorService.process(job, done)); diff --git a/packages/backend/src/queue/QueueLoggerService.ts b/packages/backend/src/queue/QueueLoggerService.ts index a311470cc9..3a8a734f10 100644 --- a/packages/backend/src/queue/QueueLoggerService.ts +++ b/packages/backend/src/queue/QueueLoggerService.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class QueueLoggerService { diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 8c300d479c..1d2feb5ef8 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -13,6 +13,7 @@ import { EndedPollNotificationProcessorService } from './processors/EndedPollNot import { DeliverProcessorService } from './processors/DeliverProcessorService.js'; import { InboxProcessorService } from './processors/InboxProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class QueueProcessorService { @@ -35,6 +36,7 @@ export class QueueProcessorService { this.logger = this.queueLoggerService.logger; } + @bindThis public start() { function renderError(e: Error): any { if (e) { // 何故かeがundefinedで来ることがある diff --git a/packages/backend/src/queue/SystemQueueProcessorsService.ts b/packages/backend/src/queue/SystemQueueProcessorsService.ts index a8af92b9ba..1ce4152b2c 100644 --- a/packages/backend/src/queue/SystemQueueProcessorsService.ts +++ b/packages/backend/src/queue/SystemQueueProcessorsService.ts @@ -7,6 +7,7 @@ import { CleanChartsProcessorService } from './processors/CleanChartsProcessorSe import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class SystemQueueProcessorsService { @@ -22,6 +23,7 @@ export class SystemQueueProcessorsService { ) { } + @bindThis public start(q: Bull.Queue): void { q.process('tickCharts', (job, done) => this.tickChartsProcessorService.process(job, done)); q.process('resyncCharts', (job, done) => this.resyncChartsProcessorService.process(job, done)); diff --git a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts index e91cba9d10..7a1e3e71be 100644 --- a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts +++ b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts @@ -7,6 +7,7 @@ import type Logger from '@/logger.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class CheckExpiredMutingsProcessorService { @@ -25,6 +26,7 @@ export class CheckExpiredMutingsProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('check-expired-mutings'); } + @bindThis public async process(job: Bull.Job>, done: () => void): Promise { this.logger.info('Checking expired mutings...'); diff --git a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts index e8e90f1422..c57086240a 100644 --- a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts @@ -17,6 +17,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js'; import ApRequestChart from '@/core/chart/charts/ap-request.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class CleanChartsProcessorService { @@ -44,6 +45,7 @@ export class CleanChartsProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('clean-charts'); } + @bindThis public async process(job: Bull.Job>, done: () => void): Promise { this.logger.info('Clean charts...'); diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts index 6eb457ce9f..8ca39a9677 100644 --- a/packages/backend/src/queue/processors/CleanProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanProcessorService.ts @@ -6,6 +6,7 @@ import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class CleanProcessorService { @@ -23,6 +24,7 @@ export class CleanProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('clean'); } + @bindThis public async process(job: Bull.Job>, done: () => void): Promise { this.logger.info('Cleaning...'); diff --git a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts index a4fd8c502d..5a33c27188 100644 --- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts @@ -7,6 +7,7 @@ import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class CleanRemoteFilesProcessorService { @@ -25,6 +26,7 @@ export class CleanRemoteFilesProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('clean-remote-files'); } + @bindThis public async process(job: Bull.Job>, done: () => void): Promise { this.logger.info('Deleting cached remote files...'); diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index 5e4c8bdd69..e36a78de6a 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -11,6 +11,7 @@ import { EmailService } from '@/core/EmailService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserDeleteJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DeleteAccountProcessorService { @@ -39,6 +40,7 @@ export class DeleteAccountProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('delete-account'); } + @bindThis public async process(job: Bull.Job): Promise { this.logger.info(`Deleting account of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts index 682382b2db..fa0c1733f6 100644 --- a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts @@ -8,6 +8,7 @@ import { DriveService } from '@/core/DriveService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DeleteDriveFilesProcessorService { @@ -29,6 +30,7 @@ export class DeleteDriveFilesProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('delete-drive-files'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Deleting drive files of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts index 6740643fe2..2fb2f56f8d 100644 --- a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts @@ -6,6 +6,7 @@ import { DriveService } from '@/core/DriveService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { ObjectStorageFileJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DeleteFileProcessorService { @@ -21,6 +22,7 @@ export class DeleteFileProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('delete-file'); } + @bindThis public async process(job: Bull.Job): Promise { const key: string = job.data.key; diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 2a4b201a7d..58969d550e 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -18,6 +18,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DeliverJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DeliverProcessorService { @@ -50,6 +51,7 @@ export class DeliverProcessorService { this.latest = null; } + @bindThis public async process(job: Bull.Job): Promise { const { host } = new URL(job.data.to); diff --git a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts index 2fc7fe219e..21d2dc9efc 100644 --- a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts +++ b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts @@ -8,6 +8,7 @@ import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { EndedPollNotificationJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class EndedPollNotificationProcessorService { @@ -29,6 +30,7 @@ export class EndedPollNotificationProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('ended-poll-notification'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { const note = await this.notesRepository.findOneBy({ id: job.data.noteId }); if (note == null || !note.hasPoll) { diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index db149b68cf..5b3c1a415b 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -12,6 +12,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ExportBlockingProcessorService { @@ -34,6 +35,7 @@ export class ExportBlockingProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('export-blocking'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Exporting blocking of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index f8f261b478..87b23f1891 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -14,6 +14,7 @@ import { createTemp, createTempDir } from '@/misc/create-temp.js'; import { DownloadService } from '@/core/DownloadService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ExportCustomEmojisProcessorService { @@ -36,6 +37,7 @@ export class ExportCustomEmojisProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info('Exporting custom emojis ...'); diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index 1e3fba06b5..064b126e44 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -13,6 +13,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ExportFollowingProcessorService { @@ -38,6 +39,7 @@ export class ExportFollowingProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('export-following'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Exporting following of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index e263c245fd..94c7ea8a46 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -12,6 +12,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ExportMutingProcessorService { @@ -37,6 +38,7 @@ export class ExportMutingProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('export-muting'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Exporting muting of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index 533d4bd7c6..8431829e91 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -13,6 +13,7 @@ import type { Note } from '@/models/entities/Note.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ExportNotesProcessorService { @@ -37,6 +38,7 @@ export class ExportNotesProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('export-notes'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Exporting notes of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index 8c3e3dbe17..a8daa5e5ee 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -12,6 +12,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ExportUserListsProcessorService { @@ -37,6 +38,7 @@ export class ExportUserListsProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('export-user-lists'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Exporting user lists of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index 9442a60d8d..2eed420e96 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -12,6 +12,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserImportJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ImportBlockingProcessorService { @@ -39,6 +40,7 @@ export class ImportBlockingProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('import-blocking'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Importing blocking of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index 492f17f9ff..0061c2a8f7 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -10,6 +10,7 @@ import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { createTempDir } from '@/misc/create-temp.js'; import { DriveService } from '@/core/DriveService.js'; import { DownloadService } from '@/core/DownloadService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserImportJobData } from '../types.js'; @@ -43,6 +44,7 @@ export class ImportCustomEmojisProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('import-custom-emojis'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info('Importing custom emojis ...'); diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index 667f7279fb..b61846d747 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -12,6 +12,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserImportJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ImportFollowingProcessorService { @@ -36,6 +37,7 @@ export class ImportFollowingProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('import-following'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Importing following of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index f3c16e73d5..21236da2ef 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -12,6 +12,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserImportJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ImportMutingProcessorService { @@ -36,6 +37,7 @@ export class ImportMutingProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('import-muting'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Importing muting of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index 1519877c5f..1bec77b837 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -13,6 +13,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserImportJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ImportUserListsProcessorService { @@ -44,6 +45,7 @@ export class ImportUserListsProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('import-user-lists'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Importing user lists of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 8f1c474020..c032122caf 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -24,6 +24,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js'; import { ApInboxService } from '@/core/activitypub/ApInboxService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DeliverJobData, InboxJobData } from '../types.js'; @@ -60,6 +61,7 @@ export class InboxProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('inbox'); } + @bindThis public async process(job: Bull.Job): Promise { const signature = job.data.signature; // HTTP-signature const activity = job.data.activity; diff --git a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts index bf2fdeb7a0..1a8fe65a4f 100644 --- a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts @@ -17,6 +17,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js'; import ApRequestChart from '@/core/chart/charts/ap-request.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ResyncChartsProcessorService { @@ -44,6 +45,7 @@ export class ResyncChartsProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('resync-charts'); } + @bindThis public async process(job: Bull.Job>, done: () => void): Promise { this.logger.info('Resync charts...'); diff --git a/packages/backend/src/queue/processors/TickChartsProcessorService.ts b/packages/backend/src/queue/processors/TickChartsProcessorService.ts index 96607e1d68..323e5227cd 100644 --- a/packages/backend/src/queue/processors/TickChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/TickChartsProcessorService.ts @@ -17,6 +17,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js'; import ApRequestChart from '@/core/chart/charts/ap-request.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class TickChartsProcessorService { @@ -44,6 +45,7 @@ export class TickChartsProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('tick-charts'); } + @bindThis public async process(job: Bull.Job>, done: () => void): Promise { this.logger.info('Tick charts...'); diff --git a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts index 43e3f37201..183ef07477 100644 --- a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts @@ -9,6 +9,7 @@ import { StatusError } from '@/misc/status-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { WebhookDeliverJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class WebhookDeliverProcessorService { @@ -27,6 +28,7 @@ export class WebhookDeliverProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('webhook'); } + @bindThis public async process(job: Bull.Job): Promise { try { this.logger.debug(`delivering ${job.data.webhookId}`); diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 015c8f2b4c..94a277f4a4 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -18,6 +18,7 @@ import type { Note } from '@/models/entities/Note.js'; import { QueryService } from '@/core/QueryService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; import type { FindOptionsWhere } from 'typeorm'; const ACTIVITY_JSON = 'application/activity+json; charset=utf-8'; @@ -57,9 +58,10 @@ export class ActivityPubServerService { private userKeypairStoreService: UserKeypairStoreService, private queryService: QueryService, ) { - this.createServer = this.createServer.bind(this); + //this.createServer = this.createServer.bind(this); } + @bindThis private setResponseType(request: FastifyRequest, reply: FastifyReply): void { const accept = request.accepts().type([ACTIVITY_JSON, LD_JSON]); if (accept === LD_JSON) { @@ -73,6 +75,7 @@ export class ActivityPubServerService { * Pack Create or Announce Activity * @param note Note */ + @bindThis private async packActivity(note: Note): Promise { if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { const renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId }); @@ -82,6 +85,7 @@ export class ActivityPubServerService { return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note); } + @bindThis private inbox(request: FastifyRequest, reply: FastifyReply) { let signature; @@ -97,6 +101,7 @@ export class ActivityPubServerService { reply.code(202); } + @bindThis private async followers( request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>, reply: FastifyReply, @@ -184,6 +189,7 @@ export class ActivityPubServerService { } } + @bindThis private async following( request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>, reply: FastifyReply, @@ -271,6 +277,7 @@ export class ActivityPubServerService { } } + @bindThis private async featured(request: FastifyRequest<{ Params: { user: string; }; }>, reply: FastifyReply) { const userId = request.params.user; @@ -304,6 +311,7 @@ export class ActivityPubServerService { return (this.apRendererService.renderActivity(rendered)); } + @bindThis private async outbox( request: FastifyRequest<{ Params: { user: string; }; @@ -390,6 +398,7 @@ export class ActivityPubServerService { } } + @bindThis private async userInfo(request: FastifyRequest, reply: FastifyReply, user: User | null) { if (user == null) { reply.code(404); @@ -401,6 +410,7 @@ export class ActivityPubServerService { return (this.apRendererService.renderActivity(await this.apRendererService.renderPerson(user as ILocalUser))); } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.addConstraintStrategy({ name: 'apOrHtml', diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 088e780d69..b7ab549611 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -19,6 +19,7 @@ import { InternalStorageService } from '@/core/InternalStorageService.js'; import { contentDisposition } from '@/misc/content-disposition.js'; import { FileInfoService } from '@/core/FileInfoService.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -45,9 +46,10 @@ export class FileServerService { ) { this.logger = this.loggerService.getLogger('server', 'gray', false); - this.createServer = this.createServer.bind(this); + //this.createServer = this.createServer.bind(this); } + @bindThis public commonReadableHandlerGenerator(reply: FastifyReply) { return (err: Error): void => { this.logger.error(err); @@ -56,6 +58,7 @@ export class FileServerService { }; } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.addHook('onRequest', (request, reply, done) => { reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); @@ -80,6 +83,7 @@ export class FileServerService { done(); } + @bindThis private async sendDriveFile(request: FastifyRequest<{ Params: { key: string; } }>, reply: FastifyReply) { const key = request.params.key; diff --git a/packages/backend/src/server/MediaProxyServerService.ts b/packages/backend/src/server/MediaProxyServerService.ts index 4d7bbdf599..733a7feeb5 100644 --- a/packages/backend/src/server/MediaProxyServerService.ts +++ b/packages/backend/src/server/MediaProxyServerService.ts @@ -14,6 +14,7 @@ import { StatusError } from '@/misc/status-error.js'; import type Logger from '@/logger.js'; import { FileInfoService } from '@/core/FileInfoService.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class MediaProxyServerService { @@ -30,9 +31,10 @@ export class MediaProxyServerService { ) { this.logger = this.loggerService.getLogger('server', 'gray', false); - this.createServer = this.createServer.bind(this); + //this.createServer = this.createServer.bind(this); } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.addHook('onRequest', (request, reply, done) => { reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); @@ -47,6 +49,7 @@ export class MediaProxyServerService { done(); } + @bindThis private async handler(request: FastifyRequest<{ Params: { url: string; }; Querystring: { url?: string; }; }>, reply: FastifyReply) { const url = 'url' in request.query ? request.query.url : 'https://' + request.params.url; diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index b85925f53e..0f3cc36dae 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -8,6 +8,7 @@ import { MetaService } from '@/core/MetaService.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Cache } from '@/misc/cache.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; const nodeinfo2_1path = '/nodeinfo/2.1'; const nodeinfo2_0path = '/nodeinfo/2.0'; @@ -27,9 +28,10 @@ export class NodeinfoServerService { private userEntityService: UserEntityService, private metaService: MetaService, ) { - this.createServer = this.createServer.bind(this); + //this.createServer = this.createServer.bind(this); } + @bindThis public getLinks() { return [/* (awaiting release) { rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1', @@ -40,6 +42,7 @@ export class NodeinfoServerService { }]; } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { const nodeinfo2 = async () => { const now = Date.now(); diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 96159cfc53..075b9cdff7 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -23,6 +23,7 @@ import { WellKnownServerService } from './WellKnownServerService.js'; import { MediaProxyServerService } from './MediaProxyServerService.js'; import { FileServerService } from './FileServerService.js'; import { ClientServerService } from './web/ClientServerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ServerService { @@ -53,6 +54,7 @@ export class ServerService { this.logger = this.loggerService.getLogger('server', 'gray', false); } + @bindThis public launch() { const fastify = Fastify({ trustProxy: true, diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts index 412c608313..ea34ad5b0f 100644 --- a/packages/backend/src/server/WellKnownServerService.ts +++ b/packages/backend/src/server/WellKnownServerService.ts @@ -10,6 +10,7 @@ import type { User } from '@/models/entities/User.js'; import * as Acct from '@/misc/acct.js'; import { NodeinfoServerService } from './NodeinfoServerService.js'; import type { FindOptionsWhere } from 'typeorm'; +import { bindThis } from '@/decorators.js'; @Injectable() export class WellKnownServerService { @@ -22,9 +23,10 @@ export class WellKnownServerService { private nodeinfoServerService: NodeinfoServerService, ) { - this.createServer = this.createServer.bind(this); + //this.createServer = this.createServer.bind(this); } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { const XRD = (...x: { element: string, value?: string, attributes?: Record }[]) => `${x.map(({ element, value, attributes }) => diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index 2e72cdf9f8..fb1e17790c 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -12,6 +12,7 @@ import type Logger from '@/logger.js'; import type { UserIpsRepository } from '@/models/index.js'; import { MetaService } from '@/core/MetaService.js'; import { createTemp } from '@/misc/create-temp.js'; +import { bindThis } from '@/decorators.js'; import { ApiError } from './error.js'; import { RateLimiterService } from './RateLimiterService.js'; import { ApiLoggerService } from './ApiLoggerService.js'; @@ -50,6 +51,7 @@ export class ApiCallService implements OnApplicationShutdown { }, 1000 * 60 * 60); } + @bindThis public handleRequest( endpoint: IEndpoint & { exec: any }, request: FastifyRequest<{ Body: Record, Querystring: Record }>, @@ -90,6 +92,7 @@ export class ApiCallService implements OnApplicationShutdown { }); } + @bindThis public async handleMultipartRequest( endpoint: IEndpoint & { exec: any }, request: FastifyRequest<{ Body: Record, Querystring: Record }>, @@ -140,6 +143,7 @@ export class ApiCallService implements OnApplicationShutdown { }); } + @bindThis private send(reply: FastifyReply, x?: any, y?: ApiError) { if (x == null) { reply.code(204); @@ -160,6 +164,7 @@ export class ApiCallService implements OnApplicationShutdown { } } + @bindThis private async logIp(request: FastifyRequest, user: ILocalUser) { const meta = await this.metaService.fetch(); if (!meta.enableIpLogging) return; @@ -183,6 +188,7 @@ export class ApiCallService implements OnApplicationShutdown { } } + @bindThis private async call( ep: IEndpoint & { exec: any }, user: CacheableLocalUser | null | undefined, @@ -315,6 +321,7 @@ export class ApiCallService implements OnApplicationShutdown { }); } + @bindThis public onApplicationShutdown(signal?: string | undefined) { clearInterval(this.userIpHistoriesClearIntervalId); } diff --git a/packages/backend/src/server/api/ApiLoggerService.ts b/packages/backend/src/server/api/ApiLoggerService.ts index c4fb25036e..cabd65fd3e 100644 --- a/packages/backend/src/server/api/ApiLoggerService.ts +++ b/packages/backend/src/server/api/ApiLoggerService.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApiLoggerService { diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index cf3f2deebf..b17456d0e2 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -14,6 +14,7 @@ import { SigninApiService } from './SigninApiService.js'; import { GithubServerService } from './integration/GithubServerService.js'; import { DiscordServerService } from './integration/DiscordServerService.js'; import { TwitterServerService } from './integration/TwitterServerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApiServerService { @@ -40,9 +41,10 @@ export class ApiServerService { private discordServerService: DiscordServerService, private twitterServerService: TwitterServerService, ) { - this.createServer = this.createServer.bind(this); + //this.createServer = this.createServer.bind(this); } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.register(cors, { origin: '*', diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts index ad387c4732..8b39f6c924 100644 --- a/packages/backend/src/server/api/AuthenticateService.ts +++ b/packages/backend/src/server/api/AuthenticateService.ts @@ -7,6 +7,7 @@ import { Cache } from '@/misc/cache.js'; import type { App } from '@/models/entities/App.js'; import { UserCacheService } from '@/core/UserCacheService.js'; import isNativeToken from '@/misc/is-native-token.js'; +import { bindThis } from '@/decorators.js'; export class AuthenticationError extends Error { constructor(message: string) { @@ -34,6 +35,7 @@ export class AuthenticateService { this.appCache = new Cache(Infinity); } + @bindThis public async authenticate(token: string | null | undefined): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> { if (token == null) { return [null, null]; diff --git a/packages/backend/src/server/api/GetterService.ts b/packages/backend/src/server/api/GetterService.ts index 70ab46ec35..c7f9916f97 100644 --- a/packages/backend/src/server/api/GetterService.ts +++ b/packages/backend/src/server/api/GetterService.ts @@ -5,6 +5,7 @@ import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class GetterService { @@ -22,6 +23,7 @@ export class GetterService { /** * Get note for API processing */ + @bindThis public async getNote(noteId: Note['id']) { const note = await this.notesRepository.findOneBy({ id: noteId }); @@ -35,6 +37,7 @@ export class GetterService { /** * Get user for API processing */ + @bindThis public async getUser(userId: User['id']) { const user = await this.usersRepository.findOneBy({ id: userId }); @@ -48,6 +51,7 @@ export class GetterService { /** * Get remote user for API processing */ + @bindThis public async getRemoteUser(userId: User['id']) { const user = await this.getUser(userId); @@ -61,6 +65,7 @@ export class GetterService { /** * Get local user for API processing */ + @bindThis public async getLocalUser(userId: User['id']) { const user = await this.getUser(userId); diff --git a/packages/backend/src/server/api/RateLimiterService.ts b/packages/backend/src/server/api/RateLimiterService.ts index 35f28bfd63..94a15f94bb 100644 --- a/packages/backend/src/server/api/RateLimiterService.ts +++ b/packages/backend/src/server/api/RateLimiterService.ts @@ -5,6 +5,7 @@ import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; import type { IEndpointMeta } from './endpoints.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class RateLimiterService { @@ -19,6 +20,7 @@ export class RateLimiterService { this.logger = this.loggerService.getLogger('limiter'); } + @bindThis public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable }, actor: string) { return new Promise((ok, reject) => { if (process.env.NODE_ENV === 'test') ok(); diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 8b3d86e5a6..b633c2888f 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -13,6 +13,7 @@ import { IdService } from '@/core/IdService.js'; import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; import { RateLimiterService } from './RateLimiterService.js'; import { SigninService } from './SigninService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class SigninApiService { @@ -42,6 +43,7 @@ export class SigninApiService { ) { } + @bindThis public async signin( request: FastifyRequest<{ Body: { diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts index 18a1d6c088..96a89956f9 100644 --- a/packages/backend/src/server/api/SigninService.ts +++ b/packages/backend/src/server/api/SigninService.ts @@ -7,6 +7,7 @@ import { IdService } from '@/core/IdService.js'; import type { ILocalUser } from '@/models/entities/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { SigninEntityService } from '@/core/entities/SigninEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class SigninService { @@ -23,6 +24,7 @@ export class SigninService { ) { } + @bindThis public signin(request: FastifyRequest, reply: FastifyReply, user: ILocalUser, redirect = false) { setImmediate(async () => { // Append signin history diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 771858d091..59676426af 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -14,6 +14,7 @@ import { EmailService } from '@/core/EmailService.js'; import { ILocalUser } from '@/models/entities/User.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { SigninService } from './SigninService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class SignupApiService { @@ -43,6 +44,7 @@ export class SignupApiService { ) { } + @bindThis public async signup( request: FastifyRequest<{ Body: { @@ -165,6 +167,7 @@ export class SignupApiService { } } + @bindThis public async signupPending(request: FastifyRequest<{ Body: { code: string; } }>, reply: FastifyReply) { const body = request.body; diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index 46eaf8566e..487eef2d50 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -13,6 +13,7 @@ import MainStreamConnection from './stream/index.js'; import { ChannelsService } from './stream/ChannelsService.js'; import type { ParsedUrlQuery } from 'querystring'; import type * as http from 'node:http'; +import { bindThis } from '@/decorators.js'; @Injectable() export class StreamingApiServerService { @@ -49,6 +50,7 @@ export class StreamingApiServerService { ) { } + @bindThis public attachStreamingApi(server: http.Server) { // Init websocket server const ws = new websocket.server({ diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index 53de8d9495..9fc1391570 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -8,6 +8,7 @@ import { UserSuspendService } from '@/core/UserSuspendService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; export const meta = { tags: ['admin'], @@ -79,6 +80,7 @@ export default class extends Endpoint { }); } + @bindThis private async unFollowAll(follower: User) { const followings = await this.followingsRepository.findBy({ followerId: follower.id, @@ -97,6 +99,7 @@ export default class extends Endpoint { } } + @bindThis private async readAllNotify(notifier: User) { await this.notificationsRepository.update({ notifierId: notifier.id, diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index c218ec4642..1068a2eec7 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -15,6 +15,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -112,6 +113,7 @@ export default class extends Endpoint { /*** * URIからUserかNoteを解決する */ + @bindThis private async fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise | null> { // ブロックしてたら中断 const fetchedMeta = await this.metaService.fetch(); @@ -144,6 +146,7 @@ export default class extends Endpoint { ); } + @bindThis private async mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise | null> { if (user != null) { return { diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index 93c22a6c0b..a7f39a78d0 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -14,6 +14,7 @@ import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { SigninService } from '../SigninService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DiscordServerService { @@ -36,9 +37,10 @@ export class DiscordServerService { private metaService: MetaService, private signinService: SigninService, ) { - this.create = this.create.bind(this); + //this.create = this.create.bind(this); } + @bindThis public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.get('/disconnect/discord', async (request, reply) => { if (!this.compareOrigin(request)) { @@ -288,10 +290,12 @@ export class DiscordServerService { done(); } + @bindThis private getUserToken(request: FastifyRequest): string | null { return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } + @bindThis private compareOrigin(request: FastifyRequest): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index 2fd20bf831..3aa04f72ee 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -14,6 +14,7 @@ import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { SigninService } from '../SigninService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class GithubServerService { @@ -36,9 +37,10 @@ export class GithubServerService { private metaService: MetaService, private signinService: SigninService, ) { - this.create = this.create.bind(this); + //this.create = this.create.bind(this); } + @bindThis public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.get('/disconnect/github', async (request, reply) => { if (!this.compareOrigin(request)) { @@ -260,10 +262,12 @@ export class GithubServerService { done(); } + @bindThis private getUserToken(request: FastifyRequest): string | null { return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } + @bindThis private compareOrigin(request: FastifyRequest): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; diff --git a/packages/backend/src/server/api/integration/TwitterServerService.ts b/packages/backend/src/server/api/integration/TwitterServerService.ts index a8447f9d49..7a127fa29a 100644 --- a/packages/backend/src/server/api/integration/TwitterServerService.ts +++ b/packages/backend/src/server/api/integration/TwitterServerService.ts @@ -14,6 +14,7 @@ import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { SigninService } from '../SigninService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class TwitterServerService { @@ -36,9 +37,10 @@ export class TwitterServerService { private metaService: MetaService, private signinService: SigninService, ) { - this.create = this.create.bind(this); + //this.create = this.create.bind(this); } + @bindThis public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.get('/disconnect/twitter', async (request, reply) => { if (!this.compareOrigin(request)) { @@ -205,10 +207,12 @@ export class TwitterServerService { done(); } + @bindThis private getUserToken(request: FastifyRequest): string | null { return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } + @bindThis private compareOrigin(request: FastifyRequest): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; diff --git a/packages/backend/src/server/api/stream/ChannelsService.ts b/packages/backend/src/server/api/stream/ChannelsService.ts index d6005b1ee8..198fc190d4 100644 --- a/packages/backend/src/server/api/stream/ChannelsService.ts +++ b/packages/backend/src/server/api/stream/ChannelsService.ts @@ -15,6 +15,7 @@ import { MessagingChannelService } from './channels/messaging.js'; import { MessagingIndexChannelService } from './channels/messaging-index.js'; import { DriveChannelService } from './channels/drive.js'; import { HashtagChannelService } from './channels/hashtag.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ChannelsService { @@ -37,6 +38,7 @@ export class ChannelsService { ) { } + @bindThis public getChannelService(name: string) { switch (name) { case 'main': return this.mainChannelService; diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 5480c12c09..3e67880b45 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -1,3 +1,4 @@ +import { bindThis } from '@/decorators.js'; import type Connection from '.'; /** @@ -43,6 +44,7 @@ export default abstract class Channel { this.connection = connection; } + @bindThis public send(typeOrPayload: any, payload?: any) { const type = payload === undefined ? typeOrPayload.type : typeOrPayload; const body = payload === undefined ? typeOrPayload.body : payload; diff --git a/packages/backend/src/server/api/stream/channels/admin.ts b/packages/backend/src/server/api/stream/channels/admin.ts index 8c3c0d2adf..210e016a7e 100644 --- a/packages/backend/src/server/api/stream/channels/admin.ts +++ b/packages/backend/src/server/api/stream/channels/admin.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class AdminChannel extends Channel { @@ -6,6 +7,7 @@ class AdminChannel extends Channel { public static shouldShare = true; public static requireCredential = true; + @bindThis public async init(params: any) { // Subscribe admin stream this.subscriber.on(`adminStream:${this.user!.id}`, data => { @@ -23,6 +25,7 @@ export class AdminChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): AdminChannel { return new AdminChannel( id, diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts index 7c34aef495..44beef2da2 100644 --- a/packages/backend/src/server/api/stream/channels/antenna.ts +++ b/packages/backend/src/server/api/stream/channels/antenna.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { NotesRepository } from '@/models/index.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; import type { StreamMessages } from '../types.js'; @@ -18,9 +19,10 @@ class AntennaChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onEvent = this.onEvent.bind(this); + //this.onEvent = this.onEvent.bind(this); } + @bindThis public async init(params: any) { this.antennaId = params.antennaId as string; @@ -28,6 +30,7 @@ class AntennaChannel extends Channel { this.subscriber.on(`antennaStream:${this.antennaId}`, this.onEvent); } + @bindThis private async onEvent(data: StreamMessages['antenna']['payload']) { if (data.type === 'note') { const note = await this.noteEntityService.pack(data.body.id, this.user, { detail: true }); @@ -45,6 +48,7 @@ class AntennaChannel extends Channel { } } + @bindThis public dispose() { // Unsubscribe events this.subscriber.off(`antennaStream:${this.antennaId}`, this.onEvent); @@ -61,6 +65,7 @@ export class AntennaChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): AntennaChannel { return new AntennaChannel( this.noteEntityService, diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 2ef70e62e9..5ba84e43c4 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -5,6 +5,7 @@ import type { User } from '@/models/entities/User.js'; import type { Packed } from '@/misc/schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; import type { StreamMessages } from '../types.js'; @@ -24,10 +25,11 @@ class ChannelChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onNote = this.onNote.bind(this); - this.emitTypers = this.emitTypers.bind(this); + //this.onNote = this.onNote.bind(this); + //this.emitTypers = this.emitTypers.bind(this); } + @bindThis public async init(params: any) { this.channelId = params.channelId as string; @@ -37,6 +39,7 @@ class ChannelChannel extends Channel { this.emitTypersIntervalId = setInterval(this.emitTypers, 5000); } + @bindThis private async onNote(note: Packed<'Note'>) { if (note.channelId !== this.channelId) return; @@ -63,6 +66,7 @@ class ChannelChannel extends Channel { this.send('note', note); } + @bindThis private onEvent(data: StreamMessages['channel']['payload']) { if (data.type === 'typing') { const id = data.body; @@ -74,6 +78,7 @@ class ChannelChannel extends Channel { } } + @bindThis private async emitTypers() { const now = new Date(); @@ -90,6 +95,7 @@ class ChannelChannel extends Channel { }); } + @bindThis public dispose() { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); @@ -110,6 +116,7 @@ export class ChannelChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): ChannelChannel { return new ChannelChannel( this.noteEntityService, diff --git a/packages/backend/src/server/api/stream/channels/drive.ts b/packages/backend/src/server/api/stream/channels/drive.ts index 80d83cd690..cfcb125b6b 100644 --- a/packages/backend/src/server/api/stream/channels/drive.ts +++ b/packages/backend/src/server/api/stream/channels/drive.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class DriveChannel extends Channel { @@ -6,6 +7,7 @@ class DriveChannel extends Channel { public static shouldShare = true; public static requireCredential = true; + @bindThis public async init(params: any) { // Subscribe drive stream this.subscriber.on(`driveStream:${this.user!.id}`, data => { @@ -23,6 +25,7 @@ export class DriveChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): DriveChannel { return new DriveChannel( id, diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index a8617582dc..34f782e580 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -6,6 +6,7 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/schema.js'; import { MetaService } from '@/core/MetaService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class GlobalTimelineChannel extends Channel { @@ -21,9 +22,10 @@ class GlobalTimelineChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onNote = this.onNote.bind(this); + //this.onNote = this.onNote.bind(this); } + @bindThis public async init(params: any) { const meta = await this.metaService.fetch(); if (meta.disableGlobalTimeline) { @@ -34,6 +36,7 @@ class GlobalTimelineChannel extends Channel { this.subscriber.on('notesStream', this.onNote); } + @bindThis private async onNote(note: Packed<'Note'>) { if (note.visibility !== 'public') return; if (note.channelId != null) return; @@ -78,6 +81,7 @@ class GlobalTimelineChannel extends Channel { this.send('note', note); } + @bindThis public dispose() { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); @@ -95,6 +99,7 @@ export class GlobalTimelineChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): GlobalTimelineChannel { return new GlobalTimelineChannel( this.metaService, diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index 0f6c081c12..073b737079 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -4,6 +4,7 @@ import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class HashtagChannel extends Channel { @@ -19,9 +20,10 @@ class HashtagChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onNote = this.onNote.bind(this); + //this.onNote = this.onNote.bind(this); } + @bindThis public async init(params: any) { this.q = params.q; @@ -31,6 +33,7 @@ class HashtagChannel extends Channel { this.subscriber.on('notesStream', this.onNote); } + @bindThis private async onNote(note: Packed<'Note'>) { const noteTags = note.tags ? note.tags.map((t: string) => t.toLowerCase()) : []; const matched = this.q.some(tags => tags.every(tag => noteTags.includes(normalizeForSearch(tag)))); @@ -53,6 +56,7 @@ class HashtagChannel extends Channel { this.send('note', note); } + @bindThis public dispose() { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); @@ -69,6 +73,7 @@ export class HashtagChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): HashtagChannel { return new HashtagChannel( this.noteEntityService, diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 16e0cebc72..5707ddd821 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -5,6 +5,7 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import { isInstanceMuted } from '@/misc/is-instance-muted.js'; import type { Packed } from '@/misc/schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class HomeTimelineChannel extends Channel { @@ -19,14 +20,16 @@ class HomeTimelineChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onNote = this.onNote.bind(this); + //this.onNote = this.onNote.bind(this); } + @bindThis public async init(params: any) { // Subscribe events this.subscriber.on('notesStream', this.onNote); } + @bindThis private async onNote(note: Packed<'Note'>) { if (note.channelId) { if (!this.followingChannels.has(note.channelId)) return; @@ -85,6 +88,7 @@ class HomeTimelineChannel extends Channel { this.send('note', note); } + @bindThis public dispose() { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); @@ -101,6 +105,7 @@ export class HomeTimelineChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): HomeTimelineChannel { return new HomeTimelineChannel( this.noteEntityService, diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index f1ce822583..6c6afb12bf 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -7,6 +7,7 @@ import type { Packed } from '@/misc/schema.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class HybridTimelineChannel extends Channel { @@ -22,9 +23,10 @@ class HybridTimelineChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onNote = this.onNote.bind(this); + //this.onNote = this.onNote.bind(this); } + @bindThis public async init(params: any): Promise { const meta = await this.metaService.fetch(); if (meta.disableLocalTimeline && !this.user!.isAdmin && !this.user!.isModerator) return; @@ -33,6 +35,7 @@ class HybridTimelineChannel extends Channel { this.subscriber.on('notesStream', this.onNote); } + @bindThis private async onNote(note: Packed<'Note'>) { // チャンネルの投稿ではなく、自分自身の投稿 または // チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または @@ -95,6 +98,7 @@ class HybridTimelineChannel extends Channel { this.send('note', note); } + @bindThis public dispose(): void { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); @@ -112,6 +116,7 @@ export class HybridTimelineChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): HybridTimelineChannel { return new HybridTimelineChannel( this.metaService, diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index 5a5a43f845..54388787ef 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -5,6 +5,7 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/schema.js'; import { MetaService } from '@/core/MetaService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class LocalTimelineChannel extends Channel { @@ -20,9 +21,10 @@ class LocalTimelineChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onNote = this.onNote.bind(this); + //this.onNote = this.onNote.bind(this); } + @bindThis public async init(params: any) { const meta = await this.metaService.fetch(); if (meta.disableLocalTimeline) { @@ -33,6 +35,7 @@ class LocalTimelineChannel extends Channel { this.subscriber.on('notesStream', this.onNote); } + @bindThis private async onNote(note: Packed<'Note'>) { if (note.user.host !== null) return; if (note.visibility !== 'public') return; @@ -75,6 +78,7 @@ class LocalTimelineChannel extends Channel { this.send('note', note); } + @bindThis public dispose() { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); @@ -92,6 +96,7 @@ export class LocalTimelineChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): LocalTimelineChannel { return new LocalTimelineChannel( this.metaService, diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts index 12908e07b4..42f255b8fe 100644 --- a/packages/backend/src/server/api/stream/channels/main.ts +++ b/packages/backend/src/server/api/stream/channels/main.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { NotesRepository } from '@/models/index.js'; import { isInstanceMuted, isUserFromMutedInstance } from '@/misc/is-instance-muted.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class MainChannel extends Channel { @@ -18,6 +19,7 @@ class MainChannel extends Channel { super(id, connection); } + @bindThis public async init(params: any) { // Subscribe main stream channel this.subscriber.on(`mainStream:${this.user!.id}`, async data => { @@ -66,6 +68,7 @@ export class MainChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): MainChannel { return new MainChannel( this.noteEntityService, diff --git a/packages/backend/src/server/api/stream/channels/messaging-index.ts b/packages/backend/src/server/api/stream/channels/messaging-index.ts index bebc07f4ad..66cb79f7a7 100644 --- a/packages/backend/src/server/api/stream/channels/messaging-index.ts +++ b/packages/backend/src/server/api/stream/channels/messaging-index.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class MessagingIndexChannel extends Channel { @@ -6,6 +7,7 @@ class MessagingIndexChannel extends Channel { public static shouldShare = true; public static requireCredential = true; + @bindThis public async init(params: any) { // Subscribe messaging index stream this.subscriber.on(`messagingIndexStream:${this.user!.id}`, data => { @@ -23,6 +25,7 @@ export class MessagingIndexChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): MessagingIndexChannel { return new MessagingIndexChannel( id, diff --git a/packages/backend/src/server/api/stream/channels/messaging.ts b/packages/backend/src/server/api/stream/channels/messaging.ts index b6ce6c217e..92af6b591c 100644 --- a/packages/backend/src/server/api/stream/channels/messaging.ts +++ b/packages/backend/src/server/api/stream/channels/messaging.ts @@ -5,6 +5,7 @@ import type { UserGroup } from '@/models/entities/UserGroup.js'; import { MessagingService } from '@/core/MessagingService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; import type { StreamMessages } from '../types.js'; @@ -31,11 +32,12 @@ class MessagingChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onEvent = this.onEvent.bind(this); - this.onMessage = this.onMessage.bind(this); - this.emitTypers = this.emitTypers.bind(this); + //this.onEvent = this.onEvent.bind(this); + //this.onMessage = this.onMessage.bind(this); + //this.emitTypers = this.emitTypers.bind(this); } + @bindThis public async init(params: any) { this.otherpartyId = params.otherparty; this.otherparty = this.otherpartyId ? await this.usersRepository.findOneByOrFail({ id: this.otherpartyId }) : null; @@ -63,6 +65,7 @@ class MessagingChannel extends Channel { this.subscriber.on(this.subCh, this.onEvent); } + @bindThis private onEvent(data: StreamMessages['messaging']['payload'] | StreamMessages['groupMessaging']['payload']) { if (data.type === 'typing') { const id = data.body; @@ -76,6 +79,7 @@ class MessagingChannel extends Channel { } } + @bindThis public onMessage(type: string, body: any) { switch (type) { case 'read': @@ -95,6 +99,7 @@ class MessagingChannel extends Channel { } } + @bindThis private async emitTypers() { const now = new Date(); @@ -111,6 +116,7 @@ class MessagingChannel extends Channel { }); } + @bindThis public dispose() { this.subscriber.off(this.subCh, this.onEvent); @@ -138,6 +144,7 @@ export class MessagingChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): MessagingChannel { return new MessagingChannel( this.usersRepository, diff --git a/packages/backend/src/server/api/stream/channels/queue-stats.ts b/packages/backend/src/server/api/stream/channels/queue-stats.ts index 1802c6723b..c773916103 100644 --- a/packages/backend/src/server/api/stream/channels/queue-stats.ts +++ b/packages/backend/src/server/api/stream/channels/queue-stats.ts @@ -1,5 +1,6 @@ import Xev from 'xev'; import { Inject, Injectable } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; const ev = new Xev(); @@ -11,18 +12,21 @@ class QueueStatsChannel extends Channel { constructor(id: string, connection: Channel['connection']) { super(id, connection); - this.onStats = this.onStats.bind(this); - this.onMessage = this.onMessage.bind(this); + //this.onStats = this.onStats.bind(this); + //this.onMessage = this.onMessage.bind(this); } + @bindThis public async init(params: any) { ev.addListener('queueStats', this.onStats); } + @bindThis private onStats(stats: any) { this.send('stats', stats); } + @bindThis public onMessage(type: string, body: any) { switch (type) { case 'requestLog': @@ -37,6 +41,7 @@ class QueueStatsChannel extends Channel { } } + @bindThis public dispose() { ev.removeListener('queueStats', this.onStats); } @@ -51,6 +56,7 @@ export class QueueStatsChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): QueueStatsChannel { return new QueueStatsChannel( id, diff --git a/packages/backend/src/server/api/stream/channels/server-stats.ts b/packages/backend/src/server/api/stream/channels/server-stats.ts index e2b00de25f..492912dbe6 100644 --- a/packages/backend/src/server/api/stream/channels/server-stats.ts +++ b/packages/backend/src/server/api/stream/channels/server-stats.ts @@ -1,5 +1,6 @@ import Xev from 'xev'; import { Inject, Injectable } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; const ev = new Xev(); @@ -11,18 +12,21 @@ class ServerStatsChannel extends Channel { constructor(id: string, connection: Channel['connection']) { super(id, connection); - this.onStats = this.onStats.bind(this); - this.onMessage = this.onMessage.bind(this); + //this.onStats = this.onStats.bind(this); + //this.onMessage = this.onMessage.bind(this); } + @bindThis public async init(params: any) { ev.addListener('serverStats', this.onStats); } + @bindThis private onStats(stats: any) { this.send('stats', stats); } + @bindThis public onMessage(type: string, body: any) { switch (type) { case 'requestLog': @@ -37,6 +41,7 @@ class ServerStatsChannel extends Channel { } } + @bindThis public dispose() { ev.removeListener('serverStats', this.onStats); } @@ -51,6 +56,7 @@ export class ServerStatsChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): ServerStatsChannel { return new ServerStatsChannel( id, diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index f9f0d02558..16af32868c 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -1,11 +1,11 @@ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; -import type { NotesRepository } from '@/models/index.js'; +import type { UserListJoiningsRepository, UserListsRepository, NotesRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class UserListChannel extends Channel { @@ -25,10 +25,11 @@ class UserListChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.updateListUsers = this.updateListUsers.bind(this); - this.onNote = this.onNote.bind(this); + //this.updateListUsers = this.updateListUsers.bind(this); + //this.onNote = this.onNote.bind(this); } + @bindThis public async init(params: any) { this.listId = params.listId as string; @@ -48,6 +49,7 @@ class UserListChannel extends Channel { this.listUsersClock = setInterval(this.updateListUsers, 5000); } + @bindThis private async updateListUsers() { const users = await this.userListJoiningsRepository.find({ where: { @@ -59,6 +61,7 @@ class UserListChannel extends Channel { this.listUsers = users.map(x => x.userId); } + @bindThis private async onNote(note: Packed<'Note'>) { if (!this.listUsers.includes(note.userId)) return; @@ -93,6 +96,7 @@ class UserListChannel extends Channel { this.send('note', note); } + @bindThis public dispose() { // Unsubscribe events this.subscriber.off(`userListStream:${this.listId}`, this.send); @@ -118,6 +122,7 @@ export class UserListChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): UserListChannel { return new UserListChannel( this.userListsRepository, diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index 0c5066b736..6763953f9d 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -8,6 +8,7 @@ import type { Packed } from '@/misc/schema.js'; import type { GlobalEventService } from '@/core/GlobalEventService.js'; import type { NoteReadService } from '@/core/NoteReadService.js'; import type { NotificationService } from '@/core/NotificationService.js'; +import { bindThis } from '@/decorators.js'; import type { ChannelsService } from './ChannelsService.js'; import type * as websocket from 'websocket'; import type { EventEmitter } from 'events'; @@ -52,10 +53,10 @@ export default class Connection { if (user) this.user = user; if (token) this.token = token; - this.onWsConnectionMessage = this.onWsConnectionMessage.bind(this); - this.onUserEvent = this.onUserEvent.bind(this); - this.onNoteStreamMessage = this.onNoteStreamMessage.bind(this); - this.onBroadcastMessage = this.onBroadcastMessage.bind(this); + //this.onWsConnectionMessage = this.onWsConnectionMessage.bind(this); + //this.onUserEvent = this.onUserEvent.bind(this); + //this.onNoteStreamMessage = this.onNoteStreamMessage.bind(this); + //this.onBroadcastMessage = this.onBroadcastMessage.bind(this); this.wsConnection.on('message', this.onWsConnectionMessage); @@ -74,6 +75,7 @@ export default class Connection { } } + @bindThis private onUserEvent(data: StreamMessages['user']['payload']) { // { type, body }と展開するとそれぞれ型が分離してしまう switch (data.type) { case 'follow': @@ -119,6 +121,7 @@ export default class Connection { /** * クライアントからメッセージ受信時 */ + @bindThis private async onWsConnectionMessage(data: websocket.Message) { if (data.type !== 'utf8') return; if (data.utf8Data == null) return; @@ -153,10 +156,12 @@ export default class Connection { } } + @bindThis private onBroadcastMessage(data: StreamMessages['broadcast']['payload']) { this.sendMessageToWs(data.type, data.body); } + @bindThis public cacheNote(note: Packed<'Note'>) { const add = (note: Packed<'Note'>) => { const existIndex = this.cachedNotes.findIndex(n => n.id === note.id); @@ -176,6 +181,7 @@ export default class Connection { if (note.renote) add(note.renote); } + @bindThis private readNote(body: any) { const id = body.id; @@ -190,6 +196,7 @@ export default class Connection { } } + @bindThis private onReadNotification(payload: any) { if (!payload.id) return; this.notificationService.readNotification(this.user!.id, [payload.id]); @@ -198,6 +205,7 @@ export default class Connection { /** * 投稿購読要求時 */ + @bindThis private onSubscribeNote(payload: any) { if (!payload.id) return; @@ -215,6 +223,7 @@ export default class Connection { /** * 投稿購読解除要求時 */ + @bindThis private onUnsubscribeNote(payload: any) { if (!payload.id) return; @@ -225,6 +234,7 @@ export default class Connection { } } + @bindThis private async onNoteStreamMessage(data: StreamMessages['note']['payload']) { this.sendMessageToWs('noteUpdated', { id: data.body.id, @@ -236,6 +246,7 @@ export default class Connection { /** * チャンネル接続要求時 */ + @bindThis private onChannelConnectRequested(payload: any) { const { channel, id, params, pong } = payload; this.connectChannel(id, params, channel, pong); @@ -244,6 +255,7 @@ export default class Connection { /** * チャンネル切断要求時 */ + @bindThis private onChannelDisconnectRequested(payload: any) { const { id } = payload; this.disconnectChannel(id); @@ -252,6 +264,7 @@ export default class Connection { /** * クライアントにメッセージ送信 */ + @bindThis public sendMessageToWs(type: string, payload: any) { this.wsConnection.send(JSON.stringify({ type: type, @@ -262,6 +275,7 @@ export default class Connection { /** * チャンネルに接続 */ + @bindThis public connectChannel(id: string, params: any, channel: string, pong = false) { const channelService = this.channelsService.getChannelService(channel); @@ -289,6 +303,7 @@ export default class Connection { * チャンネルから切断 * @param id チャンネルコネクションID */ + @bindThis public disconnectChannel(id: string) { const channel = this.channels.find(c => c.id === id); @@ -302,6 +317,7 @@ export default class Connection { * チャンネルへメッセージ送信要求時 * @param data メッセージ */ + @bindThis private onChannelMessageRequested(data: any) { const channel = this.channels.find(c => c.id === data.id); if (channel != null && channel.onMessage != null) { @@ -309,12 +325,14 @@ export default class Connection { } } + @bindThis private typingOnChannel(channel: ChannelModel['id']) { if (this.user) { this.globalEventService.publishChannelStream(channel, 'typing', this.user.id); } } + @bindThis private typingOnMessaging(param: { partner?: User['id']; group?: UserGroup['id']; }) { if (this.user) { if (param.partner) { @@ -325,6 +343,7 @@ export default class Connection { } } + @bindThis private async updateFollowing() { const followings = await this.followingsRepository.find({ where: { @@ -336,6 +355,7 @@ export default class Connection { this.following = new Set(followings.map(x => x.followeeId)); } + @bindThis private async updateMuting() { const mutings = await this.mutingsRepository.find({ where: { @@ -347,6 +367,7 @@ export default class Connection { this.muting = new Set(mutings.map(x => x.muteeId)); } + @bindThis private async updateBlocking() { // ここでいうBlockingは被Blockingの意 const blockings = await this.blockingsRepository.find({ where: { @@ -358,6 +379,7 @@ export default class Connection { this.blocking = new Set(blockings.map(x => x.blockerId)); } + @bindThis private async updateFollowingChannels() { const followings = await this.channelFollowingsRepository.find({ where: { @@ -369,6 +391,7 @@ export default class Connection { this.followingChannels = new Set(followings.map(x => x.followeeId)); } + @bindThis private async updateUserProfile() { this.userProfile = await this.userProfilesRepository.findOneBy({ userId: this.user!.id, @@ -378,6 +401,7 @@ export default class Connection { /** * ストリームが切れたとき */ + @bindThis public dispose() { for (const c of this.channels.filter(c => c.dispose)) { if (c.dispose) c.dispose(); diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 3fcf8b7c0d..727cf92831 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -23,6 +23,7 @@ import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import type { ChannelsRepository, ClipsRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { deepClone } from '@/misc/clone.js'; +import { bindThis } from '@/decorators.js'; import manifest from './manifest.json' assert { type: 'json' }; import { FeedService } from './FeedService.js'; import { UrlPreviewService } from './UrlPreviewService.js'; @@ -80,9 +81,10 @@ export class ClientServerService { @Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue, @Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue, ) { - this.createServer = this.createServer.bind(this); + //this.createServer = this.createServer.bind(this); } + @bindThis private async manifestHandler(reply: FastifyReply) { const res = deepClone(manifest); @@ -96,6 +98,7 @@ export class ClientServerService { return (res); } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { /* TODO //#region Bull Dashboard diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts index 1d7d49961d..a14609adf9 100644 --- a/packages/backend/src/server/web/FeedService.ts +++ b/packages/backend/src/server/web/FeedService.ts @@ -7,6 +7,7 @@ import type { Config } from '@/config.js'; import type { User } from '@/models/entities/User.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class FeedService { @@ -31,6 +32,7 @@ export class FeedService { ) { } + @bindThis public async packFeed(user: User) { const author = { link: `${this.config.url}/@${user.username}`, diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 69f52cc2f2..69bb232d4a 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -9,6 +9,7 @@ import { HttpRequestService } from '@/core/HttpRequestService.js'; import type Logger from '@/logger.js'; import { query } from '@/misc/prelude/url.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UrlPreviewService { @@ -28,6 +29,7 @@ export class UrlPreviewService { this.logger = this.loggerService.getLogger('url-preview'); } + @bindThis private wrap(url?: string): string | null { return url != null ? url.match(/^https?:\/\//) @@ -39,6 +41,7 @@ export class UrlPreviewService { : null; } + @bindThis public async handle( request: FastifyRequest<{ Querystring: { url: string; lang: string; } }>, reply: FastifyReply, diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts index cad4d8af6c..9efed267ea 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.ts @@ -15,6 +15,7 @@ export class MockResolver extends Resolver { }); } + @bindThis public async resolve(value: string | IObject): Promise { if (typeof value !== 'string') return value; -- cgit v1.2.3-freya From c0c23b135c036326b5c846d7c4d2d07753520ec2 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 4 Dec 2022 17:33:51 +0900 Subject: bull-board復活 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/package.json | 4 + .../backend/src/server/web/ClientServerService.ts | 23 +++-- yarn.lock | 112 ++++++++++++++++++++- 3 files changed, 126 insertions(+), 13 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/package.json b/packages/backend/package.json index fd94ab854b..1a64873e7c 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -21,8 +21,12 @@ "@tensorflow/tfjs-node": "4.1.0" }, "dependencies": { + "@bull-board/api": "^4.6.4", + "@bull-board/fastify": "^4.6.4", + "@bull-board/ui": "^4.7.0", "@discordapp/twemoji": "14.0.2", "@fastify/accepts": "4.1.0", + "@fastify/cookie": "^8.3.0", "@fastify/cors": "8.2.0", "@fastify/multipart": "7.3.0", "@fastify/static": "6.5.1", diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 727cf92831..ae776adf18 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -2,6 +2,9 @@ import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { PathOrFileDescriptor, readFileSync } from 'node:fs'; import { Inject, Injectable } from '@nestjs/common'; +import { createBullBoard } from '@bull-board/api'; +import { BullAdapter } from '@bull-board/api/bullAdapter.js'; +import { FastifyAdapter } from '@bull-board/fastify'; import ms from 'ms'; import sharp from 'sharp'; import pug from 'pug'; @@ -9,6 +12,7 @@ import { In, IsNull } from 'typeorm'; import { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify'; import fastifyStatic from '@fastify/static'; import fastifyView from '@fastify/view'; +import fastifyCookie from '@fastify/cookie'; import type { Config } from '@/config.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; import { DI } from '@/di-symbols.js'; @@ -100,28 +104,28 @@ export class ClientServerService { @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { - /* TODO + fastify.register(fastifyCookie, {}); + //#region Bull Dashboard const bullBoardPath = '/queue'; // Authenticate - app.use(async (request, reply) => { - if (ctx.path === bullBoardPath || ctx.path.startsWith(bullBoardPath + '/')) { - const token = ctx.cookies.get('token'); + fastify.addHook('onRequest', async (request, reply) => { + if (request.url === bullBoardPath || request.url.startsWith(bullBoardPath + '/')) { + const token = request.cookies.token; if (token == null) { reply.code(401); - return; + throw new Error('login required'); } const user = await this.usersRepository.findOneBy({ token }); if (user == null || !(user.isAdmin || user.isModerator)) { reply.code(403); - return; + throw new Error('access denied'); } } - await next(); }); - const serverAdapter = new KoaAdapter(); + const serverAdapter = new FastifyAdapter(); createBullBoard({ queues: [ @@ -137,9 +141,8 @@ export class ClientServerService { }); serverAdapter.setBasePath(bullBoardPath); - app.use(serverAdapter.registerPlugin()); + fastify.register(serverAdapter.registerPlugin(), { prefix: bullBoardPath }); //#endregion - */ fastify.register(fastifyView, { root: _dirname + '/views', diff --git a/yarn.lock b/yarn.lock index 05d62cb639..24d77d3b9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -417,6 +417,55 @@ __metadata: languageName: node linkType: hard +"@bull-board/api@npm:4.6.4, @bull-board/api@npm:^4.6.4": + version: 4.6.4 + resolution: "@bull-board/api@npm:4.6.4" + dependencies: + redis-info: ^3.0.8 + checksum: d8dbd574bd6096f214d13697fbbd3d5760a3dcb5a1d63b6eb74e47a338efca3248a71fecfd8d6801081fe670508fcaf3512b9d0e6dcf2e74abcdc38a14eeee27 + languageName: node + linkType: hard + +"@bull-board/api@npm:4.7.0": + version: 4.7.0 + resolution: "@bull-board/api@npm:4.7.0" + dependencies: + redis-info: ^3.0.8 + checksum: 4de2ffb061f634539ab836c84e0babd3498a395d979afb36e8c8e24c36b9922824d3eab0ae1fc8a209fbe3cbb4d29660884d8cbd04bf912018e5e04d8c773105 + languageName: node + linkType: hard + +"@bull-board/fastify@npm:^4.6.4": + version: 4.6.4 + resolution: "@bull-board/fastify@npm:4.6.4" + dependencies: + "@bull-board/api": 4.6.4 + "@bull-board/ui": 4.6.4 + "@fastify/static": ^6.4.0 + "@fastify/view": ^7.0.0 + ejs: ^3.1.8 + checksum: be167a58b863fa4446e555f10bf8ec1aebe319dac068c1a11d8147b05229fe9d3e79333318a2871372e02cc0de2b6f257f208f805f6e2a33c92048fd35c07331 + languageName: node + linkType: hard + +"@bull-board/ui@npm:4.6.4": + version: 4.6.4 + resolution: "@bull-board/ui@npm:4.6.4" + dependencies: + "@bull-board/api": 4.6.4 + checksum: c7f7134f9272c321426063ac33916d736a33d8331b577fb239ce1bda53f8007ae2b2df5cbc51f1ab7537874dd7b22bd1534a70e3e2bb8451c57a4314f6263df1 + languageName: node + linkType: hard + +"@bull-board/ui@npm:^4.7.0": + version: 4.7.0 + resolution: "@bull-board/ui@npm:4.7.0" + dependencies: + "@bull-board/api": 4.7.0 + checksum: 6eefc1363c03897dd01f0b3d538ee53e709b5428f9dc6c11baba9a7d8e94614ec223ca46e37245c24f9a58e5821df1eec776216181f4936e2c7be98e054452e8 + languageName: node + linkType: hard + "@chainsafe/is-ip@npm:^2.0.1": version: 2.0.1 resolution: "@chainsafe/is-ip@npm:2.0.1" @@ -695,6 +744,16 @@ __metadata: languageName: node linkType: hard +"@fastify/cookie@npm:^8.3.0": + version: 8.3.0 + resolution: "@fastify/cookie@npm:8.3.0" + dependencies: + cookie: ^0.5.0 + fastify-plugin: ^4.0.0 + checksum: d5dfb5c85d4ae02188aad63c8cf055d3b904287bc2750964ee0b8589c5b5ae32850e8fc9456a8830e9f4e9f24d5059e54dac084835d6ef4cbba7fa4b9c4673b3 + languageName: node + linkType: hard + "@fastify/cors@npm:8.2.0": version: 8.2.0 resolution: "@fastify/cors@npm:8.2.0" @@ -744,7 +803,7 @@ __metadata: languageName: node linkType: hard -"@fastify/static@npm:6.5.1": +"@fastify/static@npm:6.5.1, @fastify/static@npm:^6.4.0": version: 6.5.1 resolution: "@fastify/static@npm:6.5.1" dependencies: @@ -759,7 +818,7 @@ __metadata: languageName: node linkType: hard -"@fastify/view@npm:7.3.0": +"@fastify/view@npm:7.3.0, @fastify/view@npm:^7.0.0": version: 7.3.0 resolution: "@fastify/view@npm:7.3.0" dependencies: @@ -3731,8 +3790,12 @@ __metadata: version: 0.0.0-use.local resolution: "backend@workspace:packages/backend" dependencies: + "@bull-board/api": ^4.6.4 + "@bull-board/fastify": ^4.6.4 + "@bull-board/ui": ^4.7.0 "@discordapp/twemoji": 14.0.2 "@fastify/accepts": 4.1.0 + "@fastify/cookie": ^8.3.0 "@fastify/cors": 8.2.0 "@fastify/multipart": 7.3.0 "@fastify/static": 6.5.1 @@ -4518,7 +4581,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": +"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -6117,6 +6180,17 @@ __metadata: languageName: node linkType: hard +"ejs@npm:^3.1.8": + version: 3.1.8 + resolution: "ejs@npm:3.1.8" + dependencies: + jake: ^10.8.5 + bin: + ejs: bin/cli.js + checksum: 1d40d198ad52e315ccf37e577bdec06e24eefdc4e3c27aafa47751a03a0c7f0ec4310254c9277a5f14763c3cd4bbacce27497332b2d87c74232b9b1defef8efc + languageName: node + linkType: hard + "electron-to-chromium@npm:^1.2.7, electron-to-chromium@npm:^1.4.251": version: 1.4.284 resolution: "electron-to-chromium@npm:1.4.284" @@ -7603,6 +7677,15 @@ __metadata: languageName: node linkType: hard +"filelist@npm:^1.0.1": + version: 1.0.4 + resolution: "filelist@npm:1.0.4" + dependencies: + minimatch: ^5.0.1 + checksum: a303573b0821e17f2d5e9783688ab6fbfce5d52aaac842790ae85e704a6f5e4e3538660a63183d6453834dedf1e0f19a9dadcebfa3e926c72397694ea11f5160 + languageName: node + linkType: hard + "fill-range@npm:^4.0.0": version: 4.0.0 resolution: "fill-range@npm:4.0.0" @@ -9790,6 +9873,20 @@ __metadata: languageName: node linkType: hard +"jake@npm:^10.8.5": + version: 10.8.5 + resolution: "jake@npm:10.8.5" + dependencies: + async: ^3.2.3 + chalk: ^4.0.2 + filelist: ^1.0.1 + minimatch: ^3.0.4 + bin: + jake: ./bin/cli.js + checksum: 56c913ecf5a8d74325d0af9bc17a233bad50977438d44864d925bb6c45c946e0fee8c4c1f5fe2225471ef40df5222e943047982717ebff0d624770564d3c46ba + languageName: node + linkType: hard + "jest-changed-files@npm:^29.2.0": version: 29.2.0 resolution: "jest-changed-files@npm:29.2.0" @@ -14035,6 +14132,15 @@ __metadata: languageName: node linkType: hard +"redis-info@npm:^3.0.8": + version: 3.1.0 + resolution: "redis-info@npm:3.1.0" + dependencies: + lodash: ^4.17.11 + checksum: d72ff0584ebb4a2149cfcfcf9142d9a7f9d0b96ae53fbf431f2738f33f1f42add6505ff73b2d640cab345923a34b217d7c728fa706cc81ad8bd8ad4c48987445 + languageName: node + linkType: hard + "redis-lock@npm:0.1.4": version: 0.1.4 resolution: "redis-lock@npm:0.1.4" -- cgit v1.2.3-freya From d8611506cf768c0761e790ac0c7657f4d6f8d25e Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 6 Dec 2022 09:21:36 +0900 Subject: inboxが処理できない問題を多分修正 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/server/ActivityPubServerService.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 94a277f4a4..d1361ed5e9 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -433,6 +433,8 @@ export class ActivityPubServerService { }); fastify.register(fastifyAccepts); + fastify.addContentTypeParser('application/activity+json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore')); + fastify.addContentTypeParser('application/ld+json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore')); //#region Routing // inbox -- cgit v1.2.3-freya From 9cda400c806eac04e4919151df1ed0cc73382dca Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 6 Dec 2022 12:41:11 +0900 Subject: fix(server): GitHub連携ができない問題を修正 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/server/api/integration/GithubServerService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index 3aa04f72ee..186748dab5 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -13,8 +13,8 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; -import { SigninService } from '../SigninService.js'; import { bindThis } from '@/decorators.js'; +import { SigninService } from '../SigninService.js'; @Injectable() export class GithubServerService { @@ -227,7 +227,7 @@ export class GithubServerService { 'Authorization': `bearer ${accessToken}`, })) as Record; - if (typeof login !== 'string' || typeof id !== 'string') { + if (typeof login !== 'string' || typeof id !== 'number') { throw new FastifyReplyError(400, 'invalid session'); } -- cgit v1.2.3-freya From 3b7c94344053e601d64abea9d7eaa7e15a32cd4d Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 6 Dec 2022 14:14:41 +0900 Subject: fix(server): cookieが設定されないのを修正 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/server/api/SigninService.ts | 2 +- packages/backend/src/server/api/integration/DiscordServerService.ts | 2 +- packages/backend/src/server/api/integration/GithubServerService.ts | 2 +- packages/backend/src/server/api/integration/TwitterServerService.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts index 96a89956f9..671278a18d 100644 --- a/packages/backend/src/server/api/SigninService.ts +++ b/packages/backend/src/server/api/SigninService.ts @@ -43,7 +43,7 @@ export class SigninService { if (redirect) { //#region Cookie - reply.cookies.set('igi', user.token!, { + reply.setCookie('igi', user.token!, { path: '/', // SEE: https://github.com/koajs/koa/issues/974 // When using a SSL proxy it should be configured to add the "X-Forwarded-Proto: https" header diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index a7f39a78d0..e6e79f3d30 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -122,7 +122,7 @@ export class DiscordServerService { response_type: 'code', }; - reply.cookies.set('signin_with_discord_sid', sessid, { + reply.setCookie('signin_with_discord_sid', sessid, { path: '/', secure: this.config.url.startsWith('https'), httpOnly: true, diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index 186748dab5..e6c42e1eeb 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -120,7 +120,7 @@ export class GithubServerService { state: uuid(), }; - reply.cookies.set('signin_with_github_sid', sessid, { + reply.setCookie('signin_with_github_sid', sessid, { path: '/', secure: this.config.url.startsWith('https'), httpOnly: true, diff --git a/packages/backend/src/server/api/integration/TwitterServerService.ts b/packages/backend/src/server/api/integration/TwitterServerService.ts index 7a127fa29a..747f59c7a1 100644 --- a/packages/backend/src/server/api/integration/TwitterServerService.ts +++ b/packages/backend/src/server/api/integration/TwitterServerService.ts @@ -112,7 +112,7 @@ export class TwitterServerService { this.redisClient.set(sessid, JSON.stringify(twCtx)); - reply.cookies.set('signin_with_twitter_sid', sessid, { + reply.setCookie('signin_with_twitter_sid', sessid, { path: '/', secure: this.config.url.startsWith('https'), httpOnly: true, -- cgit v1.2.3-freya From 9d14476a6ae8f9b69f595bf5ee2649d8f9a70e3e Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 6 Dec 2022 14:17:53 +0900 Subject: fix(server): cookieが取得されないのを修正 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/server/api/integration/DiscordServerService.ts | 4 ++-- packages/backend/src/server/api/integration/GithubServerService.ts | 2 +- packages/backend/src/server/api/integration/TwitterServerService.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index e6e79f3d30..72e67edf30 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -13,8 +13,8 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; -import { SigninService } from '../SigninService.js'; import { bindThis } from '@/decorators.js'; +import { SigninService } from '../SigninService.js'; @Injectable() export class DiscordServerService { @@ -140,7 +140,7 @@ export class DiscordServerService { const oauth2 = await getOAuth2(); if (!userToken) { - const sessid = request.cookies.get('signin_with_discord_sid'); + const sessid = request.cookies['signin_with_discord_sid']; if (!sessid) { throw new FastifyReplyError(400, 'invalid session'); diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index e6c42e1eeb..cea4c6b983 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -138,7 +138,7 @@ export class GithubServerService { const oauth2 = await getOath2(); if (!userToken) { - const sessid = request.cookies.get('signin_with_github_sid'); + const sessid = request.cookies['signin_with_github_sid']; if (!sessid) { throw new FastifyReplyError(400, 'invalid session'); diff --git a/packages/backend/src/server/api/integration/TwitterServerService.ts b/packages/backend/src/server/api/integration/TwitterServerService.ts index 747f59c7a1..688fa1a5bd 100644 --- a/packages/backend/src/server/api/integration/TwitterServerService.ts +++ b/packages/backend/src/server/api/integration/TwitterServerService.ts @@ -13,8 +13,8 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; -import { SigninService } from '../SigninService.js'; import { bindThis } from '@/decorators.js'; +import { SigninService } from '../SigninService.js'; @Injectable() export class TwitterServerService { @@ -127,7 +127,7 @@ export class TwitterServerService { const twAuth = await getTwAuth(); if (userToken == null) { - const sessid = request.cookies.get('signin_with_twitter_sid'); + const sessid = request.cookies['signin_with_twitter_sid']; if (sessid == null) { throw new FastifyReplyError(400, 'invalid session'); -- cgit v1.2.3-freya From 8b4ba370f733b17da3837b15499449c49cec8a80 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 8 Dec 2022 07:46:11 +0900 Subject: Update ServerService.ts --- packages/backend/src/server/ServerService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 075b9cdff7..abc169f714 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -150,6 +150,6 @@ export class ServerService { } }); - fastify.listen({ port: this.config.port }); + fastify.listen({ port: this.config.port, host: this.config.host ?? 'localhost' }); } } -- cgit v1.2.3-freya From 686bea6e2ab86d0485b6ec2c1b79ea098ba78c13 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 8 Dec 2022 13:40:42 +0900 Subject: Update ServerService.ts --- packages/backend/src/server/ServerService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index abc169f714..beb26e43d1 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -150,6 +150,6 @@ export class ServerService { } }); - fastify.listen({ port: this.config.port, host: this.config.host ?? 'localhost' }); + fastify.listen({ port: this.config.port, host: '0.0.0.0' }); } } -- cgit v1.2.3-freya From e81c2962a0b04e38a4c974e50d2cd6bc495cbdf2 Mon Sep 17 00:00:00 2001 From: tamaina Date: Thu, 8 Dec 2022 14:49:49 +0900 Subject: enhance: AVIF support (#9281) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Make image/avif browsersafe * server side * change FileInfoService * :v: * avifはMastodonでは絶望的 see https://github.com/misskey-dev/misskey/issues/9283 Co-authored-by: syuilo --- packages/backend/src/const.ts | 1 + packages/backend/src/core/DriveService.ts | 9 ++++--- packages/backend/src/core/FileInfoService.ts | 29 +++++++++++++++++++--- .../src/core/entities/DriveFileEntityService.ts | 2 +- packages/backend/src/misc/is-mime-image.ts | 2 +- packages/backend/src/server/FileServerService.ts | 2 +- packages/client/src/const.ts | 1 + packages/client/src/pages/user/index.photos.vue | 2 ++ 8 files changed, 38 insertions(+), 10 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts index 6d3b9559e8..e2203b6b45 100644 --- a/packages/backend/src/const.ts +++ b/packages/backend/src/const.ts @@ -12,6 +12,7 @@ export const FILE_TYPE_BROWSERSAFE = [ 'image/gif', 'image/jpeg', 'image/webp', + 'image/avif', 'image/apng', 'image/bmp', 'image/tiff', diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 895073c32c..bbdb5fae83 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -138,6 +138,7 @@ export class DriveService { if (type === 'image/jpeg') ext = '.jpg'; if (type === 'image/png') ext = '.png'; if (type === 'image/webp') ext = '.webp'; + if (type === 'image/avif') ext = '.avif'; if (type === 'image/apng') ext = '.apng'; if (type === 'image/vnd.mozilla.apng') ext = '.apng'; } @@ -262,7 +263,7 @@ export class DriveService { } } - if (!['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml'].includes(type)) { + if (!['image/jpeg', 'image/png', 'image/webp', 'image/avif', 'image/svg+xml'].includes(type)) { this.registerLogger.debug('web image and thumbnail not created (not an required file)'); return { webpublic: null, @@ -287,7 +288,7 @@ export class DriveService { } satisfyWebpublic = !!( - type !== 'image/svg+xml' && type !== 'image/webp' && + type !== 'image/svg+xml' && type !== 'image/webp' && type !== 'image/avif' && !(metadata.exif ?? metadata.iptc ?? metadata.xmp ?? metadata.tifftagPhotoshop) && metadata.width && metadata.width <= 2048 && metadata.height && metadata.height <= 2048 @@ -307,7 +308,7 @@ export class DriveService { this.registerLogger.info('creating web image'); try { - if (['image/jpeg', 'image/webp'].includes(type)) { + if (['image/jpeg', 'image/webp', 'image/avif'].includes(type)) { webpublic = await this.imageProcessingService.convertSharpToJpeg(img, 2048, 2048); } else if (['image/png'].includes(type)) { webpublic = await this.imageProcessingService.convertSharpToPng(img, 2048, 2048); @@ -329,7 +330,7 @@ export class DriveService { let thumbnail: IImage | null = null; try { - if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(type)) { + if (['image/jpeg', 'image/webp', 'image/avif', 'image/png', 'image/svg+xml'].includes(type)) { thumbnail = await this.imageProcessingService.convertSharpToWebp(img, 498, 280); } else { this.registerLogger.debug('thumbnail not created (not an required file)'); diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts index bea1b3402e..8043993047 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -73,7 +73,18 @@ export class FileInfoService { let height: number | undefined; let orientation: number | undefined; - if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/bmp', 'image/tiff', 'image/svg+xml', 'image/vnd.adobe.photoshop'].includes(type.mime)) { + if ([ + 'image/png', + 'image/gif', + 'image/jpeg', + 'image/webp', + 'image/avif', + 'image/apng', + 'image/bmp', + 'image/tiff', + 'image/svg+xml', + 'image/vnd.adobe.photoshop', + ].includes(type.mime)) { const imageSize = await this.detectImageSize(path).catch(e => { warnings.push(`detectImageSize failed: ${e}`); return undefined; @@ -100,7 +111,15 @@ export class FileInfoService { let blurhash: string | undefined; - if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/svg+xml'].includes(type.mime)) { + if ([ + 'image/jpeg', + 'image/gif', + 'image/png', + 'image/apng', + 'image/webp', + 'image/avif', + 'image/svg+xml', + ].includes(type.mime)) { blurhash = await this.getBlurhash(path).catch(e => { warnings.push(`getBlurhash failed: ${e}`); return undefined; @@ -156,7 +175,11 @@ export class FileInfoService { return [sensitive, porn]; } - if (['image/jpeg', 'image/png', 'image/webp'].includes(mime)) { + if ([ + 'image/jpeg', + 'image/png', + 'image/webp', + ].includes(mime)) { const result = await this.aiService.detectSensitive(source); if (result) { [sensitive, porn] = judgePrediction(result); diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index 706c8c1186..7f54cfdeac 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -89,7 +89,7 @@ export class DriveFileEntityService { } } - const isImage = file.type && ['image/png', 'image/apng', 'image/gif', 'image/jpeg', 'image/webp', 'image/svg+xml'].includes(file.type); + const isImage = file.type && ['image/png', 'image/apng', 'image/gif', 'image/jpeg', 'image/webp', 'image/avif', 'image/svg+xml'].includes(file.type); return thumbnail ? (file.thumbnailUrl ?? (isImage ? (file.webpublicUrl ?? file.url) : null)) : (file.webpublicUrl ?? file.url); } diff --git a/packages/backend/src/misc/is-mime-image.ts b/packages/backend/src/misc/is-mime-image.ts index 8993ede33a..168a9a7af6 100644 --- a/packages/backend/src/misc/is-mime-image.ts +++ b/packages/backend/src/misc/is-mime-image.ts @@ -2,7 +2,7 @@ import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; const dictionary = { 'safe-file': FILE_TYPE_BROWSERSAFE, - 'sharp-convertible-image': ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/svg+xml'], + 'sharp-convertible-image': ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/avif', 'image/svg+xml'], }; export const isMimeImage = (mime: string, type: keyof typeof dictionary): boolean => dictionary[type].includes(mime); diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index b7ab549611..8b1a130657 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -114,7 +114,7 @@ export class FileServerService { const convertFile = async () => { if (isThumbnail) { - if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(mime)) { + if (['image/jpeg', 'image/webp', 'image/avif', 'image/png', 'image/svg+xml'].includes(mime)) { return await this.imageProcessingService.convertToWebp(path, 498, 280); } else if (mime.startsWith('video/')) { return await this.videoProcessingService.generateVideoThumbnail(path); diff --git a/packages/client/src/const.ts b/packages/client/src/const.ts index 505cf2748e..77366cf07b 100644 --- a/packages/client/src/const.ts +++ b/packages/client/src/const.ts @@ -7,6 +7,7 @@ export const FILE_TYPE_BROWSERSAFE = [ 'image/gif', 'image/jpeg', 'image/webp', + 'image/avif', 'image/apng', 'image/bmp', 'image/tiff', diff --git a/packages/client/src/pages/user/index.photos.vue b/packages/client/src/pages/user/index.photos.vue index 5c9a73dcb7..fae2c005d8 100644 --- a/packages/client/src/pages/user/index.photos.vue +++ b/packages/client/src/pages/user/index.photos.vue @@ -47,6 +47,8 @@ function thumbnail(image: misskey.entities.DriveFile): string { onMounted(() => { const image = [ 'image/jpeg', + 'image/webp', + 'image/avif', 'image/png', 'image/gif', 'image/apng', -- cgit v1.2.3-freya From 6d46e5cf7759e29abcfc63dea81e325c2870acea Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 8 Dec 2022 17:16:50 +0900 Subject: enhance: fallback option for media proxy --- packages/backend/src/server/MediaProxyServerService.ts | 17 +++++++++++++++++ packages/client/src/scripts/media-proxy.ts | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/MediaProxyServerService.ts b/packages/backend/src/server/MediaProxyServerService.ts index 733a7feeb5..8ffcbe6bad 100644 --- a/packages/backend/src/server/MediaProxyServerService.ts +++ b/packages/backend/src/server/MediaProxyServerService.ts @@ -1,7 +1,10 @@ import * as fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; import { Inject, Injectable } from '@nestjs/common'; import { FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest } from 'fastify'; import sharp from 'sharp'; +import fastifyStatic from '@fastify/static'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { isMimeImage } from '@/misc/is-mime-image.js'; @@ -16,6 +19,11 @@ import { FileInfoService } from '@/core/FileInfoService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); + +const assets = `${_dirname}/../../server/file/assets/`; + @Injectable() export class MediaProxyServerService { private logger: Logger; @@ -41,6 +49,11 @@ export class MediaProxyServerService { done(); }); + fastify.register(fastifyStatic, { + root: _dirname, + serve: false, + }); + fastify.get<{ Params: { url: string; }; Querystring: { url?: string; }; @@ -125,6 +138,10 @@ export class MediaProxyServerService { return image.data; } catch (err) { this.logger.error(`${err}`); + + if ('fallback' in request.query) { + return reply.sendFile('/dummy.png', assets); + } if (err instanceof StatusError && (err.statusCode === 302 || err.isClientError)) { reply.code(err.statusCode); diff --git a/packages/client/src/scripts/media-proxy.ts b/packages/client/src/scripts/media-proxy.ts index 506cb78291..aaf7f9e610 100644 --- a/packages/client/src/scripts/media-proxy.ts +++ b/packages/client/src/scripts/media-proxy.ts @@ -4,7 +4,8 @@ import { url } from '@/config'; export function getProxiedImageUrl(imageUrl: string, type?: 'preview'): string { return `${url}/proxy/image.webp?${query({ url: imageUrl, - ...(type ? { [type]: "1" } : {}), + fallback: '1', + ...(type ? { [type]: '1' } : {}), })}`; } -- cgit v1.2.3-freya From b4b742ca893b8575db2bf18442ef83bed5f84a1f Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 10 Dec 2022 15:25:39 +0900 Subject: fix #9299 --- packages/backend/src/server/api/ApiCallService.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index fb1e17790c..40ad7e8c27 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -147,6 +147,7 @@ export class ApiCallService implements OnApplicationShutdown { private send(reply: FastifyReply, x?: any, y?: ApiError) { if (x == null) { reply.code(204); + reply.send(); } else if (typeof x === 'number' && y) { reply.code(x); reply.send({ -- cgit v1.2.3-freya From 4b98920f02a16498592d67bb1b9ce8c09e0228b2 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Wed, 14 Dec 2022 00:01:45 +0900 Subject: Fix import related TypeScript errors (#9321) * Add missing @types packages * Fix TS1272 type only imports * Fix TS2821 import assertion --- packages/backend/package.json | 4 ++ packages/backend/src/core/LoggerService.ts | 7 ++-- packages/backend/src/logger.ts | 7 ++-- .../backend/src/server/ActivityPubServerService.ts | 2 +- packages/backend/src/server/FileServerService.ts | 2 +- .../backend/src/server/MediaProxyServerService.ts | 2 +- .../backend/src/server/NodeinfoServerService.ts | 2 +- .../backend/src/server/WellKnownServerService.ts | 2 +- packages/backend/src/server/api/ApiCallService.ts | 2 +- .../backend/src/server/api/ApiServerService.ts | 2 +- .../backend/src/server/api/SigninApiService.ts | 2 +- packages/backend/src/server/api/SigninService.ts | 2 +- .../backend/src/server/api/SignupApiService.ts | 2 +- .../server/api/integration/DiscordServerService.ts | 2 +- .../server/api/integration/GithubServerService.ts | 2 +- .../server/api/integration/TwitterServerService.ts | 2 +- .../backend/src/server/web/ClientServerService.ts | 2 +- .../backend/src/server/web/UrlPreviewService.ts | 2 +- packages/backend/tsconfig.json | 2 +- yarn.lock | 43 ++++++++++++++++++++++ 20 files changed, 71 insertions(+), 22 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/package.json b/packages/backend/package.json index 7c84562980..e6a247f44e 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -137,8 +137,11 @@ "@types/bcryptjs": "2.4.2", "@types/bull": "4.10.0", "@types/cbor": "6.0.0", + "@types/color-convert": "^2.0.0", + "@types/content-disposition": "^0.5.5", "@types/escape-regexp": "0.0.1", "@types/fluent-ffmpeg": "2.1.20", + "@types/ioredis": "4.28.10", "@types/jest": "29.2.4", "@types/js-yaml": "4.0.5", "@types/jsdom": "20.0.1", @@ -162,6 +165,7 @@ "@types/sharp": "0.31.0", "@types/sinonjs__fake-timers": "8.1.2", "@types/speakeasy": "2.0.7", + "@types/syslog-pro": "^1.0.0", "@types/tinycolor2": "1.4.3", "@types/tmp": "0.2.3", "@types/unzipper": "0.10.5", diff --git a/packages/backend/src/core/LoggerService.ts b/packages/backend/src/core/LoggerService.ts index 4303f3ae22..221631f129 100644 --- a/packages/backend/src/core/LoggerService.ts +++ b/packages/backend/src/core/LoggerService.ts @@ -4,6 +4,7 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; +import type { KEYWORD } from 'color-convert/conversions'; @Injectable() export class LoggerService { @@ -15,9 +16,9 @@ export class LoggerService { ) { if (this.config.syslog) { this.syslogClient = new SyslogPro.RFC5424({ - applacationName: 'Misskey', + applicationName: 'Misskey', timestamp: true, - encludeStructuredData: true, + includeStructuredData: true, color: true, extendedColor: true, server: { @@ -29,7 +30,7 @@ export class LoggerService { } @bindThis - public getLogger(domain: string, color?: string | undefined, store?: boolean) { + public getLogger(domain: string, color?: KEYWORD | undefined, store?: boolean) { return new Logger(domain, color, store, this.syslogClient); } } diff --git a/packages/backend/src/logger.ts b/packages/backend/src/logger.ts index 5cc0d9795c..e7d7051630 100644 --- a/packages/backend/src/logger.ts +++ b/packages/backend/src/logger.ts @@ -4,10 +4,11 @@ import { default as convertColor } from 'color-convert'; import { format as dateFormat } from 'date-fns'; import { bindThis } from '@/decorators.js'; import { envOption } from './env.js'; +import type { KEYWORD } from 'color-convert/conversions'; type Context = { name: string; - color?: string; + color?: KEYWORD; }; type Level = 'error' | 'success' | 'warning' | 'debug' | 'info'; @@ -18,7 +19,7 @@ export default class Logger { private store: boolean; private syslogClient: any | null = null; - constructor(context: string, color?: string, store = true, syslogClient = null) { + constructor(context: string, color?: KEYWORD, store = true, syslogClient = null) { this.context = { name: context, color: color, @@ -28,7 +29,7 @@ export default class Logger { } @bindThis - public createSubLogger(context: string, color?: string, store = true): Logger { + public createSubLogger(context: string, color?: KEYWORD, store = true): Logger { const logger = new Logger(context, color, store); logger.parentLogger = this; return logger; diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index d1361ed5e9..f7e0d744a8 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -1,5 +1,4 @@ import { Inject, Injectable } from '@nestjs/common'; -import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import fastifyAccepts from '@fastify/accepts'; import httpSignature from '@peertube/http-signature'; import { Brackets, In, IsNull, LessThan, Not } from 'typeorm'; @@ -19,6 +18,7 @@ import { QueryService } from '@/core/QueryService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; +import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import type { FindOptionsWhere } from 'typeorm'; const ACTIVITY_JSON = 'application/activity+json; charset=utf-8'; diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 8b1a130657..134b3df327 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -2,7 +2,6 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; import { Inject, Injectable } from '@nestjs/common'; -import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import fastifyStatic from '@fastify/static'; import rename from 'rename'; import type { Config } from '@/config.js'; @@ -20,6 +19,7 @@ import { contentDisposition } from '@/misc/content-disposition.js'; import { FileInfoService } from '@/core/FileInfoService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; +import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); diff --git a/packages/backend/src/server/MediaProxyServerService.ts b/packages/backend/src/server/MediaProxyServerService.ts index 8ffcbe6bad..2437f9f900 100644 --- a/packages/backend/src/server/MediaProxyServerService.ts +++ b/packages/backend/src/server/MediaProxyServerService.ts @@ -2,7 +2,6 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; import { Inject, Injectable } from '@nestjs/common'; -import { FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest } from 'fastify'; import sharp from 'sharp'; import fastifyStatic from '@fastify/static'; import { DI } from '@/di-symbols.js'; @@ -18,6 +17,7 @@ import type Logger from '@/logger.js'; import { FileInfoService } from '@/core/FileInfoService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; +import type { FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest } from 'fastify'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index 0f3cc36dae..86d87872b2 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -1,5 +1,4 @@ import { Inject, Injectable } from '@nestjs/common'; -import { FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest } from 'fastify'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { NotesRepository, UsersRepository } from '@/models/index.js'; @@ -9,6 +8,7 @@ import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Cache } from '@/misc/cache.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; +import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; const nodeinfo2_1path = '/nodeinfo/2.1'; const nodeinfo2_0path = '/nodeinfo/2.0'; diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts index ea34ad5b0f..bf0b2a09e4 100644 --- a/packages/backend/src/server/WellKnownServerService.ts +++ b/packages/backend/src/server/WellKnownServerService.ts @@ -1,5 +1,4 @@ import { Inject, Injectable } from '@nestjs/common'; -import { FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest } from 'fastify'; import { IsNull, MoreThan } from 'typeorm'; import vary from 'vary'; import { DI } from '@/di-symbols.js'; @@ -11,6 +10,7 @@ import * as Acct from '@/misc/acct.js'; import { NodeinfoServerService } from './NodeinfoServerService.js'; import type { FindOptionsWhere } from 'typeorm'; import { bindThis } from '@/decorators.js'; +import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; @Injectable() export class WellKnownServerService { diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index 40ad7e8c27..8cc7382c58 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -3,7 +3,6 @@ import { pipeline } from 'node:stream'; import * as fs from 'node:fs'; import { promisify } from 'node:util'; import { Inject, Injectable } from '@nestjs/common'; -import { FastifyRequest, FastifyReply } from 'fastify'; import { DI } from '@/di-symbols.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; import type { CacheableLocalUser, ILocalUser, User } from '@/models/entities/User.js'; @@ -17,6 +16,7 @@ import { ApiError } from './error.js'; import { RateLimiterService } from './RateLimiterService.js'; import { ApiLoggerService } from './ApiLoggerService.js'; import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; +import type { FastifyRequest, FastifyReply } from 'fastify'; import type { OnApplicationShutdown } from '@nestjs/common'; import type { IEndpointMeta, IEndpoint } from './endpoints.js'; diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index b17456d0e2..dca827c55a 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -1,5 +1,4 @@ import { Inject, Injectable } from '@nestjs/common'; -import { FastifyInstance, FastifyPluginOptions } from 'fastify'; import cors from '@fastify/cors'; import multipart from '@fastify/multipart'; import { ModuleRef, repl } from '@nestjs/core'; @@ -15,6 +14,7 @@ import { GithubServerService } from './integration/GithubServerService.js'; import { DiscordServerService } from './integration/DiscordServerService.js'; import { TwitterServerService } from './integration/TwitterServerService.js'; import { bindThis } from '@/decorators.js'; +import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; @Injectable() export class ApiServerService { diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index b633c2888f..10f8423d44 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -3,7 +3,6 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; import { IsNull } from 'typeorm'; -import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import { DI } from '@/di-symbols.js'; import type { UserSecurityKeysRepository, SigninsRepository, UserProfilesRepository, AttestationChallengesRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; @@ -14,6 +13,7 @@ import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationSe import { RateLimiterService } from './RateLimiterService.js'; import { SigninService } from './SigninService.js'; import { bindThis } from '@/decorators.js'; +import type { FastifyRequest, FastifyReply } from 'fastify'; @Injectable() export class SigninApiService { diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts index 671278a18d..89a8a9ff16 100644 --- a/packages/backend/src/server/api/SigninService.ts +++ b/packages/backend/src/server/api/SigninService.ts @@ -1,5 +1,4 @@ import { Inject, Injectable } from '@nestjs/common'; -import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import { DI } from '@/di-symbols.js'; import type { SigninsRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; @@ -8,6 +7,7 @@ import type { ILocalUser } from '@/models/entities/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { SigninEntityService } from '@/core/entities/SigninEntityService.js'; import { bindThis } from '@/decorators.js'; +import type { FastifyRequest, FastifyReply } from 'fastify'; @Injectable() export class SigninService { diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 59676426af..bba81250ab 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -1,7 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import rndstr from 'rndstr'; import bcrypt from 'bcryptjs'; -import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import { DI } from '@/di-symbols.js'; import type { RegistrationTicketsRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; @@ -15,6 +14,7 @@ import { ILocalUser } from '@/models/entities/User.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { SigninService } from './SigninService.js'; import { bindThis } from '@/decorators.js'; +import type { FastifyRequest, FastifyReply } from 'fastify'; @Injectable() export class SignupApiService { diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index 72e67edf30..805056da8b 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -3,7 +3,6 @@ import Redis from 'ioredis'; import { OAuth2 } from 'oauth'; import { v4 as uuid } from 'uuid'; import { IsNull } from 'typeorm'; -import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import type { Config } from '@/config.js'; import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; @@ -15,6 +14,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { bindThis } from '@/decorators.js'; import { SigninService } from '../SigninService.js'; +import type { FastifyInstance, FastifyRequest, FastifyPluginOptions } from 'fastify'; @Injectable() export class DiscordServerService { diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index cea4c6b983..6f38c262a1 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -3,7 +3,6 @@ import Redis from 'ioredis'; import { OAuth2 } from 'oauth'; import { v4 as uuid } from 'uuid'; import { IsNull } from 'typeorm'; -import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import type { Config } from '@/config.js'; import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; @@ -15,6 +14,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { bindThis } from '@/decorators.js'; import { SigninService } from '../SigninService.js'; +import type { FastifyInstance, FastifyRequest, FastifyPluginOptions } from 'fastify'; @Injectable() export class GithubServerService { diff --git a/packages/backend/src/server/api/integration/TwitterServerService.ts b/packages/backend/src/server/api/integration/TwitterServerService.ts index 688fa1a5bd..9cfadbfa1a 100644 --- a/packages/backend/src/server/api/integration/TwitterServerService.ts +++ b/packages/backend/src/server/api/integration/TwitterServerService.ts @@ -1,6 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; import Redis from 'ioredis'; -import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import { v4 as uuid } from 'uuid'; import { IsNull } from 'typeorm'; import autwh from 'autwh'; @@ -15,6 +14,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { bindThis } from '@/decorators.js'; import { SigninService } from '../SigninService.js'; +import type { FastifyInstance, FastifyRequest, FastifyPluginOptions } from 'fastify'; @Injectable() export class TwitterServerService { diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index ae776adf18..58452ae826 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -9,7 +9,6 @@ import ms from 'ms'; import sharp from 'sharp'; import pug from 'pug'; import { In, IsNull } from 'typeorm'; -import { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify'; import fastifyStatic from '@fastify/static'; import fastifyView from '@fastify/view'; import fastifyCookie from '@fastify/cookie'; @@ -31,6 +30,7 @@ import { bindThis } from '@/decorators.js'; import manifest from './manifest.json' assert { type: 'json' }; import { FeedService } from './FeedService.js'; import { UrlPreviewService } from './UrlPreviewService.js'; +import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 69bb232d4a..baef8fa993 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -1,6 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; import summaly from 'summaly'; -import { FastifyRequest, FastifyReply } from 'fastify'; import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; @@ -10,6 +9,7 @@ import type Logger from '@/logger.js'; import { query } from '@/misc/prelude/url.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; +import type { FastifyRequest, FastifyReply } from 'fastify'; @Injectable() export class UrlPreviewService { diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index 2c8adf7708..544b529e94 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -10,7 +10,7 @@ "declaration": false, "sourceMap": false, "target": "es2021", - "module": "es2022", + "module": "esnext", "moduleResolution": "node", "allowSyntheticDefaultImports": true, "removeComments": false, diff --git a/yarn.lock b/yarn.lock index efd4592985..2b72b2180b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2220,6 +2220,29 @@ __metadata: languageName: node linkType: hard +"@types/color-convert@npm:^2.0.0": + version: 2.0.0 + resolution: "@types/color-convert@npm:2.0.0" + dependencies: + "@types/color-name": "*" + checksum: 027b68665dc2278cc2d83e796ada0a05a08aa5a11297e227c48c7f9f6eac518dec98578ab0072bd211963d3e4b431da70b20ea28d6c3136d0badfd3f9913baee + languageName: node + linkType: hard + +"@types/color-name@npm:*": + version: 1.1.1 + resolution: "@types/color-name@npm:1.1.1" + checksum: b71fcad728cc68abcba1d405742134410c8f8eb3c2ef18113b047afca158ad23a4f2c229bcf71a38f4a818dead375c45b20db121d0e69259c2d81e97a740daa6 + languageName: node + linkType: hard + +"@types/content-disposition@npm:^0.5.5": + version: 0.5.5 + resolution: "@types/content-disposition@npm:0.5.5" + checksum: fdf7379db1d509990bcf9a21d85f05aad878596f28b1418f9179f6436cb22513262c670ce88c6055054a7f5804a9303eeacb70aa59a5e11ffdc1434559db9692 + languageName: node + linkType: hard + "@types/disposable-email-domains@npm:^1.0.1": version: 1.0.2 resolution: "@types/disposable-email-domains@npm:1.0.2" @@ -2314,6 +2337,15 @@ __metadata: languageName: node linkType: hard +"@types/ioredis@npm:4.28.10": + version: 4.28.10 + resolution: "@types/ioredis@npm:4.28.10" + dependencies: + "@types/node": "*" + checksum: 0f2788cf25f490d3b345db8c5f8b8ce3f6c92cc99abcf744c8f974f02b9b3875233b3d22098614c462a0d6c41c523bd655509418ea88eb6249db6652290ce7cf + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.4 resolution: "@types/istanbul-lib-coverage@npm:2.0.4" @@ -2667,6 +2699,13 @@ __metadata: languageName: node linkType: hard +"@types/syslog-pro@npm:^1.0.0": + version: 1.0.0 + resolution: "@types/syslog-pro@npm:1.0.0" + checksum: d0dcd87efad8a629bba449f86a617605a3fbffa5c18a8b309c82e7b85036ac21cfd34711fd522f50528dd0f0d07bdb66261a6f9ef20f2a9133e847b2e717c1bc + languageName: node + linkType: hard + "@types/throttle-debounce@npm:5.0.0": version: 5.0.0 resolution: "@types/throttle-debounce@npm:5.0.0" @@ -4045,8 +4084,11 @@ __metadata: "@types/bcryptjs": 2.4.2 "@types/bull": 4.10.0 "@types/cbor": 6.0.0 + "@types/color-convert": ^2.0.0 + "@types/content-disposition": ^0.5.5 "@types/escape-regexp": 0.0.1 "@types/fluent-ffmpeg": 2.1.20 + "@types/ioredis": 4.28.10 "@types/jest": 29.2.4 "@types/js-yaml": 4.0.5 "@types/jsdom": 20.0.1 @@ -4070,6 +4112,7 @@ __metadata: "@types/sharp": 0.31.0 "@types/sinonjs__fake-timers": 8.1.2 "@types/speakeasy": 2.0.7 + "@types/syslog-pro": ^1.0.0 "@types/tinycolor2": 1.4.3 "@types/tmp": 0.2.3 "@types/unzipper": 0.10.5 -- cgit v1.2.3-freya From 9c5dfd2da78c538142437f60229536967f7fc112 Mon Sep 17 00:00:00 2001 From: tamaina Date: Wed, 14 Dec 2022 08:32:52 +0000 Subject: chore(backend): fix duplicated css preload --- packages/backend/src/server/web/views/base.pug | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 5bb156f0f4..4238e15f04 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -37,11 +37,9 @@ html link(rel='stylesheet' href='/assets/fontawesome/css/all.css') link(rel='modulepreload' href=`/assets/${clientEntry.file}`) - each href in clientEntry.css - link(rel='preload' href=`/assets/${href}` as='style') - - each href in clientEntry.css - link(rel='preload' href=`/assets/${href}` as='style') + if Array.isArray(clientEntry.css) + each href in clientEntry.css + link(rel='preload' href=`/assets/${href}` as='style') title block title -- cgit v1.2.3-freya From 96dda253b01ffb78a216bbdc36e629b4ab3f1902 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 17 Dec 2022 11:02:49 +0900 Subject: fix(server): GitHubログインしようとするとreply.setCookie is not a function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #9327 --- packages/backend/src/server/api/ApiServerService.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index dca827c55a..884ed0c8cb 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -1,11 +1,13 @@ import { Inject, Injectable } from '@nestjs/common'; import cors from '@fastify/cors'; import multipart from '@fastify/multipart'; +import fastifyCookie from '@fastify/cookie'; import { ModuleRef, repl } from '@nestjs/core'; import type { Config } from '@/config.js'; import type { UsersRepository, InstancesRepository, AccessTokensRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; import endpoints, { IEndpoint } from './endpoints.js'; import { ApiCallService } from './ApiCallService.js'; import { SignupApiService } from './SignupApiService.js'; @@ -13,7 +15,6 @@ import { SigninApiService } from './SigninApiService.js'; import { GithubServerService } from './integration/GithubServerService.js'; import { DiscordServerService } from './integration/DiscordServerService.js'; import { TwitterServerService } from './integration/TwitterServerService.js'; -import { bindThis } from '@/decorators.js'; import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; @Injectable() @@ -57,6 +58,8 @@ export class ApiServerService { }, }); + fastify.register(fastifyCookie, {}); + // Prevent cache fastify.addHook('onRequest', (request, reply, done) => { reply.header('Cache-Control', 'private, max-age=0, must-revalidate'); -- cgit v1.2.3-freya From 4ecc42744c3c8b68e38f58bfe03919bf437f137a Mon Sep 17 00:00:00 2001 From: tamaina Date: Sun, 18 Dec 2022 01:59:59 +0900 Subject: enhance: Implement the toggle to (or not to) close push notifications when notifications or messages are read (#9219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * create file * wip * fix * wip * tabun dekita * :v: * implement subscribe push notification button to tutorial * check-exists→show-registration * add column sendReadMessage * fix migration file * sw api * change PushNotificationService * wip * :v: * fix tutorial footer flex --- locales/ja-JP.yml | 11 ++ ...138716634-whetherPushNotifyToSendReadMessage.js | 11 ++ .../backend/src/core/PushNotificationService.ts | 8 + .../backend/src/models/entities/SwSubscription.ts | 5 + packages/backend/src/server/api/EndpointsModule.ts | 6 + packages/backend/src/server/api/endpoints.ts | 4 + .../src/server/api/endpoints/sw/register.ts | 20 +++ .../server/api/endpoints/sw/show-registration.ts | 66 ++++++++ .../src/server/api/endpoints/sw/unregister.ts | 4 +- .../server/api/endpoints/sw/update-registration.ts | 82 ++++++++++ .../components/MkPushNotificationAllowButton.vue | 167 +++++++++++++++++++++ .../client/src/pages/settings/notifications.vue | 30 ++++ packages/client/src/pages/timeline.tutorial.vue | 116 ++++++++------ packages/client/src/scripts/initialize-sw.ts | 55 ------- 14 files changed, 481 insertions(+), 104 deletions(-) create mode 100644 packages/backend/migration/1669138716634-whetherPushNotifyToSendReadMessage.js create mode 100644 packages/backend/src/server/api/endpoints/sw/show-registration.ts create mode 100644 packages/backend/src/server/api/endpoints/sw/update-registration.ts create mode 100644 packages/client/src/components/MkPushNotificationAllowButton.vue (limited to 'packages/backend/src/server') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 3fba36a754..3a53f470e3 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -13,6 +13,7 @@ fetchingAsApObject: "連合に照会中" ok: "OK" gotIt: "わかった" cancel: "キャンセル" +noThankYou: "やめておく" enterUsername: "ユーザー名を入力" renotedBy: "{user}がRenote" noNotes: "ノートはありません" @@ -898,6 +899,13 @@ navbar: "ナビゲーションバー" shuffle: "シャッフル" account: "アカウント" move: "移動" +pushNotification: "プッシュ通知" +subscribePushNotification: "プッシュ通知を有効化" +unsubscribePushNotification: "プッシュ通知を停止する" +pushNotificationAlreadySubscribed: "プッシュ通知は有効です" +pushNotificationNotSupported: "ブラウザかインスタンスがプッシュ通知に非対応" +sendPushNotificationReadMessage: "通知やメッセージが既読になったらプッシュ通知を削除する" +sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」という通知が一瞬表示されるようになります。端末の電池消費量が増加する可能性があります。" _sensitiveMediaDetection: description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" @@ -1235,6 +1243,9 @@ _tutorial: step7_1: "これで、Misskeyの基本的な使い方の説明は終わりました。お疲れ様でした。" step7_2: "もっとMisskeyについて知りたいときは、{help}を見てみてください。" step7_3: "では、Misskeyをお楽しみください🚀" + step8_1: "最後に、プッシュ通知を有効化してみませんか?" + step8_2: "プッシュ通知を受け取ることで、Misskeyを開いていない時にもリアクションやフォロー、メンションなどに気づけます。" + step8_3: "通知の設定は後から変更できます。" _2fa: alreadyRegistered: "既に設定は完了しています。" diff --git a/packages/backend/migration/1669138716634-whetherPushNotifyToSendReadMessage.js b/packages/backend/migration/1669138716634-whetherPushNotifyToSendReadMessage.js new file mode 100644 index 0000000000..2265b00617 --- /dev/null +++ b/packages/backend/migration/1669138716634-whetherPushNotifyToSendReadMessage.js @@ -0,0 +1,11 @@ +export class whetherPushNotifyToSendReadMessage1669138716634 { + name = 'whetherPushNotifyToSendReadMessage1669138716634' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "sw_subscription" ADD "sendReadMessage" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "sw_subscription" DROP COLUMN "sendReadMessage"`); + } +} diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index df5284de4b..842cd1a9f8 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -69,6 +69,14 @@ export class PushNotificationService { }); for (const subscription of subscriptions) { + // Continue if sendReadMessage is false + if ([ + 'readNotifications', + 'readAllNotifications', + 'readAllMessagingMessages', + 'readAllMessagingMessagesOfARoom', + ].includes(type) && !subscription.sendReadMessage) continue; + const pushSubscription = { endpoint: subscription.endpoint, keys: { diff --git a/packages/backend/src/models/entities/SwSubscription.ts b/packages/backend/src/models/entities/SwSubscription.ts index 51b9786e96..0658294983 100644 --- a/packages/backend/src/models/entities/SwSubscription.ts +++ b/packages/backend/src/models/entities/SwSubscription.ts @@ -34,4 +34,9 @@ export class SwSubscription { length: 128, }) public publickey: string; + + @Column('boolean', { + default: false, + }) + public sendReadMessage: boolean; } diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index e41ed388b4..647f60317a 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -272,6 +272,8 @@ import * as ep___resetDb from './endpoints/reset-db.js'; import * as ep___resetPassword from './endpoints/reset-password.js'; import * as ep___serverInfo from './endpoints/server-info.js'; import * as ep___stats from './endpoints/stats.js'; +import * as ep___sw_show_registration from './endpoints/sw/show-registration.js'; +import * as ep___sw_update_registration from './endpoints/sw/update-registration.js'; import * as ep___sw_register from './endpoints/sw/register.js'; import * as ep___sw_unregister from './endpoints/sw/unregister.js'; import * as ep___test from './endpoints/test.js'; @@ -588,6 +590,8 @@ const $resetDb: Provider = { provide: 'ep:reset-db', useClass: ep___resetDb.defa const $resetPassword: Provider = { provide: 'ep:reset-password', useClass: ep___resetPassword.default }; const $serverInfo: Provider = { provide: 'ep:server-info', useClass: ep___serverInfo.default }; const $stats: Provider = { provide: 'ep:stats', useClass: ep___stats.default }; +const $sw_show_registration: Provider = { provide: 'ep:sw/show-registration', useClass: ep___sw_show_registration.default }; +const $sw_update_registration: Provider = { provide: 'ep:sw/update-registration', useClass: ep___sw_update_registration.default }; const $sw_register: Provider = { provide: 'ep:sw/register', useClass: ep___sw_register.default }; const $sw_unregister: Provider = { provide: 'ep:sw/unregister', useClass: ep___sw_unregister.default }; const $test: Provider = { provide: 'ep:test', useClass: ep___test.default }; @@ -908,6 +912,8 @@ const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.d $resetPassword, $serverInfo, $stats, + $sw_show_registration, + $sw_update_registration, $sw_register, $sw_unregister, $test, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index b2ab36e070..6d10cb8f35 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -271,6 +271,8 @@ import * as ep___resetDb from './endpoints/reset-db.js'; import * as ep___resetPassword from './endpoints/reset-password.js'; import * as ep___serverInfo from './endpoints/server-info.js'; import * as ep___stats from './endpoints/stats.js'; +import * as ep___sw_show_registration from './endpoints/sw/show-registration.js'; +import * as ep___sw_update_registration from './endpoints/sw/update-registration.js'; import * as ep___sw_register from './endpoints/sw/register.js'; import * as ep___sw_unregister from './endpoints/sw/unregister.js'; import * as ep___test from './endpoints/test.js'; @@ -585,6 +587,8 @@ const eps = [ ['reset-password', ep___resetPassword], ['server-info', ep___serverInfo], ['stats', ep___stats], + ['sw/show-registration', ep___sw_show_registration], + ['sw/update-registration', ep___sw_update_registration], ['sw/register', ep___sw_register], ['sw/unregister', ep___sw_unregister], ['test', ep___test], diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index ddec877dd4..bfd5de7b00 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -25,6 +25,18 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + userId: { + type: 'string', + optional: false, nullable: false, + }, + endpoint: { + type: 'string', + optional: false, nullable: false, + }, + sendReadMessage: { + type: 'boolean', + optional: false, nullable: false, + }, }, }, } as const; @@ -35,6 +47,7 @@ export const paramDef = { endpoint: { type: 'string' }, auth: { type: 'string' }, publickey: { type: 'string' }, + sendReadMessage: { type: 'boolean', default: false }, }, required: ['endpoint', 'auth', 'publickey'], } as const; @@ -64,6 +77,9 @@ export default class extends Endpoint { return { state: 'already-subscribed' as const, key: instance.swPublicKey, + userId: me.id, + endpoint: exist.endpoint, + sendReadMessage: exist.sendReadMessage, }; } @@ -74,11 +90,15 @@ export default class extends Endpoint { endpoint: ps.endpoint, auth: ps.auth, publickey: ps.publickey, + sendReadMessage: ps.sendReadMessage, }); return { state: 'subscribed' as const, key: instance.swPublicKey, + userId: me.id, + endpoint: ps.endpoint, + sendReadMessage: ps.sendReadMessage, }; }); } diff --git a/packages/backend/src/server/api/endpoints/sw/show-registration.ts b/packages/backend/src/server/api/endpoints/sw/show-registration.ts new file mode 100644 index 0000000000..bede10be5c --- /dev/null +++ b/packages/backend/src/server/api/endpoints/sw/show-registration.ts @@ -0,0 +1,66 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type { SwSubscriptionsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; + +export const meta = { + tags: ['account'], + + requireCredential: true, + + description: 'Check push notification registration exists.', + + res: { + type: 'object', + optional: false, nullable: true, + properties: { + userId: { + type: 'string', + optional: false, nullable: false, + }, + endpoint: { + type: 'string', + optional: false, nullable: false, + }, + sendReadMessage: { + type: 'boolean', + optional: false, nullable: false, + }, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + endpoint: { type: 'string' }, + }, + required: ['endpoint'], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.swSubscriptionsRepository) + private swSubscriptionsRepository: SwSubscriptionsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + // if already subscribed + const exist = await this.swSubscriptionsRepository.findOneBy({ + userId: me.id, + endpoint: ps.endpoint, + }); + + if (exist != null) { + return { + userId: exist.userId, + endpoint: exist.endpoint, + sendReadMessage: exist.sendReadMessage, + }; + } + + return null; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index 5772eeee26..f12b98617d 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -6,7 +6,7 @@ import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], - requireCredential: true, + requireCredential: false, description: 'Unregister from receiving push notifications.', } as const; @@ -28,7 +28,7 @@ export default class extends Endpoint { ) { super(meta, paramDef, async (ps, me) => { await this.swSubscriptionsRepository.delete({ - userId: me.id, + ...(me ? { userId: me.id } : {}), endpoint: ps.endpoint, }); }); diff --git a/packages/backend/src/server/api/endpoints/sw/update-registration.ts b/packages/backend/src/server/api/endpoints/sw/update-registration.ts new file mode 100644 index 0000000000..9f08c8148d --- /dev/null +++ b/packages/backend/src/server/api/endpoints/sw/update-registration.ts @@ -0,0 +1,82 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type { SwSubscriptionsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; + +export const meta = { + tags: ['account'], + + requireCredential: true, + + description: 'Update push notification registration.', + + res: { + type: 'object', + optional: false, nullable: false, + properties: { + userId: { + type: 'string', + optional: false, nullable: false, + }, + endpoint: { + type: 'string', + optional: false, nullable: false, + }, + sendReadMessage: { + type: 'boolean', + optional: false, nullable: false, + }, + }, + }, + errors: { + noSuchRegistration: { + message: 'No such registration.', + code: 'NO_SUCH_REGISTRATION', + id: ' b09d8066-8064-5613-efb6-0e963b21d012', + }, + } +} as const; + +export const paramDef = { + type: 'object', + properties: { + endpoint: { type: 'string' }, + sendReadMessage: { type: 'boolean' }, + }, + required: ['endpoint'], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.swSubscriptionsRepository) + private swSubscriptionsRepository: SwSubscriptionsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const swSubscription = await this.swSubscriptionsRepository.findOneBy({ + userId: me.id, + endpoint: ps.endpoint, + }); + + if (swSubscription === null) { + throw new ApiError(meta.errors.noSuchRegistration); + } + + if (ps.sendReadMessage !== undefined) { + swSubscription.sendReadMessage = ps.sendReadMessage; + } + + await this.swSubscriptionsRepository.update(swSubscription.id, { + sendReadMessage: swSubscription.sendReadMessage, + }); + + return { + userId: swSubscription.userId, + endpoint: swSubscription.endpoint, + sendReadMessage: swSubscription.sendReadMessage, + }; + }); + } +} diff --git a/packages/client/src/components/MkPushNotificationAllowButton.vue b/packages/client/src/components/MkPushNotificationAllowButton.vue new file mode 100644 index 0000000000..a762914e64 --- /dev/null +++ b/packages/client/src/components/MkPushNotificationAllowButton.vue @@ -0,0 +1,167 @@ + + + diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue index 5703e0c6b6..77ec567da4 100644 --- a/packages/client/src/pages/settings/notifications.vue +++ b/packages/client/src/pages/settings/notifications.vue @@ -6,6 +6,18 @@ {{ i18n.ts.markAsReadAllUnreadNotes }} {{ i18n.ts.markAsReadAllTalkMessages }} + + + + + + + + @@ -15,10 +27,16 @@ import { notificationTypes } from 'misskey-js'; import FormButton from '@/components/MkButton.vue'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; +import FormSwitch from '@/components/form/switch.vue'; import * as os from '@/os'; import { $i } from '@/account'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue'; + +let allowButton = $ref>(); +let pushRegistrationInServer = $computed(() => allowButton?.pushRegistrationInServer); +let sendReadMessage = $computed(() => pushRegistrationInServer?.sendReadMessage || false); async function readAllUnreadNotes() { await os.api('i/read-all-unread-notes'); @@ -49,6 +67,18 @@ function configure() { }, 'closed'); } +function onChangeSendReadMessage(v: boolean) { + if (!pushRegistrationInServer) return; + + os.apiWithDialog('sw/update-registration', { + endpoint: pushRegistrationInServer.endpoint, + sendReadMessage: v, + }).then(res => { + if (!allowButton) return; + allowButton.pushRegistrationInServer = res; + }); +} + const headerActions = $computed(() => []); const headerTabs = $computed(() => []); diff --git a/packages/client/src/pages/timeline.tutorial.vue b/packages/client/src/pages/timeline.tutorial.vue index 7f08ccc2a1..9683cc22a5 100644 --- a/packages/client/src/pages/timeline.tutorial.vue +++ b/packages/client/src/pages/timeline.tutorial.vue @@ -1,6 +1,17 @@
{{ i18n.ts._tutorial.step5_3 }}
- {{ i18n.ts._tutorial.step5_4 }} + {{ i18n.ts._tutorial.step5_4 }}
{{ i18n.ts._tutorial.step6_1 }}
@@ -48,19 +59,20 @@
{{ i18n.ts._tutorial.step7_3 }}
+
+
{{ i18n.ts._tutorial.step8_1 }}
+
{{ i18n.ts._tutorial.step8_2 }}
+ {{ i18n.ts._tutorial.step8_3 }} +
- @@ -68,53 +80,63 @@ - diff --git a/packages/client/src/scripts/initialize-sw.ts b/packages/client/src/scripts/initialize-sw.ts index 7bacfbdf00..de52f30523 100644 --- a/packages/client/src/scripts/initialize-sw.ts +++ b/packages/client/src/scripts/initialize-sw.ts @@ -1,6 +1,3 @@ -import { instance } from '@/instance'; -import { $i } from '@/account'; -import { api } from '@/os'; import { lang } from '@/config'; export async function initializeSw() { @@ -12,57 +9,5 @@ export async function initializeSw() { msg: 'initialize', lang, }); - - if (instance.swPublickey && ('PushManager' in window) && $i && $i.token) { - // SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters - registration.pushManager.subscribe({ - userVisibleOnly: true, - applicationServerKey: urlBase64ToUint8Array(instance.swPublickey) - }) - .then(subscription => { - function encode(buffer: ArrayBuffer | null) { - return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer))); - } - - // Register - api('sw/register', { - endpoint: subscription.endpoint, - auth: encode(subscription.getKey('auth')), - publickey: encode(subscription.getKey('p256dh')) - }); - }) - // When subscribe failed - .catch(async (err: Error) => { - // 通知が許可されていなかったとき - if (err.name === 'NotAllowedError') { - return; - } - - // 違うapplicationServerKey (または gcm_sender_id)のサブスクリプションが - // 既に存在していることが原因でエラーになった可能性があるので、 - // そのサブスクリプションを解除しておく - const subscription = await registration.pushManager.getSubscription(); - if (subscription) subscription.unsubscribe(); - }); - } }); } - -/** - * Convert the URL safe base64 string to a Uint8Array - * @param base64String base64 string - */ -function urlBase64ToUint8Array(base64String: string): Uint8Array { - const padding = '='.repeat((4 - base64String.length % 4) % 4); - const base64 = (base64String + padding) - .replace(/-/g, '+') - .replace(/_/g, '/'); - - const rawData = window.atob(base64); - const outputArray = new Uint8Array(rawData.length); - - for (let i = 0; i < rawData.length; ++i) { - outputArray[i] = rawData.charCodeAt(i); - } - return outputArray; -} -- cgit v1.2.3-freya From ac0a59835e7567dfece97ae0240197c9e924d882 Mon Sep 17 00:00:00 2001 From: tamaina Date: Mon, 19 Dec 2022 08:59:17 +0900 Subject: wip (#9349) --- packages/backend/src/server/web/views/base.pug | 2 +- packages/client/package.json | 6 +- packages/client/src/init.ts | 2 + yarn.lock | 426 ++++++++++++------------- 4 files changed, 219 insertions(+), 217 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 4238e15f04..7d802d37ce 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -39,7 +39,7 @@ html if Array.isArray(clientEntry.css) each href in clientEntry.css - link(rel='preload' href=`/assets/${href}` as='style') + link(rel='stylesheet' href=`/assets/${href}`) title block title diff --git a/packages/client/package.json b/packages/client/package.json index 2c9b3cbb21..1962f0c6ee 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -13,7 +13,7 @@ "@rollup/plugin-json": "5.0.2", "@rollup/pluginutils": "5.0.2", "@syuilo/aiscript": "0.11.1", - "@vitejs/plugin-vue": "3", + "@vitejs/plugin-vue": "4.0.0", "@vue/compiler-sfc": "3.2.45", "autobind-decorator": "2.4.0", "autosize": "5.0.2", @@ -42,6 +42,7 @@ "punycode": "2.1.1", "querystring": "0.2.1", "rndstr": "1.0.0", + "rollup": "3.7.2", "s-age": "1.1.2", "sass": "1.56.2", "seedrandom": "3.0.5", @@ -58,6 +59,7 @@ "typescript": "4.9.4", "uuid": "9.0.0", "vanilla-tilt": "1.7.3", + "vite": "4.0.2", "vue": "3.2.45", "vue-prism-editor": "2.0.0-alpha.2", "vuedraggable": "4.0.1" @@ -83,9 +85,7 @@ "eslint": "8.29.0", "eslint-plugin-import": "2.26.0", "eslint-plugin-vue": "9.8.0", - "rollup": "3.7.2", "start-server-and-test": "1.15.2", - "vite": "3.2.5", "vue-eslint-parser": "^9.1.0", "vue-tsc": "^1.0.13" } diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index f9e08d79b1..89692d5fbb 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -1,6 +1,8 @@ /** * Client entry point */ +// https://vitejs.dev/config/build-options.html#build-modulepreload +import 'vite/modulepreload-polyfill'; import '@/style.scss'; diff --git a/yarn.lock b/yarn.lock index 96db703b28..121818b16c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -642,13 +642,76 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.15.18": - version: 0.15.18 - resolution: "@esbuild/android-arm@npm:0.15.18" +"@esbuild/android-arm64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/android-arm64@npm:0.16.9" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/android-arm@npm:0.16.9" conditions: os=android & cpu=arm languageName: node linkType: hard +"@esbuild/android-x64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/android-x64@npm:0.16.9" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/darwin-arm64@npm:0.16.9" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/darwin-x64@npm:0.16.9" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/freebsd-arm64@npm:0.16.9" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/freebsd-x64@npm:0.16.9" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/linux-arm64@npm:0.16.9" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/linux-arm@npm:0.16.9" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/linux-ia32@npm:0.16.9" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.14.54": version: 0.14.54 resolution: "@esbuild/linux-loong64@npm:0.14.54" @@ -656,13 +719,90 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.15.18": - version: 0.15.18 - resolution: "@esbuild/linux-loong64@npm:0.15.18" +"@esbuild/linux-loong64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/linux-loong64@npm:0.16.9" conditions: os=linux & cpu=loong64 languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/linux-mips64el@npm:0.16.9" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/linux-ppc64@npm:0.16.9" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/linux-riscv64@npm:0.16.9" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/linux-s390x@npm:0.16.9" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/linux-x64@npm:0.16.9" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/netbsd-x64@npm:0.16.9" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/openbsd-x64@npm:0.16.9" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/sunos-x64@npm:0.16.9" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/win32-arm64@npm:0.16.9" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/win32-ia32@npm:0.16.9" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.16.9": + version: 0.16.9 + resolution: "@esbuild/win32-x64@npm:0.16.9" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint/eslintrc@npm:^1.3.3": version: 1.3.3 resolution: "@eslint/eslintrc@npm:1.3.3" @@ -2868,13 +3008,13 @@ __metadata: languageName: node linkType: hard -"@vitejs/plugin-vue@npm:3": - version: 3.2.0 - resolution: "@vitejs/plugin-vue@npm:3.2.0" +"@vitejs/plugin-vue@npm:4.0.0": + version: 4.0.0 + resolution: "@vitejs/plugin-vue@npm:4.0.0" peerDependencies: - vite: ^3.0.0 + vite: ^4.0.0 vue: ^3.2.25 - checksum: 64774f770e0e21ce7eb36743d614e3f197a35f5b67b2d3800c61766e649f43dc497bb037738ffafd607eb488064ab132c23766190e7ab557a0b88c0051f7a794 + checksum: 5a53414912db644ca7a663d87c8d4838841dbf794772105c9b3bc3fb10214fc86c2f1f1a47350f264370e9a5fd2475f3b6882778b2440b5085d3fe7550ced542 languageName: node linkType: hard @@ -4952,7 +5092,7 @@ __metadata: "@types/ws": 8.5.3 "@typescript-eslint/eslint-plugin": 5.46.0 "@typescript-eslint/parser": 5.46.0 - "@vitejs/plugin-vue": 3 + "@vitejs/plugin-vue": 4.0.0 "@vue/compiler-sfc": 3.2.45 autobind-decorator: 2.4.0 autosize: 5.0.2 @@ -5004,7 +5144,7 @@ __metadata: typescript: 4.9.4 uuid: 9.0.0 vanilla-tilt: 1.7.3 - vite: 3.2.5 + vite: 4.0.2 vue: 3.2.45 vue-eslint-parser: ^9.1.0 vue-prism-editor: 2.0.0-alpha.2 @@ -6549,13 +6689,6 @@ __metadata: languageName: node linkType: hard -"esbuild-android-64@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-android-64@npm:0.15.18" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - "esbuild-android-arm64@npm:0.14.54": version: 0.14.54 resolution: "esbuild-android-arm64@npm:0.14.54" @@ -6563,13 +6696,6 @@ __metadata: languageName: node linkType: hard -"esbuild-android-arm64@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-android-arm64@npm:0.15.18" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - "esbuild-darwin-64@npm:0.14.54": version: 0.14.54 resolution: "esbuild-darwin-64@npm:0.14.54" @@ -6577,13 +6703,6 @@ __metadata: languageName: node linkType: hard -"esbuild-darwin-64@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-darwin-64@npm:0.15.18" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "esbuild-darwin-arm64@npm:0.14.54": version: 0.14.54 resolution: "esbuild-darwin-arm64@npm:0.14.54" @@ -6591,13 +6710,6 @@ __metadata: languageName: node linkType: hard -"esbuild-darwin-arm64@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-darwin-arm64@npm:0.15.18" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "esbuild-freebsd-64@npm:0.14.54": version: 0.14.54 resolution: "esbuild-freebsd-64@npm:0.14.54" @@ -6605,13 +6717,6 @@ __metadata: languageName: node linkType: hard -"esbuild-freebsd-64@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-freebsd-64@npm:0.15.18" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "esbuild-freebsd-arm64@npm:0.14.54": version: 0.14.54 resolution: "esbuild-freebsd-arm64@npm:0.14.54" @@ -6619,13 +6724,6 @@ __metadata: languageName: node linkType: hard -"esbuild-freebsd-arm64@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-freebsd-arm64@npm:0.15.18" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - "esbuild-linux-32@npm:0.14.54": version: 0.14.54 resolution: "esbuild-linux-32@npm:0.14.54" @@ -6633,13 +6731,6 @@ __metadata: languageName: node linkType: hard -"esbuild-linux-32@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-linux-32@npm:0.15.18" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - "esbuild-linux-64@npm:0.14.54": version: 0.14.54 resolution: "esbuild-linux-64@npm:0.14.54" @@ -6647,13 +6738,6 @@ __metadata: languageName: node linkType: hard -"esbuild-linux-64@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-linux-64@npm:0.15.18" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - "esbuild-linux-arm64@npm:0.14.54": version: 0.14.54 resolution: "esbuild-linux-arm64@npm:0.14.54" @@ -6661,13 +6745,6 @@ __metadata: languageName: node linkType: hard -"esbuild-linux-arm64@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-linux-arm64@npm:0.15.18" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - "esbuild-linux-arm@npm:0.14.54": version: 0.14.54 resolution: "esbuild-linux-arm@npm:0.14.54" @@ -6675,13 +6752,6 @@ __metadata: languageName: node linkType: hard -"esbuild-linux-arm@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-linux-arm@npm:0.15.18" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "esbuild-linux-mips64le@npm:0.14.54": version: 0.14.54 resolution: "esbuild-linux-mips64le@npm:0.14.54" @@ -6689,13 +6759,6 @@ __metadata: languageName: node linkType: hard -"esbuild-linux-mips64le@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-linux-mips64le@npm:0.15.18" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - "esbuild-linux-ppc64le@npm:0.14.54": version: 0.14.54 resolution: "esbuild-linux-ppc64le@npm:0.14.54" @@ -6703,13 +6766,6 @@ __metadata: languageName: node linkType: hard -"esbuild-linux-ppc64le@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-linux-ppc64le@npm:0.15.18" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - "esbuild-linux-riscv64@npm:0.14.54": version: 0.14.54 resolution: "esbuild-linux-riscv64@npm:0.14.54" @@ -6717,13 +6773,6 @@ __metadata: languageName: node linkType: hard -"esbuild-linux-riscv64@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-linux-riscv64@npm:0.15.18" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - "esbuild-linux-s390x@npm:0.14.54": version: 0.14.54 resolution: "esbuild-linux-s390x@npm:0.14.54" @@ -6731,13 +6780,6 @@ __metadata: languageName: node linkType: hard -"esbuild-linux-s390x@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-linux-s390x@npm:0.15.18" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - "esbuild-netbsd-64@npm:0.14.54": version: 0.14.54 resolution: "esbuild-netbsd-64@npm:0.14.54" @@ -6745,13 +6787,6 @@ __metadata: languageName: node linkType: hard -"esbuild-netbsd-64@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-netbsd-64@npm:0.15.18" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - "esbuild-openbsd-64@npm:0.14.54": version: 0.14.54 resolution: "esbuild-openbsd-64@npm:0.14.54" @@ -6759,13 +6794,6 @@ __metadata: languageName: node linkType: hard -"esbuild-openbsd-64@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-openbsd-64@npm:0.15.18" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - "esbuild-sunos-64@npm:0.14.54": version: 0.14.54 resolution: "esbuild-sunos-64@npm:0.14.54" @@ -6773,13 +6801,6 @@ __metadata: languageName: node linkType: hard -"esbuild-sunos-64@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-sunos-64@npm:0.15.18" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - "esbuild-windows-32@npm:0.14.54": version: 0.14.54 resolution: "esbuild-windows-32@npm:0.14.54" @@ -6787,13 +6808,6 @@ __metadata: languageName: node linkType: hard -"esbuild-windows-32@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-windows-32@npm:0.15.18" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "esbuild-windows-64@npm:0.14.54": version: 0.14.54 resolution: "esbuild-windows-64@npm:0.14.54" @@ -6801,13 +6815,6 @@ __metadata: languageName: node linkType: hard -"esbuild-windows-64@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-windows-64@npm:0.15.18" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "esbuild-windows-arm64@npm:0.14.54": version: 0.14.54 resolution: "esbuild-windows-arm64@npm:0.14.54" @@ -6815,13 +6822,6 @@ __metadata: languageName: node linkType: hard -"esbuild-windows-arm64@npm:0.15.18": - version: 0.15.18 - resolution: "esbuild-windows-arm64@npm:0.15.18" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "esbuild@npm:^0.14.42": version: 0.14.54 resolution: "esbuild@npm:0.14.54" @@ -6896,80 +6896,80 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.15.9": - version: 0.15.18 - resolution: "esbuild@npm:0.15.18" - dependencies: - "@esbuild/android-arm": 0.15.18 - "@esbuild/linux-loong64": 0.15.18 - esbuild-android-64: 0.15.18 - esbuild-android-arm64: 0.15.18 - esbuild-darwin-64: 0.15.18 - esbuild-darwin-arm64: 0.15.18 - esbuild-freebsd-64: 0.15.18 - esbuild-freebsd-arm64: 0.15.18 - esbuild-linux-32: 0.15.18 - esbuild-linux-64: 0.15.18 - esbuild-linux-arm: 0.15.18 - esbuild-linux-arm64: 0.15.18 - esbuild-linux-mips64le: 0.15.18 - esbuild-linux-ppc64le: 0.15.18 - esbuild-linux-riscv64: 0.15.18 - esbuild-linux-s390x: 0.15.18 - esbuild-netbsd-64: 0.15.18 - esbuild-openbsd-64: 0.15.18 - esbuild-sunos-64: 0.15.18 - esbuild-windows-32: 0.15.18 - esbuild-windows-64: 0.15.18 - esbuild-windows-arm64: 0.15.18 +"esbuild@npm:^0.16.3": + version: 0.16.9 + resolution: "esbuild@npm:0.16.9" + dependencies: + "@esbuild/android-arm": 0.16.9 + "@esbuild/android-arm64": 0.16.9 + "@esbuild/android-x64": 0.16.9 + "@esbuild/darwin-arm64": 0.16.9 + "@esbuild/darwin-x64": 0.16.9 + "@esbuild/freebsd-arm64": 0.16.9 + "@esbuild/freebsd-x64": 0.16.9 + "@esbuild/linux-arm": 0.16.9 + "@esbuild/linux-arm64": 0.16.9 + "@esbuild/linux-ia32": 0.16.9 + "@esbuild/linux-loong64": 0.16.9 + "@esbuild/linux-mips64el": 0.16.9 + "@esbuild/linux-ppc64": 0.16.9 + "@esbuild/linux-riscv64": 0.16.9 + "@esbuild/linux-s390x": 0.16.9 + "@esbuild/linux-x64": 0.16.9 + "@esbuild/netbsd-x64": 0.16.9 + "@esbuild/openbsd-x64": 0.16.9 + "@esbuild/sunos-x64": 0.16.9 + "@esbuild/win32-arm64": 0.16.9 + "@esbuild/win32-ia32": 0.16.9 + "@esbuild/win32-x64": 0.16.9 dependenciesMeta: "@esbuild/android-arm": optional: true - "@esbuild/linux-loong64": + "@esbuild/android-arm64": optional: true - esbuild-android-64: + "@esbuild/android-x64": optional: true - esbuild-android-arm64: + "@esbuild/darwin-arm64": optional: true - esbuild-darwin-64: + "@esbuild/darwin-x64": optional: true - esbuild-darwin-arm64: + "@esbuild/freebsd-arm64": optional: true - esbuild-freebsd-64: + "@esbuild/freebsd-x64": optional: true - esbuild-freebsd-arm64: + "@esbuild/linux-arm": optional: true - esbuild-linux-32: + "@esbuild/linux-arm64": optional: true - esbuild-linux-64: + "@esbuild/linux-ia32": optional: true - esbuild-linux-arm: + "@esbuild/linux-loong64": optional: true - esbuild-linux-arm64: + "@esbuild/linux-mips64el": optional: true - esbuild-linux-mips64le: + "@esbuild/linux-ppc64": optional: true - esbuild-linux-ppc64le: + "@esbuild/linux-riscv64": optional: true - esbuild-linux-riscv64: + "@esbuild/linux-s390x": optional: true - esbuild-linux-s390x: + "@esbuild/linux-x64": optional: true - esbuild-netbsd-64: + "@esbuild/netbsd-x64": optional: true - esbuild-openbsd-64: + "@esbuild/openbsd-x64": optional: true - esbuild-sunos-64: + "@esbuild/sunos-x64": optional: true - esbuild-windows-32: + "@esbuild/win32-arm64": optional: true - esbuild-windows-64: + "@esbuild/win32-ia32": optional: true - esbuild-windows-arm64: + "@esbuild/win32-x64": optional: true bin: esbuild: bin/esbuild - checksum: ec12682b2cb2d4f0669d0e555028b87a9284ca7f6a1b26e35e69a8697165b35cc682ad598abc70f0bbcfdc12ca84ef888caf5ceee389237862e8f8c17da85f89 + checksum: e7053a94178e548ae166a3bdff1e2d2c6b4ac6fe5d17434eda8222d742b44b2ff5653434a03b2075676827ad8ca37ff24f3383f7b8da5ebb35d8fac4ae7b89f3 languageName: node linkType: hard @@ -13542,7 +13542,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.18": +"postcss@npm:^8.4.20": version: 8.4.20 resolution: "postcss@npm:8.4.20" dependencies: @@ -14733,9 +14733,9 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^2.79.1": - version: 2.79.1 - resolution: "rollup@npm:2.79.1" +"rollup@npm:^3.7.0": + version: 3.7.5 + resolution: "rollup@npm:3.7.5" dependencies: fsevents: ~2.3.2 dependenciesMeta: @@ -14743,7 +14743,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 6a2bf167b3587d4df709b37d149ad0300692cc5deb510f89ac7bdc77c8738c9546ae3de9322b0968e1ed2b0e984571f5f55aae28fa7de4cfcb1bc5402a4e2be6 + checksum: 772f85e9f12c83091cbe8ef31dc563198566cfe536dc8099dd6d4e372daa370f9d455cc42c7993d22b02521e25d53733df7e98d2a993e5a22bf11b2591de4e2d languageName: node linkType: hard @@ -17014,15 +17014,15 @@ __metadata: languageName: node linkType: hard -"vite@npm:3.2.5": - version: 3.2.5 - resolution: "vite@npm:3.2.5" +"vite@npm:4.0.2": + version: 4.0.2 + resolution: "vite@npm:4.0.2" dependencies: - esbuild: ^0.15.9 + esbuild: ^0.16.3 fsevents: ~2.3.2 - postcss: ^8.4.18 + postcss: ^8.4.20 resolve: ^1.22.1 - rollup: ^2.79.1 + rollup: ^3.7.0 peerDependencies: "@types/node": ">= 14" less: "*" @@ -17048,7 +17048,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: ad35b7008c2b62a167d1d1a82f0a0c60fa457733f1969e9eedf0b0077f67a7ac74b4c9477e75a397895150f09b6551f0c17841c5b05c34d9fe302bb0b5dc28a8 + checksum: b259782d83f293289f1cd7802cc525c75691fd45a1ded74e58365b5bff8f93f255daad4480269cda1660bf831f628b7a7224c9f591f039b5c0ccf351a8ee04ee languageName: node linkType: hard -- cgit v1.2.3-freya From 5e448deb0333dc1c9c1760e754c0f174232caeb9 Mon Sep 17 00:00:00 2001 From: Effy Elden Date: Mon, 19 Dec 2022 11:00:13 +1100 Subject: Fix peers API returning suspended instances (#9348) * Fix peers API returning suspended instances * Fix missing comma --- packages/backend/src/server/api/ApiServerService.ts | 3 +++ 1 file changed, 3 insertions(+) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index 884ed0c8cb..e9641ea330 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -138,6 +138,9 @@ export class ApiServerService { fastify.get('/v1/instance/peers', async (request, reply) => { const instances = await this.instancesRepository.find({ select: ['host'], + where: { + isSuspended: false, + }, }); return instances.map(instance => instance.host); -- cgit v1.2.3-freya From f83bd31fd5fee0bb2209ad0e5359ffa473ffb30e Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 19 Dec 2022 13:53:41 +0900 Subject: wip --- packages/backend/src/server/web/views/base.pug | 2 +- packages/client/src/account.ts | 2 +- .../client/src/components/MkAbuseReportWindow.vue | 2 +- .../src/components/MkChannelFollowButton.vue | 4 ++-- packages/client/src/components/MkDialog.vue | 2 +- packages/client/src/components/MkDrive.file.vue | 4 ++-- packages/client/src/components/MkDrive.folder.vue | 2 +- .../client/src/components/MkDrive.navFolder.vue | 2 +- packages/client/src/components/MkDrive.vue | 6 ++--- packages/client/src/components/MkEmojiPicker.vue | 2 +- packages/client/src/components/MkFollowButton.vue | 6 ++--- packages/client/src/components/MkGoogle.vue | 2 +- .../client/src/components/MkModalPageWindow.vue | 6 ++--- packages/client/src/components/MkModalWindow.vue | 2 +- packages/client/src/components/MkNote.vue | 18 +++++++------- packages/client/src/components/MkNoteDetailed.vue | 18 +++++++------- packages/client/src/components/MkNotification.vue | 8 +++---- packages/client/src/components/MkPageWindow.vue | 6 ++--- packages/client/src/components/MkPoll.vue | 2 +- packages/client/src/components/MkPostForm.vue | 10 ++++---- packages/client/src/components/MkRenoteButton.vue | 4 ++-- packages/client/src/components/MkSignup.vue | 10 ++++---- .../client/src/components/MkSubNoteContent.vue | 2 +- packages/client/src/components/MkVisibility.vue | 2 +- .../client/src/components/MkVisibilityPicker.vue | 4 ++-- packages/client/src/components/MkWaitingDialog.vue | 2 +- packages/client/src/components/MkWidgets.vue | 6 ++--- packages/client/src/components/form/checkbox.vue | 2 +- packages/client/src/components/form/input.vue | 2 +- packages/client/src/components/form/link.vue | 2 +- packages/client/src/components/global/MkA.vue | 4 ++-- packages/client/src/components/page/page.post.vue | 4 ++-- packages/client/src/navbar.ts | 14 +++++------ packages/client/src/pages/about-misskey.vue | 2 +- packages/client/src/pages/about.emojis.vue | 2 +- packages/client/src/pages/about.federation.vue | 2 +- packages/client/src/pages/about.vue | 2 +- packages/client/src/pages/admin-file.vue | 4 ++-- packages/client/src/pages/admin/abuses.vue | 2 +- packages/client/src/pages/admin/ads.vue | 4 ++-- packages/client/src/pages/admin/announcements.vue | 4 ++-- packages/client/src/pages/admin/email-settings.vue | 2 +- .../client/src/pages/admin/emoji-edit-dialog.vue | 2 +- packages/client/src/pages/admin/emojis.vue | 10 ++++---- packages/client/src/pages/admin/files.vue | 6 ++--- packages/client/src/pages/admin/index.vue | 24 +++++++++---------- packages/client/src/pages/admin/integrations.vue | 2 +- packages/client/src/pages/admin/object-storage.vue | 4 ++-- packages/client/src/pages/admin/other-settings.vue | 4 ++-- packages/client/src/pages/admin/relays.vue | 8 +++---- packages/client/src/pages/admin/security.vue | 2 +- packages/client/src/pages/admin/settings.vue | 12 +++++----- packages/client/src/pages/admin/users.vue | 6 ++--- packages/client/src/pages/announcements.vue | 2 +- packages/client/src/pages/antenna-timeline.vue | 2 +- packages/client/src/pages/api-console.vue | 2 +- packages/client/src/pages/channel-editor.vue | 4 ++-- packages/client/src/pages/channel.vue | 2 +- packages/client/src/pages/channels.vue | 4 ++-- packages/client/src/pages/clip.vue | 4 ++-- packages/client/src/pages/drive.vue | 2 +- packages/client/src/pages/emojis.emoji.vue | 2 +- packages/client/src/pages/explore.users.vue | 6 ++--- packages/client/src/pages/explore.vue | 4 ++-- packages/client/src/pages/favorites.vue | 2 +- packages/client/src/pages/follow-requests.vue | 2 +- packages/client/src/pages/gallery/edit.vue | 4 ++-- packages/client/src/pages/gallery/index.vue | 4 ++-- packages/client/src/pages/gallery/post.vue | 4 ++-- packages/client/src/pages/instance-info.vue | 2 +- packages/client/src/pages/messaging/index.vue | 4 ++-- .../src/pages/messaging/messaging-room.form.vue | 2 +- packages/client/src/pages/my-antennas/index.vue | 2 +- packages/client/src/pages/my-clips/index.vue | 6 ++--- packages/client/src/pages/my-lists/index.vue | 4 ++-- packages/client/src/pages/notifications.vue | 4 ++-- .../pages/page-editor/els/page-editor.el.if.vue | 2 +- .../pages/page-editor/els/page-editor.el.post.vue | 2 +- .../page-editor/els/page-editor.el.section.vue | 2 +- .../pages/page-editor/page-editor.container.vue | 2 +- .../client/src/pages/page-editor/page-editor.vue | 14 +++++------ packages/client/src/pages/page.vue | 4 ++-- packages/client/src/pages/pages.vue | 4 ++-- packages/client/src/pages/registry.keys.vue | 2 +- packages/client/src/pages/registry.value.vue | 2 +- packages/client/src/pages/registry.vue | 2 +- packages/client/src/pages/search.vue | 2 +- packages/client/src/pages/settings/accounts.vue | 4 ++-- packages/client/src/pages/settings/apps.vue | 2 +- packages/client/src/pages/settings/drive.vue | 2 +- packages/client/src/pages/settings/email.vue | 2 +- packages/client/src/pages/settings/general.vue | 2 +- packages/client/src/pages/settings/index.vue | 12 +++++----- packages/client/src/pages/settings/integration.vue | 2 +- .../client/src/pages/settings/notifications.vue | 4 ++-- packages/client/src/pages/settings/other.vue | 4 ++-- .../client/src/pages/settings/plugin.install.vue | 2 +- packages/client/src/pages/settings/plugin.vue | 4 ++-- packages/client/src/pages/settings/profile.vue | 4 ++-- packages/client/src/pages/settings/reaction.vue | 2 +- packages/client/src/pages/settings/security.vue | 2 +- .../client/src/pages/settings/theme.install.vue | 2 +- .../client/src/pages/settings/theme.manage.vue | 2 +- packages/client/src/pages/settings/theme.vue | 2 +- .../client/src/pages/settings/webhook.edit.vue | 2 +- packages/client/src/pages/settings/webhook.new.vue | 2 +- packages/client/src/pages/settings/webhook.vue | 2 +- packages/client/src/pages/share.vue | 2 +- packages/client/src/pages/tag.vue | 2 +- packages/client/src/pages/theme-editor.vue | 2 +- packages/client/src/pages/timeline.tutorial.vue | 2 +- packages/client/src/pages/timeline.vue | 10 ++++---- packages/client/src/pages/user-list-timeline.vue | 2 +- packages/client/src/pages/user/home.vue | 2 +- packages/client/src/pages/user/index.activity.vue | 2 +- packages/client/src/pages/user/index.vue | 4 ++-- packages/client/src/pages/welcome.entrance.a.vue | 2 +- packages/client/src/pages/welcome.entrance.c.vue | 2 +- packages/client/src/pages/welcome.timeline.vue | 2 +- packages/client/src/scripts/get-note-menu.ts | 28 +++++++++++----------- packages/client/src/scripts/get-user-menu.ts | 4 ++-- packages/client/src/scripts/hpml/lib.ts | 6 ++--- packages/client/src/scripts/select-file.ts | 4 ++-- packages/client/src/style.scss | 5 ++++ .../client/src/ui/_common_/navbar-for-mobile.vue | 6 ++--- packages/client/src/ui/_common_/navbar.vue | 6 ++--- packages/client/src/ui/classic.header.vue | 6 ++--- packages/client/src/ui/classic.sidebar.vue | 6 ++--- packages/client/src/ui/classic.widgets.vue | 2 +- packages/client/src/ui/deck.vue | 10 ++++---- packages/client/src/ui/deck/column.vue | 4 ++-- .../client/src/ui/deck/notifications-column.vue | 2 +- packages/client/src/ui/deck/tl-column.vue | 10 ++++---- packages/client/src/ui/universal.vue | 4 ++-- packages/client/src/ui/universal.widgets.vue | 2 +- packages/client/src/ui/visitor/b.vue | 4 ++-- packages/client/src/ui/visitor/header.vue | 6 ++--- packages/client/src/widgets/federation.vue | 2 +- packages/client/src/widgets/notifications.vue | 4 ++-- packages/client/src/widgets/rss-ticker.vue | 2 +- packages/client/src/widgets/rss.vue | 2 +- packages/client/src/widgets/timeline.vue | 16 ++++++------- packages/client/src/widgets/trends.vue | 2 +- 143 files changed, 311 insertions(+), 306 deletions(-) (limited to 'packages/backend/src/server') diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 7d802d37ce..472defa102 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -34,7 +34,7 @@ html link(rel='prefetch' href='https://xn--931a.moe/assets/info.jpg') link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg') link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg') - link(rel='stylesheet' href='/assets/fontawesome/css/all.css') + link(rel='stylesheet' href='https://unpkg.com/@tabler/icons@1.117.0/iconfont/tabler-icons.min.css') link(rel='modulepreload' href=`/assets/${clientEntry.file}`) if Array.isArray(clientEntry.css) diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index e9c29d6b0d..e981536dd8 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -213,7 +213,7 @@ export async function openAccountMenu(opts: { avatar: $i, }, null, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, { type: 'parent', - icon: 'fas fa-plus', + icon: 'ti ti-plus', text: i18n.ts.addAccount, children: [{ text: i18n.ts.existingAccount, diff --git a/packages/client/src/components/MkAbuseReportWindow.vue b/packages/client/src/components/MkAbuseReportWindow.vue index 1862d0a0e4..03890b4e97 100644 --- a/packages/client/src/components/MkAbuseReportWindow.vue +++ b/packages/client/src/components/MkAbuseReportWindow.vue @@ -1,7 +1,7 @@ diff --git a/packages/client/src/pages/settings/webhook.new.vue b/packages/client/src/pages/settings/webhook.new.vue index fcf1329ff6..b42f283945 100644 --- a/packages/client/src/pages/settings/webhook.new.vue +++ b/packages/client/src/pages/settings/webhook.new.vue @@ -26,7 +26,7 @@
- {{ i18n.ts.create }} + {{ i18n.ts.create }}
diff --git a/packages/client/src/pages/settings/webhook.vue b/packages/client/src/pages/settings/webhook.vue index 1a7e73940c..d3ad475cd8 100644 --- a/packages/client/src/pages/settings/webhook.vue +++ b/packages/client/src/pages/settings/webhook.vue @@ -13,7 +13,7 @@ {{ webhook.name || webhook.url }} diff --git a/packages/client/src/pages/share.vue b/packages/client/src/pages/share.vue index 69d22ed632..a7e797eeab 100644 --- a/packages/client/src/pages/share.vue +++ b/packages/client/src/pages/share.vue @@ -158,7 +158,7 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: i18n.ts.share, - icon: 'fas fa-share-alt', + icon: 'ti ti-share', }); diff --git a/packages/client/src/pages/tag.vue b/packages/client/src/pages/tag.vue index 5498c2999d..72775ed5c9 100644 --- a/packages/client/src/pages/tag.vue +++ b/packages/client/src/pages/tag.vue @@ -30,6 +30,6 @@ const headerTabs = $computed(() => []); definePageMetadata(computed(() => ({ title: props.tag, - icon: 'fas fa-hashtag', + icon: 'ti ti-hash', }))); diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue index 7dfeee16ed..f0435427eb 100644 --- a/packages/client/src/pages/theme-editor.vue +++ b/packages/client/src/pages/theme-editor.vue @@ -215,7 +215,7 @@ const headerActions = $computed(() => [{ handler: showPreview, }, { asFullButton: true, - icon: 'fas fa-check', + icon: 'ti ti-check', text: i18n.ts.saveAs, handler: saveAs, }]); diff --git a/packages/client/src/pages/timeline.tutorial.vue b/packages/client/src/pages/timeline.tutorial.vue index 9683cc22a5..b1a1af1383 100644 --- a/packages/client/src/pages/timeline.tutorial.vue +++ b/packages/client/src/pages/timeline.tutorial.vue @@ -71,7 +71,7 @@ {{ i18n.ts.noThankYou }} diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue index 9d42997025..8c2dc92ccd 100644 --- a/packages/client/src/pages/timeline.vue +++ b/packages/client/src/pages/timeline.vue @@ -114,22 +114,22 @@ const headerActions = $computed(() => []); const headerTabs = $computed(() => [{ key: 'home', title: i18n.ts._timelines.home, - icon: 'fas fa-home', + icon: 'ti ti-home-2', iconOnly: true, }, ...(isLocalTimelineAvailable ? [{ key: 'local', title: i18n.ts._timelines.local, - icon: 'fas fa-comments', + icon: 'ti ti-messages', iconOnly: true, }, { key: 'social', title: i18n.ts._timelines.social, - icon: 'fas fa-share-alt', + icon: 'ti ti-share', iconOnly: true, }] : []), ...(isGlobalTimelineAvailable ? [{ key: 'global', title: i18n.ts._timelines.global, - icon: 'fas fa-globe', + icon: 'ti ti-world', iconOnly: true, }] : []), { icon: 'fas fa-list-ul', @@ -150,7 +150,7 @@ const headerTabs = $computed(() => [{ definePageMetadata(computed(() => ({ title: i18n.ts.timeline, - icon: src === 'local' ? 'fas fa-comments' : src === 'social' ? 'fas fa-share-alt' : src === 'global' ? 'fas fa-globe' : 'fas fa-home', + icon: src === 'local' ? 'ti ti-messages' : src === 'social' ? 'ti ti-share' : src === 'global' ? 'ti ti-world' : 'ti ti-home-2', }))); diff --git a/packages/client/src/pages/user-list-timeline.vue b/packages/client/src/pages/user-list-timeline.vue index 4a534e47ba..773b20d80c 100644 --- a/packages/client/src/pages/user-list-timeline.vue +++ b/packages/client/src/pages/user-list-timeline.vue @@ -69,7 +69,7 @@ const headerActions = $computed(() => list ? [{ text: i18n.ts.jumpToSpecifiedDate, handler: timetravel, }, { - icon: 'fas fa-cog', + icon: 'ti ti-settings', text: i18n.ts.settings, handler: settings, }] : []); diff --git a/packages/client/src/pages/user/home.vue b/packages/client/src/pages/user/home.vue index 352db4616e..98c7e9d116 100644 --- a/packages/client/src/pages/user/home.vue +++ b/packages/client/src/pages/user/home.vue @@ -25,7 +25,7 @@ {{ i18n.ts.followsYou }}
- +
diff --git a/packages/client/src/pages/user/index.activity.vue b/packages/client/src/pages/user/index.activity.vue index 630a2cea7d..7f25176e82 100644 --- a/packages/client/src/pages/user/index.activity.vue +++ b/packages/client/src/pages/user/index.activity.vue @@ -3,7 +3,7 @@ diff --git a/packages/client/src/pages/user/index.vue b/packages/client/src/pages/user/index.vue index 7e635f8b2e..21f1d25dfd 100644 --- a/packages/client/src/pages/user/index.vue +++ b/packages/client/src/pages/user/index.vue @@ -69,7 +69,7 @@ const headerActions = $computed(() => []); const headerTabs = $computed(() => user ? [{ key: 'home', title: i18n.ts.overview, - icon: 'fas fa-home', + icon: 'ti ti-home-2', }, ...($i && ($i.id === user.id)) || user.publicReactions ? [{ key: 'reactions', title: i18n.ts.reaction, @@ -77,7 +77,7 @@ const headerTabs = $computed(() => user ? [{ }] : [], { key: 'clips', title: i18n.ts.clips, - icon: 'fas fa-paperclip', + icon: 'ti ti-paperclip', }, { key: 'pages', title: i18n.ts.pages, diff --git a/packages/client/src/pages/welcome.entrance.a.vue b/packages/client/src/pages/welcome.entrance.a.vue index 827162a0c0..119ba96ec9 100644 --- a/packages/client/src/pages/welcome.entrance.a.vue +++ b/packages/client/src/pages/welcome.entrance.a.vue @@ -15,7 +15,7 @@
- +

diff --git a/packages/client/src/pages/welcome.entrance.c.vue b/packages/client/src/pages/welcome.entrance.c.vue index a590834a4c..535580e723 100644 --- a/packages/client/src/pages/welcome.entrance.c.vue +++ b/packages/client/src/pages/welcome.entrance.c.vue @@ -41,7 +41,7 @@

- +