summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/endpoints
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/server/api/endpoints')
-rw-r--r--packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts56
-rw-r--r--packages/backend/src/server/api/endpoints/admin/accounts/create.ts59
-rw-r--r--packages/backend/src/server/api/endpoints/admin/accounts/delete.ts82
-rw-r--r--packages/backend/src/server/api/endpoints/admin/ad/create.ts44
-rw-r--r--packages/backend/src/server/api/endpoints/admin/ad/delete.ts24
-rw-r--r--packages/backend/src/server/api/endpoints/admin/ad/list.ts30
-rw-r--r--packages/backend/src/server/api/endpoints/admin/ad/update.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/admin/announcements/create.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/admin/announcements/delete.ts24
-rw-r--r--packages/backend/src/server/api/endpoints/admin/announcements/list.ts61
-rw-r--r--packages/backend/src/server/api/endpoints/admin/announcements/update.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/admin/delete-account.ts32
-rw-r--r--packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts32
-rw-r--r--packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts52
-rw-r--r--packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts32
-rw-r--r--packages/backend/src/server/api/endpoints/admin/drive/files.ts66
-rw-r--r--packages/backend/src/server/api/endpoints/admin/drive/show-file.ts52
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts45
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/add.ts90
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/copy.ts95
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts47
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/delete.ts43
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts19
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts55
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/list.ts56
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts45
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts41
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts41
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/update.ts43
-rw-r--r--packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts32
-rw-r--r--packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts35
-rw-r--r--packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts43
-rw-r--r--packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/admin/get-index-stats.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/admin/get-table-stats.ts43
-rw-r--r--packages/backend/src/server/api/endpoints/admin/get-user-ips.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/admin/invite.ts46
-rw-r--r--packages/backend/src/server/api/endpoints/admin/meta.ts192
-rw-r--r--packages/backend/src/server/api/endpoints/admin/moderators/add.ts44
-rw-r--r--packages/backend/src/server/api/endpoints/admin/moderators/remove.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/admin/promo/create.ts48
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/clear.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/stats.ts42
-rw-r--r--packages/backend/src/server/api/endpoints/admin/relays/add.ts28
-rw-r--r--packages/backend/src/server/api/endpoints/admin/relays/list.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/admin/relays/remove.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/admin/reset-password.ts57
-rw-r--r--packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts62
-rw-r--r--packages/backend/src/server/api/endpoints/admin/send-email.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/admin/server-info.ts78
-rw-r--r--packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts28
-rw-r--r--packages/backend/src/server/api/endpoints/admin/show-user.ts112
-rw-r--r--packages/backend/src/server/api/endpoints/admin/show-users.ts84
-rw-r--r--packages/backend/src/server/api/endpoints/admin/silence-user.ts53
-rw-r--r--packages/backend/src/server/api/endpoints/admin/suspend-user.ts129
-rw-r--r--packages/backend/src/server/api/endpoints/admin/unsilence-user.ts47
-rw-r--r--packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts47
-rw-r--r--packages/backend/src/server/api/endpoints/admin/update-meta.ts530
-rw-r--r--packages/backend/src/server/api/endpoints/admin/update-user-note.ts35
-rw-r--r--packages/backend/src/server/api/endpoints/admin/vacuum.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/announcements.ts55
-rw-r--r--packages/backend/src/server/api/endpoints/antennas/create.ts107
-rw-r--r--packages/backend/src/server/api/endpoints/antennas/delete.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/antennas/list.ts29
-rw-r--r--packages/backend/src/server/api/endpoints/antennas/notes.ts100
-rw-r--r--packages/backend/src/server/api/endpoints/antennas/show.ts39
-rw-r--r--packages/backend/src/server/api/endpoints/antennas/update.ts114
-rw-r--r--packages/backend/src/server/api/endpoints/ap/get.ts24
-rw-r--r--packages/backend/src/server/api/endpoints/ap/show.ts165
-rw-r--r--packages/backend/src/server/api/endpoints/app/create.ts64
-rw-r--r--packages/backend/src/server/api/endpoints/app/show.ts41
-rw-r--r--packages/backend/src/server/api/endpoints/auth/accept.ts100
-rw-r--r--packages/backend/src/server/api/endpoints/auth/session/generate.ts70
-rw-r--r--packages/backend/src/server/api/endpoints/auth/session/show.ts37
-rw-r--r--packages/backend/src/server/api/endpoints/auth/session/userkey.ts92
-rw-r--r--packages/backend/src/server/api/endpoints/blocking/create.ts77
-rw-r--r--packages/backend/src/server/api/endpoints/blocking/delete.ts76
-rw-r--r--packages/backend/src/server/api/endpoints/blocking/list.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/channels/create.ts67
-rw-r--r--packages/backend/src/server/api/endpoints/channels/featured.ts31
-rw-r--r--packages/backend/src/server/api/endpoints/channels/follow.ts54
-rw-r--r--packages/backend/src/server/api/endpoints/channels/followed.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/channels/owned.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/channels/show.ts35
-rw-r--r--packages/backend/src/server/api/endpoints/channels/timeline.ts80
-rw-r--r--packages/backend/src/server/api/endpoints/channels/unfollow.ts47
-rw-r--r--packages/backend/src/server/api/endpoints/channels/update.ts80
-rw-r--r--packages/backend/src/server/api/endpoints/charts/active-users.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/charts/ap-request.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/charts/drive.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/charts/federation.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/charts/hashtag.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/charts/instance.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/charts/notes.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/charts/user/drive.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/charts/user/following.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/charts/user/notes.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/charts/user/reactions.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/charts/users.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/clips/add-note.ts74
-rw-r--r--packages/backend/src/server/api/endpoints/clips/create.ts42
-rw-r--r--packages/backend/src/server/api/endpoints/clips/delete.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/clips/list.ts29
-rw-r--r--packages/backend/src/server/api/endpoints/clips/notes.ts97
-rw-r--r--packages/backend/src/server/api/endpoints/clips/remove-note.ts55
-rw-r--r--packages/backend/src/server/api/endpoints/clips/show.ts43
-rw-r--r--packages/backend/src/server/api/endpoints/clips/update.ts49
-rw-r--r--packages/backend/src/server/api/endpoints/drive.ts33
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files.ts56
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts52
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/check-existence.ts28
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/create.ts106
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/delete.ts47
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts31
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/find.ts33
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/show.ts73
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/update.ts101
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts39
-rw-r--r--packages/backend/src/server/api/endpoints/drive/folders.ts42
-rw-r--r--packages/backend/src/server/api/endpoints/drive/folders/create.ts77
-rw-r--r--packages/backend/src/server/api/endpoints/drive/folders/delete.ts61
-rw-r--r--packages/backend/src/server/api/endpoints/drive/folders/find.ts33
-rw-r--r--packages/backend/src/server/api/endpoints/drive/folders/show.ts43
-rw-r--r--packages/backend/src/server/api/endpoints/drive/folders/update.ts128
-rw-r--r--packages/backend/src/server/api/endpoints/drive/stream.ts46
-rw-r--r--packages/backend/src/server/api/endpoints/email-address/available.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/endpoint.ts29
-rw-r--r--packages/backend/src/server/api/endpoints/endpoints.ts15
-rw-r--r--packages/backend/src/server/api/endpoints/export-custom-emojis.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/federation/followers.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/federation/following.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/federation/instances.ts157
-rw-r--r--packages/backend/src/server/api/endpoints/federation/show-instance.ts30
-rw-r--r--packages/backend/src/server/api/endpoints/federation/stats.ts102
-rw-r--r--packages/backend/src/server/api/endpoints/federation/update-remote-user.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/federation/users.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/fetch-rss.ts44
-rw-r--r--packages/backend/src/server/api/endpoints/following/create.ts88
-rw-r--r--packages/backend/src/server/api/endpoints/following/delete.ts70
-rw-r--r--packages/backend/src/server/api/endpoints/following/invalidate.ts70
-rw-r--r--packages/backend/src/server/api/endpoints/following/requests/accept.ts39
-rw-r--r--packages/backend/src/server/api/endpoints/following/requests/cancel.ts58
-rw-r--r--packages/backend/src/server/api/endpoints/following/requests/list.ts29
-rw-r--r--packages/backend/src/server/api/endpoints/following/requests/reject.ts33
-rw-r--r--packages/backend/src/server/api/endpoints/gallery/featured.ts33
-rw-r--r--packages/backend/src/server/api/endpoints/gallery/popular.ts31
-rw-r--r--packages/backend/src/server/api/endpoints/gallery/posts.ts32
-rw-r--r--packages/backend/src/server/api/endpoints/gallery/posts/create.ts71
-rw-r--r--packages/backend/src/server/api/endpoints/gallery/posts/delete.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/gallery/posts/like.ts71
-rw-r--r--packages/backend/src/server/api/endpoints/gallery/posts/show.ts35
-rw-r--r--packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts49
-rw-r--r--packages/backend/src/server/api/endpoints/gallery/posts/update.ts70
-rw-r--r--packages/backend/src/server/api/endpoints/get-online-users-count.ts30
-rw-r--r--packages/backend/src/server/api/endpoints/hashtags/list.ts79
-rw-r--r--packages/backend/src/server/api/endpoints/hashtags/search.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/hashtags/show.ts33
-rw-r--r--packages/backend/src/server/api/endpoints/hashtags/trend.ts162
-rw-r--r--packages/backend/src/server/api/endpoints/hashtags/users.ts67
-rw-r--r--packages/backend/src/server/api/endpoints/i.ts33
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/done.ts52
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/key-done.ts216
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/password-less.ts24
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/register-key.ts84
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/register.ts79
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts62
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/unregister.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/apps.ts52
-rw-r--r--packages/backend/src/server/api/endpoints/i/authorized-apps.ts51
-rw-r--r--packages/backend/src/server/api/endpoints/i/change-password.ts42
-rw-r--r--packages/backend/src/server/api/endpoints/i/delete-account.ts47
-rw-r--r--packages/backend/src/server/api/endpoints/i/export-blocking.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/i/export-following.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/i/export-mute.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/i/export-notes.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/i/export-user-lists.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/i/favorites.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/gallery/likes.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/i/gallery/posts.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts30
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-blocking.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-following.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-muting.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-user-lists.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/i/notifications.ts194
-rw-r--r--packages/backend/src/server/api/endpoints/i/page-likes.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/i/pages.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/i/pin.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts59
-rw-r--r--packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/i/read-announcement.ts75
-rw-r--r--packages/backend/src/server/api/endpoints/i/regenerate-token.ts65
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/get-all.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/get-detail.ts44
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/get.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/keys.ts32
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/remove.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/scopes.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/set.ts81
-rw-r--r--packages/backend/src/server/api/endpoints/i/revoke-token.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/signin-history.ts32
-rw-r--r--packages/backend/src/server/api/endpoints/i/unpin.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/i/update-email.ts104
-rw-r--r--packages/backend/src/server/api/endpoints/i/update.ts261
-rw-r--r--packages/backend/src/server/api/endpoints/i/user-group-invites.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/webhooks/create.ts51
-rw-r--r--packages/backend/src/server/api/endpoints/i/webhooks/delete.ts42
-rw-r--r--packages/backend/src/server/api/endpoints/i/webhooks/list.ts26
-rw-r--r--packages/backend/src/server/api/endpoints/i/webhooks/show.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/i/webhooks/update.ts56
-rw-r--r--packages/backend/src/server/api/endpoints/messaging/history.ts117
-rw-r--r--packages/backend/src/server/api/endpoints/messaging/messages.ts151
-rw-r--r--packages/backend/src/server/api/endpoints/messaging/messages/create.ts138
-rw-r--r--packages/backend/src/server/api/endpoints/messaging/messages/delete.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/messaging/messages/read.ts48
-rw-r--r--packages/backend/src/server/api/endpoints/meta.ts226
-rw-r--r--packages/backend/src/server/api/endpoints/miauth/gen-token.ts62
-rw-r--r--packages/backend/src/server/api/endpoints/mute/create.ts93
-rw-r--r--packages/backend/src/server/api/endpoints/mute/delete.ts71
-rw-r--r--packages/backend/src/server/api/endpoints/mute/list.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/my/apps.ts43
-rw-r--r--packages/backend/src/server/api/endpoints/notes.ts106
-rw-r--r--packages/backend/src/server/api/endpoints/notes/children.ts85
-rw-r--r--packages/backend/src/server/api/endpoints/notes/clips.ts51
-rw-r--r--packages/backend/src/server/api/endpoints/notes/conversation.ts74
-rw-r--r--packages/backend/src/server/api/endpoints/notes/create.ts234
-rw-r--r--packages/backend/src/server/api/endpoints/notes/delete.ts43
-rw-r--r--packages/backend/src/server/api/endpoints/notes/favorites/create.ts65
-rw-r--r--packages/backend/src/server/api/endpoints/notes/favorites/delete.ts52
-rw-r--r--packages/backend/src/server/api/endpoints/notes/featured.ts81
-rw-r--r--packages/backend/src/server/api/endpoints/notes/global-timeline.ts112
-rw-r--r--packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts175
-rw-r--r--packages/backend/src/server/api/endpoints/notes/local-timeline.ts137
-rw-r--r--packages/backend/src/server/api/endpoints/notes/mentions.ts101
-rw-r--r--packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts112
-rw-r--r--packages/backend/src/server/api/endpoints/notes/polls/vote.ts194
-rw-r--r--packages/backend/src/server/api/endpoints/notes/reactions.ts66
-rw-r--r--packages/backend/src/server/api/endpoints/notes/reactions/create.ts39
-rw-r--r--packages/backend/src/server/api/endpoints/notes/reactions/delete.ts35
-rw-r--r--packages/backend/src/server/api/endpoints/notes/renotes.ts74
-rw-r--r--packages/backend/src/server/api/endpoints/notes/replies.ts63
-rw-r--r--packages/backend/src/server/api/endpoints/notes/search-by-tag.ts145
-rw-r--r--packages/backend/src/server/api/endpoints/notes/search.ts139
-rw-r--r--packages/backend/src/server/api/endpoints/notes/show.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/notes/state.ts74
-rw-r--r--packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts67
-rw-r--r--packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/notes/timeline.ts177
-rw-r--r--packages/backend/src/server/api/endpoints/notes/translate.ts111
-rw-r--r--packages/backend/src/server/api/endpoints/notes/unrenote.ts48
-rw-r--r--packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts148
-rw-r--r--packages/backend/src/server/api/endpoints/notes/watching/create.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/notes/watching/delete.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/notifications/create.ts28
-rw-r--r--packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts45
-rw-r--r--packages/backend/src/server/api/endpoints/notifications/read.ts20
-rw-r--r--packages/backend/src/server/api/endpoints/page-push.ts51
-rw-r--r--packages/backend/src/server/api/endpoints/pages/create.ts101
-rw-r--r--packages/backend/src/server/api/endpoints/pages/delete.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/pages/featured.ts33
-rw-r--r--packages/backend/src/server/api/endpoints/pages/like.ts71
-rw-r--r--packages/backend/src/server/api/endpoints/pages/show.ts64
-rw-r--r--packages/backend/src/server/api/endpoints/pages/unlike.ts49
-rw-r--r--packages/backend/src/server/api/endpoints/pages/update.ts107
-rw-r--r--packages/backend/src/server/api/endpoints/ping.ts19
-rw-r--r--packages/backend/src/server/api/endpoints/pinned-users.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/promo/read.ts59
-rw-r--r--packages/backend/src/server/api/endpoints/request-reset-password.ts93
-rw-r--r--packages/backend/src/server/api/endpoints/reset-db.ts30
-rw-r--r--packages/backend/src/server/api/endpoints/reset-password.ts51
-rw-r--r--packages/backend/src/server/api/endpoints/server-info.ts45
-rw-r--r--packages/backend/src/server/api/endpoints/stats.ts84
-rw-r--r--packages/backend/src/server/api/endpoints/sw/register.ts77
-rw-r--r--packages/backend/src/server/api/endpoints/sw/unregister.ts26
-rw-r--r--packages/backend/src/server/api/endpoints/test.ts15
-rw-r--r--packages/backend/src/server/api/endpoints/username/available.ts42
-rw-r--r--packages/backend/src/server/api/endpoints/users.ts85
-rw-r--r--packages/backend/src/server/api/endpoints/users/clips.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/users/followers.ts93
-rw-r--r--packages/backend/src/server/api/endpoints/users/following.ts93
-rw-r--r--packages/backend/src/server/api/endpoints/users/gallery/posts.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts127
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/create.ts59
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/delete.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts63
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/invite.ts112
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/joined.ts44
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/leave.ts43
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/owned.ts29
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/pull.ts61
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/show.ts54
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/transfer.ts75
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/update.ts45
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/create.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/delete.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/list.ts29
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/pull.ts62
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/push.ts95
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/show.ts39
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/update.ts45
-rw-r--r--packages/backend/src/server/api/endpoints/users/notes.ts138
-rw-r--r--packages/backend/src/server/api/endpoints/users/pages.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/users/reactions.ts54
-rw-r--r--packages/backend/src/server/api/endpoints/users/recommendation.ts64
-rw-r--r--packages/backend/src/server/api/endpoints/users/relation.ts27
-rw-r--r--packages/backend/src/server/api/endpoints/users/report-abuse.ts125
-rw-r--r--packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts140
-rw-r--r--packages/backend/src/server/api/endpoints/users/search.ts166
-rw-r--r--packages/backend/src/server/api/endpoints/users/show.ts108
-rw-r--r--packages/backend/src/server/api/endpoints/users/stats.ts183
315 files changed, 11615 insertions, 7600 deletions
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<typeof meta, typeof paramDef> {
+ 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');
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const { account, secret } = await signup({
- username: ps.username,
- password: ps.password,
- });
+ 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 res = await Users.pack(account, account, {
- detail: true,
- includeSecrets: true,
- });
+ const { account, secret } = await this.signupService.signup({
+ username: ps.username,
+ password: ps.password,
+ });
- (res as any).token = secret;
+ const res = await this.userEntityService.pack(account, account, {
+ detail: true,
+ includeSecrets: true,
+ });
- return res;
-});
+ (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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<Announcement, number>();
+ 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<Announcement, number>();
- 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (user == null) {
- throw new Error('user not found');
- }
+ private moderationLogService: ModerationLogService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const user = await this.usersRepository.findOneBy({ id: ps.userId });
- if (!Users.isLocalUser(user)) {
- throw new Error('user is not local user');
- }
+ if (user == null) {
+ throw new Error('user not found');
+ }
- /*if (user.isAdmin) {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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),
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 Emojis.update(emoji.id, {
- updatedAt: new Date(),
- aliases: [...new Set(emoji.aliases.concat(ps.aliases))],
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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);
+ 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 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]));
+ 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 db.queryResultCache!.remove(['meta_emojis']);
+ await this.db.queryResultCache!.remove(['meta_emojis']);
- publishBroadcastStream('emojiAdded', {
- emoji: await Emojis.pack(emoji.id),
- });
+ this.globalEventService.publishBroadcastStream('emojiAdded', {
+ emoji: await this.emojiEntityService.pack(emoji.id),
+ });
- insertModerationLog(me, 'addEmoji', {
- emojiId: emoji.id,
- });
+ this.moderationLogService.insertModerationLog(me, 'addEmoji', {
+ emojiId: emoji.id,
+ });
- return {
- id: 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<typeof meta, typeof paramDef> {
+ 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);
+ }
- 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]));
+ let driveFile: DriveFile;
- await db.queryResultCache!.remove(['meta_emojis']);
+ try {
+ // Create file
+ driveFile = await this.driveService.uploadFromUrl({ url: emoji.originalUrl, user: null, force: true });
+ } catch (e) {
+ throw new ApiError();
+ }
- publishBroadcastStream('emojiAdded', {
- emoji: await Emojis.pack(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]));
- return {
- id: copied.id,
- };
-});
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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),
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 Emojis.update(emoji.id, {
- updatedAt: new Date(),
- aliases: emoji.aliases.filter(x => !ps.aliases.includes(x)),
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const pairs = await Promise.all(followings.map(f => Promise.all([
- Users.findOneByOrFail({ id: f.followerId }),
- Users.findOneByOrFail({ id: f.followeeId }),
- ])));
+ @Inject(DI.notesRepository)
+ private followingsRepository: FollowingsRepository,
- for (const pair of pairs) {
- deleteFollowing(pair[0], pair[1]);
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<string, { count: number; size: number; }>;
- 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<string, { count: number; size: number; }>;
+ 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<typeof meta, typeof paramDef> {
+ 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)
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.registrationTicketsRepository)
+ private registrationTicketsRepository: RegistrationTicketsRepository,
- await RegistrationTickets.insert({
- id: genId(),
- createdAt: new Date(),
- code,
- });
+ 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)
+ });
- return {
- code,
- };
-});
+ 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<typeof meta, typeof paramDef> {
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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.isAdmin) {
- throw new Error('cannot mark as moderator if admin user');
- }
+ if (user == null) {
+ throw new Error('user not found');
+ }
- await Users.update(user.id, {
- isModerator: true,
- });
+ if (user.isAdmin) {
+ throw new Error('cannot mark as moderator if admin user');
+ }
- publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: true });
-});
+ await this.usersRepository.update(user.id, {
+ isModerator: true,
+ });
+
+ this.globalEventService.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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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']);
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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][];
+ 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]);
- }
- }
+ 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]);
+ }
+ }
- res.sort((a, b) => b[1] - a[1]);
+ res.sort((a, b) => b[1] - a[1]);
- return res;
-});
+ 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']);
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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][];
+ 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]);
- }
- }
+ 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]);
+ }
+ }
- res.sort((a, b) => b[1] - a[1]);
+ res.sort((a, b) => b[1] - a[1]);
- return res;
-});
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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;
+// TODO: ロジックをサービスに切り出す
+
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- const report = await AbuseUserReports.findOneByOrFail({ id: ps.reportId });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (report == null) {
- throw new Error('report not found');
- }
+ @Inject(DI.abuseUserReportsRepository)
+ private abuseUserReportsRepository: AbuseUserReportsRepository,
- if (ps.forward && report.targetUserHost != null) {
- const actor = await getInstanceActor();
- const targetUser = await Users.findOneByOrFail({ id: report.targetUserId });
+ private queueService: QueueService,
+ private instanceActorService: InstanceActorService,
+ private apRendererService: ApRendererService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const report = await this.abuseUserReportsRepository.findOneBy({ id: ps.reportId });
- deliver(actor, renderActivity(renderFlag(actor, [targetUser.uri!], report.comment)), targetUser.inbox);
- }
+ if (report == null) {
+ throw new Error('report not found');
+ }
- await AbuseUserReports.update(report.id, {
- resolved: true,
- assigneeId: me.id,
- forwarded: ps.forward && report.targetUserHost != null,
- });
-});
+ 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,
+ });
+ });
+ }
+}
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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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] = '<MASKED>');
- });
+ 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] = '<MASKED>');
+ });
+
+ 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<typeof meta, typeof paramDef> {
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (user == null) {
- throw new Error('user not found');
- }
+ private moderationLogService: ModerationLogService,
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const user = await this.usersRepository.findOneBy({ id: ps.userId });
- if (user.isAdmin) {
- throw new Error('cannot silence admin');
- }
+ if (user == null) {
+ throw new Error('user not found');
+ }
- await Users.update(user.id, {
- isSilenced: true,
- });
+ if (user.isAdmin) {
+ throw new Error('cannot silence admin');
+ }
- publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: true });
+ await this.usersRepository.update(user.id, {
+ isSilenced: true,
+ });
- insertModerationLog(me, 'silence', {
- targetId: user.id,
- });
-});
+ this.globalEventService.publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: true });
+
+ this.moderationLogService.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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (user == null) {
- throw new Error('user not found');
- }
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
- if (user.isAdmin) {
- throw new Error('cannot suspend admin');
- }
+ @Inject(DI.notificationsRepository)
+ private notificationsRepository: NotificationsRepository,
- if (user.isModerator) {
- throw new Error('cannot suspend moderator');
- }
+ 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 });
- await Users.update(user.id, {
- isSuspended: true,
- });
+ if (user == null) {
+ throw new Error('user not found');
+ }
- insertModerationLog(me, 'suspend', {
- targetId: user.id,
- });
+ if (user.isAdmin) {
+ throw new Error('cannot suspend admin');
+ }
- // Terminate streaming
- if (Users.isLocalUser(user)) {
- publishUserEvent(user.id, 'terminate', {});
- }
+ if (user.isModerator) {
+ throw new Error('cannot suspend moderator');
+ }
+
+ await this.usersRepository.update(user.id, {
+ isSuspended: true,
+ });
- (async () => {
- await doPostSuspend(user).catch(e => {});
- await unFollowAll(user).catch(e => {});
- await readAllNotify(user).catch(e => {});
- })();
-});
+ this.moderationLogService.insertModerationLog(me, 'suspend', {
+ targetId: user.id,
+ });
-async function unFollowAll(follower: User) {
- const followings = await Followings.findBy({
- followerId: follower.id,
- });
+ // Terminate streaming
+ if (this.userEntityService.isLocalUser(user)) {
+ this.globalEventService.publishUserEvent(user.id, 'terminate', {});
+ }
- for (const following of followings) {
- const followee = await Users.findOneBy({
- id: following.followeeId,
+ (async () => {
+ await this.userSuspendService.doPostSuspend(user).catch(e => {});
+ await this.#unFollowAll(user).catch(e => {});
+ await this.#readAllNotify(user).catch(e => {});
+ })();
});
+ }
- if (followee == null) {
- throw `Cant find followee ${following.followeeId}`;
+ async #unFollowAll(follower: User) {
+ const followings = await this.followingsRepository.findBy({
+ followerId: follower.id,
+ });
+
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (user == null) {
- throw new Error('user not found');
- }
+ 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 Users.update(user.id, {
- isSilenced: false,
- });
+ await this.usersRepository.update(user.id, {
+ isSilenced: false,
+ });
- publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: false });
+ this.globalEventService.publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: false });
- insertModerationLog(me, 'unsilence', {
- targetId: user.id,
- });
-});
+ this.moderationLogService.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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (user == null) {
- throw new Error('user not found');
- }
+ 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 Users.update(user.id, {
- isSuspended: false,
- });
+ await this.usersRepository.update(user.id, {
+ isSuspended: false,
+ });
- insertModerationLog(me, 'unsuspend', {
- targetId: user.id,
- });
+ this.moderationLogService.insertModerationLog(me, 'unsuspend', {
+ targetId: user.id,
+ });
- doPostUnsuspend(user);
-});
+ this.userSuspendService.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<Meta>;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.db)
+ private db: DataSource,
- if (typeof ps.disableRegistration === 'boolean') {
- set.disableRegistration = ps.disableRegistration;
- }
+ private moderationLogService: ModerationLogService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const set = {} as Partial<Meta>;
- if (typeof ps.disableLocalTimeline === 'boolean') {
- set.disableLocalTimeline = ps.disableLocalTimeline;
- }
+ if (typeof ps.disableRegistration === 'boolean') {
+ set.disableRegistration = ps.disableRegistration;
+ }
- if (typeof ps.disableGlobalTimeline === 'boolean') {
- set.disableGlobalTimeline = ps.disableGlobalTimeline;
- }
+ if (typeof ps.disableLocalTimeline === 'boolean') {
+ set.disableLocalTimeline = ps.disableLocalTimeline;
+ }
- if (typeof ps.useStarForReactionFallback === 'boolean') {
- set.useStarForReactionFallback = ps.useStarForReactionFallback;
- }
+ if (typeof ps.disableGlobalTimeline === 'boolean') {
+ set.disableGlobalTimeline = ps.disableGlobalTimeline;
+ }
- if (Array.isArray(ps.pinnedUsers)) {
- set.pinnedUsers = ps.pinnedUsers.filter(Boolean);
- }
+ if (typeof ps.useStarForReactionFallback === 'boolean') {
+ set.useStarForReactionFallback = ps.useStarForReactionFallback;
+ }
- if (Array.isArray(ps.hiddenTags)) {
- set.hiddenTags = ps.hiddenTags.filter(Boolean);
- }
+ if (Array.isArray(ps.pinnedUsers)) {
+ set.pinnedUsers = ps.pinnedUsers.filter(Boolean);
+ }
- if (Array.isArray(ps.blockedHosts)) {
- set.blockedHosts = ps.blockedHosts.filter(Boolean);
- }
+ if (Array.isArray(ps.hiddenTags)) {
+ set.hiddenTags = ps.hiddenTags.filter(Boolean);
+ }
- if (ps.themeColor !== undefined) {
- set.themeColor = ps.themeColor;
- }
+ if (Array.isArray(ps.blockedHosts)) {
+ set.blockedHosts = ps.blockedHosts.filter(Boolean);
+ }
- if (ps.mascotImageUrl !== undefined) {
- set.mascotImageUrl = ps.mascotImageUrl;
- }
+ if (ps.themeColor !== undefined) {
+ set.themeColor = ps.themeColor;
+ }
- if (ps.bannerUrl !== undefined) {
- set.bannerUrl = ps.bannerUrl;
- }
+ if (ps.mascotImageUrl !== undefined) {
+ set.mascotImageUrl = ps.mascotImageUrl;
+ }
- if (ps.iconUrl !== undefined) {
- set.iconUrl = ps.iconUrl;
- }
+ if (ps.bannerUrl !== undefined) {
+ set.bannerUrl = ps.bannerUrl;
+ }
- if (ps.backgroundImageUrl !== undefined) {
- set.backgroundImageUrl = ps.backgroundImageUrl;
- }
+ if (ps.iconUrl !== undefined) {
+ set.iconUrl = ps.iconUrl;
+ }
- if (ps.logoImageUrl !== undefined) {
- set.logoImageUrl = ps.logoImageUrl;
- }
+ if (ps.backgroundImageUrl !== undefined) {
+ set.backgroundImageUrl = ps.backgroundImageUrl;
+ }
- if (ps.name !== undefined) {
- set.name = ps.name;
- }
+ if (ps.logoImageUrl !== undefined) {
+ set.logoImageUrl = ps.logoImageUrl;
+ }
- if (ps.description !== undefined) {
- set.description = ps.description;
- }
+ if (ps.name !== undefined) {
+ set.name = ps.name;
+ }
- if (ps.defaultLightTheme !== undefined) {
- set.defaultLightTheme = ps.defaultLightTheme;
- }
+ if (ps.description !== undefined) {
+ set.description = ps.description;
+ }
- if (ps.defaultDarkTheme !== undefined) {
- set.defaultDarkTheme = ps.defaultDarkTheme;
- }
+ if (ps.defaultLightTheme !== undefined) {
+ set.defaultLightTheme = ps.defaultLightTheme;
+ }
- if (ps.localDriveCapacityMb !== undefined) {
- set.localDriveCapacityMb = ps.localDriveCapacityMb;
- }
+ if (ps.defaultDarkTheme !== undefined) {
+ set.defaultDarkTheme = ps.defaultDarkTheme;
+ }
- if (ps.remoteDriveCapacityMb !== undefined) {
- set.remoteDriveCapacityMb = ps.remoteDriveCapacityMb;
- }
+ if (ps.localDriveCapacityMb !== undefined) {
+ set.localDriveCapacityMb = ps.localDriveCapacityMb;
+ }
- if (ps.cacheRemoteFiles !== undefined) {
- set.cacheRemoteFiles = ps.cacheRemoteFiles;
- }
+ if (ps.remoteDriveCapacityMb !== undefined) {
+ set.remoteDriveCapacityMb = ps.remoteDriveCapacityMb;
+ }
- if (ps.emailRequiredForSignup !== undefined) {
- set.emailRequiredForSignup = ps.emailRequiredForSignup;
- }
+ if (ps.cacheRemoteFiles !== undefined) {
+ set.cacheRemoteFiles = ps.cacheRemoteFiles;
+ }
- if (ps.enableHcaptcha !== undefined) {
- set.enableHcaptcha = ps.enableHcaptcha;
- }
+ if (ps.emailRequiredForSignup !== undefined) {
+ set.emailRequiredForSignup = ps.emailRequiredForSignup;
+ }
- if (ps.hcaptchaSiteKey !== undefined) {
- set.hcaptchaSiteKey = ps.hcaptchaSiteKey;
- }
+ if (ps.enableHcaptcha !== undefined) {
+ set.enableHcaptcha = ps.enableHcaptcha;
+ }
- if (ps.hcaptchaSecretKey !== undefined) {
- set.hcaptchaSecretKey = ps.hcaptchaSecretKey;
- }
+ if (ps.hcaptchaSiteKey !== undefined) {
+ set.hcaptchaSiteKey = ps.hcaptchaSiteKey;
+ }
- if (ps.enableRecaptcha !== undefined) {
- set.enableRecaptcha = ps.enableRecaptcha;
- }
+ if (ps.hcaptchaSecretKey !== undefined) {
+ set.hcaptchaSecretKey = ps.hcaptchaSecretKey;
+ }
- if (ps.recaptchaSiteKey !== undefined) {
- set.recaptchaSiteKey = ps.recaptchaSiteKey;
- }
+ if (ps.enableRecaptcha !== undefined) {
+ set.enableRecaptcha = ps.enableRecaptcha;
+ }
- if (ps.recaptchaSecretKey !== undefined) {
- set.recaptchaSecretKey = ps.recaptchaSecretKey;
- }
+ if (ps.recaptchaSiteKey !== undefined) {
+ set.recaptchaSiteKey = ps.recaptchaSiteKey;
+ }
- if (ps.sensitiveMediaDetection !== undefined) {
- set.sensitiveMediaDetection = ps.sensitiveMediaDetection;
- }
+ if (ps.recaptchaSecretKey !== undefined) {
+ set.recaptchaSecretKey = ps.recaptchaSecretKey;
+ }
- if (ps.sensitiveMediaDetectionSensitivity !== undefined) {
- set.sensitiveMediaDetectionSensitivity = ps.sensitiveMediaDetectionSensitivity;
- }
+ if (ps.sensitiveMediaDetection !== undefined) {
+ set.sensitiveMediaDetection = ps.sensitiveMediaDetection;
+ }
- if (ps.setSensitiveFlagAutomatically !== undefined) {
- set.setSensitiveFlagAutomatically = ps.setSensitiveFlagAutomatically;
- }
+ if (ps.sensitiveMediaDetectionSensitivity !== undefined) {
+ set.sensitiveMediaDetectionSensitivity = ps.sensitiveMediaDetectionSensitivity;
+ }
- if (ps.enableSensitiveMediaDetectionForVideos !== undefined) {
- set.enableSensitiveMediaDetectionForVideos = ps.enableSensitiveMediaDetectionForVideos;
- }
+ if (ps.setSensitiveFlagAutomatically !== undefined) {
+ set.setSensitiveFlagAutomatically = ps.setSensitiveFlagAutomatically;
+ }
- if (ps.proxyAccountId !== undefined) {
- set.proxyAccountId = ps.proxyAccountId;
- }
+ if (ps.enableSensitiveMediaDetectionForVideos !== undefined) {
+ set.enableSensitiveMediaDetectionForVideos = ps.enableSensitiveMediaDetectionForVideos;
+ }
- if (ps.maintainerName !== undefined) {
- set.maintainerName = ps.maintainerName;
- }
+ if (ps.proxyAccountId !== undefined) {
+ set.proxyAccountId = ps.proxyAccountId;
+ }
- if (ps.maintainerEmail !== undefined) {
- set.maintainerEmail = ps.maintainerEmail;
- }
+ if (ps.maintainerName !== undefined) {
+ set.maintainerName = ps.maintainerName;
+ }
- if (Array.isArray(ps.langs)) {
- set.langs = ps.langs.filter(Boolean);
- }
+ if (ps.maintainerEmail !== undefined) {
+ set.maintainerEmail = ps.maintainerEmail;
+ }
- if (Array.isArray(ps.pinnedPages)) {
- set.pinnedPages = ps.pinnedPages.filter(Boolean);
- }
+ if (Array.isArray(ps.langs)) {
+ set.langs = ps.langs.filter(Boolean);
+ }
- if (ps.pinnedClipId !== undefined) {
- set.pinnedClipId = ps.pinnedClipId;
- }
+ if (Array.isArray(ps.pinnedPages)) {
+ set.pinnedPages = ps.pinnedPages.filter(Boolean);
+ }
- if (ps.summalyProxy !== undefined) {
- set.summalyProxy = ps.summalyProxy;
- }
+ if (ps.pinnedClipId !== undefined) {
+ set.pinnedClipId = ps.pinnedClipId;
+ }
- if (ps.enableTwitterIntegration !== undefined) {
- set.enableTwitterIntegration = ps.enableTwitterIntegration;
- }
+ if (ps.summalyProxy !== undefined) {
+ set.summalyProxy = ps.summalyProxy;
+ }
- if (ps.twitterConsumerKey !== undefined) {
- set.twitterConsumerKey = ps.twitterConsumerKey;
- }
+ if (ps.enableTwitterIntegration !== undefined) {
+ set.enableTwitterIntegration = ps.enableTwitterIntegration;
+ }
- if (ps.twitterConsumerSecret !== undefined) {
- set.twitterConsumerSecret = ps.twitterConsumerSecret;
- }
+ if (ps.twitterConsumerKey !== undefined) {
+ set.twitterConsumerKey = ps.twitterConsumerKey;
+ }
- if (ps.enableGithubIntegration !== undefined) {
- set.enableGithubIntegration = ps.enableGithubIntegration;
- }
+ if (ps.twitterConsumerSecret !== undefined) {
+ set.twitterConsumerSecret = ps.twitterConsumerSecret;
+ }
- if (ps.githubClientId !== undefined) {
- set.githubClientId = ps.githubClientId;
- }
+ if (ps.enableGithubIntegration !== undefined) {
+ set.enableGithubIntegration = ps.enableGithubIntegration;
+ }
- if (ps.githubClientSecret !== undefined) {
- set.githubClientSecret = ps.githubClientSecret;
- }
+ if (ps.githubClientId !== undefined) {
+ set.githubClientId = ps.githubClientId;
+ }
- if (ps.enableDiscordIntegration !== undefined) {
- set.enableDiscordIntegration = ps.enableDiscordIntegration;
- }
+ if (ps.githubClientSecret !== undefined) {
+ set.githubClientSecret = ps.githubClientSecret;
+ }
- if (ps.discordClientId !== undefined) {
- set.discordClientId = ps.discordClientId;
- }
+ if (ps.enableDiscordIntegration !== undefined) {
+ set.enableDiscordIntegration = ps.enableDiscordIntegration;
+ }
- if (ps.discordClientSecret !== undefined) {
- set.discordClientSecret = ps.discordClientSecret;
- }
+ if (ps.discordClientId !== undefined) {
+ set.discordClientId = ps.discordClientId;
+ }
- if (ps.enableEmail !== undefined) {
- set.enableEmail = ps.enableEmail;
- }
+ if (ps.discordClientSecret !== undefined) {
+ set.discordClientSecret = ps.discordClientSecret;
+ }
- if (ps.email !== undefined) {
- set.email = ps.email;
- }
+ if (ps.enableEmail !== undefined) {
+ set.enableEmail = ps.enableEmail;
+ }
- if (ps.smtpSecure !== undefined) {
- set.smtpSecure = ps.smtpSecure;
- }
+ if (ps.email !== undefined) {
+ set.email = ps.email;
+ }
- if (ps.smtpHost !== undefined) {
- set.smtpHost = ps.smtpHost;
- }
+ if (ps.smtpSecure !== undefined) {
+ set.smtpSecure = ps.smtpSecure;
+ }
- if (ps.smtpPort !== undefined) {
- set.smtpPort = ps.smtpPort;
- }
+ if (ps.smtpHost !== undefined) {
+ set.smtpHost = ps.smtpHost;
+ }
- if (ps.smtpUser !== undefined) {
- set.smtpUser = ps.smtpUser;
- }
+ if (ps.smtpPort !== undefined) {
+ set.smtpPort = ps.smtpPort;
+ }
- if (ps.smtpPass !== undefined) {
- set.smtpPass = ps.smtpPass;
- }
+ if (ps.smtpUser !== undefined) {
+ set.smtpUser = ps.smtpUser;
+ }
- if (ps.errorImageUrl !== undefined) {
- set.errorImageUrl = ps.errorImageUrl;
- }
+ if (ps.smtpPass !== undefined) {
+ set.smtpPass = ps.smtpPass;
+ }
- if (ps.enableServiceWorker !== undefined) {
- set.enableServiceWorker = ps.enableServiceWorker;
- }
+ if (ps.errorImageUrl !== undefined) {
+ set.errorImageUrl = ps.errorImageUrl;
+ }
- if (ps.swPublicKey !== undefined) {
- set.swPublicKey = ps.swPublicKey;
- }
+ if (ps.enableServiceWorker !== undefined) {
+ set.enableServiceWorker = ps.enableServiceWorker;
+ }
- if (ps.swPrivateKey !== undefined) {
- set.swPrivateKey = ps.swPrivateKey;
- }
+ if (ps.swPublicKey !== undefined) {
+ set.swPublicKey = ps.swPublicKey;
+ }
- if (ps.tosUrl !== undefined) {
- set.ToSUrl = ps.tosUrl;
- }
+ if (ps.swPrivateKey !== undefined) {
+ set.swPrivateKey = ps.swPrivateKey;
+ }
- if (ps.repositoryUrl !== undefined) {
- set.repositoryUrl = ps.repositoryUrl;
- }
+ if (ps.tosUrl !== undefined) {
+ set.ToSUrl = ps.tosUrl;
+ }
- if (ps.feedbackUrl !== undefined) {
- set.feedbackUrl = ps.feedbackUrl;
- }
+ if (ps.repositoryUrl !== undefined) {
+ set.repositoryUrl = ps.repositoryUrl;
+ }
- if (ps.useObjectStorage !== undefined) {
- set.useObjectStorage = ps.useObjectStorage;
- }
+ if (ps.feedbackUrl !== undefined) {
+ set.feedbackUrl = ps.feedbackUrl;
+ }
- if (ps.objectStorageBaseUrl !== undefined) {
- set.objectStorageBaseUrl = ps.objectStorageBaseUrl;
- }
+ if (ps.useObjectStorage !== undefined) {
+ set.useObjectStorage = ps.useObjectStorage;
+ }
- if (ps.objectStorageBucket !== undefined) {
- set.objectStorageBucket = ps.objectStorageBucket;
- }
+ if (ps.objectStorageBaseUrl !== undefined) {
+ set.objectStorageBaseUrl = ps.objectStorageBaseUrl;
+ }
- if (ps.objectStoragePrefix !== undefined) {
- set.objectStoragePrefix = ps.objectStoragePrefix;
- }
+ if (ps.objectStorageBucket !== undefined) {
+ set.objectStorageBucket = ps.objectStorageBucket;
+ }
- if (ps.objectStorageEndpoint !== undefined) {
- set.objectStorageEndpoint = ps.objectStorageEndpoint;
- }
+ if (ps.objectStoragePrefix !== undefined) {
+ set.objectStoragePrefix = ps.objectStoragePrefix;
+ }
- if (ps.objectStorageRegion !== undefined) {
- set.objectStorageRegion = ps.objectStorageRegion;
- }
+ if (ps.objectStorageEndpoint !== undefined) {
+ set.objectStorageEndpoint = ps.objectStorageEndpoint;
+ }
- if (ps.objectStoragePort !== undefined) {
- set.objectStoragePort = ps.objectStoragePort;
- }
+ if (ps.objectStorageRegion !== undefined) {
+ set.objectStorageRegion = ps.objectStorageRegion;
+ }
- if (ps.objectStorageAccessKey !== undefined) {
- set.objectStorageAccessKey = ps.objectStorageAccessKey;
- }
+ if (ps.objectStoragePort !== undefined) {
+ set.objectStoragePort = ps.objectStoragePort;
+ }
- if (ps.objectStorageSecretKey !== undefined) {
- set.objectStorageSecretKey = ps.objectStorageSecretKey;
- }
+ if (ps.objectStorageAccessKey !== undefined) {
+ set.objectStorageAccessKey = ps.objectStorageAccessKey;
+ }
- if (ps.objectStorageUseSSL !== undefined) {
- set.objectStorageUseSSL = ps.objectStorageUseSSL;
- }
+ if (ps.objectStorageSecretKey !== undefined) {
+ set.objectStorageSecretKey = ps.objectStorageSecretKey;
+ }
- if (ps.objectStorageUseProxy !== undefined) {
- set.objectStorageUseProxy = ps.objectStorageUseProxy;
- }
+ if (ps.objectStorageUseSSL !== undefined) {
+ set.objectStorageUseSSL = ps.objectStorageUseSSL;
+ }
- if (ps.objectStorageSetPublicRead !== undefined) {
- set.objectStorageSetPublicRead = ps.objectStorageSetPublicRead;
- }
+ if (ps.objectStorageUseProxy !== undefined) {
+ set.objectStorageUseProxy = ps.objectStorageUseProxy;
+ }
- if (ps.objectStorageS3ForcePathStyle !== undefined) {
- set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle;
- }
+ if (ps.objectStorageSetPublicRead !== undefined) {
+ set.objectStorageSetPublicRead = ps.objectStorageSetPublicRead;
+ }
- if (ps.deeplAuthKey !== undefined) {
- if (ps.deeplAuthKey === '') {
- set.deeplAuthKey = null;
- } else {
- set.deeplAuthKey = ps.deeplAuthKey;
- }
- }
+ if (ps.objectStorageS3ForcePathStyle !== undefined) {
+ set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle;
+ }
- if (ps.deeplIsPro !== undefined) {
- set.deeplIsPro = ps.deeplIsPro;
- }
+ if (ps.deeplAuthKey !== undefined) {
+ if (ps.deeplAuthKey === '') {
+ set.deeplAuthKey = null;
+ } else {
+ set.deeplAuthKey = ps.deeplAuthKey;
+ }
+ }
- if (ps.enableIpLogging !== undefined) {
- set.enableIpLogging = ps.enableIpLogging;
- }
+ if (ps.deeplIsPro !== undefined) {
+ set.deeplIsPro = ps.deeplIsPro;
+ }
- if (ps.enableActiveEmailValidation !== undefined) {
- set.enableActiveEmailValidation = ps.enableActiveEmailValidation;
- }
+ if (ps.enableIpLogging !== undefined) {
+ set.enableIpLogging = ps.enableIpLogging;
+ }
- await db.transaction(async transactionalEntityManager => {
- const metas = await transactionalEntityManager.find(Meta, {
- order: {
- id: 'DESC',
- },
- });
+ 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];
+ const meta = metas[0];
- if (meta) {
- await transactionalEntityManager.update(Meta, meta.id, set);
- } else {
- await transactionalEntityManager.save(Meta, set);
- }
- });
+ if (meta) {
+ await transactionalEntityManager.update(Meta, meta.id, set);
+ } else {
+ await transactionalEntityManager.save(Meta, set);
+ }
+ });
- insertModerationLog(me, 'updateMeta');
-});
+ this.moderationLogService.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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.antennasRepository)
+ private antennasRepository: AntennasRepository,
- if (ps.src === 'list' && ps.userListId) {
- userList = await UserLists.findOneBy({
- id: ps.userListId,
- userId: user.id,
- });
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- 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.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- if (userGroupJoining == null) {
- throw new ApiError(meta.errors.noSuchUserGroup);
- }
- }
+ 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,
+ });
- 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]));
+ 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,
+ });
- publishInternalEvent('antennaCreated', antenna);
+ if (userGroupJoining == null) {
+ throw new ApiError(meta.errors.noSuchUserGroup);
+ }
+ }
- return await Antennas.pack(antenna);
-});
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.antennasRepository)
+ private antennasRepository: AntennasRepository,
- if (antenna == null) {
- throw new ApiError(meta.errors.noSuchAntenna);
- }
+ 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 Antennas.delete(antenna.id);
+ await this.antennasRepository.delete(antenna.id);
- publishInternalEvent('antennaDeleted', antenna);
-});
+ this.globalEventService.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<typeof meta, typeof paramDef> {
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- if (antenna == null) {
- throw new ApiError(meta.errors.noSuchAntenna);
- }
+ @Inject(DI.antennasRepository)
+ private antennasRepository: AntennasRepository,
- 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 });
+ @Inject(DI.antennaNotesRepository)
+ private antennaNotesRepository: AntennaNotesRepository,
- generateVisibilityQuery(query, user);
- generateMutedUserQuery(query, user);
- generateBlockedUserQuery(query, user);
+ 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,
+ });
- const notes = await query
- .take(ps.limit)
- .getMany();
+ if (antenna == null) {
+ throw new ApiError(meta.errors.noSuchAntenna);
+ }
- if (notes.length > 0) {
- readNote(user.id, notes);
- }
+ 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);
- return await Notes.packMany(notes, user);
-});
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.antennasRepository)
+ private antennasRepository: AntennasRepository,
- if (antenna == null) {
- throw new ApiError(meta.errors.noSuchAntenna);
- }
+ 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 Antennas.pack(antenna);
-});
+ return await this.antennaEntityService.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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.antennasRepository)
+ private antennasRepository: AntennasRepository,
- if (antenna == null) {
- throw new ApiError(meta.errors.noSuchAntenna);
- }
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- let userList;
- let userGroupJoining;
+ @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 (ps.src === 'list' && ps.userListId) {
- userList = await UserLists.findOneBy({
- id: ps.userListId,
- userId: user.id,
- });
+ if (antenna == null) {
+ throw new ApiError(meta.errors.noSuchAntenna);
+ }
- 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,
- });
+ let userList;
+ let userGroupJoining;
- if (userGroupJoining == null) {
- throw new ApiError(meta.errors.noSuchUserGroup);
- }
- }
+ if (ps.src === 'list' && ps.userListId) {
+ userList = await this.userListsRepository.findOneBy({
+ id: ps.userListId,
+ userId: me.id,
+ });
- 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,
- });
+ 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,
+ });
- publishInternalEvent('antennaUpdated', await Antennas.findOneByOrFail({ id: antenna.id }));
+ if (userGroupJoining == null) {
+ throw new ApiError(meta.errors.noSuchUserGroup);
+ }
+ }
- return await Antennas.pack(antenna.id);
-});
+ 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);
+ });
+ }
+}
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<typeof meta, typeof paramDef> {
+ 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);
- }
-});
-
-/***
- * URIからUserかNoteを解決する
- */
-async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
- // ブロックしてたら中断
- const fetchedMeta = await fetchMeta();
- if (fetchedMeta.blockedHosts.includes(extractDbHost(uri))) return null;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const dbResolver = new DbResolver();
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- let local = await mergePack(me, ...await Promise.all([
- dbResolver.getUserFromApId(uri),
- dbResolver.getNoteFromApId(uri),
- ]));
- if (local != null) return local;
+ 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);
+ }
+ });
+ }
- // リモートから一旦オブジェクトフェッチ
- const resolver = new Resolver();
- const object = await resolver.resolve(uri) as any;
+ /***
+ * URIからUserかNoteを解決する
+ */
+ async #fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
+ // ブロックしてたら中断
+ const fetchedMeta = await this.metaService.fetch();
+ if (fetchedMeta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) return null;
- // /@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),
+ 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<SchemaType<typeof meta.res> | 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<SchemaType<typeof meta.res> | 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);
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.appsRepository)
+ private appsRepository: AppsRepository,
- // for backward compatibility
- const permission = unique(ps.permission.map(v => v.replace(/^(.+)(\/|-)(read|write)$/, '$3:$1')));
+ private appEntityService: AppEntityService,
+ private idService: IdService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Generate secret
+ const secret = secureRndstr(32, true);
- // 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]));
+ // for backward compatibility
+ const permission = unique(ps.permission.map(v => v.replace(/^(.+)(\/|-)(read|write)$/, '$3:$1')));
- return await Apps.pack(app, null, {
- detail: true,
- includeSecret: true,
- });
-});
+ // 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;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.appsRepository)
+ private appsRepository: AppsRepository,
- // Lookup app
- const ap = await Apps.findOneBy({ id: ps.appId });
+ private appEntityService: AppEntityService,
+ ) {
+ super(meta, paramDef, async (ps, user, token) => {
+ const isSecure = user != null && token == null;
- if (ap == null) {
- throw new ApiError(meta.errors.noSuchApp);
- }
+ // Lookup app
+ const ap = await this.appsRepository.findOneBy({ id: ps.appId });
+
+ if (ap == null) {
+ throw new ApiError(meta.errors.noSuchApp);
+ }
- return await Apps.pack(ap, user, {
- detail: true,
- includeSecret: isSecure && (ap.userId === user!.id),
- });
-});
+ return await this.appEntityService.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<typeof meta, typeof paramDef> {
+ 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,
- // Generate access token
- const accessToken = secureRndstr(32, true);
+ private idService: IdService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Fetch token
+ const session = await this.authSessionsRepository
+ .findOneBy({ token: ps.token });
- // Fetch exist access token
- const exist = await AccessTokens.findOneBy({
- appId: session.appId,
- userId: user.id,
- });
+ if (session == null) {
+ throw new ApiError(meta.errors.noSuchSession);
+ }
- if (exist == null) {
- // Lookup app
- const app = await Apps.findOneByOrFail({ id: session.appId });
+ // Generate access token
+ const accessToken = secureRndstr(32, true);
- // Generate Hash
- const sha256 = crypto.createHash('sha256');
- sha256.update(accessToken + app.secret);
- const hash = sha256.digest('hex');
+ // Fetch exist access token
+ const exist = await this.accessTokensRepository.findOneBy({
+ appId: session.appId,
+ userId: me.id,
+ });
- const now = new Date();
+ if (exist == null) {
+ // Lookup app
+ const app = await this.appsRepository.findOneByOrFail({ id: session.appId });
- // Insert access token doc
- await AccessTokens.insert({
- id: genId(),
- createdAt: now,
- lastUsedAt: now,
- appId: session.appId,
- userId: user.id,
- token: accessToken,
- hash: hash,
+ // 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<typeof meta, typeof paramDef> {
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.authSessionsRepository)
+ private authSessionsRepository: AuthSessionsRepository,
- if (session == null) {
- throw new ApiError(meta.errors.noSuchSession);
- }
+ private authSessionEntityService: AuthSessionEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Lookup session
+ const session = await this.authSessionsRepository.findOneBy({
+ token: ps.token,
+ });
+
+ if (session == null) {
+ throw new ApiError(meta.errors.noSuchSession);
+ }
- return await AuthSessions.pack(session, user);
-});
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (app == null) {
- throw new ApiError(meta.errors.noSuchApp);
- }
+ @Inject(DI.appsRepository)
+ private appsRepository: AppsRepository,
- // Fetch token
- const session = await AuthSessions.findOneBy({
- token: ps.token,
- appId: app.id,
- });
+ @Inject(DI.authSessionsRepository)
+ private authSessionsRepository: AuthSessionsRepository,
- if (session == null) {
- throw new ApiError(meta.errors.noSuchSession);
- }
+ @Inject(DI.accessTokensRepository)
+ private accessTokensRepository: AccessTokensRepository,
- if (session.userId == null) {
- throw new ApiError(meta.errors.pendingSession);
- }
+ 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);
+ }
- // Lookup access token
- const accessToken = await AccessTokens.findOneByOrFail({
- appId: app.id,
- userId: session.userId,
- });
+ // Fetch token
+ const session = await this.authSessionsRepository.findOneBy({
+ token: ps.token,
+ appId: app.id,
+ });
- // Delete session
- AuthSessions.delete(session.id);
+ if (session == null) {
+ throw new ApiError(meta.errors.noSuchSession);
+ }
- return {
- accessToken: accessToken.token,
- user: await Users.pack(session.userId, null, {
- detail: true,
- }),
- };
-});
+ 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,
+ }),
+ };
+ });
+ }
+}
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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- // 自分自身
- if (user.id === ps.userId) {
- throw new ApiError(meta.errors.blockeeIsYourself);
- }
+ @Inject(DI.blockingsRepository)
+ private blockingsRepository: BlockingsRepository,
- // 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;
- });
+ 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 already blocking
- const exist = await Blockings.findOneBy({
- blockerId: blocker.id,
- blockeeId: blockee.id,
- });
+ // 自分自身
+ if (me.id === ps.userId) {
+ throw new ApiError(meta.errors.blockeeIsYourself);
+ }
- if (exist != null) {
- throw new ApiError(meta.errors.alreadyBlocking);
- }
+ // 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;
+ });
- await create(blocker, blockee);
+ // Check if already blocking
+ const exist = await this.blockingsRepository.findOneBy({
+ blockerId: blocker.id,
+ blockeeId: blockee.id,
+ });
- NoteWatchings.delete({
- userId: blocker.id,
- noteUserId: blockee.id,
- });
+ if (exist != null) {
+ throw new ApiError(meta.errors.alreadyBlocking);
+ }
- return await Users.pack(blockee.id, blocker, {
- detail: true,
- });
-});
+ await this.userBlockingService.block(blocker, blockee);
+
+ return await this.userEntityService.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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- // Check if the blockee is yourself
- if (user.id === ps.userId) {
- throw new ApiError(meta.errors.blockeeIsYourself);
- }
+ @Inject(DI.blockingsRepository)
+ private blockingsRepository: BlockingsRepository,
- // 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;
- });
+ 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 not blocking
- const exist = await Blockings.findOneBy({
- blockerId: blocker.id,
- blockeeId: blockee.id,
- });
+ // Check if the blockee is yourself
+ if (me.id === ps.userId) {
+ throw new ApiError(meta.errors.blockeeIsYourself);
+ }
- if (exist == null) {
- throw new ApiError(meta.errors.notBlocking);
- }
+ // 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,
+ });
- // Delete blocking
- await deleteBlocking(blocker, blockee);
+ if (exist == null) {
+ throw new ApiError(meta.errors.notBlocking);
+ }
- return await Users.pack(blockee.id, blocker, {
- detail: true,
- });
-});
+ // Delete blocking
+ await this.userBlockingService.unblock(blocker, blockee);
+
+ return await this.userEntityService.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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.blockingsRepository)
+ private blockingsRepository: BlockingsRepository,
- const blockings = await query
- .take(ps.limit)
- .getMany();
+ 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 });
- return await Blockings.packMany(blockings, me);
-});
+ 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<typeof meta, typeof paramDef> {
+ 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,
+ });
- 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]));
+ if (banner == null) {
+ throw new ApiError(meta.errors.noSuchFile);
+ }
+ }
- 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.channelFollowingsRepository)
+ private channelFollowingsRepository: ChannelFollowingsRepository,
- const followings = await query
- .take(ps.limit)
- .getMany();
+ 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 });
- return await Promise.all(followings.map(x => Channels.pack(x.followeeId, me)));
-});
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.channelsRepository)
+ private channelsRepository: ChannelsRepository,
- const channels = await query
- .take(ps.limit)
- .getMany();
+ 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 });
- return await Promise.all(channels.map(x => Channels.pack(x, me)));
-});
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.channelsRepository)
+ private channelsRepository: ChannelsRepository,
- if (channel == null) {
- throw new ApiError(meta.errors.noSuchChannel);
- }
+ 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 Channels.pack(channel, me);
-});
+ return await this.channelEntityService.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<typeof meta, typeof paramDef> {
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.channelsRepository)
+ private channelsRepository: ChannelsRepository,
- if (channel == null) {
- throw new ApiError(meta.errors.noSuchChannel);
- }
+ @Inject(DI.channelFollowingsRepository)
+ private channelFollowingsRepository: ChannelFollowingsRepository,
+
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const channel = await this.channelsRepository.findOneBy({
+ id: ps.channelId,
+ });
- await ChannelFollowings.delete({
- followerId: user.id,
- followeeId: channel.id,
- });
+ if (channel == null) {
+ throw new ApiError(meta.errors.noSuchChannel);
+ }
- publishUserEvent(user.id, 'unfollowChannel', channel);
-});
+ await this.channelFollowingsRepository.delete({
+ followerId: me.id,
+ followeeId: channel.id,
+ });
+
+ this.globalEventService.publishUserEvent(me.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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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]));
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.clipsRepository)
+ private clipsRepository: ClipsRepository,
- return await Clips.pack(clip);
-});
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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);
- }
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
- await Clips.delete(clip.id);
-});
+ await this.clipsRepository.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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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 });
+ private noteEntityService: NoteEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const clip = await this.clipsRepository.findOneBy({
+ id: ps.clipId,
+ });
- if (user) {
- generateVisibilityQuery(query, user);
- generateMutedUserQuery(query, user);
- generateBlockedUserQuery(query, user);
- }
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+
+ if (!clip.isPublic && (me == null || (clip.userId !== me.id))) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
- const notes = await query
- .take(ps.limit)
- .getMany();
+ 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 });
- return await Notes.packMany(notes, user);
-});
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.clipsRepository)
+ private clipsRepository: ClipsRepository,
- if (clip == null) {
- throw new ApiError(meta.errors.noSuchClip);
- }
+ @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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.clipsRepository)
+ private clipsRepository: ClipsRepository,
- if (clip == null) {
- 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.isPublic && (me == null || (clip.userId !== me.id))) {
- throw new ApiError(meta.errors.noSuchClip);
- }
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.clipsRepository)
+ private clipsRepository: ClipsRepository,
- if (clip == null) {
- 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,
+ userId: me.id,
+ });
+
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
- await Clips.update(clip.id, {
- name: ps.name,
- description: ps.description,
- isPublic: ps.isPublic,
- });
+ await this.clipsRepository.update(clip.id, {
+ name: ps.name,
+ description: ps.description,
+ isPublic: ps.isPublic,
+ });
- return await Clips.pack(clip.id);
-});
+ 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);
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 DriveFiles.calcDriveUsageOf(user.id);
+ // Calculate drive usage
+ const usage = await this.driveFileEntityService.calcDriveUsageOf(me.id);
- return {
- capacity: 1024 * 1024 * (user.driveCapacityOverrideMb || instance.localDriveCapacityMb),
- usage: usage,
- };
-});
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
- if (ps.folderId) {
- query.andWhere('file.folderId = :folderId', { folderId: ps.folderId });
- } else {
- query.andWhere('file.folderId IS NULL');
- }
+ 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.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.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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
- if (file == null) {
- throw new ApiError(meta.errors.noSuchFile);
- }
+ @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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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;
-});
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
- return await DriveFiles.packMany(files, { self: true });
-});
+ 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(),
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
- return await Promise.all(files.map(file => DriveFiles.pack(file, { self: true })));
-});
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
- if (file == null) {
- throw new ApiError(meta.errors.noSuchFile);
- }
+ @Inject(DI.driveFoldersRepository)
+ private driveFoldersRepository: DriveFoldersRepository,
- if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) {
- throw new ApiError(meta.errors.accessDenied);
- }
+ private driveFileEntityService: DriveFileEntityService,
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
- if (ps.name) file.name = ps.name;
- if (!DriveFiles.validateFileName(file.name)) {
- throw new ApiError(meta.errors.invalidFileName);
- }
+ if (file == null) {
+ throw new ApiError(meta.errors.noSuchFile);
+ }
- if (ps.comment !== undefined) file.comment = ps.comment;
+ if ((!me.isAdmin && !me.isModerator) && (file.userId !== me.id)) {
+ throw new ApiError(meta.errors.accessDenied);
+ }
- if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive;
+ if (ps.name) file.name = ps.name;
+ if (!this.driveFileEntityService.validateFileName(file.name)) {
+ throw new ApiError(meta.errors.invalidFileName);
+ }
- if (ps.folderId !== undefined) {
- if (ps.folderId === null) {
- file.folderId = null;
- } else {
- const folder = await DriveFolders.findOneBy({
- id: ps.folderId,
- userId: user.id,
- });
+ if (ps.comment !== undefined) file.comment = ps.comment;
- if (folder == null) {
- throw new ApiError(meta.errors.noSuchFolder);
- }
+ if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive;
- file.folderId = folder.id;
- }
- }
+ if (ps.folderId !== undefined) {
+ if (ps.folderId === null) {
+ file.folderId = null;
+ } else {
+ const folder = await this.driveFoldersRepository.findOneBy({
+ id: ps.folderId,
+ userId: me.id,
+ });
- await DriveFiles.update(file.id, {
- name: file.name,
- comment: file.comment,
- folderId: file.folderId,
- isSensitive: file.isSensitive,
- });
+ if (folder == null) {
+ throw new ApiError(meta.errors.noSuchFolder);
+ }
- const fileObj = await DriveFiles.pack(file, { self: true });
+ file.folderId = folder.id;
+ }
+ }
- // Publish fileUpdated event
- publishDriveStream(user.id, 'fileUpdated', fileObj);
+ await this.driveFilesRepository.update(file.id, {
+ name: file.name,
+ comment: file.comment,
+ folderId: file.folderId,
+ isSensitive: file.isSensitive,
+ });
- return fileObj;
-});
+ const fileObj = await this.driveFileEntityService.pack(file, { self: true });
+
+ // Publish fileUpdated event
+ this.globalEventService.publishDriveStream(me.id, 'fileUpdated', 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<typeof meta, typeof paramDef> {
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.driveFoldersRepository)
+ private driveFoldersRepository: DriveFoldersRepository,
- if (ps.folderId) {
- query.andWhere('folder.parentId = :parentId', { parentId: ps.folderId });
- } else {
- query.andWhere('folder.parentId IS NULL');
- }
+ 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();
+ const folders = await query.take(ps.limit).getMany();
- return await Promise.all(folders.map(folder => DriveFolders.pack(folder)));
-});
+ return await Promise.all(folders.map(folder => this.driveFolderEntityService.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<typeof meta, typeof paramDef> {
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
- if (folder == null) {
- throw new ApiError(meta.errors.noSuchFolder);
- }
+ @Inject(DI.driveFoldersRepository)
+ private driveFoldersRepository: DriveFoldersRepository,
- const [childFoldersCount, childFilesCount] = await Promise.all([
- DriveFolders.countBy({ parentId: folder.id }),
- DriveFiles.countBy({ folderId: folder.id }),
- ]);
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Get folder
+ const folder = await this.driveFoldersRepository.findOneBy({
+ id: ps.folderId,
+ userId: me.id,
+ });
- if (childFoldersCount !== 0 || childFilesCount !== 0) {
- throw new ApiError(meta.errors.hasChildFilesOrFolders);
- }
+ if (folder == null) {
+ throw new ApiError(meta.errors.noSuchFolder);
+ }
+
+ const [childFoldersCount, childFilesCount] = await Promise.all([
+ this.driveFoldersRepository.countBy({ parentId: folder.id }),
+ this.driveFilesRepository.countBy({ folderId: folder.id }),
+ ]);
- await DriveFolders.delete(folder.id);
+ if (childFoldersCount !== 0 || childFilesCount !== 0) {
+ throw new ApiError(meta.errors.hasChildFilesOrFolders);
+ }
- // Publish folderCreated event
- publishDriveStream(user.id, 'folderDeleted', folder.id);
-});
+ 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(),
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.driveFoldersRepository)
+ private driveFoldersRepository: DriveFoldersRepository,
- return await Promise.all(folders.map(folder => DriveFolders.pack(folder)));
-});
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.driveFoldersRepository)
+ private driveFoldersRepository: DriveFoldersRepository,
- if (folder == null) {
- throw new ApiError(meta.errors.noSuchFolder);
- }
+ 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 DriveFolders.pack(folder, {
- detail: true,
- });
-});
+ return await this.driveFolderEntityService.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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.driveFoldersRepository)
+ private driveFoldersRepository: DriveFoldersRepository,
- 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,
+ 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<boolean> {
- // Fetch folder
- const folder2 = await DriveFolders.findOneBy({
- id: folderId,
- });
+ if (ps.name) folder.name = ps.name;
- if (folder2!.id === folder!.id) {
- return true;
- } else if (folder2!.parentId) {
- return await checkCircle(folder2!.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 {
- return false;
- }
- }
+ // Get parent folder
+ const parent = await this.driveFoldersRepository.findOneBy({
+ id: ps.parentId,
+ userId: me.id,
+ });
- if (parent.parentId !== null) {
- if (await checkCircle(parent.parentId)) {
- throw new ApiError(meta.errors.recursiveNesting);
+ if (parent == null) {
+ throw new ApiError(meta.errors.noSuchParentFolder);
+ }
+
+ // Check if the circular reference will occur
+ const checkCircle = async (folderId: string): Promise<boolean> => {
+ // 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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.type) {
+ if (ps.type.endsWith('/*')) {
+ query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' });
+ } else {
+ query.andWhere('file.type = :type', { type: ps.type });
+ }
+ }
- const files = await query.take(ps.limit).getMany();
+ const files = await query.take(ps.limit).getMany();
- return await DriveFiles.packMany(files, { detail: false, self: true });
-});
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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');
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.instancesRepository)
+ private instancesRepository: InstancesRepository,
- 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;
+ private instanceEntityService: InstanceEntityService,
+ private metaService: MetaService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.instancesRepository.createQueryBuilder('instance');
- default: query.orderBy('instance.id', 'DESC'); break;
- }
+ 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;
- 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 });
- }
- }
+ default: query.orderBy('instance.id', 'DESC'); break;
+ }
- if (typeof ps.notResponding === 'boolean') {
- if (ps.notResponding) {
- query.andWhere('instance.isNotResponding = TRUE');
- } else {
- query.andWhere('instance.isNotResponding = FALSE');
- }
- }
+ 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.suspended === 'boolean') {
- if (ps.suspended) {
- query.andWhere('instance.isSuspended = TRUE');
- } else {
- query.andWhere('instance.isSuspended = FALSE');
- }
- }
+ if (typeof ps.notResponding === 'boolean') {
+ if (ps.notResponding) {
+ query.andWhere('instance.isNotResponding = TRUE');
+ } else {
+ query.andWhere('instance.isNotResponding = 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.suspended === 'boolean') {
+ if (ps.suspended) {
+ query.andWhere('instance.isSuspended = TRUE');
+ } else {
+ query.andWhere('instance.isSuspended = FALSE');
+ }
+ }
- if (typeof ps.subscribing === 'boolean') {
- if (ps.subscribing) {
- query.andWhere('instance.followersCount > 0');
- } else {
- query.andWhere('instance.followersCount = 0');
- }
- }
+ 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.publishing === 'boolean') {
- if (ps.publishing) {
- query.andWhere('instance.followingCount > 0');
- } else {
- query.andWhere('instance.followingCount = 0');
- }
- }
+ if (typeof ps.subscribing === 'boolean') {
+ if (ps.subscribing) {
+ query.andWhere('instance.followersCount > 0');
+ } else {
+ query.andWhere('instance.followersCount = 0');
+ }
+ }
- if (ps.host) {
- query.andWhere('instance.host like :host', { host: '%' + ps.host.toLowerCase() + '%' });
- }
+ if (typeof ps.publishing === 'boolean') {
+ if (ps.publishing) {
+ query.andWhere('instance.followingCount > 0');
+ } else {
+ query.andWhere('instance.followingCount = 0');
+ }
+ }
- const instances = await query.take(ps.limit).skip(ps.offset).getMany();
+ if (ps.host) {
+ query.andWhere('instance.host like :host', { host: '%' + ps.host.toLowerCase() + '%' });
+ }
- return await Instances.packMany(instances);
-});
+ const instances = await query.take(ps.limit).skip(ps.offset).getMany();
+
+ return await this.instanceEntityService.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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
- const text = await res.text();
+ 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,
+ });
- return rssParser.parseString(text);
-});
+ 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;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- // 自分自身
- if (user.id === ps.userId) {
- throw new ApiError(meta.errors.followeeIsYourself);
- }
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
- // 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;
- });
+ private userEntityService: UserEntityService,
+ private getterService: GetterService,
+ private userFollowingService: UserFollowingService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const follower = me;
- // Check if already following
- const exist = await Followings.findOneBy({
- followerId: follower.id,
- followeeId: followee.id,
- });
+ // 自分自身
+ if (me.id === ps.userId) {
+ throw new ApiError(meta.errors.followeeIsYourself);
+ }
- if (exist != null) {
- throw new ApiError(meta.errors.alreadyFollowing);
- }
+ // 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;
+ });
- 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;
- }
+ // 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);
+ }
- return await Users.pack(followee.id, user);
-});
+ 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);
+ });
+ }
+}
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;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- // Check if the followee is yourself
- if (user.id === ps.userId) {
- throw new ApiError(meta.errors.followeeIsYourself);
- }
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
- // 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;
- });
+ private userEntityService: UserEntityService,
+ private getterService: GetterService,
+ private userFollowingService: UserFollowingService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const follower = me;
- // Check not following
- const exist = await Followings.findOneBy({
- followerId: follower.id,
- followeeId: followee.id,
- });
+ // Check if the followee is yourself
+ if (me.id === ps.userId) {
+ throw new ApiError(meta.errors.followeeIsYourself);
+ }
- if (exist == null) {
- throw new ApiError(meta.errors.notFollowing);
- }
+ // 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,
+ });
- await deleteFollowing(follower, followee);
+ if (exist == null) {
+ throw new ApiError(meta.errors.notFollowing);
+ }
- return await Users.pack(followee.id, user);
-});
+ await this.userFollowingService.unfollow(follower, followee);
+
+ return await this.userEntityService.pack(followee.id, me);
+ });
+ }
+}
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;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- // Check if the follower is yourself
- if (user.id === ps.userId) {
- throw new ApiError(meta.errors.followerIsYourself);
- }
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
- // 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;
- });
+ private userEntityService: UserEntityService,
+ private getterService: GetterService,
+ private userFollowingService: UserFollowingService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const followee = me;
- // Check not following
- const exist = await Followings.findOneBy({
- followerId: follower.id,
- followeeId: followee.id,
- });
+ // Check if the follower is yourself
+ if (me.id === ps.userId) {
+ throw new ApiError(meta.errors.followerIsYourself);
+ }
- if (exist == null) {
- throw new ApiError(meta.errors.notFollowing);
- }
+ // 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,
+ });
- await deleteFollowing(follower, followee);
+ if (exist == null) {
+ throw new ApiError(meta.errors.notFollowing);
+ }
- return await Users.pack(followee.id, user);
-});
+ await this.userFollowingService.unfollow(follower, followee);
+
+ return await this.userEntityService.pack(followee.id, me);
+ });
+ }
+}
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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 acceptFollowRequest(user, follower).catch(e => {
- if (e.id === '8884c2dd-5795-4ac9-b27e-6a01d38190f9') throw new ApiError(meta.errors.noFollowRequest);
- throw e;
- });
+ 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;
-});
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
- 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;
- }
+ 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;
+ });
- return await Users.pack(followee.id, user);
-});
+ 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<typeof meta, typeof paramDef> {
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 rejectFollowRequest(user, follower);
+ await this.userFollowingService.rejectFollowRequest(me, follower);
- return;
-});
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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);
- }
+ if (post == null) {
+ throw new ApiError(meta.errors.noSuchPost);
+ }
- await GalleryPosts.delete(post.id);
-});
+ await this.galleryPostsRepository.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<typeof meta, typeof paramDef> {
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.galleryPostsRepository)
+ private galleryPostsRepository: GalleryPostsRepository,
- if (post == null) {
- throw new ApiError(meta.errors.noSuchPost);
- }
+ 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 GalleryPosts.pack(post, me);
-});
+ return await this.galleryPostEntityService.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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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));
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- const now = new Date(); // 5分単位で丸めた現在日時
- now.setMinutes(Math.round(now.getMinutes() / 5) * 5, 0, 0);
+ private metaService: MetaService,
+ ) {
+ super(meta, paramDef, async () => {
+ const instance = await this.metaService.fetch(true);
+ const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
- 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();
+ const now = new Date(); // 5分単位で丸めた現在日時
+ now.setMinutes(Math.round(now.getMinutes() / 5) * 5, 0, 0);
- if (tagNotes.length === 0) {
- return [];
- }
+ 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;
+ 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);
+ 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);
+ // タグを人気順に並べ替え
+ 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<number[]>[] = [];
+ //#region 2(または3)で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する
+ const countPromises: Promise<number[]>[] = [];
- const range = 20;
+ const range = 20;
- // 10分
- const interval = 1000 * 60 * 10;
+ // 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))
- )));
- }
+ 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 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 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],
- }));
+ const stats = hots.map((tag, i) => ({
+ tag,
+ chart: countsLog.map(counts => counts[i]),
+ usersCount: totalCounts[i],
+ }));
- return stats;
-});
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- // ここで渡ってきている user はキャッシュされていて古い可能性もあるので id だけ渡す
- return await Users.pack<true, true>(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<true, true>(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, '');
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const token = ps.token.replace(/\s/g, '');
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- if (profile.twoFactorTempSecret == null) {
- throw new Error('二段階認証の設定が開始されていません');
- }
+ if (profile.twoFactorTempSecret == null) {
+ throw new Error('二段階認証の設定が開始されていません');
+ }
- const verified = (speakeasy as any).totp.verify({
- secret: profile.twoFactorTempSecret,
- encoding: 'base32',
- token: token,
- });
+ const verified = (speakeasy as any).totp.verify({
+ secret: profile.twoFactorTempSecret,
+ encoding: 'base32',
+ token: token,
+ });
- if (!verified) {
- throw new Error('not verified');
- }
+ if (!verified) {
+ throw new Error('not verified');
+ }
- await UserProfiles.update(user.id, {
- twoFactorSecret: profile.twoFactorTempSecret,
- twoFactorEnabled: true,
- });
-});
+ await this.userProfilesRepository.update(me.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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- if (!same) {
- throw new Error('incorrect password');
- }
+ @Inject(DI.userSecurityKeysRepository)
+ private userSecurityKeysRepository: UserSecurityKeysRepository,
- if (!profile.twoFactorEnabled) {
- throw new Error('2fa not enabled');
- }
+ @Inject(DI.attestationChallengesRepository)
+ private attestationChallengesRepository: AttestationChallengesRepository,
- const clientData = JSON.parse(ps.clientDataJSON);
+ private userEntityService: UserEntityService,
+ private globalEventService: GlobalEventService,
+ private twoFactorAuthenticationService: TwoFactorAuthenticationService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const rpIdHashReal = this.twoFactorAuthenticationService.hash(Buffer.from(this.config.hostname, 'utf-8'));
- 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 profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, 'utf-8'));
+ // Compare password
+ const same = await bcrypt.compare(ps.password, profile.password!);
- const attestation = await cborDecodeFirst(ps.attestationObject);
+ if (!same) {
+ throw new Error('incorrect password');
+ }
- const rpIdHash = attestation.authData.slice(0, 32);
- if (!rpIdHashReal.equals(rpIdHash)) {
- throw new Error('rpIdHash mismatch');
- }
+ if (!profile.twoFactorEnabled) {
+ throw new Error('2fa not enabled');
+ }
- const flags = attestation.authData[32];
+ const clientData = JSON.parse(ps.clientDataJSON);
- // eslint:disable-next-line:no-bitwise
- if (!(flags & 1)) {
- throw new Error('user not present');
- }
+ 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 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<number, any> = await cborDecodeFirst(publicKeyData);
- if (publicKey.get(3) !== -7) {
- throw new Error('alg mismatch');
- }
+ const clientDataJSONHash = this.twoFactorAuthenticationService.hash(Buffer.from(ps.clientDataJSON, 'utf-8'));
- if (!(procedures as any)[attestation.fmt]) {
- throw new Error('unsupported fmt');
- }
+ const attestation = await cborDecodeFirst(ps.attestationObject);
- 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 rpIdHash = attestation.authData.slice(0, 32);
+ if (!rpIdHashReal.equals(rpIdHash)) {
+ throw new Error('rpIdHash mismatch');
+ }
- const attestationChallenge = await AttestationChallenges.findOneBy({
- userId: user.id,
- id: ps.challengeId,
- registrationChallenge: true,
- challenge: hash(clientData.challenge).toString('hex'),
- });
+ const flags = attestation.authData[32];
- if (!attestationChallenge) {
- throw new Error('non-existent challenge');
- }
+ // 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<number, any> = 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');
- await AttestationChallenges.delete({
- userId: user.id,
- id: ps.challengeId,
- });
+ const attestationChallenge = await this.attestationChallengesRepository.findOneBy({
+ userId: me.id,
+ id: ps.challengeId,
+ registrationChallenge: true,
+ challenge: this.twoFactorAuthenticationService.hash(clientData.challenge).toString('hex'),
+ });
- // Expired challenge (> 5min old)
- if (
- new Date().getTime() - attestationChallenge.createdAt.getTime() >=
+ 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');
- }
+ ) {
+ throw new Error('expired challenge');
+ }
- const credentialIdString = credentialId.toString('hex');
+ const credentialIdString = credentialId.toString('hex');
- await UserSecurityKeys.insert({
- userId: user.id,
- id: credentialIdString,
- lastUsed: new Date(),
- name: ps.name,
- publicKey: verificationData.publicKey.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
- publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, {
- detail: true,
- includeSecrets: true,
- }));
+ // 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,
- };
-});
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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!);
- // 32 byte challenge
- const entropy = await randomBytes(32);
- const challenge = entropy.toString('base64')
- .replace(/=/g, '')
- .replace(/\+/g, '-')
- .replace(/\//g, '_');
+ if (!same) {
+ throw new Error('incorrect password');
+ }
- const challengeId = genId();
+ if (!profile.twoFactorEnabled) {
+ throw new Error('2fa not enabled');
+ }
- await AttestationChallenges.insert({
- userId: user.id,
- id: challengeId,
- challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'),
- createdAt: new Date(),
- registrationChallenge: true,
- });
+ // 32 byte challenge
+ const entropy = await randomBytes(32);
+ const challenge = entropy.toString('base64')
+ .replace(/=/g, '')
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_');
- return {
- challengeId,
- challenge,
- };
-});
+ 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<typeof meta, typeof paramDef> {
+ 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,
- });
+ 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: user.username,
- issuer: config.host,
- });
- const dataUrl = await QRCode.toDataURL(url);
+ // 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: user.username,
- issuer: config.host,
- };
-});
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userSecurityKeysRepository)
+ private userSecurityKeysRepository: UserSecurityKeysRepository,
- // 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 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!);
- // Make sure we only delete the user's own creds
- await UserSecurityKeys.delete({
- userId: user.id,
- id: ps.credentialId,
- });
+ if (!same) {
+ throw new Error('incorrect password');
+ }
- // Publish meUpdated event
- publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, {
- detail: true,
- includeSecrets: true,
- }));
+ // Make sure we only delete the user's own creds
+ await this.userSecurityKeysRepository.delete({
+ userId: me.id,
+ id: ps.credentialId,
+ });
- return {};
-});
+ // Publish meUpdated event
+ this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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!);
+ // Compare password
+ const same = await bcrypt.compare(ps.currentPassword, profile.password!);
- if (!same) {
- throw new Error('incorrect 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);
+ // 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,
- });
-});
+ await this.userProfilesRepository.update(me.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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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');
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.noteFavoritesRepository)
+ private noteFavoritesRepository: NoteFavoritesRepository,
- const favorites = await query
- .take(ps.limit)
- .getMany();
+ 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');
- return await NoteFavorites.packMany(favorites, user);
-});
+ 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<typeof meta, typeof paramDef> {
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 = :meId', { meId: me.id });
- 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/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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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 [];
- }
- // 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const mutingQuery = Mutings.createQueryBuilder('muting')
- .select('muting.muteeId')
- .where('muting.muterId = :muterId', { muterId: user.id });
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
- const mutingInstanceQuery = UserProfiles.createQueryBuilder('user_profile')
- .select('user_profile.mutedInstances')
- .where('user_profile.userId = :muterId', { muterId: user.id });
+ @Inject(DI.mutingsRepository)
+ private mutingsRepository: MutingsRepository,
- const suspendedQuery = Users.createQueryBuilder('users')
- .select('users.id')
- .where('users.isSuspended = TRUE');
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- 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');
+ @Inject(DI.notificationsRepository)
+ private notificationsRepository: NotificationsRepository,
- // muted users
- query.andWhere(new Brackets(qb => { qb
- .where(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`)
- .orWhere('notification.notifierId IS NULL');
- }));
- query.setParameters(mutingQuery.getParameters());
+ 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 });
- // muted instances
- query.andWhere(new Brackets(qb => { qb
- .andWhere('notifier.host IS NULL')
- .orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`);
- }));
- query.setParameters(mutingInstanceQuery.getParameters());
+ const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
+ .select('muting.muteeId')
+ .where('muting.muterId = :muterId', { muterId: me.id });
- // suspended users
- query.andWhere(new Brackets(qb => { qb
- .where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`)
- .orWhere('notification.notifierId IS NULL');
- }));
+ const mutingInstanceQuery = this.userProfilesRepository.createQueryBuilder('user_profile')
+ .select('user_profile.mutedInstances')
+ .where('user_profile.userId = :muterId', { muterId: me.id });
- if (ps.following) {
- query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: user.id });
- query.setParameters(followingQuery.getParameters());
- }
+ const suspendedQuery = this.usersRepository.createQueryBuilder('users')
+ .select('users.id')
+ .where('users.isSuspended = TRUE');
- 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 });
- }
+ 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');
- if (ps.unreadOnly) {
- query.andWhere('notification.isRead = false');
- }
+ // muted users
+ query.andWhere(new Brackets(qb => { qb
+ .where(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`)
+ .orWhere('notification.notifierId IS NULL');
+ }));
+ query.setParameters(mutingQuery.getParameters());
- const notifications = await query.take(ps.limit).getMany();
+ // muted instances
+ query.andWhere(new Brackets(qb => { qb
+ .andWhere('notifier.host IS NULL')
+ .orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`);
+ }));
+ query.setParameters(mutingInstanceQuery.getParameters());
- // Mark all as read
- if (notifications.length > 0 && ps.markAsRead) {
- readNotification(user.id, notifications.map(x => x.id));
- }
+ // suspended users
+ query.andWhere(new Brackets(qb => { qb
+ .where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`)
+ .orWhere('notification.notifierId IS NULL');
+ }));
- const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!);
+ if (ps.following) {
+ query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: me.id });
+ query.setParameters(followingQuery.getParameters());
+ }
- if (notes.length > 0) {
- read(user.id, notes);
- }
+ 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 });
+ }
- return await Notifications.packMany(notifications, user.id);
-});
+ 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);
+ });
+ }
+}
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<typeof meta, typeof paramDef> {
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 = :meId', { meId: me.id });
- 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/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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 Users.pack<true, true>(user.id, user, {
- detail: true,
- });
-});
+ return await this.userEntityService.pack<true, true>(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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.messagingMessagesRepository)
+ private messagingMessagesRepository: MessagingMessagesRepository,
- const joinings = await UserGroupJoinings.findBy({ userId: user.id });
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- 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()));
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Update documents
+ await this.messagingMessagesRepository.update({
+ recipientId: me.id,
+ isRead: false,
+ }, {
+ isRead: true,
+ });
- publishMainStream(user.id, 'readAllMessagingMessages');
-});
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 items = await query.getMany();
- const res = {} as Record<string, any>;
+ const res = {} as Record<string, any>;
- for (const item of items) {
- res[item.key] = item.value;
- }
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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();
+ const item = await query.getOne();
- if (item == null) {
- throw new ApiError(meta.errors.noSuchKey);
- }
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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();
+ const item = await query.getOne();
- if (item == null) {
- throw new ApiError(meta.errors.noSuchKey);
- }
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 items = await query.getMany();
- const res = {} as Record<string, string>;
+ const res = {} as Record<string, string>;
- for (const item of items) {
- const type = typeof item.value;
- res[item.key] =
+ 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<typeof meta, typeof paramDef> {
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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();
+ const item = await query.getOne();
- if (item == null) {
- throw new ApiError(meta.errors.noSuchKey);
- }
+ if (item == null) {
+ throw new ApiError(meta.errors.noSuchKey);
+ }
- await RegistryItems.remove(item);
-});
+ await this.registryItemsRepository.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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 items = await query.getMany();
- const res = [] as string[][];
+ const res = [] as string[][];
- for (const item of items) {
- if (res.some(scope => scope.join('.') === item.scope.join('.'))) continue;
- res.push(item.scope);
- }
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.registryItemsRepository)
+ private registryItemsRepository: RegistryItemsRepository,
- const existingItem = await query.getOne();
+ 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 });
- 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,
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 Users.pack<true, true>(user.id, user, {
- detail: true,
- });
-});
+ return await this.userEntityService.pack<true, true>(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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (!same) {
- throw new ApiError(meta.errors.incorrectPassword);
- }
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- if (ps.email != null) {
- const available = await validateEmailForAccount(ps.email);
- if (!available) {
- throw new ApiError(meta.errors.unavailable);
- }
- }
+ private userEntityService: UserEntityService,
+ private emailService: EmailService,
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- await UserProfiles.update(user.id, {
- email: ps.email,
- emailVerified: false,
- emailVerifyCode: null,
- });
+ // Compare password
+ const same = await bcrypt.compare(ps.password, profile.password!);
- const iObj = await Users.pack(user.id, user, {
- detail: true,
- includeSecrets: true,
- });
+ if (!same) {
+ throw new ApiError(meta.errors.incorrectPassword);
+ }
- // Publish meUpdated event
- publishMainStream(user.id, 'meUpdated', iObj);
+ if (ps.email != null) {
+ const available = await this.emailService.validateEmailForAccount(ps.email);
+ if (!available) {
+ throw new ApiError(meta.errors.unavailable);
+ }
+ }
- if (ps.email != null) {
- const code = rndstr('a-z0-9', 16);
+ await this.userProfilesRepository.update(me.id, {
+ email: ps.email,
+ emailVerified: false,
+ emailVerifyCode: null,
+ });
- await UserProfiles.update(user.id, {
- emailVerifyCode: code,
- });
+ const iObj = await this.userEntityService.pack(me.id, me, {
+ detail: true,
+ includeSecrets: true,
+ });
- const link = `${config.url}/verify-email/${code}`;
+ // Publish meUpdated event
+ this.globalEventService.publishMainStream(me.id, 'meUpdated', iObj);
- sendEmail(ps.email, 'Email verification',
- `To verify email, please click this link:<br><a href="${link}">${link}</a>`,
- `To verify email, please click this link: ${link}`);
- }
+ if (ps.email != null) {
+ const code = rndstr('a-z0-9', 16);
+
+ await this.userProfilesRepository.update(me.id, {
+ emailVerifyCode: code,
+ });
- return iObj;
-});
+ const link = `${this.config.url}/verify-email/${code}`;
+
+ this.emailService.sendEmail(ps.email, 'Email verification',
+ `To verify email, please click this link:<br><a href="${link}">${link}</a>`,
+ `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;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const updates = {} as Partial<User>;
- const profileUpdates = {} as Partial<UserProfile>;
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
- 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);
+ @Inject(DI.pagesRepository)
+ private pagesRepository: PagesRepository,
- try {
- new RE2(regexp[1], regexp[2]);
- } catch (err) {
- throw new ApiError(meta.errors.invalidRegexp);
- }
- });
+ 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;
- 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;
+ const updates = {} as Partial<User>;
+ const profileUpdates = {} as Partial<UserProfile>;
- if (ps.avatarId) {
- const avatar = await DriveFiles.findOneBy({ id: ps.avatarId });
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
- 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.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);
- if (ps.bannerId) {
- const banner = await DriveFiles.findOneBy({ id: ps.bannerId });
+ try {
+ new RE2(regexp[1], regexp[2]);
+ } catch (err) {
+ throw new ApiError(meta.errors.invalidRegexp);
+ }
+ });
- if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
- if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage);
- }
+ 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.pinnedPageId) {
- const page = await Pages.findOneBy({ id: ps.pinnedPageId });
+ if (ps.avatarId) {
+ const avatar = await this.driveFilesRepository.findOneBy({ id: ps.avatarId });
- if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage);
+ 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.pinnedPageId = page.id;
- } else if (ps.pinnedPageId === null) {
- profileUpdates.pinnedPageId = null;
- }
+ if (ps.bannerId) {
+ const banner = await this.driveFilesRepository.findOneBy({ id: ps.bannerId });
- 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 (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
+ if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage);
+ }
- //#region emojis/tags
+ if (ps.pinnedPageId) {
+ const page = await this.pagesRepository.findOneBy({ id: ps.pinnedPageId });
- let emojis = [] as string[];
- let tags = [] as string[];
+ if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage);
- const newName = updates.name === undefined ? user.name : updates.name;
- const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description;
+ profileUpdates.pinnedPageId = page.id;
+ } else if (ps.pinnedPageId === null) {
+ profileUpdates.pinnedPageId = null;
+ }
- if (newName != null) {
- const tokens = mfm.parseSimple(newName);
- emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!));
- }
+ 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 (newDescription != null) {
- const tokens = mfm.parse(newDescription);
- emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!));
- tags = extractHashtags(tokens!).map(tag => normalizeForSearch(tag)).splice(0, 32);
- }
+ //#region emojis/tags
- updates.emojis = emojis;
- updates.tags = tags;
+ let emojis = [] as string[];
+ let tags = [] as string[];
- // ハッシュタグ更新
- updateUsertags(user, tags);
- //#endregion
+ const newName = updates.name === undefined ? user.name : updates.name;
+ const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description;
- 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 (newName != null) {
+ const tokens = mfm.parseSimple(newName);
+ emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!));
+ }
- const iObj = await Users.pack<true, true>(user.id, user, {
- detail: true,
- includeSecrets: isSecure,
- });
+ if (newDescription != null) {
+ const tokens = mfm.parse(newDescription);
+ emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!));
+ tags = extractHashtags(tokens!).map(tag => normalizeForSearch(tag)).splice(0, 32);
+ }
- // Publish meUpdated event
- publishMainStream(user.id, 'meUpdated', iObj);
- publishUserEvent(user.id, 'updateUserProfile', await UserProfiles.findOneBy({ userId: user.id }));
+ updates.emojis = emojis;
+ updates.tags = tags;
- // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認
- if (user.isLocked && ps.isLocked === false) {
- acceptAllFollowRequests(user);
- }
+ // ハッシュタグ更新
+ this.hashtagService.updateUsertags(user, tags);
+ //#endregion
+
+ 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);
- // フォロワーにUpdateを配信
- publishToFollowers(user.id);
+ const iObj = await this.userEntityService.pack<true, true>(user.id, user, {
+ detail: true,
+ includeSecrets: isSecure,
+ });
- return iObj;
-});
+ // 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) {
+ this.userFollowingService.acceptAllFollowRequests(user);
+ }
+
+ // フォロワーにUpdateを配信
+ this.accountUpdateService.publishToFollowers(user.id);
+
+ 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<typeof meta, typeof paramDef> {
+ 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]));
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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]));
- publishInternalEvent('webhookCreated', webhook);
+ this.globalEventService.publishInternalEvent('webhookCreated', webhook);
- return 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.webhooksRepository)
+ private webhooksRepository: WebhooksRepository,
- if (webhook == null) {
- throw new ApiError(meta.errors.noSuchWebhook);
- }
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const webhook = await this.webhooksRepository.findOneBy({
+ id: ps.webhookId,
+ userId: me.id,
+ });
- await Webhooks.delete(webhook.id);
+ if (webhook == null) {
+ throw new ApiError(meta.errors.noSuchWebhook);
+ }
- publishInternalEvent('webhookDeleted', webhook);
-});
+ await this.webhooksRepository.delete(webhook.id);
+
+ this.globalEventService.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<typeof meta, typeof paramDef> {
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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);
- }
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.webhooksRepository)
+ private webhooksRepository: WebhooksRepository,
- if (webhook == null) {
- throw new ApiError(meta.errors.noSuchWebhook);
- }
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.messagingMessagesRepository)
+ private messagingMessagesRepository: MessagingMessagesRepository,
- const groups = ps.group ? await UserGroupJoinings.findBy({
- userId: user.id,
- }).then(xs => xs.map(x => x.userGroupId)) : [];
+ @Inject(DI.mutingsRepository)
+ private mutingsRepository: MutingsRepository,
- if (ps.group && groups.length === 0) {
- return [];
- }
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- const history: MessagingMessage[] = [];
+ private messagingMessageEntityService: MessagingMessageEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const mute = await this.mutingsRepository.findBy({
+ muterId: me.id,
+ });
- 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 groups = ps.group ? await this.userGroupJoiningsRepository.findBy({
+ userId: me.id,
+ }).then(xs => xs.map(x => x.userGroupId)) : [];
- const query = MessagingMessages.createQueryBuilder('message')
- .orderBy('message.createdAt', 'DESC');
+ if (ps.group && groups.length === 0) {
+ return [];
+ }
- if (ps.group) {
- query.where(`message.groupId IN (:...groups)`, { groups: groups });
+ const history: MessagingMessage[] = [];
- 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`);
+ 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!);
- if (found.length > 0) {
- query.andWhere(`message.userId NOT IN (:...found)`, { found: found });
- query.andWhere(`message.recipientId NOT IN (:...found)`, { found: found });
- }
+ const query = this.messagingMessagesRepository.createQueryBuilder('message')
+ .orderBy('message.createdAt', 'DESC');
- 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) });
- }
- }
+ if (ps.group) {
+ query.where('message.groupId IN (:...groups)', { groups: groups });
- const message = await query.getOne();
+ 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 (message) {
- history.push(message);
- } else {
- break;
- }
- }
+ if (found.length > 0) {
+ query.andWhere('message.userId NOT IN (:...found)', { found: found });
+ query.andWhere('message.recipientId NOT IN (:...found)', { found: found });
+ }
- return await Promise.all(history.map(h => MessagingMessages.pack(h.id, user)));
-});
+ 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;
+ }
+ }
+
+ return await Promise.all(history.map(h => this.messagingMessageEntityService.pack(h.id, me)));
+ });
+ }
+}
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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.messagingMessagesRepository)
+ private messagingMessagesRepository: MessagingMessagesRepository,
- 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);
+ @Inject(DI.userGroupsRepository)
+ private userGroupRepository: UserGroupsRepository,
- const messages = await query.take(ps.limit).getMany();
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- // Mark all as read
- if (ps.markAsRead) {
- readUserMessagingMessage(user.id, recipient.id, messages.filter(m => m.recipientId === user.id).map(x => x.id));
+ 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;
+ });
- // リモートユーザーとのメッセージだったら既読配信
- if (Users.isLocalUser(user) && Users.isRemoteUser(recipient)) {
- deliverReadActivity(user, recipient, messages);
- }
- }
+ 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);
- 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 });
+ const messages = await query.take(ps.limit).getMany();
- if (recipientGroup == null) {
- throw new ApiError(meta.errors.noSuchGroup);
- }
+ // 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));
- // check joined
- const joining = await UserGroupJoinings.findOneBy({
- userId: user.id,
- userGroupId: recipientGroup.id,
- });
+ // リモートユーザーとのメッセージだったら既読配信
+ if (this.userEntityService.isLocalUser(me) && this.userEntityService.isRemoteUser(recipient)) {
+ this.messagingService.deliverReadActivity(me, recipient, messages);
+ }
+ }
- if (joining == null) {
- throw new ApiError(meta.errors.groupAccessDenied);
- }
+ 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 });
- const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId)
- .andWhere(`message.groupId = :groupId`, { groupId: recipientGroup.id });
+ if (recipientGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
- const messages = await query.take(ps.limit).getMany();
+ // check joined
+ const joining = await this.userGroupJoiningsRepository.findOneBy({
+ userId: me.id,
+ userGroupId: recipientGroup.id,
+ });
- // Mark all as read
- if (ps.markAsRead) {
- readGroupMessagingMessage(user.id, recipientGroup.id, messages.map(x => x.id));
- }
+ if (joining == null) {
+ throw new ApiError(meta.errors.groupAccessDenied);
+ }
- return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, {
- populateGroup: false,
- })));
+ 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,
+ })));
+ }
+ });
}
-});
+}
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;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- if (ps.userId != null) {
- // Myself
- if (ps.userId === user.id) {
- throw new ApiError(meta.errors.recipientIsYourself);
- }
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- // 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;
- });
+ @Inject(DI.blockingsRepository)
+ private blockingsRepository: BlockingsRepository,
- // 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! });
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
- if (recipientGroup == null) {
- throw new ApiError(meta.errors.noSuchGroup);
- }
+ private getterService: GetterService,
+ private messagingService: MessagingService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ let recipientUser: User | null;
+ let recipientGroup: UserGroup | null;
- // check joined
- const joining = await UserGroupJoinings.findOneBy({
- userId: user.id,
- userGroupId: recipientGroup.id,
- });
+ if (ps.userId != null) {
+ // Myself
+ if (ps.userId === me.id) {
+ throw new ApiError(meta.errors.recipientIsYourself);
+ }
- if (joining == null) {
- throw new ApiError(meta.errors.groupAccessDenied);
- }
- }
+ // 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;
+ });
- let file = null;
- if (ps.fileId != null) {
- file = await DriveFiles.findOneBy({
- id: ps.fileId,
- userId: user.id,
- });
+ // 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 (file == null) {
- throw new ApiError(meta.errors.noSuchFile);
- }
- }
+ if (recipientGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
- // テキストが無いかつ添付ファイルも無かったらエラー
- if (ps.text == null && file == null) {
- throw new ApiError(meta.errors.contentRequired);
- }
+ // check joined
+ const joining = await this.userGroupJoiningsRepository.findOneBy({
+ userId: me.id,
+ userGroupId: recipientGroup.id,
+ });
- return await createMessage(user, recipientUser, recipientGroup, ps.text, file);
-});
+ 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);
+ });
+ }
+}
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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.messagingMessagesRepository)
+ private messagingMessagesRepository: MessagingMessagesRepository,
- if (message == null) {
- throw new ApiError(meta.errors.noSuchMessage);
- }
+ 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 deleteMessage(message);
-});
+ await this.messagingService.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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.messagingMessagesRepository)
+ private messagingMessagesRepository: MessagingMessagesRepository,
- if (message == null) {
- throw new ApiError(meta.errors.noSuchMessage);
- }
+ private messagingService: MessagingService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const message = await this.messagingMessagesRepository.findOneBy({ id: ps.messageId });
- 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;
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.mutingsRepository)
+ private mutingsRepository: MutingsRepository,
- // 自分自身
- if (user.id === ps.userId) {
- throw new ApiError(meta.errors.muteeIsYourself);
- }
+ private globalEventService: GlobalEventService,
+ private getterService: GetterService,
+ private idService: IdService,
+ ) {
+ 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;
- });
+ // 自分自身
+ if (me.id === ps.userId) {
+ throw new ApiError(meta.errors.muteeIsYourself);
+ }
- // Check if already 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.alreadyMuting);
- }
+ // Check if already muting
+ const exist = await this.mutingsRepository.findOneBy({
+ muterId: muter.id,
+ muteeId: mutee.id,
+ });
- if (ps.expiresAt && ps.expiresAt <= Date.now()) {
- return;
- }
+ if (exist != null) {
+ throw new ApiError(meta.errors.alreadyMuting);
+ }
- // 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);
+ if (ps.expiresAt && ps.expiresAt <= Date.now()) {
+ return;
+ }
- publishUserEvent(user.id, 'mute', mutee);
+ // 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);
- NoteWatchings.delete({
- userId: muter.id,
- noteUserId: mutee.id,
- });
-});
+ this.globalEventService.publishUserEvent(me.id, 'mute', mutee);
+ });
+ }
+}
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<typeof meta, typeof paramDef> {
+ 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.mutingsRepository)
+ private mutingsRepository: MutingsRepository,
- const mutings = await query
- .take(ps.limit)
- .getMany();
+ 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 });
- return await Mutings.packMany(mutings, me);
-});
+ 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,
- };
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.appsRepository)
+ private appsRepository: AppsRepository,
- const apps = await Apps.find({
- where: query,
- take: ps.limit,
- skip: ps.offset,
- });
+ private appEntityService: AppEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = {
+ userId: me.id,
+ };
- return await Promise.all(apps.map(app => Apps.pack(app, user, {
- detail: true,
- })));
-});
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.clipsRepository)
+ private clipsRepository: ClipsRepository,
- const clipNotes = await ClipNotes.findBy({
- noteId: note.id,
- });
+ @Inject(DI.clipNotesRepository)
+ private clipNotesRepository: ClipNotesRepository,
- const clips = await Clips.findBy({
- id: In(clipNotes.map(x => x.clipId)),
- isPublic: true,
- });
+ 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;
+ });
- return await Promise.all(clips.map(x => Clips.pack(x)));
-});
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if ((!user.isAdmin && !user.isModerator) && (note.userId !== user.id)) {
- throw new ApiError(meta.errors.accessDenied);
- }
+ 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;
+ });
+
+ if ((!me.isAdmin && !me.isModerator) && (note.userId !== me.id)) {
+ throw new ApiError(meta.errors.accessDenied);
+ }
- // この操作を行うのが投稿者とは限らない(例えばモデレーター)ため
- await deleteNote(await Users.findOneByOrFail({ id: note.userId }), note);
-});
+ // この操作を行うのが投稿者とは限らない(例えばモデレーター)ため
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.noteFavoritesRepository)
+ private noteFavoritesRepository: NoteFavoritesRepository,
- // if already favorited
- const exist = await NoteFavorites.findOneBy({
- noteId: note.id,
- userId: user.id,
- });
+ 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 (exist != null) {
- throw new ApiError(meta.errors.alreadyFavorited);
- }
+ // if already favorited
+ const exist = await this.noteFavoritesRepository.findOneBy({
+ noteId: note.id,
+ userId: me.id,
+ });
+
+ if (exist != null) {
+ throw new ApiError(meta.errors.alreadyFavorited);
+ }
- // Create favorite
- await NoteFavorites.insert({
- id: genId(),
- createdAt: new Date(),
- noteId: note.id,
- userId: user.id,
- });
-});
+ // 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.noteFavoritesRepository)
+ private noteFavoritesRepository: NoteFavoritesRepository,
- // if already favorited
- const exist = await NoteFavorites.findOneBy({
- noteId: note.id,
- userId: user.id,
- });
+ 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 (exist == null) {
- throw new ApiError(meta.errors.notFavorited);
- }
+ // if already favorited
+ const exist = await this.noteFavoritesRepository.findOneBy({
+ noteId: note.id,
+ userId: me.id,
+ });
+
+ if (exist == null) {
+ throw new ApiError(meta.errors.notFavorited);
+ }
- // Delete favorite
- await NoteFavorites.delete(exist.id);
-});
+ // 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- //#region Construct query
- const followingQuery = Followings.createQueryBuilder('following')
- .select('following.followeeId')
- .where('following.followerId = :followerId', { followerId: user.id });
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
- 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());
+ 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);
+ }
- generateChannelQuery(query, user);
- generateRepliesQuery(query, user);
- generateVisibilityQuery(query, user);
- generateMutedUserQuery(query, user);
- generateMutedNoteQuery(query, user);
- generateBlockedUserQuery(query, user);
+ //#region Construct query
+ const followingQuery = this.followingsRepository.createQueryBuilder('following')
+ .select('following.followeeId')
+ .where('following.followerId = :followerId', { followerId: me.id });
- 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)');
- }));
- }
+ 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());
- 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)');
- }));
- }
+ 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.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.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.withFiles) {
- query.andWhere('note.fileIds != \'{}\'');
- }
- //#endregion
+ 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)');
+ }));
+ }
- const timeline = await query.take(ps.limit).getMany();
+ if (ps.withFiles) {
+ query.andWhere('note.fileIds != \'{}\'');
+ }
+ //#endregion
- process.nextTick(() => {
- activeUsersChart.read(user);
- });
+ const timeline = await query.take(ps.limit).getMany();
- return await Notes.packMany(timeline, user);
-});
+ process.nextTick(() => {
+ this.activeUsersChart.read(me);
+ });
+
+ return await this.noteEntityService.packMany(timeline, me);
+ });
+ }
+}
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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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() });
- }));
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- //#region exclude arleady voted polls
- const votedQuery = PollVotes.createQueryBuilder('vote')
- .select('vote.noteId')
- .where('vote.userId = :meId', { meId: user.id });
+ @Inject(DI.pollsRepository)
+ private pollsRepository: PollsRepository,
- query
- .andWhere(`poll.noteId NOT IN (${ votedQuery.getQuery() })`);
+ @Inject(DI.pollVotesRepository)
+ private pollVotesRepository: PollVotesRepository,
- query.setParameters(votedQuery.getParameters());
- //#endregion
+ @Inject(DI.mutingsRepository)
+ private mutingsRepository: MutingsRepository,
- //#region mute
- const mutingQuery = Mutings.createQueryBuilder('muting')
- .select('muting.muteeId')
- .where('muting.muterId = :muterId', { muterId: user.id });
+ 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() });
+ }));
- query
- .andWhere(`poll.userId NOT IN (${ mutingQuery.getQuery() })`);
+ //#region exclude arleady voted polls
+ const votedQuery = this.pollVotesRepository.createQueryBuilder('vote')
+ .select('vote.noteId')
+ .where('vote.userId = :meId', { meId: me.id });
- query.setParameters(mutingQuery.getParameters());
- //#endregion
+ query
+ .andWhere(`poll.noteId NOT IN (${ votedQuery.getQuery() })`);
- const polls = await query
- .orderBy('poll.noteId', 'DESC')
- .take(ps.limit)
- .skip(ps.offset)
- .getMany();
+ query.setParameters(votedQuery.getParameters());
+ //#endregion
- if (polls.length === 0) return [];
+ //#region mute
+ const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
+ .select('muting.muteeId')
+ .where('muting.muterId = :muterId', { muterId: me.id });
- const notes = await Notes.find({
- where: {
- id: In(polls.map(poll => poll.noteId)),
- },
- order: {
- createdAt: 'DESC',
- },
- });
+ 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;
+// TODO: ロジックをサービスに切り出す
+
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const createdAt = new Date();
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- // 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;
- });
+ @Inject(DI.blockingsRepository)
+ private blockingsRepository: BlockingsRepository,
- if (!note.hasPoll) {
- throw new ApiError(meta.errors.noPoll);
- }
+ @Inject(DI.pollsRepository)
+ private pollsRepository: PollsRepository,
- // 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);
- }
- }
+ @Inject(DI.pollVotesRepository)
+ private pollVotesRepository: PollVotesRepository,
- const poll = await Polls.findOneByOrFail({ noteId: note.id });
+ 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();
- if (poll.expiresAt && poll.expiresAt < createdAt) {
- throw new ApiError(meta.errors.alreadyExpired);
- }
+ // 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 (poll.choices[ps.choice] == null) {
- throw new ApiError(meta.errors.invalidChoice);
- }
+ if (!note.hasPoll) {
+ throw new ApiError(meta.errors.noPoll);
+ }
+
+ // 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);
+ }
+ }
- // if already voted
- const exist = await PollVotes.findBy({
- noteId: note.id,
- userId: user.id,
- });
+ const poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id });
- if (exist.length) {
- if (poll.multiple) {
- if (exist.some(x => x.choice === ps.choice)) {
- throw new ApiError(meta.errors.alreadyVoted);
+ if (poll.expiresAt && poll.expiresAt < createdAt) {
+ throw new ApiError(meta.errors.alreadyExpired);
}
- } 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]));
+ if (poll.choices[ps.choice] == null) {
+ throw new ApiError(meta.errors.invalidChoice);
+ }
- // 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}'`);
+ // 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);
+ }
+ } else {
+ throw new ApiError(meta.errors.alreadyVoted);
+ }
+ }
+
+ // 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]));
- publishNoteStream(note.id, 'pollVoted', {
- choice: ps.choice,
- userId: user.id,
- });
+ // 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}'`);
- // Notify
- createNotification(note.userId, 'pollVote', {
- notifierId: user.id,
- noteId: note.id,
- choice: ps.choice,
- });
+ this.globalEventService.publishNoteStream(note.id, 'pollVoted', {
+ choice: ps.choice,
+ userId: me.id,
+ });
- // 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,
+ // Notify
+ this.createNotificationService.createNotification(note.userId, 'pollVote', {
+ notifierId: me.id,
noteId: note.id,
choice: ps.choice,
});
- }
- });
- // リモート投票の場合リプライ送信
- if (note.userHost != null) {
- const pollOwner = await Users.findOneByOrFail({ id: note.userId }) as IRemoteUser;
+ // リモート投票の場合リプライ送信
+ if (note.userHost != null) {
+ const pollOwner = await this.usersRepository.findOneByOrFail({ id: note.userId }) as IRemoteUser;
- deliver(user, renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox);
- }
+ 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<NoteReaction>;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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<NoteReaction>;
- 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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');
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- generateVisibilityQuery(query, user);
- if (user) generateMutedUserQuery(query, user);
- if (user) generateBlockedUserQuery(query, user);
+ 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');
- const timeline = 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(timeline, user);
-});
+ 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<typeof meta, typeof paramDef> {
+ 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);
+// TODO: ロジックをサービスに切り出す
- const notes = await query.take(ps.limit).getMany();
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
+
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- return await Notes.packMany(notes, me);
- } else {
- const userQuery = ps.userId != null ? [{
- term: {
- userId: ps.userId,
- },
- }] : [];
+ 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);
- const hostQuery = ps.userId == null ?
- ps.host === null ? [{
- bool: {
- must_not: {
- exists: {
- field: 'userHost',
- },
- },
- },
- }] : ps.host !== undefined ? [{
- term: {
- userHost: ps.host,
- },
- }] : []
- : [];
+ if (ps.userId) {
+ query.andWhere('note.userId = :userId', { userId: ps.userId });
+ } else if (ps.channelId) {
+ query.andWhere('note.channelId = :channelId', { channelId: ps.channelId });
+ }
- 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',
- }],
- },
- });
+ 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');
- const hits = result.body.hits.hits.map((hit: any) => hit._id);
+ this.queryService.generateVisibilityQuery(query, me);
+ if (me) this.queryService.generateMutedUserQuery(query, me);
+ if (me) this.queryService.generateBlockedUserQuery(query, me);
- if (hits.length === 0) return [];
+ const notes = await query.take(ps.limit).getMany();
- // Fetch found notes
- const notes = await Notes.find({
- where: {
- id: In(hits),
- },
- order: {
- id: -1,
- },
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- return await Notes.pack(note, user, {
- detail: true,
- });
-});
+ 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<typeof meta, typeof paramDef> {
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- const mutedNotes = await Notes.find({
- where: [{
- id: note.threadId || note.id,
- }, {
- threadId: note.threadId || note.id,
- }],
- });
+ @Inject(DI.noteThreadMutingsRepository)
+ private noteThreadMutingsRepository: NoteThreadMutingsRepository,
- await readNote(user.id, mutedNotes);
+ 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;
+ });
- await NoteThreadMutings.insert({
- id: genId(),
- createdAt: new Date(),
- threadId: note.threadId || note.id,
- userId: user.id,
- });
-});
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.noteThreadMutingsRepository)
+ private noteThreadMutingsRepository: NoteThreadMutingsRepository,
- await NoteThreadMutings.delete({
- threadId: note.threadId || note.id,
- userId: user.id,
- });
-});
+ 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;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- //#region Construct query
- const followingQuery = Followings.createQueryBuilder('following')
- .select('following.followeeId')
- .where('following.followerId = :followerId', { followerId: user.id });
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
- 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());
+ 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;
- generateChannelQuery(query, user);
- generateRepliesQuery(query, user);
- generateVisibilityQuery(query, user);
- generateMutedUserQuery(query, user);
- generateMutedNoteQuery(query, user);
- generateBlockedUserQuery(query, user);
+ //#region Construct query
+ const followingQuery = this.followingsRepository.createQueryBuilder('following')
+ .select('following.followeeId')
+ .where('following.followerId = :followerId', { followerId: me.id });
- 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)');
- }));
- }
+ 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());
- 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)');
- }));
- }
+ 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.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.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.withFiles) {
- query.andWhere('note.fileIds != \'{}\'');
- }
- //#endregion
+ 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)');
+ }));
+ }
- const timeline = await query.take(ps.limit).getMany();
+ if (ps.withFiles) {
+ query.andWhere('note.fileIds != \'{}\'');
+ }
+ //#endregion
- process.nextTick(() => {
- activeUsersChart.read(user);
- });
+ const timeline = await query.take(ps.limit).getMany();
- return await Notes.packMany(timeline, user);
-});
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
+
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- if (!(await Notes.isVisibleForMe(note, user ? user.id : null))) {
- return 204; // TODO: 良い感じのエラー返す
- }
+ 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 (note.text == null) {
- return 204;
- }
+ if (!(await this.noteEntityService.isVisibleForMe(note, me ? me.id : null))) {
+ return 204; // TODO: 良い感じのエラー返す
+ }
- const instance = await fetchMeta();
+ if (note.text == null) {
+ return 204;
+ }
- if (instance.deeplAuthKey == null) {
- return 204; // TODO: 良い感じのエラー返す
- }
+ const instance = await this.metaService.fetch();
- let targetLang = ps.targetLang;
- if (targetLang.includes('-')) targetLang = targetLang.split('-')[0];
+ if (instance.deeplAuthKey == null) {
+ return 204; // TODO: 良い感じのエラー返す
+ }
- const params = new URLSearchParams();
- params.append('auth_key', instance.deeplAuthKey);
- params.append('text', note.text);
- params.append('target_lang', targetLang);
+ let targetLang = ps.targetLang;
+ if (targetLang.includes('-')) targetLang = targetLang.split('-')[0];
- const endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate';
+ const params = new URLSearchParams();
+ params.append('auth_key', instance.deeplAuthKey);
+ params.append('text', note.text);
+ params.append('target_lang', targetLang);
- 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 endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate';
- const json = (await res.json()) as {
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const renotes = await Notes.findBy({
- userId: user.id,
- renoteId: note.id,
- });
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- for (const note of renotes) {
- deleteNote(await Users.findOneByOrFail({ id: user.id }), note);
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- if (list == null) {
- throw new ApiError(meta.errors.noSuchList);
- }
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- //#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 });
+ @Inject(DI.userListJoiningsRepository)
+ private userListJoiningsRepository: UserListJoiningsRepository,
- generateVisibilityQuery(query, user);
+ 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 (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 (list == null) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
- 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)');
- }));
- }
+ //#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 });
- 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)');
- }));
- }
+ this.queryService.generateVisibilityQuery(query, me);
- if (ps.withFiles) {
- query.andWhere('note.fileIds != \'{}\'');
- }
- //#endregion
+ 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)');
+ }));
+ }
- const timeline = await query.take(ps.limit).getMany();
+ 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)');
+ }));
+ }
- activeUsersChart.read(user);
+ 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)');
+ }));
+ }
- return await Notes.packMany(timeline, user);
-});
+ 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);
+ });
+ }
+}
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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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);
- }
- if (page.userId !== user.id) {
- throw new ApiError(meta.errors.accessDenied);
- }
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 Pages.delete(page.id);
-});
+ await this.pagesRepository.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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.promoReadsRepository)
+ private promoReadsRepository: PromoReadsRepository,
- const exist = await PromoReads.findOneBy({
- noteId: note.id,
- userId: user.id,
- });
+ 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;
+ });
- if (exist != null) {
- return;
- }
+ const exist = await this.promoReadsRepository.findOneBy({
+ noteId: note.id,
+ userId: me.id,
+ });
+
+ if (exist != null) {
+ return;
+ }
- await PromoReads.insert({
- id: genId(),
- createdAt: new Date(),
- noteId: note.id,
- userId: user.id,
- });
-});
+ 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(),
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
+
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- // 合致するユーザーが登録されていなかったら無視
- if (user == null) {
- return;
- }
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+ @Inject(DI.passwordResetRequestsRepository)
+ private passwordResetRequestsRepository: PasswordResetRequestsRepository,
- // 合致するメアドが登録されていなかったら無視
- if (profile.email !== ps.email) {
- return;
- }
+ 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 (!profile.emailVerified) {
- return;
- }
+ // 合致するユーザーが登録されていなかったら無視
+ if (user == null) {
+ return;
+ }
+
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
- const token = rndstr('a-z0-9', 64);
+ // 合致するメアドが登録されていなかったら無視
+ if (profile.email !== ps.email) {
+ return;
+ }
- await PasswordResetRequests.insert({
- id: genId(),
- createdAt: new Date(),
- userId: profile.userId,
- token,
- });
+ // メアドが認証されていなかったら無視
+ if (!profile.emailVerified) {
+ return;
+ }
- const link = `${config.url}/reset-password/${token}`;
+ const token = rndstr('a-z0-9', 64);
- sendEmail(ps.email, 'Password reset requested',
- `To reset password, please click this link:<br><a href="${link}">${link}</a>`,
- `To reset password, please click this link: ${link}`);
-});
+ 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:<br><a href="${link}">${link}</a>`,
+ `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';
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.db)
+ private db: DataSource,
- await resetDb();
+ @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 new Promise(resolve => setTimeout(resolve, 1000));
-});
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.passwordResetRequestsRepository)
+ private passwordResetRequestsRepository: PasswordResetRequestsRepository,
- // 発行してから30分以上経過していたら無効
- if (Date.now() - req.createdAt.getTime() > 1000 * 60 * 30) {
- throw new Error(); // TODO
- }
+ @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);
+ // 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,
- });
+ await this.userProfilesRepository.update(req.userId, {
+ password: hash,
+ });
- PasswordResetRequests.delete(req.id);
-});
+ this.passwordResetRequestsRepository.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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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(),
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const exist2 = await UsedUsernames.countBy({ username: ps.username.toLowerCase() });
+ @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(),
+ });
- return {
- available: exist === 0 && exist2 === 0,
- };
-});
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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 });
+ 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 (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);
+ 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);
+ }
+ }
+ }
- return await Followings.packMany(followings, me, { populateFollower: true });
-});
+ const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId)
+ .andWhere('following.followeeId = :userId', { userId: user.id })
+ .innerJoinAndSelect('following.follower', 'follower');
+
+ 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<typeof meta, typeof paramDef> {
+ 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 });
+ 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 (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);
+ 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);
+ }
+ }
+ }
- return await Followings.packMany(followings, me, { populateFollowee: true });
-});
+ const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId)
+ .andWhere('following.followerId = :userId', { userId: user.id })
+ .innerJoinAndSelect('following.followee', 'followee');
+
+ 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<typeof meta, typeof paramDef> {
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- // Fetch recent notes
- const recentNotes = await Notes.find({
- where: {
- userId: user.id,
- replyId: Not(IsNull()),
- },
- order: {
- id: -1,
- },
- take: 1000,
- select: ['replyId'],
- });
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- // 投稿が少なかったら中断
- if (recentNotes.length === 0) {
- return [];
- }
+ 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;
+ });
- // TODO ミュートを考慮
- const replyTargetNotes = await Notes.find({
- where: {
- id: In(recentNotes.map(p => p.replyId)),
- },
- select: ['userId'],
- });
+ // Fetch recent notes
+ const recentNotes = await this.notesRepository.find({
+ where: {
+ userId: user.id,
+ replyId: Not(IsNull()),
+ },
+ order: {
+ id: -1,
+ },
+ take: 1000,
+ select: ['replyId'],
+ });
- const repliedUsers: any = {};
+ // 投稿が少なかったら中断
+ if (recentNotes.length === 0) {
+ return [];
+ }
- // Extract replies from recent notes
- for (const userId of replyTargetNotes.map(x => x.userId.toString())) {
- if (repliedUsers[userId]) {
- repliedUsers[userId]++;
- } else {
- repliedUsers[userId] = 1;
- }
- }
+ // TODO ミュートを考慮
+ const replyTargetNotes = await this.notesRepository.find({
+ where: {
+ id: In(recentNotes.map(p => p.replyId)),
+ },
+ select: ['userId'],
+ });
+
+ const repliedUsers: any = {};
- // Calc peak
- const peak = maximum(Object.values(repliedUsers));
+ // Extract replies from recent notes
+ for (const userId of replyTargetNotes.map(x => x.userId.toString())) {
+ if (repliedUsers[userId]) {
+ repliedUsers[userId]++;
+ } else {
+ repliedUsers[userId] = 1;
+ }
+ }
- // Sort replies by frequency
- const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]);
+ // Calc peak
+ const peak = maximum(Object.values(repliedUsers));
- // Extract top replied users
- const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit);
+ // Sort replies by frequency
+ const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]);
- // 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,
- })));
+ // Extract top replied users
+ const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit);
- return repliesObj;
-});
+ // 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;
+ });
+ }
+}
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]));
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- // Push the owner
- await UserGroupJoinings.insert({
- id: genId(),
- createdAt: new Date(),
- userId: user.id,
- userGroupId: userGroup.id,
- } as UserGroupJoining);
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- return await UserGroups.pack(userGroup);
-});
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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);
- }
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
- await UserGroups.delete(userGroup.id);
-});
+ await this.userGroupsRepository.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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupInvitationsRepository)
+ private userGroupInvitationsRepository: UserGroupInvitationsRepository,
- if (invitation == null) {
- throw new ApiError(meta.errors.noSuchInvitation);
- }
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- if (invitation.userId !== user.id) {
- throw new ApiError(meta.errors.noSuchInvitation);
- }
+ private idService: IdService,
+ ) {
+ 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);
+ }
- // Push the user
- await UserGroupJoinings.insert({
- id: genId(),
- createdAt: new Date(),
- userId: user.id,
- userGroupId: invitation.userGroupId,
- } as UserGroupJoining);
+ if (invitation.userId !== me.id) {
+ throw new ApiError(meta.errors.noSuchInvitation);
+ }
- UserGroupInvitations.delete(invitation.id);
-});
+ // 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 == null) {
+ throw new ApiError(meta.errors.noSuchInvitation);
+ }
- if (invitation.userId !== user.id) {
- throw new ApiError(meta.errors.noSuchInvitation);
- }
+ if (invitation.userId !== me.id) {
+ throw new ApiError(meta.errors.noSuchInvitation);
+ }
- await UserGroupInvitations.delete(invitation.id);
-});
+ await this.userGroupInvitationsRepository.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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- if (userGroup == null) {
- throw new ApiError(meta.errors.noSuchGroup);
- }
+ @Inject(DI.userGroupInvitationsRepository)
+ private userGroupInvitationsRepository: UserGroupInvitationsRepository,
- // 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;
- });
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- const joining = await UserGroupJoinings.findOneBy({
- userGroupId: userGroup.id,
- userId: user.id,
- });
+ 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 (joining) {
- throw new ApiError(meta.errors.alreadyAdded);
- }
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
- const existInvitation = await UserGroupInvitations.findOneBy({
- userGroupId: userGroup.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;
+ });
- if (existInvitation) {
- throw new ApiError(meta.errors.alreadyInvited);
- }
+ const joining = await this.userGroupJoiningsRepository.findOneBy({
+ userGroupId: userGroup.id,
+ userId: user.id,
+ });
+
+ if (joining) {
+ throw new ApiError(meta.errors.alreadyAdded);
+ }
- 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]));
+ const existInvitation = await this.userGroupInvitationsRepository.findOneBy({
+ userGroupId: userGroup.id,
+ userId: user.id,
+ });
- // 通知を作成
- createNotification(user.id, 'groupInvited', {
- notifierId: me.id,
- userGroupInvitationId: invitation.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,
+ });
+ });
+ }
+}
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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- const joinings = await UserGroupJoinings.findBy({
- userId: me.id,
- ...(ownedGroups.length > 0 ? {
- userGroupId: Not(In(ownedGroups.map(x => x.id))),
- } : {}),
- });
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- return await Promise.all(joinings.map(x => UserGroups.pack(x.userGroupId)));
-});
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- if (userGroup == null) {
- throw new ApiError(meta.errors.noSuchGroup);
- }
+ @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 (me.id === userGroup.userId) {
- throw new ApiError(meta.errors.youAreOwner);
- }
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- return await Promise.all(userGroups.map(x => UserGroups.pack(x)));
-});
+ 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<typeof meta, typeof paramDef> {
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- if (userGroup == null) {
- throw new ApiError(meta.errors.noSuchGroup);
- }
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- const joining = await UserGroupJoinings.findOneBy({
- userId: me.id,
- userGroupId: userGroup.id,
- });
+ private userGroupEntityService: UserGroupEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Fetch the group
+ const userGroup = await this.userGroupsRepository.findOneBy({
+ id: ps.groupId,
+ });
- if (joining == null && userGroup.userId !== me.id) {
- throw new ApiError(meta.errors.noSuchGroup);
- }
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+
+ const joining = await this.userGroupJoiningsRepository.findOneBy({
+ userId: me.id,
+ userGroupId: userGroup.id,
+ });
- return await UserGroups.pack(userGroup);
-});
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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 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,
+ });
- const joining = await UserGroupJoinings.findOneBy({
- userGroupId: userGroup.id,
- userId: user.id,
- });
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
- if (joining == null) {
- throw new ApiError(meta.errors.noSuchGroupMember);
- }
+ // 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,
+ });
- await UserGroups.update(userGroup.id, {
- userId: ps.userId,
- });
+ if (joining == null) {
+ throw new ApiError(meta.errors.noSuchGroupMember);
+ }
- return await UserGroups.pack(userGroup.id);
-});
+ await this.userGroupsRepository.update(userGroup.id, {
+ userId: ps.userId,
+ });
+
+ return await this.userGroupEntityService.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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- if (userGroup == null) {
- 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,
+ userId: me.id,
+ });
+
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
- await UserGroups.update(userGroup.id, {
- name: ps.name,
- });
+ await this.userGroupsRepository.update(userGroup.id, {
+ name: ps.name,
+ });
- return await UserGroups.pack(userGroup.id);
-});
+ 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]));
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- return await UserLists.pack(userList);
-});
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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);
- }
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
- await UserLists.delete(userList.id);
-});
+ await this.userListsRepository.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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- return await Promise.all(userLists.map(x => UserLists.pack(x)));
-});
+ 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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- if (userList == null) {
- throw new ApiError(meta.errors.noSuchList);
- }
+ @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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- if (userList == null) {
- throw new ApiError(meta.errors.noSuchList);
- }
+ @Inject(DI.userListJoiningsRepository)
+ private userListJoiningsRepository: UserListJoiningsRepository,
- // 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;
- });
+ @Inject(DI.blockingsRepository)
+ private blockingsRepository: BlockingsRepository,
- // Check blocking
- if (user.id !== me.id) {
- const block = await Blockings.findOneBy({
- blockerId: user.id,
- blockeeId: me.id,
- });
- if (block) {
- throw new ApiError(meta.errors.youHaveBeenBlocked);
- }
- }
+ 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,
+ });
- const exist = await UserListJoinings.findOneBy({
- userListId: userList.id,
- userId: user.id,
- });
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
- if (exist) {
- throw new ApiError(meta.errors.alreadyAdded);
- }
+ // 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);
+ }
+ }
- // Push the user
- await pushUserToUserList(user, userList);
-});
+ 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);
+ });
+ }
+}
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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- if (userList == null) {
- throw new ApiError(meta.errors.noSuchList);
- }
+ 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 UserLists.pack(userList);
-});
+ return await this.userListEntityService.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,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- if (userList == null) {
- throw new ApiError(meta.errors.noSuchList);
- }
+ 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);
+ }
- await UserLists.update(userList.id, {
- name: ps.name,
- });
+ await this.userListsRepository.update(userList.id, {
+ name: ps.name,
+ });
- return await UserLists.pack(userList.id);
-});
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ 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.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');
+ 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;
+ });
- generateVisibilityQuery(query, me);
- if (me) {
- generateMutedUserQuery(query, me, user);
- generateBlockedUserQuery(query, me);
- }
+ //#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');
- if (ps.withFiles) {
- query.andWhere('note.fileIds != \'{}\'');
- }
+ 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.includeReplies) {
- query.andWhere('note.replyId IS NULL');
- }
+ 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.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.includeReplies) {
+ query.andWhere('note.replyId IS NULL');
+ }
- //#endregion
+ 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)');
+ }));
+ }
- const timeline = await query.take(ps.limit).getMany();
+ //#endregion
- return await Notes.packMany(timeline, 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/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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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<typeof meta, typeof paramDef> {
+ 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (user.id === me.id) {
- throw new ApiError(meta.errors.cannotReportYourself);
- }
+ @Inject(DI.abuseUserReportsRepository)
+ private abuseUserReportsRepository: AbuseUserReportsRepository,
- if (user.isAdmin) {
- throw new ApiError(meta.errors.cannotReportAdmin);
- }
+ 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;
+ });
- 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]));
+ if (user.id === me.id) {
+ throw new ApiError(meta.errors.cannotReportYourself);
+ }
- // Publish event to moderators
- setImmediate(async () => {
- const moderators = await Users.find({
- where: [{
- isAdmin: true,
- }, {
- isModerator: true,
- }],
- });
+ 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]));
- const meta = await fetchMeta();
- if (meta.email) {
- sendEmail(meta.email, 'New abuse report',
- sanitizeHtml(ps.comment),
- sanitizeHtml(ps.comment));
- }
- });
-});
+ // 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));
+ }
+ });
+ });
+ }
+}
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日
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (ps.host) {
- const q = Users.createQueryBuilder('user')
- .where('user.isSuspended = FALSE')
- .andWhere('user.host LIKE :host', { host: ps.host.toLowerCase() + '%' });
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
- if (ps.username) {
- q.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' });
- }
+ private userEntityService: UserEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日
- q.andWhere('user.updatedAt IS NOT NULL');
- q.orderBy('user.updatedAt', 'DESC');
+ if (ps.host) {
+ const q = this.usersRepository.createQueryBuilder('user')
+ .where('user.isSuspended = FALSE')
+ .andWhere('user.host LIKE :host', { host: ps.host.toLowerCase() + '%' });
- const users = await q.take(ps.limit).getMany();
+ if (ps.username) {
+ q.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' });
+ }
- return await Users.packMany(users, me, { detail: ps.detail });
- } else if (ps.username) {
- let users: User[] = [];
+ q.andWhere('user.updatedAt IS NOT NULL');
+ q.orderBy('user.updatedAt', 'DESC');
- if (me) {
- const followingQuery = Followings.createQueryBuilder('following')
- .select('following.followeeId')
- .where('following.followerId = :followerId', { followerId: me.id });
+ const users = await q.take(ps.limit).getMany();
- 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 });
- }));
+ return await this.userEntityService.packMany(users, me, { detail: ps.detail });
+ } else if (ps.username) {
+ let users: User[] = [];
- query.setParameters(followingQuery.getParameters());
+ if (me) {
+ const followingQuery = this.followingsRepository.createQueryBuilder('following')
+ .select('following.followeeId')
+ .where('following.followerId = :followerId', { followerId: me.id });
- users = await query
- .orderBy('user.usernameLower', 'ASC')
- .take(ps.limit)
- .getMany();
+ 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 });
+ }));
- 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');
+ query.setParameters(followingQuery.getParameters());
- otherQuery.setParameters(followingQuery.getParameters());
+ users = await query
+ .orderBy('user.usernameLower', 'ASC')
+ .take(ps.limit)
+ .getMany();
- const otherUsers = await otherQuery
- .orderBy('user.updatedAt', 'DESC')
- .take(ps.limit - users.length)
- .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');
- users = users.concat(otherUsers);
+ 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日
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const isUsername = ps.query.startsWith('@');
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- let users: User[] = [];
+ private userEntityService: UserEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日
- 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');
+ const isUsername = ps.query.startsWith('@');
- if (ps.origin === 'local') {
- usernameQuery.andWhere('user.host IS NULL');
- } else if (ps.origin === 'remote') {
- usernameQuery.andWhere('user.host IS NOT NULL');
- }
+ let users: User[] = [];
- 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 + '%' });
+ 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');
- // Also search username if it qualifies as username
- if (Users.validateLocalUsername(ps.query)) {
- qb.orWhere('user.usernameLower LIKE :username', { username: '%' + ps.query.toLowerCase() + '%' });
+ if (ps.origin === 'local') {
+ usernameQuery.andWhere('user.host IS NULL');
+ } else if (ps.origin === 'remote') {
+ usernameQuery.andWhere('user.host IS NOT NULL');
}
- }))
- .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 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 + '%' });
- users = await nameQuery
- .orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
- .take(ps.limit)
- .skip(ps.offset)
- .getMany();
+ // 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 (users.length < ps.limit) {
- const profQuery = UserProfiles.createQueryBuilder('prof')
- .select('prof.userId')
- .where('prof.description ILIKE :query', { query: '%' + ps.query + '%' });
+ if (ps.origin === 'local') {
+ nameQuery.andWhere('user.host IS NULL');
+ } else if (ps.origin === 'remote') {
+ nameQuery.andWhere('user.host IS NOT NULL');
+ }
- if (ps.origin === 'local') {
- profQuery.andWhere('prof.userHost IS NULL');
- } else if (ps.origin === 'remote') {
- profQuery.andWhere('prof.userHost IS NOT NULL');
- }
+ users = await nameQuery
+ .orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
+ .take(ps.limit)
+ .skip(ps.offset)
+ .getMany();
- 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());
+ if (users.length < ps.limit) {
+ const profQuery = this.userProfilesRepository.createQueryBuilder('prof')
+ .select('prof.userId')
+ .where('prof.description ILIKE :query', { query: '%' + ps.query + '%' });
- users = users.concat(await query
- .orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
- .take(ps.limit)
- .skip(ps.offset)
- .getMany(),
- );
- }
- }
+ 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());
- return await Users.packMany(users, me, { detail: ps.detail });
-});
+ 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 });
+ });
+ }
+}
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;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const isAdminOrModerator = me && (me.isAdmin || me.isModerator);
+ private userEntityService: UserEntityService,
+ private resolveUserService: ResolveUserService,
+ private apiLoggerService: ApiLoggerService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ let user;
- if (ps.userIds) {
- if (ps.userIds.length === 0) {
- return [];
- }
+ const isAdminOrModerator = me && (me.isAdmin || me.isModerator);
- const users = await Users.findBy(isAdminOrModerator ? {
- id: In(ps.userIds),
- } : {
- id: In(ps.userIds),
- isSuspended: false,
- });
+ 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)!);
- }
+ // リクエストされた通りに並べ替え
+ 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<User> = ps.userId != null
- ? { id: ps.userId }
- : { usernameLower: ps.username!.toLowerCase(), host: IsNull() };
+ 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<User> = ps.userId != null
+ ? { id: ps.userId }
+ : { usernameLower: ps.username!.toLowerCase(), host: IsNull() };
- user = await Users.findOneBy(q);
- }
+ user = await this.usersRepository.findOneBy(q);
+ }
- if (user == null || (!isAdminOrModerator && user.isSuspended)) {
- throw new ApiError(meta.errors.noSuchUser);
- }
+ if (user == null || (!isAdminOrModerator && user.isSuspended)) {
+ throw new ApiError(meta.errors.noSuchUser);
+ }
- return await Users.pack(user, me, {
- detail: true,
+ 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<typeof meta, typeof paramDef> {
+ 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,
- 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),
- });
+ @Inject(DI.pageLikesRepository)
+ private pageLikesRepository: PageLikesRepository,
- result.followingCount = result.localFollowingCount + result.remoteFollowingCount;
- result.followersCount = result.localFollowersCount + result.remoteFollowersCount;
+ @Inject(DI.noteFavoritesRepository)
+ private noteFavoritesRepository: NoteFavoritesRepository,
- return result;
-});
+ @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;
+ });
+ }
+}