From b75184ec8e3436200bacdcd832e3324702553d20 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 18 Sep 2022 03:27:08 +0900 Subject: なんかもうめっちゃ変えた MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/endpoints/admin/abuse-user-reports.ts | 56 +- .../server/api/endpoints/admin/accounts/create.ts | 67 ++- .../server/api/endpoints/admin/accounts/delete.ts | 82 +-- .../src/server/api/endpoints/admin/ad/create.ts | 44 +- .../src/server/api/endpoints/admin/ad/delete.ts | 24 +- .../src/server/api/endpoints/admin/ad/list.ts | 30 +- .../src/server/api/endpoints/admin/ad/update.ts | 40 +- .../api/endpoints/admin/announcements/create.ts | 40 +- .../api/endpoints/admin/announcements/delete.ts | 24 +- .../api/endpoints/admin/announcements/list.ts | 61 ++- .../api/endpoints/admin/announcements/update.ts | 34 +- .../server/api/endpoints/admin/delete-account.ts | 32 +- .../endpoints/admin/delete-all-files-of-a-user.ts | 32 +- .../api/endpoints/admin/drive-capacity-override.ts | 58 +- .../endpoints/admin/drive/clean-remote-files.ts | 18 +- .../server/api/endpoints/admin/drive/cleanup.ts | 32 +- .../src/server/api/endpoints/admin/drive/files.ts | 66 ++- .../server/api/endpoints/admin/drive/show-file.ts | 52 +- .../api/endpoints/admin/emoji/add-aliases-bulk.ts | 47 +- .../src/server/api/endpoints/admin/emoji/add.ts | 104 ++-- .../src/server/api/endpoints/admin/emoji/copy.ts | 101 ++-- .../api/endpoints/admin/emoji/delete-bulk.ts | 47 +- .../src/server/api/endpoints/admin/emoji/delete.ts | 43 +- .../server/api/endpoints/admin/emoji/import-zip.ts | 19 +- .../api/endpoints/admin/emoji/list-remote.ts | 55 +- .../src/server/api/endpoints/admin/emoji/list.ts | 56 +- .../endpoints/admin/emoji/remove-aliases-bulk.ts | 47 +- .../api/endpoints/admin/emoji/set-aliases-bulk.ts | 41 +- .../api/endpoints/admin/emoji/set-category-bulk.ts | 41 +- .../src/server/api/endpoints/admin/emoji/update.ts | 43 +- .../endpoints/admin/federation/delete-all-files.ts | 32 +- .../federation/refresh-remote-instance-metadata.ts | 35 +- .../admin/federation/remove-all-following.ts | 47 +- .../endpoints/admin/federation/update-instance.ts | 36 +- .../server/api/endpoints/admin/get-index-stats.ts | 34 +- .../server/api/endpoints/admin/get-table-stats.ts | 43 +- .../src/server/api/endpoints/admin/get-user-ips.ts | 36 +- .../src/server/api/endpoints/admin/invite.ts | 50 +- .../backend/src/server/api/endpoints/admin/meta.ts | 192 +++---- .../server/api/endpoints/admin/moderators/add.ts | 50 +- .../api/endpoints/admin/moderators/remove.ts | 38 +- .../src/server/api/endpoints/admin/promo/create.ts | 48 +- .../src/server/api/endpoints/admin/queue/clear.ts | 23 +- .../api/endpoints/admin/queue/deliver-delayed.ts | 44 +- .../api/endpoints/admin/queue/inbox-delayed.ts | 44 +- .../src/server/api/endpoints/admin/queue/stats.ts | 42 +- .../src/server/api/endpoints/admin/relays/add.ts | 28 +- .../src/server/api/endpoints/admin/relays/list.ts | 18 +- .../server/api/endpoints/admin/relays/remove.ts | 18 +- .../server/api/endpoints/admin/reset-password.ts | 57 +- .../endpoints/admin/resolve-abuse-user-report.ts | 68 ++- .../src/server/api/endpoints/admin/send-email.ts | 18 +- .../src/server/api/endpoints/admin/server-info.ts | 78 +-- .../api/endpoints/admin/show-moderation-logs.ts | 28 +- .../src/server/api/endpoints/admin/show-user.ts | 112 ++-- .../src/server/api/endpoints/admin/show-users.ts | 84 +-- .../src/server/api/endpoints/admin/silence-user.ts | 61 ++- .../src/server/api/endpoints/admin/suspend-user.ts | 145 ++--- .../server/api/endpoints/admin/unsilence-user.ts | 53 +- .../server/api/endpoints/admin/unsuspend-user.ts | 53 +- .../src/server/api/endpoints/admin/update-meta.ts | 582 +++++++++++---------- .../server/api/endpoints/admin/update-user-note.ts | 35 +- .../src/server/api/endpoints/admin/vacuum.ts | 36 -- .../src/server/api/endpoints/announcements.ts | 55 +- .../src/server/api/endpoints/antennas/create.ts | 113 ++-- .../src/server/api/endpoints/antennas/delete.ts | 44 +- .../src/server/api/endpoints/antennas/list.ts | 29 +- .../src/server/api/endpoints/antennas/notes.ts | 108 ++-- .../src/server/api/endpoints/antennas/show.ts | 41 +- .../src/server/api/endpoints/antennas/update.ts | 126 +++-- .../backend/src/server/api/endpoints/ap/get.ts | 24 +- .../backend/src/server/api/endpoints/ap/show.ts | 167 +++--- .../backend/src/server/api/endpoints/app/create.ts | 70 ++- .../backend/src/server/api/endpoints/app/show.ts | 45 +- .../src/server/api/endpoints/auth/accept.ts | 110 ++-- .../server/api/endpoints/auth/session/generate.ts | 70 ++- .../src/server/api/endpoints/auth/session/show.ts | 39 +- .../server/api/endpoints/auth/session/userkey.ts | 104 ++-- .../src/server/api/endpoints/blocking/create.ts | 89 ++-- .../src/server/api/endpoints/blocking/delete.ts | 86 +-- .../src/server/api/endpoints/blocking/list.ts | 40 +- .../src/server/api/endpoints/channels/create.ts | 69 ++- .../src/server/api/endpoints/channels/featured.ts | 31 +- .../src/server/api/endpoints/channels/follow.ts | 54 +- .../src/server/api/endpoints/channels/followed.ts | 40 +- .../src/server/api/endpoints/channels/owned.ts | 40 +- .../src/server/api/endpoints/channels/show.ts | 37 +- .../src/server/api/endpoints/channels/timeline.ts | 80 +-- .../src/server/api/endpoints/channels/unfollow.ts | 51 +- .../src/server/api/endpoints/channels/update.ts | 80 +-- .../server/api/endpoints/charts/active-users.ts | 23 +- .../src/server/api/endpoints/charts/ap-request.ts | 23 +- .../src/server/api/endpoints/charts/drive.ts | 23 +- .../src/server/api/endpoints/charts/federation.ts | 23 +- .../src/server/api/endpoints/charts/hashtag.ts | 23 +- .../src/server/api/endpoints/charts/instance.ts | 23 +- .../src/server/api/endpoints/charts/notes.ts | 23 +- .../src/server/api/endpoints/charts/user/drive.ts | 23 +- .../server/api/endpoints/charts/user/following.ts | 23 +- .../src/server/api/endpoints/charts/user/notes.ts | 23 +- .../server/api/endpoints/charts/user/reactions.ts | 23 +- .../src/server/api/endpoints/charts/users.ts | 23 +- .../src/server/api/endpoints/clips/add-note.ts | 74 ++- .../src/server/api/endpoints/clips/create.ts | 44 +- .../src/server/api/endpoints/clips/delete.ts | 36 +- .../backend/src/server/api/endpoints/clips/list.ts | 29 +- .../src/server/api/endpoints/clips/notes.ts | 99 ++-- .../src/server/api/endpoints/clips/remove-note.ts | 57 +- .../backend/src/server/api/endpoints/clips/show.ts | 45 +- .../src/server/api/endpoints/clips/update.ts | 51 +- packages/backend/src/server/api/endpoints/drive.ts | 37 +- .../src/server/api/endpoints/drive/files.ts | 58 +- .../api/endpoints/drive/files/attached-notes.ts | 54 +- .../api/endpoints/drive/files/check-existence.ts | 30 +- .../src/server/api/endpoints/drive/files/create.ts | 106 ++-- .../src/server/api/endpoints/drive/files/delete.ts | 47 +- .../api/endpoints/drive/files/find-by-hash.ts | 33 +- .../src/server/api/endpoints/drive/files/find.ts | 35 +- .../src/server/api/endpoints/drive/files/show.ts | 73 +-- .../src/server/api/endpoints/drive/files/update.ts | 101 ++-- .../api/endpoints/drive/files/upload-from-url.ts | 39 +- .../src/server/api/endpoints/drive/folders.ts | 46 +- .../server/api/endpoints/drive/folders/create.ts | 77 +-- .../server/api/endpoints/drive/folders/delete.ts | 63 ++- .../src/server/api/endpoints/drive/folders/find.ts | 35 +- .../src/server/api/endpoints/drive/folders/show.ts | 45 +- .../server/api/endpoints/drive/folders/update.ts | 132 ++--- .../src/server/api/endpoints/drive/stream.ts | 48 +- .../api/endpoints/email-address/available.ts | 18 +- .../backend/src/server/api/endpoints/endpoint.ts | 29 +- .../backend/src/server/api/endpoints/endpoints.ts | 15 +- .../server/api/endpoints/export-custom-emojis.ts | 18 +- .../server/api/endpoints/federation/followers.ts | 36 +- .../server/api/endpoints/federation/following.ts | 36 +- .../server/api/endpoints/federation/instances.ts | 177 ++++--- .../api/endpoints/federation/show-instance.ts | 30 +- .../src/server/api/endpoints/federation/stats.ts | 102 ++-- .../api/endpoints/federation/update-remote-user.ts | 23 +- .../src/server/api/endpoints/federation/users.ts | 36 +- .../backend/src/server/api/endpoints/fetch-rss.ts | 48 +- .../src/server/api/endpoints/following/create.ts | 98 ++-- .../src/server/api/endpoints/following/delete.ts | 80 +-- .../server/api/endpoints/following/invalidate.ts | 80 +-- .../api/endpoints/following/requests/accept.ts | 43 +- .../api/endpoints/following/requests/cancel.ts | 60 ++- .../api/endpoints/following/requests/list.ts | 29 +- .../api/endpoints/following/requests/reject.ts | 37 +- .../src/server/api/endpoints/gallery/featured.ts | 33 +- .../src/server/api/endpoints/gallery/popular.ts | 31 +- .../src/server/api/endpoints/gallery/posts.ts | 32 +- .../server/api/endpoints/gallery/posts/create.ts | 71 ++- .../server/api/endpoints/gallery/posts/delete.ts | 36 +- .../src/server/api/endpoints/gallery/posts/like.ts | 71 ++- .../src/server/api/endpoints/gallery/posts/show.ts | 37 +- .../server/api/endpoints/gallery/posts/unlike.ts | 49 +- .../server/api/endpoints/gallery/posts/update.ts | 70 ++- .../server/api/endpoints/get-online-users-count.ts | 30 +- .../src/server/api/endpoints/hashtags/list.ts | 79 +-- .../src/server/api/endpoints/hashtags/search.ts | 34 +- .../src/server/api/endpoints/hashtags/show.ts | 33 +- .../src/server/api/endpoints/hashtags/trend.ts | 184 ++++--- .../src/server/api/endpoints/hashtags/users.ts | 67 ++- packages/backend/src/server/api/endpoints/i.ts | 33 +- .../backend/src/server/api/endpoints/i/2fa/done.ts | 60 ++- .../src/server/api/endpoints/i/2fa/key-done.ts | 248 +++++---- .../server/api/endpoints/i/2fa/password-less.ts | 24 +- .../src/server/api/endpoints/i/2fa/register-key.ts | 90 ++-- .../src/server/api/endpoints/i/2fa/register.ts | 85 +-- .../src/server/api/endpoints/i/2fa/remove-key.ts | 70 ++- .../src/server/api/endpoints/i/2fa/unregister.ts | 38 +- .../backend/src/server/api/endpoints/i/apps.ts | 52 +- .../src/server/api/endpoints/i/authorized-apps.ts | 51 +- .../src/server/api/endpoints/i/change-password.ts | 48 +- .../src/server/api/endpoints/i/delete-account.ts | 47 +- .../src/server/api/endpoints/i/export-blocking.ts | 18 +- .../src/server/api/endpoints/i/export-following.ts | 18 +- .../src/server/api/endpoints/i/export-mute.ts | 18 +- .../src/server/api/endpoints/i/export-notes.ts | 18 +- .../server/api/endpoints/i/export-user-lists.ts | 18 +- .../src/server/api/endpoints/i/favorites.ts | 42 +- .../src/server/api/endpoints/i/gallery/likes.ts | 40 +- .../src/server/api/endpoints/i/gallery/posts.ts | 40 +- .../api/endpoints/i/get-word-muted-notes-count.ts | 30 +- .../src/server/api/endpoints/i/import-blocking.ts | 34 +- .../src/server/api/endpoints/i/import-following.ts | 34 +- .../src/server/api/endpoints/i/import-muting.ts | 34 +- .../server/api/endpoints/i/import-user-lists.ts | 34 +- .../src/server/api/endpoints/i/notifications.ts | 222 ++++---- .../src/server/api/endpoints/i/page-likes.ts | 40 +- .../backend/src/server/api/endpoints/i/pages.ts | 40 +- packages/backend/src/server/api/endpoints/i/pin.ts | 40 +- .../api/endpoints/i/read-all-messaging-messages.ts | 65 ++- .../api/endpoints/i/read-all-unread-notes.ts | 36 +- .../server/api/endpoints/i/read-announcement.ts | 75 ++- .../src/server/api/endpoints/i/regenerate-token.ts | 65 ++- .../src/server/api/endpoints/i/registry/get-all.ts | 44 +- .../server/api/endpoints/i/registry/get-detail.ts | 48 +- .../src/server/api/endpoints/i/registry/get.ts | 42 +- .../api/endpoints/i/registry/keys-with-type.ts | 46 +- .../src/server/api/endpoints/i/registry/keys.ts | 32 +- .../src/server/api/endpoints/i/registry/remove.ts | 42 +- .../src/server/api/endpoints/i/registry/scopes.ts | 46 +- .../src/server/api/endpoints/i/registry/set.ts | 85 +-- .../src/server/api/endpoints/i/revoke-token.ts | 38 +- .../src/server/api/endpoints/i/signin-history.ts | 32 +- .../backend/src/server/api/endpoints/i/unpin.ts | 36 +- .../src/server/api/endpoints/i/update-email.ts | 122 +++-- .../backend/src/server/api/endpoints/i/update.ts | 273 +++++----- .../server/api/endpoints/i/user-group-invites.ts | 38 +- .../src/server/api/endpoints/i/webhooks/create.ts | 55 +- .../src/server/api/endpoints/i/webhooks/delete.ts | 46 +- .../src/server/api/endpoints/i/webhooks/list.ts | 26 +- .../src/server/api/endpoints/i/webhooks/show.ts | 36 +- .../src/server/api/endpoints/i/webhooks/update.ts | 58 +- .../src/server/api/endpoints/messaging/history.ts | 131 +++-- .../src/server/api/endpoints/messaging/messages.ts | 171 +++--- .../api/endpoints/messaging/messages/create.ts | 154 +++--- .../api/endpoints/messaging/messages/delete.ts | 40 +- .../api/endpoints/messaging/messages/read.ts | 52 +- packages/backend/src/server/api/endpoints/meta.ts | 226 ++++---- .../src/server/api/endpoints/miauth/gen-token.ts | 62 ++- .../src/server/api/endpoints/mute/create.ts | 107 ++-- .../src/server/api/endpoints/mute/delete.ts | 71 ++- .../backend/src/server/api/endpoints/mute/list.ts | 40 +- .../backend/src/server/api/endpoints/my/apps.ts | 47 +- packages/backend/src/server/api/endpoints/notes.ts | 106 ++-- .../src/server/api/endpoints/notes/children.ts | 85 +-- .../src/server/api/endpoints/notes/clips.ts | 57 +- .../src/server/api/endpoints/notes/conversation.ts | 74 +-- .../src/server/api/endpoints/notes/create.ts | 234 +++++---- .../src/server/api/endpoints/notes/delete.ts | 45 +- .../server/api/endpoints/notes/favorites/create.ts | 69 ++- .../server/api/endpoints/notes/favorites/delete.ts | 56 +- .../src/server/api/endpoints/notes/featured.ts | 81 +-- .../server/api/endpoints/notes/global-timeline.ts | 112 ++-- .../server/api/endpoints/notes/hybrid-timeline.ts | 193 +++---- .../server/api/endpoints/notes/local-timeline.ts | 137 ++--- .../src/server/api/endpoints/notes/mentions.ts | 101 ++-- .../api/endpoints/notes/polls/recommendation.ts | 130 +++-- .../src/server/api/endpoints/notes/polls/vote.ts | 204 ++++---- .../src/server/api/endpoints/notes/reactions.ts | 66 ++- .../server/api/endpoints/notes/reactions/create.ts | 39 +- .../server/api/endpoints/notes/reactions/delete.ts | 35 +- .../src/server/api/endpoints/notes/renotes.ts | 74 +-- .../src/server/api/endpoints/notes/replies.ts | 69 ++- .../server/api/endpoints/notes/search-by-tag.ts | 145 ++--- .../src/server/api/endpoints/notes/search.ts | 151 ++---- .../backend/src/server/api/endpoints/notes/show.ts | 40 +- .../src/server/api/endpoints/notes/state.ts | 74 +-- .../api/endpoints/notes/thread-muting/create.ts | 73 ++- .../api/endpoints/notes/thread-muting/delete.ts | 40 +- .../src/server/api/endpoints/notes/timeline.ts | 185 ++++--- .../src/server/api/endpoints/notes/translate.ts | 129 +++-- .../src/server/api/endpoints/notes/unrenote.ts | 52 +- .../api/endpoints/notes/user-list-timeline.ts | 166 +++--- .../server/api/endpoints/notes/watching/create.ts | 38 -- .../server/api/endpoints/notes/watching/delete.ts | 38 -- .../server/api/endpoints/notifications/create.ts | 28 +- .../endpoints/notifications/mark-all-as-read.ts | 45 +- .../src/server/api/endpoints/notifications/read.ts | 20 +- .../backend/src/server/api/endpoints/page-push.ts | 51 +- .../src/server/api/endpoints/pages/create.ts | 101 ++-- .../src/server/api/endpoints/pages/delete.ts | 34 +- .../src/server/api/endpoints/pages/featured.ts | 33 +- .../backend/src/server/api/endpoints/pages/like.ts | 71 ++- .../backend/src/server/api/endpoints/pages/show.ts | 64 ++- .../src/server/api/endpoints/pages/unlike.ts | 49 +- .../src/server/api/endpoints/pages/update.ts | 107 ++-- packages/backend/src/server/api/endpoints/ping.ts | 19 +- .../src/server/api/endpoints/pinned-users.ts | 38 +- .../backend/src/server/api/endpoints/promo/read.ts | 63 ++- .../server/api/endpoints/request-reset-password.ts | 107 ++-- .../backend/src/server/api/endpoints/reset-db.ts | 34 +- .../src/server/api/endpoints/reset-password.ts | 57 +- .../src/server/api/endpoints/server-info.ts | 45 +- packages/backend/src/server/api/endpoints/stats.ts | 84 +-- .../src/server/api/endpoints/sw/register.ts | 77 +-- .../src/server/api/endpoints/sw/unregister.ts | 26 +- packages/backend/src/server/api/endpoints/test.ts | 15 +- .../src/server/api/endpoints/username/available.ts | 46 +- packages/backend/src/server/api/endpoints/users.ts | 85 +-- .../src/server/api/endpoints/users/clips.ts | 38 +- .../src/server/api/endpoints/users/followers.ts | 95 ++-- .../src/server/api/endpoints/users/following.ts | 95 ++-- .../server/api/endpoints/users/gallery/posts.ts | 36 +- .../users/get-frequently-replied-users.ts | 133 +++-- .../server/api/endpoints/users/groups/create.ts | 63 ++- .../server/api/endpoints/users/groups/delete.ts | 36 +- .../endpoints/users/groups/invitations/accept.ts | 65 ++- .../endpoints/users/groups/invitations/reject.ts | 44 +- .../server/api/endpoints/users/groups/invite.ts | 126 +++-- .../server/api/endpoints/users/groups/joined.ts | 48 +- .../src/server/api/endpoints/users/groups/leave.ts | 45 +- .../src/server/api/endpoints/users/groups/owned.ts | 31 +- .../src/server/api/endpoints/users/groups/pull.ts | 61 ++- .../src/server/api/endpoints/users/groups/show.ts | 56 +- .../server/api/endpoints/users/groups/transfer.ts | 85 +-- .../server/api/endpoints/users/groups/update.ts | 47 +- .../src/server/api/endpoints/users/lists/create.ts | 42 +- .../src/server/api/endpoints/users/lists/delete.ts | 36 +- .../src/server/api/endpoints/users/lists/list.ts | 31 +- .../src/server/api/endpoints/users/lists/pull.ts | 64 ++- .../src/server/api/endpoints/users/lists/push.ts | 103 ++-- .../src/server/api/endpoints/users/lists/show.ts | 41 +- .../src/server/api/endpoints/users/lists/update.ts | 47 +- .../src/server/api/endpoints/users/notes.ts | 144 ++--- .../src/server/api/endpoints/users/pages.ts | 38 +- .../src/server/api/endpoints/users/reactions.ts | 54 +- .../server/api/endpoints/users/recommendation.ts | 64 ++- .../src/server/api/endpoints/users/relation.ts | 27 +- .../src/server/api/endpoints/users/report-abuse.ts | 129 +++-- .../endpoints/users/search-by-username-and-host.ts | 168 +++--- .../src/server/api/endpoints/users/search.ts | 186 ++++--- .../backend/src/server/api/endpoints/users/show.ts | 124 +++-- .../src/server/api/endpoints/users/stats.ts | 187 ++++--- 315 files changed, 12111 insertions(+), 8096 deletions(-) delete mode 100644 packages/backend/src/server/api/endpoints/admin/vacuum.ts delete mode 100644 packages/backend/src/server/api/endpoints/notes/watching/create.ts delete mode 100644 packages/backend/src/server/api/endpoints/notes/watching/delete.ts (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 333746f423..480ae7166e 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -1,6 +1,8 @@ -import define from '../../define.js'; -import { AbuseUserReports } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AbuseUserReportsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -77,33 +79,43 @@ export const paramDef = { sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, state: { type: 'string', nullable: true, default: null }, - reporterOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "combined" }, - targetUserOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "combined" }, + reporterOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' }, + targetUserOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' }, forwarded: { type: 'boolean', default: false }, }, required: [], } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const query = makePaginationQuery(AbuseUserReports.createQueryBuilder('report'), ps.sinceId, ps.untilId); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.abuseUserReportsRepository) + private abuseUserReportsRepository: AbuseUserReportsRepository, - switch (ps.state) { - case 'resolved': query.andWhere('report.resolved = TRUE'); break; - case 'unresolved': query.andWhere('report.resolved = FALSE'); break; - } + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.abuseUserReportsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId); - switch (ps.reporterOrigin) { - case 'local': query.andWhere('report.reporterHost IS NULL'); break; - case 'remote': query.andWhere('report.reporterHost IS NOT NULL'); break; - } + switch (ps.state) { + case 'resolved': query.andWhere('report.resolved = TRUE'); break; + case 'unresolved': query.andWhere('report.resolved = FALSE'); break; + } - switch (ps.targetUserOrigin) { - case 'local': query.andWhere('report.targetUserHost IS NULL'); break; - case 'remote': query.andWhere('report.targetUserHost IS NOT NULL'); break; - } + switch (ps.reporterOrigin) { + case 'local': query.andWhere('report.reporterHost IS NULL'); break; + case 'remote': query.andWhere('report.reporterHost IS NOT NULL'); break; + } - const reports = await query.take(ps.limit).getMany(); + switch (ps.targetUserOrigin) { + case 'local': query.andWhere('report.targetUserHost IS NULL'); break; + case 'remote': query.andWhere('report.targetUserHost IS NOT NULL'); break; + } - return await AbuseUserReports.packMany(reports); -}); + const reports = await query.take(ps.limit).getMany(); + + return await this.abuseUserReportEntityService.packMany(reports); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index 5f89219991..1b173379a0 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -1,7 +1,11 @@ -import define from '../../../define.js'; -import { Users } from '@/models/index.js'; -import { signup } from '../../../common/signup.js'; +import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { SignupService } from '@/core/SignupService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { localUsernameSchema, passwordSchema } from '@/models/entities/User.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -22,31 +26,42 @@ export const meta = { export const paramDef = { type: 'object', properties: { - username: Users.localUsernameSchema, - password: Users.passwordSchema, + username: localUsernameSchema, + password: passwordSchema, }, required: ['username', 'password'], } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, _me) => { - const me = _me ? await Users.findOneByOrFail({ id: _me.id }) : null; - const noUsers = (await Users.countBy({ - host: IsNull(), - })) === 0; - if (!noUsers && !me?.isAdmin) throw new Error('access denied'); - - const { account, secret } = await signup({ - username: ps.username, - password: ps.password, - }); - - const res = await Users.pack(account, account, { - detail: true, - includeSecrets: true, - }); - - (res as any).token = secret; - - return res; -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private userEntityService: UserEntityService, + private signupService: SignupService, + ) { + super(meta, paramDef, async (ps, _me) => { + const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null; + const noUsers = (await this.usersRepository.countBy({ + host: IsNull(), + })) === 0; + if (!noUsers && !me?.isAdmin) throw new Error('access denied'); + + const { account, secret } = await this.signupService.signup({ + username: ps.username, + password: ps.password, + }); + + const res = await this.userEntityService.pack(account, account, { + detail: true, + includeSecrets: true, + }); + + (res as any).token = secret; + + return res; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts index 629d700582..2e0222f0c6 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -1,8 +1,10 @@ -import define from '../../../define.js'; -import { Users } from '@/models/index.js'; -import { doPostSuspend } from '@/services/suspend-user.js'; -import { publishUserEvent } from '@/services/stream.js'; -import { createDeleteAccountJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { QueueService } from '@/core/QueueService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { UserSuspendService } from '@/core/UserSuspendService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -20,40 +22,52 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user == null) { - throw new Error('user not found'); - } + private queueService: QueueService, + private globalEventService: GlobalEventService, + private userSuspendService: UserSuspendService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); - if (user.isAdmin) { - throw new Error('cannot suspend admin'); - } + if (user == null) { + throw new Error('user not found'); + } - if (user.isModerator) { - throw new Error('cannot suspend moderator'); - } + if (user.isAdmin) { + throw new Error('cannot suspend admin'); + } - if (Users.isLocalUser(user)) { - // 物理削除する前にDelete activityを送信する - await doPostSuspend(user).catch(e => {}); + if (user.isModerator) { + throw new Error('cannot suspend moderator'); + } - createDeleteAccountJob(user, { - soft: false, - }); - } else { - createDeleteAccountJob(user, { - soft: true, // リモートユーザーの削除は、完全にDBから物理削除してしまうと再度連合してきてアカウントが復活する可能性があるため、soft指定する - }); - } + if (this.userEntityService.isLocalUser(user)) { + // 物理削除する前にDelete activityを送信する + await this.userSuspendService.doPostSuspend(user).catch(err => {}); - await Users.update(user.id, { - isDeleted: true, - }); + this.queueService.createDeleteAccountJob(user, { + soft: false, + }); + } else { + this.queueService.createDeleteAccountJob(user, { + soft: true, // リモートユーザーの削除は、完全にDBから物理削除してしまうと再度連合してきてアカウントが復活する可能性があるため、soft指定する + }); + } - if (Users.isLocalUser(user)) { - // Terminate streaming - publishUserEvent(user.id, 'terminate', {}); + await this.usersRepository.update(user.id, { + isDeleted: true, + }); + + if (this.userEntityService.isLocalUser(user)) { + // Terminate streaming + this.globalEventService.publishUserEvent(user.id, 'terminate', {}); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts index ab2c50b50f..6b32391e8d 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import { Ads } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AdsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -24,16 +26,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - await Ads.insert({ - id: genId(), - createdAt: new Date(), - expiresAt: new Date(ps.expiresAt), - url: ps.url, - imageUrl: ps.imageUrl, - priority: ps.priority, - ratio: ps.ratio, - place: ps.place, - memo: ps.memo, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.adsRepository) + private adsRepository: AdsRepository, + + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + await this.adsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + expiresAt: new Date(ps.expiresAt), + url: ps.url, + imageUrl: ps.imageUrl, + priority: ps.priority, + ratio: ps.ratio, + place: ps.place, + memo: ps.memo, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts index 0ead2be005..7abefe156b 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { Ads } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AdsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -26,10 +28,18 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const ad = await Ads.findOneBy({ id: ps.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.adsRepository) + private adsRepository: AdsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const ad = await this.adsRepository.findOneBy({ id: ps.id }); - if (ad == null) throw new ApiError(meta.errors.noSuchAd); + if (ad == null) throw new ApiError(meta.errors.noSuchAd); - await Ads.delete(ad.id); -}); + await this.adsRepository.delete(ad.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts index 74f154f272..efece31bbf 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import { Ads } from '@/models/index.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AdsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -20,11 +22,21 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const query = makePaginationQuery(Ads.createQueryBuilder('ad'), ps.sinceId, ps.untilId) - .andWhere('ad.expiresAt > :now', { now: new Date() }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.adsRepository) + private adsRepository: AdsRepository, - const ads = await query.take(ps.limit).getMany(); + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId) + .andWhere('ad.expiresAt > :now', { now: new Date() }); - return ads; -}); + const ads = await query.take(ps.limit).getMany(); + + return ads; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts index 650f8670e3..098a593379 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { Ads } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AdsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -33,18 +35,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const ad = await Ads.findOneBy({ id: ps.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private adsRepository: AdsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const ad = await this.adsRepository.findOneBy({ id: ps.id }); - if (ad == null) throw new ApiError(meta.errors.noSuchAd); + if (ad == null) throw new ApiError(meta.errors.noSuchAd); - await Ads.update(ad.id, { - url: ps.url, - place: ps.place, - priority: ps.priority, - ratio: ps.ratio, - memo: ps.memo, - imageUrl: ps.imageUrl, - expiresAt: new Date(ps.expiresAt), - }); -}); + await this.adsRepository.update(ad.id, { + url: ps.url, + place: ps.place, + priority: ps.priority, + ratio: ps.ratio, + memo: ps.memo, + imageUrl: ps.imageUrl, + expiresAt: new Date(ps.expiresAt), + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index 33076b6d30..ee07170d62 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import { Announcements } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AnnouncementsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -55,15 +57,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const announcement = await Announcements.insert({ - id: genId(), - createdAt: new Date(), - updatedAt: null, - title: ps.title, - text: ps.text, - imageUrl: ps.imageUrl, - }).then(x => Announcements.findOneByOrFail(x.identifiers[0])); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.announcementsRepository) + private announcementsRepository: AnnouncementsRepository, - return Object.assign({}, announcement, { createdAt: announcement.createdAt.toISOString(), updatedAt: null }); -}); + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const announcement = await this.announcementsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + updatedAt: null, + title: ps.title, + text: ps.text, + imageUrl: ps.imageUrl, + }).then(x => this.announcementsRepository.findOneByOrFail(x.identifiers[0])); + + return Object.assign({}, announcement, { createdAt: announcement.createdAt.toISOString(), updatedAt: null }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts index c17765f4fc..9a67bdb1aa 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { Announcements } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AnnouncementsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -26,10 +28,18 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const announcement = await Announcements.findOneBy({ id: ps.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.announcementsRepository) + private announcementsRepository: AnnouncementsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const announcement = await this.announcementsRepository.findOneBy({ id: ps.id }); - if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); + if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); - await Announcements.delete(announcement.id); -}); + await this.announcementsRepository.delete(announcement.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 7a5758d75b..35c14abda2 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -1,7 +1,9 @@ -import { Announcements, AnnouncementReads } from '@/models/index.js'; -import { Announcement } from '@/models/entities/announcement.js'; -import define from '../../../define.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/index.js'; +import type { Announcement } from '@/models/entities/Announcement.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -64,26 +66,39 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const query = makePaginationQuery(Announcements.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.announcementsRepository) + private announcementsRepository: AnnouncementsRepository, - const announcements = await query.take(ps.limit).getMany(); + @Inject(DI.announcementReadsRepository) + private announcementReadsRepository: AnnouncementReadsRepository, - const reads = new Map(); + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); - for (const announcement of announcements) { - reads.set(announcement, await AnnouncementReads.countBy({ - announcementId: announcement.id, - })); - } + const announcements = await query.take(ps.limit).getMany(); + + const reads = new Map(); - return announcements.map(announcement => ({ - id: announcement.id, - createdAt: announcement.createdAt.toISOString(), - updatedAt: announcement.updatedAt?.toISOString() ?? null, - title: announcement.title, - text: announcement.text, - imageUrl: announcement.imageUrl, - reads: reads.get(announcement)!, - })); -}); + for (const announcement of announcements) { + reads.set(announcement, await this.announcementReadsRepository.countBy({ + announcementId: announcement.id, + })); + } + + return announcements.map(announcement => ({ + id: announcement.id, + createdAt: announcement.createdAt.toISOString(), + updatedAt: announcement.updatedAt?.toISOString() ?? null, + title: announcement.title, + text: announcement.text, + imageUrl: announcement.imageUrl, + reads: reads.get(announcement)!, + })); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index 61ce106d88..38358dff10 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { Announcements } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AnnouncementsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -29,15 +31,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const announcement = await Announcements.findOneBy({ id: ps.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.announcementsRepository) + private announcementsRepository: AnnouncementsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const announcement = await this.announcementsRepository.findOneBy({ id: ps.id }); - if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); + if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); - await Announcements.update(announcement.id, { - updatedAt: new Date(), - title: ps.title, - text: ps.text, - imageUrl: ps.imageUrl, - }); -}); + await this.announcementsRepository.update(announcement.id, { + updatedAt: new Date(), + title: ps.title, + text: ps.text, + imageUrl: ps.imageUrl, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/delete-account.ts b/packages/backend/src/server/api/endpoints/admin/delete-account.ts index 2d7ef2f236..c8b67fe1c0 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-account.ts @@ -1,6 +1,8 @@ -import { Users } from '@/models/index.js'; -import { deleteAccount } from '@/services/delete-account.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DeleteAccountService } from '@/core/DeleteAccountService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -21,11 +23,21 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const user = await Users.findOneByOrFail({ id: ps.userId }); - if (user.isDeleted) { - return; - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private deleteAccountService: DeleteAccountService, + ) { + super(meta, paramDef, async (ps) => { + const user = await this.usersRepository.findOneByOrFail({ id: ps.userId }); + if (user.isDeleted) { + return; + } - await deleteAccount(user); -}); + await this.deleteAccountService.deleteAccount(user); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts index dc1976624d..051e4c60fb 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts @@ -1,6 +1,8 @@ -import define from '../../define.js'; -import { deleteFile } from '@/services/drive/delete-file.js'; -import { DriveFiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DriveService } from '@/core/DriveService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -18,12 +20,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const files = await DriveFiles.findBy({ - userId: ps.userId, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - for (const file of files) { - deleteFile(file); + private driveService: DriveService, + ) { + super(meta, paramDef, async (ps, me) => { + const files = await this.driveFilesRepository.findBy({ + userId: ps.userId, + }); + + for (const file of files) { + this.driveService.deleteFile(file); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts index a4b29770e1..770bade06d 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; -import { Users } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { DI } from '@/di-symbols.js'; + export const meta = { tags: ['admin'], @@ -19,29 +21,39 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); - - if (user == null) { - throw new Error('user not found'); - } - - if (!Users.isLocalUser(user)) { - throw new Error('user is not local user'); - } - - /*if (user.isAdmin) { +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + if (!this.userEntityService.isLocalUser(user)) { + throw new Error('user is not local user'); + } + + /*if (user.isAdmin) { throw new Error('cannot suspend admin'); } if (user.isModerator) { throw new Error('cannot suspend moderator'); }*/ - await Users.update(user.id, { - driveCapacityOverrideMb: ps.overrideMb, - }); + await this.usersRepository.update(user.id, { + driveCapacityOverrideMb: ps.overrideMb, + }); - insertModerationLog(me, 'change-drive-capacity-override', { - targetId: user.id, - }); -}); + this.moderationLogService.insertModerationLog(me, 'change-drive-capacity-override', { + targetId: user.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts index bab149532e..2cc4e70e55 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts @@ -1,5 +1,6 @@ -import define from '../../../define.js'; -import { createCleanRemoteFilesJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { tags: ['admin'], @@ -15,6 +16,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - createCleanRemoteFilesJob(); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createCleanRemoteFilesJob(); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts index 3db942e6cd..3927a89f90 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts @@ -1,7 +1,9 @@ import { IsNull } from 'typeorm'; -import define from '../../../define.js'; -import { deleteFile } from '@/services/drive/delete-file.js'; -import { DriveFiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DriveService } from '@/core/DriveService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -17,12 +19,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const files = await DriveFiles.findBy({ - userId: IsNull(), - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - for (const file of files) { - deleteFile(file); + private driveService: DriveService, + ) { + super(meta, paramDef, async (ps, me) => { + const files = await this.driveFilesRepository.findBy({ + userId: IsNull(), + }); + + for (const file of files) { + this.driveService.deleteFile(file); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index ba32aac431..88529ab0aa 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -1,6 +1,8 @@ -import { DriveFiles } from '@/models/index.js'; -import define from '../../../define.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DriveFilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -39,32 +41,42 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (ps.userId) { - query.andWhere('file.userId = :userId', { userId: ps.userId }); - } else { - if (ps.origin === 'local') { - query.andWhere('file.userHost IS NULL'); - } else if (ps.origin === 'remote') { - query.andWhere('file.userHost IS NOT NULL'); - } + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId); - if (ps.hostname) { - query.andWhere('file.userHost = :hostname', { hostname: ps.hostname }); - } - } + if (ps.userId) { + query.andWhere('file.userId = :userId', { userId: ps.userId }); + } else { + if (ps.origin === 'local') { + query.andWhere('file.userHost IS NULL'); + } else if (ps.origin === 'remote') { + query.andWhere('file.userHost IS NOT NULL'); + } - if (ps.type) { - if (ps.type.endsWith('/*')) { - query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); - } else { - query.andWhere('file.type = :type', { type: ps.type }); - } - } + if (ps.hostname) { + query.andWhere('file.userHost = :hostname', { hostname: ps.hostname }); + } + } - const files = await query.take(ps.limit).getMany(); + if (ps.type) { + if (ps.type.endsWith('/*')) { + query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); + } else { + query.andWhere('file.type = :type', { type: ps.type }); + } + } - return await DriveFiles.packMany(files, { detail: true, withUser: true, self: true }); -}); + const files = await query.take(ps.limit).getMany(); + + return await this.driveFileEntityService.packMany(files, { detail: true, withUser: true, self: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index e9117a23c8..45ea9cdb50 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -1,5 +1,7 @@ -import { DriveFiles } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DriveFilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -169,25 +171,33 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const file = ps.fileId ? await DriveFiles.findOneBy({ id: ps.fileId }) : await DriveFiles.findOne({ - where: [{ - url: ps.url, - }, { - thumbnailUrl: ps.url, - }, { - webpublicUrl: ps.url, - }], - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const file = ps.fileId ? await this.driveFilesRepository.findOneBy({ id: ps.fileId }) : await this.driveFilesRepository.findOne({ + where: [{ + url: ps.url, + }, { + thumbnailUrl: ps.url, + }, { + webpublicUrl: ps.url, + }], + }); - if (file == null) { - throw new ApiError(meta.errors.noSuchFile); - } + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } - if (!me.isAdmin) { - delete file.requestIp; - delete file.requestHeaders; - } + if (!me.isAdmin) { + delete file.requestIp; + delete file.requestHeaders; + } - return file; -}); + return file; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts index 232fbbd573..0b6e744ef8 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts @@ -1,8 +1,8 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { In } from 'typeorm'; -import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, In } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -24,18 +24,31 @@ export const paramDef = { required: ['ids', 'aliases'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const emojis = await Emojis.findBy({ - id: In(ps.ids), - }); - - for (const emoji of emojis) { - await Emojis.update(emoji.id, { - updatedAt: new Date(), - aliases: [...new Set(emoji.aliases.concat(ps.aliases))], +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const emojis = await this.emojisRepository.findBy({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await this.emojisRepository.update(emoji.id, { + updatedAt: new Date(), + aliases: [...new Set(emoji.aliases.concat(ps.aliases))], + }); + } + + await this.db.queryResultCache!.remove(['meta_emojis']); }); } - - await db.queryResultCache!.remove(['meta_emojis']); -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index 67349c24e0..daa57e8eb2 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -1,11 +1,14 @@ -import define from '../../../define.js'; -import { Emojis, DriveFiles } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { ApiError } from '../../../error.js'; +import { Inject, Injectable } from '@nestjs/common'; import rndstr from 'rndstr'; -import { publishBroadcastStream } from '@/services/stream.js'; -import { db } from '@/db/postgre.js'; +import { DataSource } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository, EmojisRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { ApiError } from '../../../error.js'; export const meta = { tags: ['admin'], @@ -30,37 +33,58 @@ export const paramDef = { required: ['fileId'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const file = await DriveFiles.findOneBy({ id: ps.fileId }); - - if (file == null) throw new ApiError(meta.errors.noSuchFile); - - const name = file.name.split('.')[0].match(/^[a-z0-9_]+$/) ? file.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`; - - const emoji = await Emojis.insert({ - id: genId(), - updatedAt: new Date(), - name: name, - category: null, - host: null, - aliases: [], - originalUrl: file.url, - publicUrl: file.webpublicUrl ?? file.url, - type: file.webpublicType ?? file.type, - }).then(x => Emojis.findOneByOrFail(x.identifiers[0])); - - await db.queryResultCache!.remove(['meta_emojis']); - - publishBroadcastStream('emojiAdded', { - emoji: await Emojis.pack(emoji.id), - }); - - insertModerationLog(me, 'addEmoji', { - emojiId: emoji.id, - }); - - return { - id: emoji.id, - }; -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + + private emojiEntityService: EmojiEntityService, + private idService: IdService, + private globalEventService: GlobalEventService, + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); + + if (file == null) throw new ApiError(meta.errors.noSuchFile); + + const name = file.name.split('.')[0].match(/^[a-z0-9_]+$/) ? file.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`; + + const emoji = await this.emojisRepository.insert({ + id: this.idService.genId(), + updatedAt: new Date(), + name: name, + category: null, + host: null, + aliases: [], + originalUrl: file.url, + publicUrl: file.webpublicUrl ?? file.url, + type: file.webpublicType ?? file.type, + }).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0])); + + await this.db.queryResultCache!.remove(['meta_emojis']); + + this.globalEventService.publishBroadcastStream('emojiAdded', { + emoji: await this.emojiEntityService.pack(emoji.id), + }); + + this.moderationLogService.insertModerationLog(me, 'addEmoji', { + emojiId: emoji.id, + }); + + return { + id: emoji.id, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index 7010ade0d8..08d40834c1 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -1,11 +1,14 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import { DI } from '@/di-symbols.js'; +import { DriveService } from '@/core/DriveService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { ApiError } from '../../../error.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { uploadFromUrl } from '@/services/drive/upload-from-url.js'; -import { publishBroadcastStream } from '@/services/stream.js'; -import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -42,41 +45,59 @@ export const paramDef = { required: ['emojiId'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const emoji = await Emojis.findOneBy({ id: ps.emojiId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, - if (emoji == null) { - throw new ApiError(meta.errors.noSuchEmoji); - } + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, - let driveFile: DriveFile; + private emojiEntityService: EmojiEntityService, + private idService: IdService, + private globalEventService: GlobalEventService, + private driveService: DriveService, + ) { + super(meta, paramDef, async (ps, me) => { + const emoji = await this.emojisRepository.findOneBy({ id: ps.emojiId }); - try { - // Create file - driveFile = await uploadFromUrl({ url: emoji.originalUrl, user: null, force: true }); - } catch (e) { - throw new ApiError(); - } + if (emoji == null) { + throw new ApiError(meta.errors.noSuchEmoji); + } + + let driveFile: DriveFile; + + try { + // Create file + driveFile = await this.driveService.uploadFromUrl({ url: emoji.originalUrl, user: null, force: true }); + } catch (e) { + throw new ApiError(); + } - const copied = await Emojis.insert({ - id: genId(), - updatedAt: new Date(), - name: emoji.name, - host: null, - aliases: [], - originalUrl: driveFile.url, - publicUrl: driveFile.webpublicUrl ?? driveFile.url, - type: driveFile.webpublicType ?? driveFile.type, - }).then(x => Emojis.findOneByOrFail(x.identifiers[0])); - - await db.queryResultCache!.remove(['meta_emojis']); - - publishBroadcastStream('emojiAdded', { - emoji: await Emojis.pack(copied.id), - }); - - return { - id: copied.id, - }; -}); + const copied = await this.emojisRepository.insert({ + id: this.idService.genId(), + updatedAt: new Date(), + name: emoji.name, + host: null, + aliases: [], + originalUrl: driveFile.url, + publicUrl: driveFile.webpublicUrl ?? driveFile.url, + type: driveFile.webpublicType ?? driveFile.type, + }).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0])); + + await this.db.queryResultCache!.remove(['meta_emojis']); + + this.globalEventService.publishBroadcastStream('emojiAdded', { + emoji: await this.emojiEntityService.pack(copied.id), + }); + + return { + id: copied.id, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts index 93a6c4e4e2..81b095cb57 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts @@ -1,9 +1,9 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { In } from 'typeorm'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, In } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; export const meta = { tags: ['admin'], @@ -22,19 +22,34 @@ export const paramDef = { required: ['ids'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const emojis = await Emojis.findBy({ - id: In(ps.ids), - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const emojis = await this.emojisRepository.findBy({ + id: In(ps.ids), + }); - for (const emoji of emojis) { - await Emojis.delete(emoji.id); + for (const emoji of emojis) { + await this.emojisRepository.delete(emoji.id); - await db.queryResultCache!.remove(['meta_emojis']); + await this.db.queryResultCache!.remove(['meta_emojis']); - insertModerationLog(me, 'deleteEmoji', { - emoji: emoji, + this.moderationLogService.insertModerationLog(me, 'deleteEmoji', { + emoji: emoji, + }); + } }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts index 67dbf28d85..e4278dc33a 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -1,8 +1,10 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -27,17 +29,32 @@ export const paramDef = { required: ['id'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const emoji = await Emojis.findOneBy({ id: ps.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const emoji = await this.emojisRepository.findOneBy({ id: ps.id }); - if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji); + if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji); - await Emojis.delete(emoji.id); + await this.emojisRepository.delete(emoji.id); - await db.queryResultCache!.remove(['meta_emojis']); + await this.db.queryResultCache!.remove(['meta_emojis']); - insertModerationLog(me, 'deleteEmoji', { - emoji: emoji, - }); -}); + this.moderationLogService.insertModerationLog(me, 'deleteEmoji', { + emoji: emoji, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts index 3f03dc2da4..6fe492cb75 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts @@ -1,6 +1,6 @@ -import define from '../../../define.js'; -import { createImportCustomEmojisJob } from '@/queue/index.js'; -import ms from 'ms'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, @@ -17,6 +17,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - createImportCustomEmojisJob(user, ps.fileId); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createImportCustomEmojisJob(me, ps.fileId); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts index d16689a280..9d6fa53417 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts @@ -1,7 +1,10 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { toPuny } from '@/misc/convert-host.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -69,23 +72,35 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const q = makePaginationQuery(Emojis.createQueryBuilder('emoji'), ps.sinceId, ps.untilId); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, - if (ps.host == null) { - q.andWhere(`emoji.host IS NOT NULL`); - } else { - q.andWhere(`emoji.host = :host`, { host: toPuny(ps.host) }); - } + private utilityService: UtilityService, + private queryService: QueryService, + private emojiEntityService: EmojiEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId); - if (ps.query) { - q.andWhere('emoji.name like :query', { query: '%' + ps.query + '%' }); - } + if (ps.host == null) { + q.andWhere('emoji.host IS NOT NULL'); + } else { + q.andWhere('emoji.host = :host', { host: this.utilityService.toPuny(ps.host) }); + } - const emojis = await q - .orderBy('emoji.id', 'DESC') - .take(ps.limit) - .getMany(); + if (ps.query) { + q.andWhere('emoji.name like :query', { query: '%' + ps.query + '%' }); + } - return Emojis.packMany(emojis); -}); + const emojis = await q + .orderBy('emoji.id', 'DESC') + .take(ps.limit) + .getMany(); + + return this.emojiEntityService.packMany(emojis); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index 6192978fad..736d664cc3 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; -import { Emoji } from '@/models/entities/emoji.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import type { Emoji } from '@/models/entities/Emoji.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -63,27 +65,37 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const q = makePaginationQuery(Emojis.createQueryBuilder('emoji'), ps.sinceId, ps.untilId) - .andWhere(`emoji.host IS NULL`); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, - let emojis: Emoji[]; + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId) + .andWhere('emoji.host IS NULL'); - if (ps.query) { - //q.andWhere('emoji.name ILIKE :q', { q: `%${ps.query}%` }); - //const emojis = await q.take(ps.limit).getMany(); + let emojis: Emoji[]; - emojis = await q.getMany(); + if (ps.query) { + //q.andWhere('emoji.name ILIKE :q', { q: `%${ps.query}%` }); + //const emojis = await q.take(ps.limit).getMany(); - emojis = emojis.filter(emoji => - emoji.name.includes(ps.query!) || - emoji.aliases.some(a => a.includes(ps.query!)) || - emoji.category?.includes(ps.query!)); + emojis = await q.getMany(); - emojis.splice(ps.limit + 1); - } else { - emojis = await q.take(ps.limit).getMany(); - } + emojis = emojis.filter(emoji => + emoji.name.includes(ps.query!) || + emoji.aliases.some(a => a.includes(ps.query!)) || + emoji.category?.includes(ps.query!)); + + emojis.splice(ps.limit + 1); + } else { + emojis = await q.take(ps.limit).getMany(); + } - return Emojis.packMany(emojis); -}); + return this.emojiEntityService.packMany(emojis); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts index a4da40fffd..d6c70eaae7 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts @@ -1,8 +1,8 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { In } from 'typeorm'; -import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, In } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -24,18 +24,31 @@ export const paramDef = { required: ['ids', 'aliases'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const emojis = await Emojis.findBy({ - id: In(ps.ids), - }); - - for (const emoji of emojis) { - await Emojis.update(emoji.id, { - updatedAt: new Date(), - aliases: emoji.aliases.filter(x => !ps.aliases.includes(x)), +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const emojis = await this.emojisRepository.findBy({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await this.emojisRepository.update(emoji.id, { + updatedAt: new Date(), + aliases: emoji.aliases.filter(x => !ps.aliases.includes(x)), + }); + } + + await this.db.queryResultCache!.remove(['meta_emojis']); }); } - - await db.queryResultCache!.remove(['meta_emojis']); -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts index ae3b190f40..c438b7f9b7 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts @@ -1,8 +1,8 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { In } from 'typeorm'; -import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, In } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -24,14 +24,27 @@ export const paramDef = { required: ['ids', 'aliases'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - await Emojis.update({ - id: In(ps.ids), - }, { - updatedAt: new Date(), - aliases: ps.aliases, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + ) { + super(meta, paramDef, async (ps, me) => { + await this.emojisRepository.update({ + id: In(ps.ids), + }, { + updatedAt: new Date(), + aliases: ps.aliases, + }); - await db.queryResultCache!.remove(['meta_emojis']); -}); + await this.db.queryResultCache!.remove(['meta_emojis']); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts index cff58d6170..4a9b31fd28 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts @@ -1,8 +1,8 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { In } from 'typeorm'; -import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, In } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -26,14 +26,27 @@ export const paramDef = { required: ['ids'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - await Emojis.update({ - id: In(ps.ids), - }, { - updatedAt: new Date(), - category: ps.category, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + ) { + super(meta, paramDef, async (ps, me) => { + await this.emojisRepository.update({ + id: In(ps.ids), + }, { + updatedAt: new Date(), + category: ps.category, + }); - await db.queryResultCache!.remove(['meta_emojis']); -}); + await this.db.queryResultCache!.remove(['meta_emojis']); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index 5b547b3b79..e6eb9eb9a6 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojisRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -35,18 +37,31 @@ export const paramDef = { required: ['id', 'name', 'aliases'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const emoji = await Emojis.findOneBy({ id: ps.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const emoji = await this.emojisRepository.findOneBy({ id: ps.id }); - if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji); + if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji); - await Emojis.update(emoji.id, { - updatedAt: new Date(), - name: ps.name, - category: ps.category, - aliases: ps.aliases, - }); + await this.emojisRepository.update(emoji.id, { + updatedAt: new Date(), + name: ps.name, + category: ps.category, + aliases: ps.aliases, + }); - await db.queryResultCache!.remove(['meta_emojis']); -}); + await this.db.queryResultCache!.remove(['meta_emojis']); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts index da54201473..789838661c 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import { deleteFile } from '@/services/drive/delete-file.js'; -import { DriveFiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DriveService } from '@/core/DriveService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -18,12 +20,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const files = await DriveFiles.findBy({ - userHost: ps.host, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - for (const file of files) { - deleteFile(file); + private driveService: DriveService, + ) { + super(meta, paramDef, async (ps, me) => { + const files = await this.driveFilesRepository.findBy({ + userHost: ps.host, + }); + + for (const file of files) { + this.driveService.deleteFile(file); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts index cb2be5ab37..476b821523 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; -import { Instances } from '@/models/index.js'; -import { toPuny } from '@/misc/convert-host.js'; -import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { InstancesRepository } from '@/models/index.js'; +import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -19,12 +21,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const instance = await Instances.findOneBy({ host: toPuny(ps.host) }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, - if (instance == null) { - throw new Error('instance not found'); - } + private utilityService: UtilityService, + private fetchInstanceMetadataService: FetchInstanceMetadataService, + ) { + super(meta, paramDef, async (ps, me) => { + const instance = await this.instancesRepository.findOneBy({ host: this.utilityService.toPuny(ps.host) }); + + if (instance == null) { + throw new Error('instance not found'); + } - fetchInstanceMetadata(instance, true); -}); + this.fetchInstanceMetadataService.fetchInstanceMetadata(instance, true); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts index b7ee27db64..67165dc47e 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import deleteFollowing from '@/services/following/delete.js'; -import { Followings, Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { FollowingsRepository, UsersRepository } from '@/models/index.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -18,17 +20,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const followings = await Followings.findBy({ - followerHost: ps.host, - }); - - const pairs = await Promise.all(followings.map(f => Promise.all([ - Users.findOneByOrFail({ id: f.followerId }), - Users.findOneByOrFail({ id: f.followeeId }), - ]))); - - for (const pair of pairs) { - deleteFollowing(pair[0], pair[1]); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private followingsRepository: FollowingsRepository, + + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + const followings = await this.followingsRepository.findBy({ + followerHost: ps.host, + }); + + const pairs = await Promise.all(followings.map(f => Promise.all([ + this.usersRepository.findOneByOrFail({ id: f.followerId }), + this.usersRepository.findOneByOrFail({ id: f.followeeId }), + ]))); + + for (const pair of pairs) { + this.userFollowingService.unfollow(pair[0], pair[1]); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index 278131fb37..b9eade5b40 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import { Instances } from '@/models/index.js'; -import { toPuny } from '@/misc/convert-host.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { InstancesRepository } from '@/models/index.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -19,14 +21,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const instance = await Instances.findOneBy({ host: toPuny(ps.host) }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, - if (instance == null) { - throw new Error('instance not found'); - } + private utilityService: UtilityService, + ) { + super(meta, paramDef, async (ps, me) => { + const instance = await this.instancesRepository.findOneBy({ host: this.utilityService.toPuny(ps.host) }); + + if (instance == null) { + throw new Error('instance not found'); + } - Instances.update({ host: toPuny(ps.host) }, { - isSuspended: ps.isSuspended, - }); -}); + this.instancesRepository.update({ host: this.utilityService.toPuny(ps.host) }, { + isSuspended: ps.isSuspended, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts index dd16473f30..e53d0bfcea 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts @@ -1,5 +1,7 @@ -import define from '../../define.js'; -import { db } from '@/db/postgre.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -15,14 +17,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const stats = await db.query(`SELECT * FROM pg_indexes;`).then(recs => { - const res = [] as { tablename: string; indexname: string; }[]; - for (const rec of recs) { - res.push(rec); - } - return res; - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + ) { + super(meta, paramDef, async () => { + const stats = await this.db.query('SELECT * FROM pg_indexes;').then(recs => { + const res = [] as { tablename: string; indexname: string; }[]; + for (const rec of recs) { + res.push(rec); + } + return res; + }); - return stats; -}); + return stats; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts index aca2540fd5..41014cb167 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts @@ -1,5 +1,7 @@ -import { db } from '@/db/postgre.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -26,24 +28,31 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const sizes = await - db.query(` +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + ) { + super(meta, paramDef, async () => { + const sizes = await this.db.query(` SELECT relname AS "table", reltuples as "count", pg_total_relation_size(C.oid) AS "size" FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE nspname NOT IN ('pg_catalog', 'information_schema') AND C.relkind <> 'i' AND nspname !~ '^pg_toast';`) - .then(recs => { - const res = {} as Record; - for (const rec of recs) { - res[rec.table] = { - count: parseInt(rec.count, 10), - size: parseInt(rec.size, 10), - }; - } - return res; - }); + .then(recs => { + const res = {} as Record; + for (const rec of recs) { + res[rec.table] = { + count: parseInt(rec.count, 10), + size: parseInt(rec.size, 10), + }; + } + return res; + }); - return sizes; -}); + return sizes; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts index e8b9cb3b09..eddaade919 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts @@ -1,5 +1,7 @@ -import { UserIps } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserIpsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -17,15 +19,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const ips = await UserIps.find({ - where: { userId: ps.userId }, - order: { createdAt: 'DESC' }, - take: 30, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userIpsRepository) + private userIpsRepository: UserIpsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const ips = await this.userIpsRepository.find({ + where: { userId: ps.userId }, + order: { createdAt: 'DESC' }, + take: 30, + }); - return ips.map(x => ({ - ip: x.ip, - createdAt: x.createdAt.toISOString(), - })); -}); + return ips.map(x => ({ + ip: x.ip, + createdAt: x.createdAt.toISOString(), + })); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/invite.ts b/packages/backend/src/server/api/endpoints/admin/invite.ts index 7e950cf87b..5fe341e5ca 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite.ts @@ -1,7 +1,9 @@ import rndstr from 'rndstr'; -import define from '../../define.js'; -import { RegistrationTickets } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistrationTicketsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -31,19 +33,29 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const code = rndstr({ - length: 8, - chars: '2-9A-HJ-NP-Z', // [0-9A-Z] w/o [01IO] (32 patterns) - }); - - await RegistrationTickets.insert({ - id: genId(), - createdAt: new Date(), - code, - }); - - return { - code, - }; -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registrationTicketsRepository) + private registrationTicketsRepository: RegistrationTicketsRepository, + + private idService: IdService, + ) { + super(meta, paramDef, async () => { + const code = rndstr({ + length: 8, + chars: '2-9A-HJ-NP-Z', // [0-9A-Z] w/o [01IO] (32 patterns) + }); + + await this.registrationTicketsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + code, + }); + + return { + code, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 8746119687..615c0a0e70 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -1,7 +1,9 @@ -import config from '@/config/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; +import { Inject, Injectable } from '@nestjs/common'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import define from '../../define.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MetaService } from '@/core/MetaService.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['meta'], @@ -340,91 +342,101 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const instance = await fetchMeta(true); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, - return { - maintainerName: instance.maintainerName, - maintainerEmail: instance.maintainerEmail, - version: config.version, - name: instance.name, - uri: config.url, - description: instance.description, - langs: instance.langs, - tosUrl: instance.ToSUrl, - repositoryUrl: instance.repositoryUrl, - feedbackUrl: instance.feedbackUrl, - disableRegistration: instance.disableRegistration, - disableLocalTimeline: instance.disableLocalTimeline, - disableGlobalTimeline: instance.disableGlobalTimeline, - driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, - driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, - emailRequiredForSignup: instance.emailRequiredForSignup, - enableHcaptcha: instance.enableHcaptcha, - hcaptchaSiteKey: instance.hcaptchaSiteKey, - enableRecaptcha: instance.enableRecaptcha, - recaptchaSiteKey: instance.recaptchaSiteKey, - swPublickey: instance.swPublicKey, - themeColor: instance.themeColor, - mascotImageUrl: instance.mascotImageUrl, - bannerUrl: instance.bannerUrl, - errorImageUrl: instance.errorImageUrl, - iconUrl: instance.iconUrl, - backgroundImageUrl: instance.backgroundImageUrl, - logoImageUrl: instance.logoImageUrl, - maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため - defaultLightTheme: instance.defaultLightTheme, - defaultDarkTheme: instance.defaultDarkTheme, - enableEmail: instance.enableEmail, - enableTwitterIntegration: instance.enableTwitterIntegration, - enableGithubIntegration: instance.enableGithubIntegration, - enableDiscordIntegration: instance.enableDiscordIntegration, - enableServiceWorker: instance.enableServiceWorker, - translatorAvailable: instance.deeplAuthKey != null, - pinnedPages: instance.pinnedPages, - pinnedClipId: instance.pinnedClipId, - cacheRemoteFiles: instance.cacheRemoteFiles, - useStarForReactionFallback: instance.useStarForReactionFallback, - pinnedUsers: instance.pinnedUsers, - hiddenTags: instance.hiddenTags, - blockedHosts: instance.blockedHosts, - hcaptchaSecretKey: instance.hcaptchaSecretKey, - recaptchaSecretKey: instance.recaptchaSecretKey, - sensitiveMediaDetection: instance.sensitiveMediaDetection, - sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity, - setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically, - enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos, - proxyAccountId: instance.proxyAccountId, - twitterConsumerKey: instance.twitterConsumerKey, - twitterConsumerSecret: instance.twitterConsumerSecret, - githubClientId: instance.githubClientId, - githubClientSecret: instance.githubClientSecret, - discordClientId: instance.discordClientId, - discordClientSecret: instance.discordClientSecret, - summalyProxy: instance.summalyProxy, - email: instance.email, - smtpSecure: instance.smtpSecure, - smtpHost: instance.smtpHost, - smtpPort: instance.smtpPort, - smtpUser: instance.smtpUser, - smtpPass: instance.smtpPass, - swPrivateKey: instance.swPrivateKey, - useObjectStorage: instance.useObjectStorage, - objectStorageBaseUrl: instance.objectStorageBaseUrl, - objectStorageBucket: instance.objectStorageBucket, - objectStoragePrefix: instance.objectStoragePrefix, - objectStorageEndpoint: instance.objectStorageEndpoint, - objectStorageRegion: instance.objectStorageRegion, - objectStoragePort: instance.objectStoragePort, - objectStorageAccessKey: instance.objectStorageAccessKey, - objectStorageSecretKey: instance.objectStorageSecretKey, - objectStorageUseSSL: instance.objectStorageUseSSL, - objectStorageUseProxy: instance.objectStorageUseProxy, - objectStorageSetPublicRead: instance.objectStorageSetPublicRead, - objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle, - deeplAuthKey: instance.deeplAuthKey, - deeplIsPro: instance.deeplIsPro, - enableIpLogging: instance.enableIpLogging, - enableActiveEmailValidation: instance.enableActiveEmailValidation, - }; -}); + private metaService: MetaService, + ) { + super(meta, paramDef, async (ps, me) => { + const instance = await this.metaService.fetch(true); + + return { + maintainerName: instance.maintainerName, + maintainerEmail: instance.maintainerEmail, + version: this.config.version, + name: instance.name, + uri: this.config.url, + description: instance.description, + langs: instance.langs, + tosUrl: instance.ToSUrl, + repositoryUrl: instance.repositoryUrl, + feedbackUrl: instance.feedbackUrl, + disableRegistration: instance.disableRegistration, + disableLocalTimeline: instance.disableLocalTimeline, + disableGlobalTimeline: instance.disableGlobalTimeline, + driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, + driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, + emailRequiredForSignup: instance.emailRequiredForSignup, + enableHcaptcha: instance.enableHcaptcha, + hcaptchaSiteKey: instance.hcaptchaSiteKey, + enableRecaptcha: instance.enableRecaptcha, + recaptchaSiteKey: instance.recaptchaSiteKey, + swPublickey: instance.swPublicKey, + themeColor: instance.themeColor, + mascotImageUrl: instance.mascotImageUrl, + bannerUrl: instance.bannerUrl, + errorImageUrl: instance.errorImageUrl, + iconUrl: instance.iconUrl, + backgroundImageUrl: instance.backgroundImageUrl, + logoImageUrl: instance.logoImageUrl, + maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため + defaultLightTheme: instance.defaultLightTheme, + defaultDarkTheme: instance.defaultDarkTheme, + enableEmail: instance.enableEmail, + enableTwitterIntegration: instance.enableTwitterIntegration, + enableGithubIntegration: instance.enableGithubIntegration, + enableDiscordIntegration: instance.enableDiscordIntegration, + enableServiceWorker: instance.enableServiceWorker, + translatorAvailable: instance.deeplAuthKey != null, + pinnedPages: instance.pinnedPages, + pinnedClipId: instance.pinnedClipId, + cacheRemoteFiles: instance.cacheRemoteFiles, + useStarForReactionFallback: instance.useStarForReactionFallback, + pinnedUsers: instance.pinnedUsers, + hiddenTags: instance.hiddenTags, + blockedHosts: instance.blockedHosts, + hcaptchaSecretKey: instance.hcaptchaSecretKey, + recaptchaSecretKey: instance.recaptchaSecretKey, + sensitiveMediaDetection: instance.sensitiveMediaDetection, + sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity, + setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically, + enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos, + proxyAccountId: instance.proxyAccountId, + twitterConsumerKey: instance.twitterConsumerKey, + twitterConsumerSecret: instance.twitterConsumerSecret, + githubClientId: instance.githubClientId, + githubClientSecret: instance.githubClientSecret, + discordClientId: instance.discordClientId, + discordClientSecret: instance.discordClientSecret, + summalyProxy: instance.summalyProxy, + email: instance.email, + smtpSecure: instance.smtpSecure, + smtpHost: instance.smtpHost, + smtpPort: instance.smtpPort, + smtpUser: instance.smtpUser, + smtpPass: instance.smtpPass, + swPrivateKey: instance.swPrivateKey, + useObjectStorage: instance.useObjectStorage, + objectStorageBaseUrl: instance.objectStorageBaseUrl, + objectStorageBucket: instance.objectStorageBucket, + objectStoragePrefix: instance.objectStoragePrefix, + objectStorageEndpoint: instance.objectStorageEndpoint, + objectStorageRegion: instance.objectStorageRegion, + objectStoragePort: instance.objectStoragePort, + objectStorageAccessKey: instance.objectStorageAccessKey, + objectStorageSecretKey: instance.objectStorageSecretKey, + objectStorageUseSSL: instance.objectStorageUseSSL, + objectStorageUseProxy: instance.objectStorageUseProxy, + objectStorageSetPublicRead: instance.objectStorageSetPublicRead, + objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle, + deeplAuthKey: instance.deeplAuthKey, + deeplIsPro: instance.deeplIsPro, + enableIpLogging: instance.enableIpLogging, + enableActiveEmailValidation: instance.enableActiveEmailValidation, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts index 7b209c2d99..fe200da6ad 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import { Users } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -18,20 +20,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const user = await Users.findOneBy({ id: ps.userId }); - - if (user == null) { - throw new Error('user not found'); - } - - if (user.isAdmin) { - throw new Error('cannot mark as moderator if admin user'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + if (user.isAdmin) { + throw new Error('cannot mark as moderator if admin user'); + } + + await this.usersRepository.update(user.id, { + isModerator: true, + }); + + this.globalEventService.publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: true }); + }); } - - await Users.update(user.id, { - isModerator: true, - }); - - publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: true }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts index a01e9f3c69..3dc7158ba9 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; -import { Users } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -18,16 +20,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const user = await Users.findOneBy({ id: ps.userId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user == null) { - throw new Error('user not found'); - } + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } - await Users.update(user.id, { - isModerator: false, - }); + await this.usersRepository.update(user.id, { + isModerator: false, + }); - publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: false }); -}); + this.globalEventService.publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: false }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index 68a17867b2..a179f163df 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { PromoNotesRepository } from '@/models/index.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { getNote } from '../../../common/getters.js'; -import { PromoNotes } from '@/models/index.js'; export const meta = { tags: ['admin'], @@ -34,21 +36,31 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.promoNotesRepository) + private promoNotesRepository: PromoNotesRepository, - const exist = await PromoNotes.findOneBy({ noteId: note.id }); + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(e => { + if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw e; + }); - if (exist != null) { - throw new ApiError(meta.errors.alreadyPromoted); - } + const exist = await this.promoNotesRepository.findOneBy({ noteId: note.id }); + + if (exist != null) { + throw new ApiError(meta.errors.alreadyPromoted); + } - await PromoNotes.insert({ - noteId: note.id, - expiresAt: new Date(ps.expiresAt), - userId: note.userId, - }); -}); + await this.promoNotesRepository.insert({ + noteId: note.id, + expiresAt: new Date(ps.expiresAt), + userId: note.userId, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts index 8f015c280a..9129f53f06 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts @@ -1,6 +1,7 @@ -import define from '../../../define.js'; -import { destroy } from '@/queue/index.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { tags: ['admin'], @@ -16,8 +17,16 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - destroy(); +@Injectable() +export default class extends Endpoint { + constructor( + private moderationLogService: ModerationLogService, + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.destroy(); - insertModerationLog(me, 'clearQueue'); -}); + this.moderationLogService.insertModerationLog(me, 'clearQueue'); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts index 70f7d77de4..4b5be70d56 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts @@ -1,6 +1,7 @@ -import { deliverQueue } from '@/queue/queues.js'; import { URL } from 'node:url'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DeliverQueue } from '@/core/queue/QueueModule.js'; export const meta = { tags: ['admin'], @@ -39,21 +40,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const jobs = await deliverQueue.getJobs(['delayed']); - - const res = [] as [string, number][]; - - for (const job of jobs) { - const host = new URL(job.data.to).host; - if (res.find(x => x[0] === host)) { - res.find(x => x[0] === host)![1]++; - } else { - res.push([host, 1]); - } - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject('queue:deliver') public deliverQueue: DeliverQueue, + ) { + super(meta, paramDef, async (ps, me) => { + const jobs = await this.deliverQueue.getJobs(['delayed']); + + const res = [] as [string, number][]; - res.sort((a, b) => b[1] - a[1]); + for (const job of jobs) { + const host = new URL(job.data.to).host; + if (res.find(x => x[0] === host)) { + res.find(x => x[0] === host)![1]++; + } else { + res.push([host, 1]); + } + } - return res; -}); + res.sort((a, b) => b[1] - a[1]); + + return res; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts index 2235ce8f97..715974e917 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts @@ -1,6 +1,7 @@ import { URL } from 'node:url'; -import define from '../../../define.js'; -import { inboxQueue } from '@/queue/queues.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { InboxQueue } from '@/core/queue/QueueModule.js'; export const meta = { tags: ['admin'], @@ -39,21 +40,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const jobs = await inboxQueue.getJobs(['delayed']); - - const res = [] as [string, number][]; - - for (const job of jobs) { - const host = new URL(job.data.signature.keyId).host; - if (res.find(x => x[0] === host)) { - res.find(x => x[0] === host)![1]++; - } else { - res.push([host, 1]); - } - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject('queue:inbox') public inboxQueue: InboxQueue, + ) { + super(meta, paramDef, async (ps, me) => { + const jobs = await this.inboxQueue.getJobs(['delayed']); + + const res = [] as [string, number][]; - res.sort((a, b) => b[1] - a[1]); + for (const job of jobs) { + const host = new URL(job.data.signature.keyId).host; + if (res.find(x => x[0] === host)) { + res.find(x => x[0] === host)![1]++; + } else { + res.push([host, 1]); + } + } - return res; -}); + res.sort((a, b) => b[1] - a[1]); + + return res; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts index 988b5a5e35..f2ca81a8da 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts @@ -1,5 +1,6 @@ -import { deliverQueue, inboxQueue, dbQueue, objectStorageQueue } from '@/queue/queues.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/queue/QueueModule.js'; export const meta = { tags: ['admin'], @@ -38,16 +39,29 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const deliverJobCounts = await deliverQueue.getJobCounts(); - const inboxJobCounts = await inboxQueue.getJobCounts(); - const dbJobCounts = await dbQueue.getJobCounts(); - const objectStorageJobCounts = await objectStorageQueue.getJobCounts(); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject('queue:system') public systemQueue: SystemQueue, + @Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue, + @Inject('queue:deliver') public deliverQueue: DeliverQueue, + @Inject('queue:inbox') public inboxQueue: InboxQueue, + @Inject('queue:db') public dbQueue: DbQueue, + @Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue, + @Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue, + ) { + super(meta, paramDef, async (ps, me) => { + const deliverJobCounts = await this.deliverQueue.getJobCounts(); + const inboxJobCounts = await this.inboxQueue.getJobCounts(); + const dbJobCounts = await this.dbQueue.getJobCounts(); + const objectStorageJobCounts = await this.objectStorageQueue.getJobCounts(); - return { - deliver: deliverJobCounts, - inbox: inboxJobCounts, - db: dbJobCounts, - objectStorage: objectStorageJobCounts, - }; -}); + return { + deliver: deliverJobCounts, + inbox: inboxJobCounts, + db: dbJobCounts, + objectStorage: objectStorageJobCounts, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/relays/add.ts b/packages/backend/src/server/api/endpoints/admin/relays/add.ts index 348e9baca1..32ad79918f 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/add.ts @@ -1,6 +1,7 @@ import { URL } from 'node:url'; -import define from '../../../define.js'; -import { addRelay } from '@/services/relay.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RelayService } from '@/core/RelayService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -54,12 +55,19 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - try { - if (new URL(ps.inbox).protocol !== 'https:') throw 'https only'; - } catch { - throw new ApiError(meta.errors.invalidUrl); - } +@Injectable() +export default class extends Endpoint { + constructor( + private relayService: RelayService, + ) { + super(meta, paramDef, async (ps, me) => { + try { + if (new URL(ps.inbox).protocol !== 'https:') throw 'https only'; + } catch { + throw new ApiError(meta.errors.invalidUrl); + } - return await addRelay(ps.inbox); -}); + return await this.relayService.addRelay(ps.inbox); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/relays/list.ts b/packages/backend/src/server/api/endpoints/admin/relays/list.ts index 89ec651e61..079b351add 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/list.ts @@ -1,5 +1,6 @@ -import define from '../../../define.js'; -import { listRelay } from '@/services/relay.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RelayService } from '@/core/RelayService.js'; export const meta = { tags: ['admin'], @@ -46,6 +47,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - return await listRelay(); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private relayService: RelayService, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.relayService.listRelay(); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts index b59cf72c58..9dc4105d14 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts @@ -1,5 +1,6 @@ -import define from '../../../define.js'; -import { removeRelay } from '@/services/relay.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RelayService } from '@/core/RelayService.js'; export const meta = { tags: ['admin'], @@ -17,6 +18,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - return await removeRelay(ps.inbox); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private relayService: RelayService, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.relayService.removeRelay(ps.inbox); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index be4c2dceed..7446746b45 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import rndstr from 'rndstr'; -import { Users, UserProfiles } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -32,29 +34,40 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const user = await Users.findOneBy({ id: ps.userId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user == null) { - throw new Error('user not found'); - } + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); - if (user.isAdmin) { - throw new Error('cannot reset password of admin'); - } + if (user == null) { + throw new Error('user not found'); + } - const passwd = rndstr('a-zA-Z0-9', 8); + if (user.isAdmin) { + throw new Error('cannot reset password of admin'); + } - // Generate hash of password - const hash = bcrypt.hashSync(passwd); + const passwd = rndstr('a-zA-Z0-9', 8); - await UserProfiles.update({ - userId: user.id, - }, { - password: hash, - }); + // Generate hash of password + const hash = bcrypt.hashSync(passwd); - return { - password: passwd, - }; -}); + await this.userProfilesRepository.update({ + userId: user.id, + }, { + password: hash, + }); + + return { + password: passwd, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index 3edae4a85f..b5828ae9be 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -1,9 +1,10 @@ -import define from '../../define.js'; -import { AbuseUserReports, Users } from '@/models/index.js'; -import { getInstanceActor } from '@/services/instance-actor.js'; -import { deliver } from '@/queue/index.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import { renderFlag } from '@/remote/activitypub/renderer/flag.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; +import { InstanceActorService } from '@/core/InstanceActorService.js'; +import { QueueService } from '@/core/QueueService.js'; +import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -21,24 +22,41 @@ export const paramDef = { required: ['reportId'], } as const; -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const report = await AbuseUserReports.findOneByOrFail({ id: ps.reportId }); - - if (report == null) { - throw new Error('report not found'); - } +// TODO: ロジックをサービスに切り出す - if (ps.forward && report.targetUserHost != null) { - const actor = await getInstanceActor(); - const targetUser = await Users.findOneByOrFail({ id: report.targetUserId }); - - deliver(actor, renderActivity(renderFlag(actor, [targetUser.uri!], report.comment)), targetUser.inbox); +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.abuseUserReportsRepository) + private abuseUserReportsRepository: AbuseUserReportsRepository, + + private queueService: QueueService, + private instanceActorService: InstanceActorService, + private apRendererService: ApRendererService, + ) { + super(meta, paramDef, async (ps, me) => { + const report = await this.abuseUserReportsRepository.findOneBy({ id: ps.reportId }); + + if (report == null) { + throw new Error('report not found'); + } + + if (ps.forward && report.targetUserHost != null) { + const actor = await this.instanceActorService.getInstanceActor(); + const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId }); + + this.queueService.deliver(actor, this.apRendererService.renderActivity(this.apRendererService.renderFlag(actor, [targetUser.uri!], report.comment)), targetUser.inbox); + } + + await this.abuseUserReportsRepository.update(report.id, { + resolved: true, + assigneeId: me.id, + forwarded: ps.forward && report.targetUserHost != null, + }); + }); } - - await AbuseUserReports.update(report.id, { - resolved: true, - assigneeId: me.id, - forwarded: ps.forward && report.targetUserHost != null, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/send-email.ts b/packages/backend/src/server/api/endpoints/admin/send-email.ts index bbdd66e4c9..7434bf4c91 100644 --- a/packages/backend/src/server/api/endpoints/admin/send-email.ts +++ b/packages/backend/src/server/api/endpoints/admin/send-email.ts @@ -1,5 +1,6 @@ -import define from '../../define.js'; -import { sendEmail } from '@/services/send-email.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmailService } from '@/core/EmailService.js'; export const meta = { tags: ['admin'], @@ -19,6 +20,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - await sendEmail(ps.to, ps.subject, ps.text, ps.text); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private emailService: EmailService, + ) { + super(meta, paramDef, async (ps, me) => { + await this.emailService.sendEmail(ps.to, ps.subject, ps.text, ps.text); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts index 85c6fb82e7..9c576dffe9 100644 --- a/packages/backend/src/server/api/endpoints/admin/server-info.ts +++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts @@ -1,8 +1,10 @@ import * as os from 'node:os'; import si from 'systeminformation'; -import define from '../../define.js'; -import { redisClient } from '../../../../db/redis.js'; -import { db } from '@/db/postgre.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import Redis from 'ioredis'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -94,34 +96,46 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const memStats = await si.mem(); - const fsStats = await si.fsSize(); - const netInterface = await si.networkInterfaceDefault(); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, - const redisServerInfo = await redisClient.info('Server'); - const m = redisServerInfo.match(new RegExp('^redis_version:(.*)', 'm')); - const redis_version = m?.[1]; + @Inject(DI.redis) + private redisClient: Redis.Redis, - return { - machine: os.hostname(), - os: os.platform(), - node: process.version, - psql: await db.query('SHOW server_version').then(x => x[0].server_version), - redis: redis_version, - cpu: { - model: os.cpus()[0].model, - cores: os.cpus().length, - }, - mem: { - total: memStats.total, - }, - fs: { - total: fsStats[0].size, - used: fsStats[0].used, - }, - net: { - interface: netInterface, - }, - }; -}); + ) { + super(meta, paramDef, async () => { + const memStats = await si.mem(); + const fsStats = await si.fsSize(); + const netInterface = await si.networkInterfaceDefault(); + + const redisServerInfo = await this.redisClient.info('Server'); + const m = redisServerInfo.match(new RegExp('^redis_version:(.*)', 'm')); + const redis_version = m?.[1]; + + return { + machine: os.hostname(), + os: os.platform(), + node: process.version, + psql: await this.db.query('SHOW server_version').then(x => x[0].server_version), + redis: redis_version, + cpu: { + model: os.cpus()[0].model, + cores: os.cpus().length, + }, + mem: { + total: memStats.total, + }, + fs: { + total: fsStats[0].size, + used: fsStats[0].used, + }, + net: { + interface: netInterface, + }, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts index 3545536aa2..2424cac425 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts @@ -1,6 +1,8 @@ -import define from '../../define.js'; -import { ModerationLogs } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ModerationLogsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -59,10 +61,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const query = makePaginationQuery(ModerationLogs.createQueryBuilder('report'), ps.sinceId, ps.untilId); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.moderationLogsRepository) + private moderationLogsRepository: ModerationLogsRepository, - const reports = await query.take(ps.limit).getMany(); + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.moderationLogsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId); - return await ModerationLogs.packMany(reports); -}); + const reports = await query.take(ps.limit).getMany(); + + return await this.moderationLogEntityService.packMany(reports); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 0d866b3113..b50564210b 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -1,5 +1,7 @@ -import { Signins, UserProfiles, Users } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, SigninsRepository, UserProfilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -22,55 +24,69 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const [user, profile] = await Promise.all([ - Users.findOneBy({ id: ps.userId }), - UserProfiles.findOneBy({ userId: ps.userId }), - ]); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user == null || profile == null) { - throw new Error('user not found'); - } + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, - const _me = await Users.findOneByOrFail({ id: me.id }); - if ((_me.isModerator && !_me.isAdmin) && user.isAdmin) { - throw new Error('cannot show info of admin'); - } + @Inject(DI.signinsRepository) + private signinsRepository: SigninsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const [user, profile] = await Promise.all([ + this.usersRepository.findOneBy({ id: ps.userId }), + this.userProfilesRepository.findOneBy({ userId: ps.userId }), + ]); - if (!_me.isAdmin) { - return { - isModerator: user.isModerator, - isSilenced: user.isSilenced, - isSuspended: user.isSuspended, - }; - } + if (user == null || profile == null) { + throw new Error('user not found'); + } - const maskedKeys = ['accessToken', 'accessTokenSecret', 'refreshToken']; - Object.keys(profile.integrations).forEach(integration => { - maskedKeys.forEach(key => profile.integrations[integration][key] = ''); - }); + const _me = await this.usersRepository.findOneByOrFail({ id: me.id }); + if ((_me.isModerator && !_me.isAdmin) && user.isAdmin) { + throw new Error('cannot show info of admin'); + } - const signins = await Signins.findBy({ userId: user.id }); + if (!_me.isAdmin) { + return { + isModerator: user.isModerator, + isSilenced: user.isSilenced, + isSuspended: user.isSuspended, + }; + } - return { - email: profile.email, - emailVerified: profile.emailVerified, - autoAcceptFollowed: profile.autoAcceptFollowed, - noCrawle: profile.noCrawle, - alwaysMarkNsfw: profile.alwaysMarkNsfw, - autoSensitive: profile.autoSensitive, - carefulBot: profile.carefulBot, - injectFeaturedNote: profile.injectFeaturedNote, - receiveAnnouncementEmail: profile.receiveAnnouncementEmail, - integrations: profile.integrations, - mutedWords: profile.mutedWords, - mutedInstances: profile.mutedInstances, - mutingNotificationTypes: profile.mutingNotificationTypes, - isModerator: user.isModerator, - isSilenced: user.isSilenced, - isSuspended: user.isSuspended, - lastActiveDate: user.lastActiveDate, - moderationNote: profile.moderationNote, - signins, - }; -}); + const maskedKeys = ['accessToken', 'accessTokenSecret', 'refreshToken']; + Object.keys(profile.integrations).forEach(integration => { + maskedKeys.forEach(key => profile.integrations[integration][key] = ''); + }); + + const signins = await this.signinsRepository.findBy({ userId: user.id }); + + return { + email: profile.email, + emailVerified: profile.emailVerified, + autoAcceptFollowed: profile.autoAcceptFollowed, + noCrawle: profile.noCrawle, + alwaysMarkNsfw: profile.alwaysMarkNsfw, + autoSensitive: profile.autoSensitive, + carefulBot: profile.carefulBot, + injectFeaturedNote: profile.injectFeaturedNote, + receiveAnnouncementEmail: profile.receiveAnnouncementEmail, + integrations: profile.integrations, + mutedWords: profile.mutedWords, + mutedInstances: profile.mutedInstances, + mutingNotificationTypes: profile.mutingNotificationTypes, + isModerator: user.isModerator, + isSilenced: user.isSilenced, + isSuspended: user.isSuspended, + lastActiveDate: user.lastActiveDate, + moderationNote: profile.moderationNote, + signins, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 8e09e72d5b..8d11e3ea7a 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -1,5 +1,7 @@ -import { Users } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -38,46 +40,54 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Users.createQueryBuilder('user'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.usersRepository.createQueryBuilder('user'); - switch (ps.state) { - case 'available': query.where('user.isSuspended = FALSE'); break; - case 'admin': query.where('user.isAdmin = TRUE'); break; - case 'moderator': query.where('user.isModerator = TRUE'); break; - case 'adminOrModerator': query.where('user.isAdmin = TRUE OR user.isModerator = TRUE'); break; - case 'alive': query.where('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; - case 'silenced': query.where('user.isSilenced = TRUE'); break; - case 'suspended': query.where('user.isSuspended = TRUE'); break; - } + switch (ps.state) { + case 'available': query.where('user.isSuspended = FALSE'); break; + case 'admin': query.where('user.isAdmin = TRUE'); break; + case 'moderator': query.where('user.isModerator = TRUE'); break; + case 'adminOrModerator': query.where('user.isAdmin = TRUE OR user.isModerator = TRUE'); break; + case 'alive': query.where('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; + case 'silenced': query.where('user.isSilenced = TRUE'); break; + case 'suspended': query.where('user.isSuspended = TRUE'); break; + } - switch (ps.origin) { - case 'local': query.andWhere('user.host IS NULL'); break; - case 'remote': query.andWhere('user.host IS NOT NULL'); break; - } + switch (ps.origin) { + case 'local': query.andWhere('user.host IS NULL'); break; + case 'remote': query.andWhere('user.host IS NOT NULL'); break; + } - if (ps.username) { - query.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' }); - } + if (ps.username) { + query.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' }); + } - if (ps.hostname) { - query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() }); - } + if (ps.hostname) { + query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() }); + } - switch (ps.sort) { - case '+follower': query.orderBy('user.followersCount', 'DESC'); break; - case '-follower': query.orderBy('user.followersCount', 'ASC'); break; - case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; - case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; - case '+updatedAt': query.orderBy('user.updatedAt', 'DESC', 'NULLS LAST'); break; - case '-updatedAt': query.orderBy('user.updatedAt', 'ASC', 'NULLS FIRST'); break; - default: query.orderBy('user.id', 'ASC'); break; - } + switch (ps.sort) { + case '+follower': query.orderBy('user.followersCount', 'DESC'); break; + case '-follower': query.orderBy('user.followersCount', 'ASC'); break; + case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; + case '+updatedAt': query.orderBy('user.updatedAt', 'DESC', 'NULLS LAST'); break; + case '-updatedAt': query.orderBy('user.updatedAt', 'ASC', 'NULLS FIRST'); break; + default: query.orderBy('user.id', 'ASC'); break; + } - query.take(ps.limit); - query.skip(ps.offset); + query.take(ps.limit); + query.skip(ps.offset); - const users = await query.getMany(); + const users = await query.getMany(); - return await Users.packMany(users, me, { detail: true }); -}); + return await this.userEntityService.packMany(users, me, { detail: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/silence-user.ts b/packages/backend/src/server/api/endpoints/admin/silence-user.ts index 17b9f3b5a0..bec8f7719e 100644 --- a/packages/backend/src/server/api/endpoints/admin/silence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/silence-user.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; -import { Users } from '@/models/index.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { publishInternalEvent } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { UsersRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -19,24 +21,35 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); - - if (user == null) { - throw new Error('user not found'); - } - - if (user.isAdmin) { - throw new Error('cannot silence admin'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private moderationLogService: ModerationLogService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + if (user.isAdmin) { + throw new Error('cannot silence admin'); + } + + await this.usersRepository.update(user.id, { + isSilenced: true, + }); + + this.globalEventService.publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: true }); + + this.moderationLogService.insertModerationLog(me, 'silence', { + targetId: user.id, + }); + }); } - - await Users.update(user.id, { - isSilenced: true, - }); - - publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: true }); - - insertModerationLog(me, 'silence', { - targetId: user.id, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index ed513eda08..fa057dadb6 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -1,10 +1,12 @@ -import define from '../../define.js'; -import deleteFollowing from '@/services/following/delete.js'; -import { Users, Followings, Notifications } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { doPostSuspend } from '@/services/suspend-user.js'; -import { publishUserEvent } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, FollowingsRepository, NotificationsRepository } from '@/models/index.js'; +import type { User } from '@/models/entities/User.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { UserSuspendService } from '@/core/UserSuspendService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -22,64 +24,83 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); - - if (user == null) { - throw new Error('user not found'); - } - - if (user.isAdmin) { - throw new Error('cannot suspend admin'); - } - - if (user.isModerator) { - throw new Error('cannot suspend moderator'); - } - - await Users.update(user.id, { - isSuspended: true, - }); - - insertModerationLog(me, 'suspend', { - targetId: user.id, - }); - - // Terminate streaming - if (Users.isLocalUser(user)) { - publishUserEvent(user.id, 'terminate', {}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + @Inject(DI.notificationsRepository) + private notificationsRepository: NotificationsRepository, + + private userFollowingService: UserFollowingService, + private userSuspendService: UserSuspendService, + private moderationLogService: ModerationLogService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + if (user.isAdmin) { + throw new Error('cannot suspend admin'); + } + + if (user.isModerator) { + throw new Error('cannot suspend moderator'); + } + + await this.usersRepository.update(user.id, { + isSuspended: true, + }); + + this.moderationLogService.insertModerationLog(me, 'suspend', { + targetId: user.id, + }); + + // Terminate streaming + if (this.userEntityService.isLocalUser(user)) { + this.globalEventService.publishUserEvent(user.id, 'terminate', {}); + } + + (async () => { + await this.userSuspendService.doPostSuspend(user).catch(e => {}); + await this.#unFollowAll(user).catch(e => {}); + await this.#readAllNotify(user).catch(e => {}); + })(); + }); } - (async () => { - await doPostSuspend(user).catch(e => {}); - await unFollowAll(user).catch(e => {}); - await readAllNotify(user).catch(e => {}); - })(); -}); - -async function unFollowAll(follower: User) { - const followings = await Followings.findBy({ - followerId: follower.id, - }); - - for (const following of followings) { - const followee = await Users.findOneBy({ - id: following.followeeId, + async #unFollowAll(follower: User) { + const followings = await this.followingsRepository.findBy({ + followerId: follower.id, }); - - if (followee == null) { - throw `Cant find followee ${following.followeeId}`; + + for (const following of followings) { + const followee = await this.usersRepository.findOneBy({ + id: following.followeeId, + }); + + if (followee == null) { + throw `Cant find followee ${following.followeeId}`; + } + + await this.userFollowingService.unfollow(follower, followee, true); } - - await deleteFollowing(follower, followee, true); } -} - -async function readAllNotify(notifier: User) { - await Notifications.update({ - notifierId: notifier.id, - isRead: false, - }, { - isRead: true, - }); + + async #readAllNotify(notifier: User) { + await this.notificationsRepository.update({ + notifierId: notifier.id, + isRead: false, + }, { + isRead: true, + }); + } } diff --git a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts index a4b373f5c7..b4671a2f41 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; -import { Users } from '@/models/index.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { publishInternalEvent } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -19,20 +21,31 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); - - if (user == null) { - throw new Error('user not found'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private moderationLogService: ModerationLogService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + await this.usersRepository.update(user.id, { + isSilenced: false, + }); + + this.globalEventService.publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: false }); + + this.moderationLogService.insertModerationLog(me, 'unsilence', { + targetId: user.id, + }); + }); } - - await Users.update(user.id, { - isSilenced: false, - }); - - publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: false }); - - insertModerationLog(me, 'unsilence', { - targetId: user.id, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index 5cf26251be..96283d251f 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; -import { Users } from '@/models/index.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { doPostUnsuspend } from '@/services/unsuspend-user.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { UserSuspendService } from '@/core/UserSuspendService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -19,20 +21,31 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); - - if (user == null) { - throw new Error('user not found'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private userSuspendService: UserSuspendService, + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + await this.usersRepository.update(user.id, { + isSuspended: false, + }); + + this.moderationLogService.insertModerationLog(me, 'unsuspend', { + targetId: user.id, + }); + + this.userSuspendService.doPostUnsuspend(user); + }); } - - await Users.update(user.id, { - isSuspended: false, - }); - - insertModerationLog(me, 'unsuspend', { - targetId: user.id, - }); - - doPostUnsuspend(user); -}); +} diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index f14aa41050..968ed4d26d 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -1,8 +1,10 @@ -import { Meta } from '@/models/entities/meta.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { Meta } from '@/models/entities/Meta.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; -import { db } from '@/db/postgre.js'; -import define from '../../define.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -107,340 +109,350 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const set = {} as Partial; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const set = {} as Partial; - if (typeof ps.disableRegistration === 'boolean') { - set.disableRegistration = ps.disableRegistration; - } - - if (typeof ps.disableLocalTimeline === 'boolean') { - set.disableLocalTimeline = ps.disableLocalTimeline; - } - - if (typeof ps.disableGlobalTimeline === 'boolean') { - set.disableGlobalTimeline = ps.disableGlobalTimeline; - } - - if (typeof ps.useStarForReactionFallback === 'boolean') { - set.useStarForReactionFallback = ps.useStarForReactionFallback; - } + if (typeof ps.disableRegistration === 'boolean') { + set.disableRegistration = ps.disableRegistration; + } - if (Array.isArray(ps.pinnedUsers)) { - set.pinnedUsers = ps.pinnedUsers.filter(Boolean); - } - - if (Array.isArray(ps.hiddenTags)) { - set.hiddenTags = ps.hiddenTags.filter(Boolean); - } + if (typeof ps.disableLocalTimeline === 'boolean') { + set.disableLocalTimeline = ps.disableLocalTimeline; + } - if (Array.isArray(ps.blockedHosts)) { - set.blockedHosts = ps.blockedHosts.filter(Boolean); - } + if (typeof ps.disableGlobalTimeline === 'boolean') { + set.disableGlobalTimeline = ps.disableGlobalTimeline; + } - if (ps.themeColor !== undefined) { - set.themeColor = ps.themeColor; - } - - if (ps.mascotImageUrl !== undefined) { - set.mascotImageUrl = ps.mascotImageUrl; - } + if (typeof ps.useStarForReactionFallback === 'boolean') { + set.useStarForReactionFallback = ps.useStarForReactionFallback; + } - if (ps.bannerUrl !== undefined) { - set.bannerUrl = ps.bannerUrl; - } - - if (ps.iconUrl !== undefined) { - set.iconUrl = ps.iconUrl; - } - - if (ps.backgroundImageUrl !== undefined) { - set.backgroundImageUrl = ps.backgroundImageUrl; - } - - if (ps.logoImageUrl !== undefined) { - set.logoImageUrl = ps.logoImageUrl; - } - - if (ps.name !== undefined) { - set.name = ps.name; - } - - if (ps.description !== undefined) { - set.description = ps.description; - } - - if (ps.defaultLightTheme !== undefined) { - set.defaultLightTheme = ps.defaultLightTheme; - } - - if (ps.defaultDarkTheme !== undefined) { - set.defaultDarkTheme = ps.defaultDarkTheme; - } - - if (ps.localDriveCapacityMb !== undefined) { - set.localDriveCapacityMb = ps.localDriveCapacityMb; - } - - if (ps.remoteDriveCapacityMb !== undefined) { - set.remoteDriveCapacityMb = ps.remoteDriveCapacityMb; - } - - if (ps.cacheRemoteFiles !== undefined) { - set.cacheRemoteFiles = ps.cacheRemoteFiles; - } - - if (ps.emailRequiredForSignup !== undefined) { - set.emailRequiredForSignup = ps.emailRequiredForSignup; - } - - if (ps.enableHcaptcha !== undefined) { - set.enableHcaptcha = ps.enableHcaptcha; - } - - if (ps.hcaptchaSiteKey !== undefined) { - set.hcaptchaSiteKey = ps.hcaptchaSiteKey; - } - - if (ps.hcaptchaSecretKey !== undefined) { - set.hcaptchaSecretKey = ps.hcaptchaSecretKey; - } - - if (ps.enableRecaptcha !== undefined) { - set.enableRecaptcha = ps.enableRecaptcha; - } - - if (ps.recaptchaSiteKey !== undefined) { - set.recaptchaSiteKey = ps.recaptchaSiteKey; - } - - if (ps.recaptchaSecretKey !== undefined) { - set.recaptchaSecretKey = ps.recaptchaSecretKey; - } - - if (ps.sensitiveMediaDetection !== undefined) { - set.sensitiveMediaDetection = ps.sensitiveMediaDetection; - } - - if (ps.sensitiveMediaDetectionSensitivity !== undefined) { - set.sensitiveMediaDetectionSensitivity = ps.sensitiveMediaDetectionSensitivity; - } + if (Array.isArray(ps.pinnedUsers)) { + set.pinnedUsers = ps.pinnedUsers.filter(Boolean); + } - if (ps.setSensitiveFlagAutomatically !== undefined) { - set.setSensitiveFlagAutomatically = ps.setSensitiveFlagAutomatically; - } + if (Array.isArray(ps.hiddenTags)) { + set.hiddenTags = ps.hiddenTags.filter(Boolean); + } - if (ps.enableSensitiveMediaDetectionForVideos !== undefined) { - set.enableSensitiveMediaDetectionForVideos = ps.enableSensitiveMediaDetectionForVideos; - } + if (Array.isArray(ps.blockedHosts)) { + set.blockedHosts = ps.blockedHosts.filter(Boolean); + } - if (ps.proxyAccountId !== undefined) { - set.proxyAccountId = ps.proxyAccountId; - } + if (ps.themeColor !== undefined) { + set.themeColor = ps.themeColor; + } - if (ps.maintainerName !== undefined) { - set.maintainerName = ps.maintainerName; - } + if (ps.mascotImageUrl !== undefined) { + set.mascotImageUrl = ps.mascotImageUrl; + } - if (ps.maintainerEmail !== undefined) { - set.maintainerEmail = ps.maintainerEmail; - } + if (ps.bannerUrl !== undefined) { + set.bannerUrl = ps.bannerUrl; + } - if (Array.isArray(ps.langs)) { - set.langs = ps.langs.filter(Boolean); - } + if (ps.iconUrl !== undefined) { + set.iconUrl = ps.iconUrl; + } - if (Array.isArray(ps.pinnedPages)) { - set.pinnedPages = ps.pinnedPages.filter(Boolean); - } + if (ps.backgroundImageUrl !== undefined) { + set.backgroundImageUrl = ps.backgroundImageUrl; + } - if (ps.pinnedClipId !== undefined) { - set.pinnedClipId = ps.pinnedClipId; - } + if (ps.logoImageUrl !== undefined) { + set.logoImageUrl = ps.logoImageUrl; + } - if (ps.summalyProxy !== undefined) { - set.summalyProxy = ps.summalyProxy; - } + if (ps.name !== undefined) { + set.name = ps.name; + } - if (ps.enableTwitterIntegration !== undefined) { - set.enableTwitterIntegration = ps.enableTwitterIntegration; - } + if (ps.description !== undefined) { + set.description = ps.description; + } - if (ps.twitterConsumerKey !== undefined) { - set.twitterConsumerKey = ps.twitterConsumerKey; - } + if (ps.defaultLightTheme !== undefined) { + set.defaultLightTheme = ps.defaultLightTheme; + } - if (ps.twitterConsumerSecret !== undefined) { - set.twitterConsumerSecret = ps.twitterConsumerSecret; - } + if (ps.defaultDarkTheme !== undefined) { + set.defaultDarkTheme = ps.defaultDarkTheme; + } - if (ps.enableGithubIntegration !== undefined) { - set.enableGithubIntegration = ps.enableGithubIntegration; - } + if (ps.localDriveCapacityMb !== undefined) { + set.localDriveCapacityMb = ps.localDriveCapacityMb; + } - if (ps.githubClientId !== undefined) { - set.githubClientId = ps.githubClientId; - } + if (ps.remoteDriveCapacityMb !== undefined) { + set.remoteDriveCapacityMb = ps.remoteDriveCapacityMb; + } - if (ps.githubClientSecret !== undefined) { - set.githubClientSecret = ps.githubClientSecret; - } + if (ps.cacheRemoteFiles !== undefined) { + set.cacheRemoteFiles = ps.cacheRemoteFiles; + } - if (ps.enableDiscordIntegration !== undefined) { - set.enableDiscordIntegration = ps.enableDiscordIntegration; - } + if (ps.emailRequiredForSignup !== undefined) { + set.emailRequiredForSignup = ps.emailRequiredForSignup; + } - if (ps.discordClientId !== undefined) { - set.discordClientId = ps.discordClientId; - } + if (ps.enableHcaptcha !== undefined) { + set.enableHcaptcha = ps.enableHcaptcha; + } - if (ps.discordClientSecret !== undefined) { - set.discordClientSecret = ps.discordClientSecret; - } + if (ps.hcaptchaSiteKey !== undefined) { + set.hcaptchaSiteKey = ps.hcaptchaSiteKey; + } - if (ps.enableEmail !== undefined) { - set.enableEmail = ps.enableEmail; - } + if (ps.hcaptchaSecretKey !== undefined) { + set.hcaptchaSecretKey = ps.hcaptchaSecretKey; + } - if (ps.email !== undefined) { - set.email = ps.email; - } + if (ps.enableRecaptcha !== undefined) { + set.enableRecaptcha = ps.enableRecaptcha; + } - if (ps.smtpSecure !== undefined) { - set.smtpSecure = ps.smtpSecure; - } + if (ps.recaptchaSiteKey !== undefined) { + set.recaptchaSiteKey = ps.recaptchaSiteKey; + } - if (ps.smtpHost !== undefined) { - set.smtpHost = ps.smtpHost; - } + if (ps.recaptchaSecretKey !== undefined) { + set.recaptchaSecretKey = ps.recaptchaSecretKey; + } - if (ps.smtpPort !== undefined) { - set.smtpPort = ps.smtpPort; - } + if (ps.sensitiveMediaDetection !== undefined) { + set.sensitiveMediaDetection = ps.sensitiveMediaDetection; + } - if (ps.smtpUser !== undefined) { - set.smtpUser = ps.smtpUser; - } + if (ps.sensitiveMediaDetectionSensitivity !== undefined) { + set.sensitiveMediaDetectionSensitivity = ps.sensitiveMediaDetectionSensitivity; + } - if (ps.smtpPass !== undefined) { - set.smtpPass = ps.smtpPass; - } + if (ps.setSensitiveFlagAutomatically !== undefined) { + set.setSensitiveFlagAutomatically = ps.setSensitiveFlagAutomatically; + } - if (ps.errorImageUrl !== undefined) { - set.errorImageUrl = ps.errorImageUrl; - } + if (ps.enableSensitiveMediaDetectionForVideos !== undefined) { + set.enableSensitiveMediaDetectionForVideos = ps.enableSensitiveMediaDetectionForVideos; + } - if (ps.enableServiceWorker !== undefined) { - set.enableServiceWorker = ps.enableServiceWorker; - } + if (ps.proxyAccountId !== undefined) { + set.proxyAccountId = ps.proxyAccountId; + } - if (ps.swPublicKey !== undefined) { - set.swPublicKey = ps.swPublicKey; - } + if (ps.maintainerName !== undefined) { + set.maintainerName = ps.maintainerName; + } - if (ps.swPrivateKey !== undefined) { - set.swPrivateKey = ps.swPrivateKey; - } + if (ps.maintainerEmail !== undefined) { + set.maintainerEmail = ps.maintainerEmail; + } - if (ps.tosUrl !== undefined) { - set.ToSUrl = ps.tosUrl; - } + if (Array.isArray(ps.langs)) { + set.langs = ps.langs.filter(Boolean); + } - if (ps.repositoryUrl !== undefined) { - set.repositoryUrl = ps.repositoryUrl; - } + if (Array.isArray(ps.pinnedPages)) { + set.pinnedPages = ps.pinnedPages.filter(Boolean); + } - if (ps.feedbackUrl !== undefined) { - set.feedbackUrl = ps.feedbackUrl; - } + if (ps.pinnedClipId !== undefined) { + set.pinnedClipId = ps.pinnedClipId; + } - if (ps.useObjectStorage !== undefined) { - set.useObjectStorage = ps.useObjectStorage; - } + if (ps.summalyProxy !== undefined) { + set.summalyProxy = ps.summalyProxy; + } - if (ps.objectStorageBaseUrl !== undefined) { - set.objectStorageBaseUrl = ps.objectStorageBaseUrl; - } + if (ps.enableTwitterIntegration !== undefined) { + set.enableTwitterIntegration = ps.enableTwitterIntegration; + } - if (ps.objectStorageBucket !== undefined) { - set.objectStorageBucket = ps.objectStorageBucket; - } + if (ps.twitterConsumerKey !== undefined) { + set.twitterConsumerKey = ps.twitterConsumerKey; + } - if (ps.objectStoragePrefix !== undefined) { - set.objectStoragePrefix = ps.objectStoragePrefix; - } + if (ps.twitterConsumerSecret !== undefined) { + set.twitterConsumerSecret = ps.twitterConsumerSecret; + } - if (ps.objectStorageEndpoint !== undefined) { - set.objectStorageEndpoint = ps.objectStorageEndpoint; - } + if (ps.enableGithubIntegration !== undefined) { + set.enableGithubIntegration = ps.enableGithubIntegration; + } - if (ps.objectStorageRegion !== undefined) { - set.objectStorageRegion = ps.objectStorageRegion; - } + if (ps.githubClientId !== undefined) { + set.githubClientId = ps.githubClientId; + } - if (ps.objectStoragePort !== undefined) { - set.objectStoragePort = ps.objectStoragePort; - } + if (ps.githubClientSecret !== undefined) { + set.githubClientSecret = ps.githubClientSecret; + } - if (ps.objectStorageAccessKey !== undefined) { - set.objectStorageAccessKey = ps.objectStorageAccessKey; - } + if (ps.enableDiscordIntegration !== undefined) { + set.enableDiscordIntegration = ps.enableDiscordIntegration; + } - if (ps.objectStorageSecretKey !== undefined) { - set.objectStorageSecretKey = ps.objectStorageSecretKey; - } + if (ps.discordClientId !== undefined) { + set.discordClientId = ps.discordClientId; + } - if (ps.objectStorageUseSSL !== undefined) { - set.objectStorageUseSSL = ps.objectStorageUseSSL; - } + if (ps.discordClientSecret !== undefined) { + set.discordClientSecret = ps.discordClientSecret; + } - if (ps.objectStorageUseProxy !== undefined) { - set.objectStorageUseProxy = ps.objectStorageUseProxy; - } + if (ps.enableEmail !== undefined) { + set.enableEmail = ps.enableEmail; + } - if (ps.objectStorageSetPublicRead !== undefined) { - set.objectStorageSetPublicRead = ps.objectStorageSetPublicRead; - } + if (ps.email !== undefined) { + set.email = ps.email; + } - if (ps.objectStorageS3ForcePathStyle !== undefined) { - set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle; - } + if (ps.smtpSecure !== undefined) { + set.smtpSecure = ps.smtpSecure; + } - if (ps.deeplAuthKey !== undefined) { - if (ps.deeplAuthKey === '') { - set.deeplAuthKey = null; - } else { - set.deeplAuthKey = ps.deeplAuthKey; - } - } + if (ps.smtpHost !== undefined) { + set.smtpHost = ps.smtpHost; + } - if (ps.deeplIsPro !== undefined) { - set.deeplIsPro = ps.deeplIsPro; - } + if (ps.smtpPort !== undefined) { + set.smtpPort = ps.smtpPort; + } - if (ps.enableIpLogging !== undefined) { - set.enableIpLogging = ps.enableIpLogging; - } + if (ps.smtpUser !== undefined) { + set.smtpUser = ps.smtpUser; + } - if (ps.enableActiveEmailValidation !== undefined) { - set.enableActiveEmailValidation = ps.enableActiveEmailValidation; - } + if (ps.smtpPass !== undefined) { + set.smtpPass = ps.smtpPass; + } - await db.transaction(async transactionalEntityManager => { - const metas = await transactionalEntityManager.find(Meta, { - order: { - id: 'DESC', - }, + if (ps.errorImageUrl !== undefined) { + set.errorImageUrl = ps.errorImageUrl; + } + + if (ps.enableServiceWorker !== undefined) { + set.enableServiceWorker = ps.enableServiceWorker; + } + + if (ps.swPublicKey !== undefined) { + set.swPublicKey = ps.swPublicKey; + } + + if (ps.swPrivateKey !== undefined) { + set.swPrivateKey = ps.swPrivateKey; + } + + if (ps.tosUrl !== undefined) { + set.ToSUrl = ps.tosUrl; + } + + if (ps.repositoryUrl !== undefined) { + set.repositoryUrl = ps.repositoryUrl; + } + + if (ps.feedbackUrl !== undefined) { + set.feedbackUrl = ps.feedbackUrl; + } + + if (ps.useObjectStorage !== undefined) { + set.useObjectStorage = ps.useObjectStorage; + } + + if (ps.objectStorageBaseUrl !== undefined) { + set.objectStorageBaseUrl = ps.objectStorageBaseUrl; + } + + if (ps.objectStorageBucket !== undefined) { + set.objectStorageBucket = ps.objectStorageBucket; + } + + if (ps.objectStoragePrefix !== undefined) { + set.objectStoragePrefix = ps.objectStoragePrefix; + } + + if (ps.objectStorageEndpoint !== undefined) { + set.objectStorageEndpoint = ps.objectStorageEndpoint; + } + + if (ps.objectStorageRegion !== undefined) { + set.objectStorageRegion = ps.objectStorageRegion; + } + + if (ps.objectStoragePort !== undefined) { + set.objectStoragePort = ps.objectStoragePort; + } + + if (ps.objectStorageAccessKey !== undefined) { + set.objectStorageAccessKey = ps.objectStorageAccessKey; + } + + if (ps.objectStorageSecretKey !== undefined) { + set.objectStorageSecretKey = ps.objectStorageSecretKey; + } + + if (ps.objectStorageUseSSL !== undefined) { + set.objectStorageUseSSL = ps.objectStorageUseSSL; + } + + if (ps.objectStorageUseProxy !== undefined) { + set.objectStorageUseProxy = ps.objectStorageUseProxy; + } + + if (ps.objectStorageSetPublicRead !== undefined) { + set.objectStorageSetPublicRead = ps.objectStorageSetPublicRead; + } + + if (ps.objectStorageS3ForcePathStyle !== undefined) { + set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle; + } + + if (ps.deeplAuthKey !== undefined) { + if (ps.deeplAuthKey === '') { + set.deeplAuthKey = null; + } else { + set.deeplAuthKey = ps.deeplAuthKey; + } + } + + if (ps.deeplIsPro !== undefined) { + set.deeplIsPro = ps.deeplIsPro; + } + + if (ps.enableIpLogging !== undefined) { + set.enableIpLogging = ps.enableIpLogging; + } + + if (ps.enableActiveEmailValidation !== undefined) { + set.enableActiveEmailValidation = ps.enableActiveEmailValidation; + } + + await this.db.transaction(async transactionalEntityManager => { + const metas = await transactionalEntityManager.find(Meta, { + order: { + id: 'DESC', + }, + }); + + const meta = metas[0]; + + if (meta) { + await transactionalEntityManager.update(Meta, meta.id, set); + } else { + await transactionalEntityManager.save(Meta, set); + } + }); + + this.moderationLogService.insertModerationLog(me, 'updateMeta'); }); - - const meta = metas[0]; - - if (meta) { - await transactionalEntityManager.update(Meta, meta.id, set); - } else { - await transactionalEntityManager.save(Meta, set); - } - }); - - insertModerationLog(me, 'updateMeta'); -}); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts index fa21ab7833..1ea0e6aac4 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts @@ -1,5 +1,7 @@ -import { UserProfiles, Users } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['admin'], @@ -18,14 +20,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user == null) { - throw new Error('user not found'); - } + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } - await UserProfiles.update({ userId: user.id }, { - moderationNote: ps.text, - }); -}); + await this.userProfilesRepository.update({ userId: user.id }, { + moderationNote: ps.text, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/vacuum.ts b/packages/backend/src/server/api/endpoints/admin/vacuum.ts deleted file mode 100644 index 0546acfacb..0000000000 --- a/packages/backend/src/server/api/endpoints/admin/vacuum.ts +++ /dev/null @@ -1,36 +0,0 @@ -import define from '../../define.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { db } from '@/db/postgre.js'; - -export const meta = { - tags: ['admin'], - - requireCredential: true, - requireModerator: true, -} as const; - -export const paramDef = { - type: 'object', - properties: { - full: { type: 'boolean' }, - analyze: { type: 'boolean' }, - }, - required: ['full', 'analyze'], -} as const; - -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const params: string[] = []; - - if (ps.full) { - params.push('FULL'); - } - - if (ps.analyze) { - params.push('ANALYZE'); - } - - db.query('VACUUM ' + params.join(' ')); - - insertModerationLog(me, 'vacuum', ps); -}); diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index 23cb93c9a5..aa44dfd5dc 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -1,6 +1,8 @@ -import { Announcements, AnnouncementReads } from '@/models/index.js'; -import define from '../define.js'; -import { makePaginationQuery } from '../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; +import { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models'; export const meta = { tags: ['meta'], @@ -63,24 +65,37 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(Announcements.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.announcementsRepository) + private announcementsRepository: AnnouncementsRepository, - const announcements = await query.take(ps.limit).getMany(); + @Inject(DI.announcementReadsRepository) + private announcementReadsRepository: AnnouncementReadsRepository, - if (user) { - const reads = (await AnnouncementReads.findBy({ - userId: user.id, - })).map(x => x.announcementId); + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); - for (const announcement of announcements) { - (announcement as any).isRead = reads.includes(announcement.id); - } - } + const announcements = await query.take(ps.limit).getMany(); + + if (me) { + const reads = (await this.announcementReadsRepository.findBy({ + userId: me.id, + })).map(x => x.announcementId); - return (ps.withUnreads ? announcements.filter((a: any) => !a.isRead) : announcements).map((a) => ({ - ...a, - createdAt: a.createdAt.toISOString(), - updatedAt: a.updatedAt?.toISOString() ?? null, - })); -}); + for (const announcement of announcements) { + (announcement as any).isRead = reads.includes(announcement.id); + } + } + + return (ps.withUnreads ? announcements.filter((a: any) => !a.isRead) : announcements).map((a) => ({ + ...a, + createdAt: a.createdAt.toISOString(), + updatedAt: a.updatedAt?.toISOString() ?? null, + })); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 7a4923b944..56bd343d55 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -1,8 +1,11 @@ -import define from '../../define.js'; -import { genId } from '@/misc/gen-id.js'; -import { Antennas, UserLists, UserGroupJoinings } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import { UserListsRepository, UserGroupJoiningsRepository, AntennasRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['antennas'], @@ -61,48 +64,66 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - let userList; - let userGroupJoining; - - if (ps.src === 'list' && ps.userListId) { - userList = await UserLists.findOneBy({ - id: ps.userListId, - userId: user.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, - if (userList == null) { - throw new ApiError(meta.errors.noSuchUserList); - } - } else if (ps.src === 'group' && ps.userGroupId) { - userGroupJoining = await UserGroupJoinings.findOneBy({ - userGroupId: ps.userGroupId, - userId: user.id, - }); + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, - if (userGroupJoining == null) { - throw new ApiError(meta.errors.noSuchUserGroup); - } - } + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private antennaEntityService: AntennaEntityService, + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + let userList; + let userGroupJoining; + + if (ps.src === 'list' && ps.userListId) { + userList = await this.userListsRepository.findOneBy({ + id: ps.userListId, + userId: me.id, + }); + + if (userList == null) { + throw new ApiError(meta.errors.noSuchUserList); + } + } else if (ps.src === 'group' && ps.userGroupId) { + userGroupJoining = await this.userGroupJoiningsRepository.findOneBy({ + userGroupId: ps.userGroupId, + userId: me.id, + }); - const antenna = await Antennas.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - name: ps.name, - src: ps.src, - userListId: userList ? userList.id : null, - userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null, - keywords: ps.keywords, - excludeKeywords: ps.excludeKeywords, - users: ps.users, - caseSensitive: ps.caseSensitive, - withReplies: ps.withReplies, - withFile: ps.withFile, - notify: ps.notify, - }).then(x => Antennas.findOneByOrFail(x.identifiers[0])); - - publishInternalEvent('antennaCreated', antenna); - - return await Antennas.pack(antenna); -}); + if (userGroupJoining == null) { + throw new ApiError(meta.errors.noSuchUserGroup); + } + } + + const antenna = await this.antennasRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + name: ps.name, + src: ps.src, + userListId: userList ? userList.id : null, + userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null, + keywords: ps.keywords, + excludeKeywords: ps.excludeKeywords, + users: ps.users, + caseSensitive: ps.caseSensitive, + withReplies: ps.withReplies, + withFile: ps.withFile, + notify: ps.notify, + }).then(x => this.antennasRepository.findOneByOrFail(x.identifiers[0])); + + this.globalEventService.publishInternalEvent('antennaCreated', antenna); + + return await this.antennaEntityService.pack(antenna); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/antennas/delete.ts b/packages/backend/src/server/api/endpoints/antennas/delete.ts index ced34ba313..127aca0c33 100644 --- a/packages/backend/src/server/api/endpoints/antennas/delete.ts +++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AntennasRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Antennas } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['antennas'], @@ -28,17 +30,27 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const antenna = await Antennas.findOneBy({ - id: ps.antennaId, - userId: user.id, - }); - - if (antenna == null) { - throw new ApiError(meta.errors.noSuchAntenna); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, + + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const antenna = await this.antennasRepository.findOneBy({ + id: ps.antennaId, + userId: me.id, + }); + + if (antenna == null) { + throw new ApiError(meta.errors.noSuchAntenna); + } + + await this.antennasRepository.delete(antenna.id); + + this.globalEventService.publishInternalEvent('antennaDeleted', antenna); + }); } - - await Antennas.delete(antenna.id); - - publishInternalEvent('antennaDeleted', antenna); -}); +} diff --git a/packages/backend/src/server/api/endpoints/antennas/list.ts b/packages/backend/src/server/api/endpoints/antennas/list.ts index c519b452ef..bdc44895cc 100644 --- a/packages/backend/src/server/api/endpoints/antennas/list.ts +++ b/packages/backend/src/server/api/endpoints/antennas/list.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { Antennas } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AntennasRepository } from '@/models/index.js'; +import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['antennas', 'account'], @@ -26,10 +29,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const antennas = await Antennas.findBy({ - userId: me.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, - return await Promise.all(antennas.map(x => Antennas.pack(x))); -}); + private antennaEntityService: AntennaEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const antennas = await this.antennasRepository.findBy({ + userId: me.id, + }); + + return await Promise.all(antennas.map(x => this.antennaEntityService.pack(x))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 8aac55b4a0..eba42afe56 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -1,11 +1,11 @@ -import define from '../../define.js'; -import readNote from '@/services/note/read.js'; -import { Antennas, Notes, AntennaNotes } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NotesRepository, AntennaNotesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteReadService } from '@/core/NoteReadService.js'; +import { DI } from '@/di-symbols.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { ApiError } from '../../error.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; export const meta = { tags: ['antennas', 'account', 'notes'], @@ -47,43 +47,61 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const antenna = await Antennas.findOneBy({ - id: ps.antennaId, - userId: user.id, - }); - - if (antenna == null) { - throw new ApiError(meta.errors.noSuchAntenna); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - const query = makePaginationQuery(Notes.createQueryBuilder('note'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .innerJoin(AntennaNotes.metadata.targetName, 'antennaNote', 'antennaNote.noteId = note.id') - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') - .andWhere('antennaNote.antennaId = :antennaId', { antennaId: antenna.id }); - - generateVisibilityQuery(query, user); - generateMutedUserQuery(query, user); - generateBlockedUserQuery(query, user); - - const notes = await query - .take(ps.limit) - .getMany(); - - if (notes.length > 0) { - readNote(user.id, notes); - } + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, + + @Inject(DI.antennaNotesRepository) + private antennaNotesRepository: AntennaNotesRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private noteReadService: NoteReadService, + ) { + super(meta, paramDef, async (ps, me) => { + const antenna = await this.antennasRepository.findOneBy({ + id: ps.antennaId, + userId: me.id, + }); + + if (antenna == null) { + throw new ApiError(meta.errors.noSuchAntenna); + } - return await Notes.packMany(notes, user); -}); + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .innerJoin(this.antennaNotesRepository.metadata.targetName, 'antennaNote', 'antennaNote.noteId = note.id') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') + .andWhere('antennaNote.antennaId = :antennaId', { antennaId: antenna.id }); + + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + + const notes = await query + .take(ps.limit) + .getMany(); + + if (notes.length > 0) { + this.noteReadService.read(me.id, notes); + } + + return await this.noteEntityService.packMany(notes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/antennas/show.ts b/packages/backend/src/server/api/endpoints/antennas/show.ts index dd693789cb..8bd8ad124d 100644 --- a/packages/backend/src/server/api/endpoints/antennas/show.ts +++ b/packages/backend/src/server/api/endpoints/antennas/show.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AntennasRepository } from '@/models/index.js'; +import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Antennas } from '@/models/index.js'; export const meta = { tags: ['antennas', 'account'], @@ -33,16 +36,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the antenna - const antenna = await Antennas.findOneBy({ - id: ps.antennaId, - userId: me.id, - }); - - if (antenna == null) { - throw new ApiError(meta.errors.noSuchAntenna); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, + + private antennaEntityService: AntennaEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the antenna + const antenna = await this.antennasRepository.findOneBy({ + id: ps.antennaId, + userId: me.id, + }); + + if (antenna == null) { + throw new ApiError(meta.errors.noSuchAntenna); + } + + return await this.antennaEntityService.pack(antenna); + }); } - - return await Antennas.pack(antenna); -}); +} diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index edfedc1752..59bba04ee1 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -1,7 +1,10 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AntennasRepository, UserListsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Antennas, UserLists, UserGroupJoinings } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['antennas'], @@ -67,55 +70,72 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch the antenna - const antenna = await Antennas.findOneBy({ - id: ps.antennaId, - userId: user.id, - }); - - if (antenna == null) { - throw new ApiError(meta.errors.noSuchAntenna); - } - - let userList; - let userGroupJoining; - - if (ps.src === 'list' && ps.userListId) { - userList = await UserLists.findOneBy({ - id: ps.userListId, - userId: user.id, +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, + + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private antennaEntityService: AntennaEntityService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the antenna + const antenna = await this.antennasRepository.findOneBy({ + id: ps.antennaId, + userId: me.id, + }); + + if (antenna == null) { + throw new ApiError(meta.errors.noSuchAntenna); + } + + let userList; + let userGroupJoining; + + if (ps.src === 'list' && ps.userListId) { + userList = await this.userListsRepository.findOneBy({ + id: ps.userListId, + userId: me.id, + }); + + if (userList == null) { + throw new ApiError(meta.errors.noSuchUserList); + } + } else if (ps.src === 'group' && ps.userGroupId) { + userGroupJoining = await this.userGroupJoiningsRepository.findOneBy({ + userGroupId: ps.userGroupId, + userId: me.id, + }); + + if (userGroupJoining == null) { + throw new ApiError(meta.errors.noSuchUserGroup); + } + } + + await this.antennasRepository.update(antenna.id, { + name: ps.name, + src: ps.src, + userListId: userList ? userList.id : null, + userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null, + keywords: ps.keywords, + excludeKeywords: ps.excludeKeywords, + users: ps.users, + caseSensitive: ps.caseSensitive, + withReplies: ps.withReplies, + withFile: ps.withFile, + notify: ps.notify, + }); + + this.globalEventService.publishInternalEvent('antennaUpdated', await this.antennasRepository.findOneByOrFail({ id: antenna.id })); + + return await this.antennaEntityService.pack(antenna.id); }); - - if (userList == null) { - throw new ApiError(meta.errors.noSuchUserList); - } - } else if (ps.src === 'group' && ps.userGroupId) { - userGroupJoining = await UserGroupJoinings.findOneBy({ - userGroupId: ps.userGroupId, - userId: user.id, - }); - - if (userGroupJoining == null) { - throw new ApiError(meta.errors.noSuchUserGroup); - } } - - await Antennas.update(antenna.id, { - name: ps.name, - src: ps.src, - userListId: userList ? userList.id : null, - userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null, - keywords: ps.keywords, - excludeKeywords: ps.excludeKeywords, - users: ps.users, - caseSensitive: ps.caseSensitive, - withReplies: ps.withReplies, - withFile: ps.withFile, - notify: ps.notify, - }); - - publishInternalEvent('antennaUpdated', await Antennas.findOneByOrFail({ id: antenna.id })); - - return await Antennas.pack(antenna.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts index 0cbe7ebc67..3d4c85e50b 100644 --- a/packages/backend/src/server/api/endpoints/ap/get.ts +++ b/packages/backend/src/server/api/endpoints/ap/get.ts @@ -1,7 +1,8 @@ -import define from '../../define.js'; -import Resolver from '@/remote/activitypub/resolver.js'; -import { ApiError } from '../../error.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ApResolverService } from '@/core/remote/activitypub/ApResolverService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['federation'], @@ -31,8 +32,15 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const resolver = new Resolver(); - const object = await resolver.resolve(ps.uri); - return object; -}); +@Injectable() +export default class extends Endpoint { + constructor( + private apResolverService: ApResolverService, + ) { + super(meta, paramDef, async (ps, me) => { + const resolver = this.apResolverService.createResolver(); + const object = await resolver.resolve(ps.uri); + return object; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 6442a1412c..eaf93ee977 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -1,18 +1,21 @@ -import define from '../../define.js'; -import config from '@/config/index.js'; -import { createPerson } from '@/remote/activitypub/models/person.js'; -import { createNote } from '@/remote/activitypub/models/note.js'; -import DbResolver from '@/remote/activitypub/db-resolver.js'; -import Resolver from '@/remote/activitypub/resolver.js'; -import { ApiError } from '../../error.js'; -import { extractDbHost } from '@/misc/convert-host.js'; -import { Users, Notes } from '@/models/index.js'; -import { Note } from '@/models/entities/note.js'; -import { CacheableLocalUser, User } from '@/models/entities/user.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { isActor, isPost, getApId } from '@/remote/activitypub/type.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; -import { SchemaType } from '@/misc/schema.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, NotesRepository } from '@/models/index.js'; +import type { Note } from '@/models/entities/Note.js'; +import type { CacheableLocalUser, User } from '@/models/entities/User.js'; +import { isActor, isPost, getApId } from '@/core/remote/activitypub/type.js'; +import type { SchemaType } from '@/misc/schema.js'; +import { ApResolverService } from '@/core/remote/activitypub/ApResolverService.js'; +import { ApDbResolverService } from '@/core/remote/activitypub/ApDbResolverService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { ApPersonService } from '@/core/remote/activitypub/models/ApPersonService.js'; +import { ApNoteService } from '@/core/remote/activitypub/models/ApNoteService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['federation'], @@ -47,8 +50,8 @@ export const meta = { type: 'object', optional: false, nullable: false, ref: 'UserDetailedNotMe', - } - } + }, + }, }, { type: 'object', @@ -62,9 +65,9 @@ export const meta = { type: 'object', optional: false, nullable: false, ref: 'Note', - } - } - } + }, + }, + }, ], }, } as const; @@ -78,70 +81,88 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const object = await fetchAny(ps.uri, me); - if (object) { - return object; - } else { - throw new ApiError(meta.errors.noSuchObject); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private utilityService: UtilityService, + private userEntityService: UserEntityService, + private noteEntityService: NoteEntityService, + private metaService: MetaService, + private apResolverService: ApResolverService, + private apDbResolverService: ApDbResolverService, + private apPersonService: ApPersonService, + private apNoteService: ApNoteService, + ) { + super(meta, paramDef, async (ps, me) => { + const object = await this.#fetchAny(ps.uri, me); + if (object) { + return object; + } else { + throw new ApiError(meta.errors.noSuchObject); + } + }); } -}); -/*** - * URIからUserかNoteを解決する - */ -async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise | null> { + /*** + * URIからUserかNoteを解決する + */ + async #fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise | null> { // ブロックしてたら中断 - const fetchedMeta = await fetchMeta(); - if (fetchedMeta.blockedHosts.includes(extractDbHost(uri))) return null; - - const dbResolver = new DbResolver(); - - let local = await mergePack(me, ...await Promise.all([ - dbResolver.getUserFromApId(uri), - dbResolver.getNoteFromApId(uri), - ])); - if (local != null) return local; - - // リモートから一旦オブジェクトフェッチ - const resolver = new Resolver(); - const object = await resolver.resolve(uri) as any; - - // /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する - // これはDBに存在する可能性があるため再度DB検索 - if (uri !== object.id) { - local = await mergePack(me, ...await Promise.all([ - dbResolver.getUserFromApId(object.id), - dbResolver.getNoteFromApId(object.id), + const fetchedMeta = await this.metaService.fetch(); + if (fetchedMeta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) return null; + + let local = await this.#mergePack(me, ...await Promise.all([ + this.apDbResolverService.getUserFromApId(uri), + this.apDbResolverService.getNoteFromApId(uri), ])); if (local != null) return local; - } - return await mergePack( - me, - isActor(object) ? await createPerson(getApId(object)) : null, - isPost(object) ? await createNote(getApId(object), undefined, true) : null, - ); -} + // リモートから一旦オブジェクトフェッチ + const resolver = this.apResolverService.createResolver(); + const object = await resolver.resolve(uri) as any; + + // /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する + // これはDBに存在する可能性があるため再度DB検索 + if (uri !== object.id) { + local = await this.#mergePack(me, ...await Promise.all([ + this.apDbResolverService.getUserFromApId(object.id), + this.apDbResolverService.getNoteFromApId(object.id), + ])); + if (local != null) return local; + } -async function mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise | null> { - if (user != null) { - return { - type: 'User', - object: await Users.pack(user, me, { detail: true }), - }; - } else if (note != null) { - try { - const object = await Notes.pack(note, me, { detail: true }); + return await this.#mergePack( + me, + isActor(object) ? await this.apPersonService.createPerson(getApId(object)) : null, + isPost(object) ? await this.apNoteService.createNote(getApId(object), undefined, true) : null, + ); + } + async #mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise | null> { + if (user != null) { return { - type: 'Note', - object, + type: 'User', + object: await this.userEntityService.pack(user, me, { detail: true }), }; - } catch (e) { - return null; + } else if (note != null) { + try { + const object = await this.noteEntityService.pack(note, me, { detail: true }); + + return { + type: 'Note', + object, + }; + } catch (e) { + return null; + } } - } - return null; + return null; + } } diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index a0a7350822..f52d18f7fe 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -1,8 +1,11 @@ -import define from '../../define.js'; -import { Apps } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { unique } from '@/prelude/array.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AppsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { unique } from '@/misc/prelude/array.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; +import { AppEntityService } from '@/core/entities/AppEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['app'], @@ -30,27 +33,38 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Generate secret - const secret = secureRndstr(32, true); - - // for backward compatibility - const permission = unique(ps.permission.map(v => v.replace(/^(.+)(\/|-)(read|write)$/, '$3:$1'))); - - // Create account - const app = await Apps.insert({ - id: genId(), - createdAt: new Date(), - userId: user ? user.id : null, - name: ps.name, - description: ps.description, - permission, - callbackUrl: ps.callbackUrl, - secret: secret, - }).then(x => Apps.findOneByOrFail(x.identifiers[0])); - - return await Apps.pack(app, null, { - detail: true, - includeSecret: true, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.appsRepository) + private appsRepository: AppsRepository, + + private appEntityService: AppEntityService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + // Generate secret + const secret = secureRndstr(32, true); + + // for backward compatibility + const permission = unique(ps.permission.map(v => v.replace(/^(.+)(\/|-)(read|write)$/, '$3:$1'))); + + // Create account + const app = await this.appsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me ? me.id : null, + name: ps.name, + description: ps.description, + permission, + callbackUrl: ps.callbackUrl, + secret: secret, + }).then(x => this.appsRepository.findOneByOrFail(x.identifiers[0])); + + return await this.appEntityService.pack(app, null, { + detail: true, + includeSecret: true, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/app/show.ts b/packages/backend/src/server/api/endpoints/app/show.ts index 451969d971..f94fed5344 100644 --- a/packages/backend/src/server/api/endpoints/app/show.ts +++ b/packages/backend/src/server/api/endpoints/app/show.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AppsRepository } from '@/models/index.js'; +import { AppEntityService } from '@/core/entities/AppEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Apps } from '@/models/index.js'; export const meta = { tags: ['app'], @@ -29,18 +32,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user, token) => { - const isSecure = user != null && token == null; - - // Lookup app - const ap = await Apps.findOneBy({ id: ps.appId }); - - if (ap == null) { - throw new ApiError(meta.errors.noSuchApp); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.appsRepository) + private appsRepository: AppsRepository, + + private appEntityService: AppEntityService, + ) { + super(meta, paramDef, async (ps, user, token) => { + const isSecure = user != null && token == null; + + // Lookup app + const ap = await this.appsRepository.findOneBy({ id: ps.appId }); + + if (ap == null) { + throw new ApiError(meta.errors.noSuchApp); + } + + return await this.appEntityService.pack(ap, user, { + detail: true, + includeSecret: isSecure && (ap.userId === user!.id), + }); + }); } - - return await Apps.pack(ap, user, { - detail: true, - includeSecret: isSecure && (ap.userId === user!.id), - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index b5c06792bb..6032b59bef 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -1,9 +1,11 @@ import * as crypto from 'node:crypto'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { AuthSessions, AccessTokens, Apps } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AuthSessionsRepository, AppsRepository, AccessTokensRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['auth'], @@ -30,49 +32,65 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch token - const session = await AuthSessions - .findOneBy({ token: ps.token }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.appsRepository) + private appsRepository: AppsRepository, - if (session == null) { - throw new ApiError(meta.errors.noSuchSession); - } + @Inject(DI.authSessionsRepository) + private authSessionsRepository: AuthSessionsRepository, + + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, + + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch token + const session = await this.authSessionsRepository + .findOneBy({ token: ps.token }); + + if (session == null) { + throw new ApiError(meta.errors.noSuchSession); + } + + // Generate access token + const accessToken = secureRndstr(32, true); - // Generate access token - const accessToken = secureRndstr(32, true); - - // Fetch exist access token - const exist = await AccessTokens.findOneBy({ - appId: session.appId, - userId: user.id, - }); - - if (exist == null) { - // Lookup app - const app = await Apps.findOneByOrFail({ id: session.appId }); - - // Generate Hash - const sha256 = crypto.createHash('sha256'); - sha256.update(accessToken + app.secret); - const hash = sha256.digest('hex'); - - const now = new Date(); - - // Insert access token doc - await AccessTokens.insert({ - id: genId(), - createdAt: now, - lastUsedAt: now, - appId: session.appId, - userId: user.id, - token: accessToken, - hash: hash, + // Fetch exist access token + const exist = await this.accessTokensRepository.findOneBy({ + appId: session.appId, + userId: me.id, + }); + + if (exist == null) { + // Lookup app + const app = await this.appsRepository.findOneByOrFail({ id: session.appId }); + + // Generate Hash + const sha256 = crypto.createHash('sha256'); + sha256.update(accessToken + app.secret); + const hash = sha256.digest('hex'); + + const now = new Date(); + + // Insert access token doc + await this.accessTokensRepository.insert({ + id: this.idService.genId(), + createdAt: now, + lastUsedAt: now, + appId: session.appId, + userId: me.id, + token: accessToken, + hash: hash, + }); + } + + // Update session + await this.authSessionsRepository.update(session.id, { + userId: me.id, + }); }); } - - // Update session - await AuthSessions.update(session.id, { - userId: user.id, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts index 717c3e5086..7f8325dbbd 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -1,9 +1,11 @@ import { v4 as uuid } from 'uuid'; -import config from '@/config/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AppsRepository, AuthSessionsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { Apps, AuthSessions } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; export const meta = { tags: ['auth'], @@ -44,29 +46,45 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - // Lookup app - const app = await Apps.findOneBy({ - secret: ps.appSecret, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, - if (app == null) { - throw new ApiError(meta.errors.noSuchApp); - } + @Inject(DI.appsRepository) + private appsRepository: AppsRepository, + + @Inject(DI.authSessionsRepository) + private authSessionsRepository: AuthSessionsRepository, + + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + // Lookup app + const app = await this.appsRepository.findOneBy({ + secret: ps.appSecret, + }); - // Generate token - const token = uuid(); + if (app == null) { + throw new ApiError(meta.errors.noSuchApp); + } - // Create session token document - const doc = await AuthSessions.insert({ - id: genId(), - createdAt: new Date(), - appId: app.id, - token: token, - }).then(x => AuthSessions.findOneByOrFail(x.identifiers[0])); + // Generate token + const token = uuid(); - return { - token: doc.token, - url: `${config.authUrl}/${doc.token}`, - }; -}); + // Create session token document + const doc = await this.authSessionsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + appId: app.id, + token: token, + }).then(x => this.authSessionsRepository.findOneByOrFail(x.identifiers[0])); + + return { + token: doc.token, + url: `${this.config.authUrl}/${doc.token}`, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/auth/session/show.ts b/packages/backend/src/server/api/endpoints/auth/session/show.ts index 3f3a4d1427..dff4c74340 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/show.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/show.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AuthSessionsRepository } from '@/models/index.js'; +import { AuthSessionEntityService } from '@/core/entities/AuthSessionEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { AuthSessions } from '@/models/index.js'; export const meta = { tags: ['auth'], @@ -46,15 +49,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Lookup session - const session = await AuthSessions.findOneBy({ - token: ps.token, - }); - - if (session == null) { - throw new ApiError(meta.errors.noSuchSession); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.authSessionsRepository) + private authSessionsRepository: AuthSessionsRepository, + + private authSessionEntityService: AuthSessionEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Lookup session + const session = await this.authSessionsRepository.findOneBy({ + token: ps.token, + }); - return await AuthSessions.pack(session, user); -}); + if (session == null) { + throw new ApiError(meta.errors.noSuchSession); + } + + return await this.authSessionEntityService.pack(session, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts index 89884ed38a..9c9f13f502 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, AppsRepository, AccessTokensRepository, AuthSessionsRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { Apps, AuthSessions, AccessTokens, Users } from '@/models/index.js'; export const meta = { tags: ['auth'], @@ -55,43 +58,62 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - // Lookup app - const app = await Apps.findOneBy({ - secret: ps.appSecret, - }); - - if (app == null) { - throw new ApiError(meta.errors.noSuchApp); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.appsRepository) + private appsRepository: AppsRepository, + + @Inject(DI.authSessionsRepository) + private authSessionsRepository: AuthSessionsRepository, + + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, + + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Lookup app + const app = await this.appsRepository.findOneBy({ + secret: ps.appSecret, + }); + + if (app == null) { + throw new ApiError(meta.errors.noSuchApp); + } + + // Fetch token + const session = await this.authSessionsRepository.findOneBy({ + token: ps.token, + appId: app.id, + }); + + if (session == null) { + throw new ApiError(meta.errors.noSuchSession); + } + + if (session.userId == null) { + throw new ApiError(meta.errors.pendingSession); + } + + // Lookup access token + const accessToken = await this.accessTokensRepository.findOneByOrFail({ + appId: app.id, + userId: session.userId, + }); + + // Delete session + this.authSessionsRepository.delete(session.id); + + return { + accessToken: accessToken.token, + user: await this.userEntityService.pack(session.userId, null, { + detail: true, + }), + }; + }); } - - // Fetch token - const session = await AuthSessions.findOneBy({ - token: ps.token, - appId: app.id, - }); - - if (session == null) { - throw new ApiError(meta.errors.noSuchSession); - } - - if (session.userId == null) { - throw new ApiError(meta.errors.pendingSession); - } - - // Lookup access token - const accessToken = await AccessTokens.findOneByOrFail({ - appId: app.id, - userId: session.userId, - }); - - // Delete session - AuthSessions.delete(session.id); - - return { - accessToken: accessToken.token, - user: await Users.pack(session.userId, null, { - detail: true, - }), - }; -}); +} diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index 0540e6ab0f..33614a1554 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -1,9 +1,12 @@ import ms from 'ms'; -import create from '@/services/blocking/create.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, BlockingsRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { UserBlockingService } from '@/core/UserBlockingService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { Blockings, NoteWatchings, Users } from '@/models/index.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['account'], @@ -53,38 +56,48 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const blocker = await Users.findOneByOrFail({ id: user.id }); - - // 自分自身 - if (user.id === ps.userId) { - throw new ApiError(meta.errors.blockeeIsYourself); - } - - // Get blockee - const blockee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Check if already blocking - const exist = await Blockings.findOneBy({ - blockerId: blocker.id, - blockeeId: blockee.id, - }); - - if (exist != null) { - throw new ApiError(meta.errors.alreadyBlocking); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private userBlockingService: UserBlockingService, + ) { + super(meta, paramDef, async (ps, me) => { + const blocker = await this.usersRepository.findOneByOrFail({ id: me.id }); + + // 自分自身 + if (me.id === ps.userId) { + throw new ApiError(meta.errors.blockeeIsYourself); + } + + // Get blockee + const blockee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check if already blocking + const exist = await this.blockingsRepository.findOneBy({ + blockerId: blocker.id, + blockeeId: blockee.id, + }); + + if (exist != null) { + throw new ApiError(meta.errors.alreadyBlocking); + } + + await this.userBlockingService.block(blocker, blockee); + + return await this.userEntityService.pack(blockee.id, blocker, { + detail: true, + }); + }); } - - await create(blocker, blockee); - - NoteWatchings.delete({ - userId: blocker.id, - noteUserId: blockee.id, - }); - - return await Users.pack(blockee.id, blocker, { - detail: true, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index 77e17b3ba9..f2cc28e922 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -1,9 +1,12 @@ import ms from 'ms'; -import deleteBlocking from '@/services/blocking/delete.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, BlockingsRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { UserBlockingService } from '@/core/UserBlockingService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { Blockings, Users } from '@/models/index.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['account'], @@ -53,34 +56,49 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const blocker = await Users.findOneByOrFail({ id: user.id }); - - // Check if the blockee is yourself - if (user.id === ps.userId) { - throw new ApiError(meta.errors.blockeeIsYourself); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private userBlockingService: UserBlockingService, + ) { + super(meta, paramDef, async (ps, me) => { + const blocker = await this.usersRepository.findOneByOrFail({ id: me.id }); + + // Check if the blockee is yourself + if (me.id === ps.userId) { + throw new ApiError(meta.errors.blockeeIsYourself); + } + + // Get blockee + const blockee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check not blocking + const exist = await this.blockingsRepository.findOneBy({ + blockerId: blocker.id, + blockeeId: blockee.id, + }); + + if (exist == null) { + throw new ApiError(meta.errors.notBlocking); + } + + // Delete blocking + await this.userBlockingService.unblock(blocker, blockee); + + return await this.userEntityService.pack(blockee.id, blocker, { + detail: true, + }); + }); } - - // Get blockee - const blockee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Check not blocking - const exist = await Blockings.findOneBy({ - blockerId: blocker.id, - blockeeId: blockee.id, - }); - - if (exist == null) { - throw new ApiError(meta.errors.notBlocking); - } - - // Delete blocking - await deleteBlocking(blocker, blockee); - - return await Users.pack(blockee.id, blocker, { - detail: true, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/blocking/list.ts b/packages/backend/src/server/api/endpoints/blocking/list.ts index 29095ebe21..4f5e11cd68 100644 --- a/packages/backend/src/server/api/endpoints/blocking/list.ts +++ b/packages/backend/src/server/api/endpoints/blocking/list.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Blockings } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { BlockingsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { BlockingEntityService } from '@/core/entities/BlockingEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], @@ -31,13 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(Blockings.createQueryBuilder('blocking'), ps.sinceId, ps.untilId) - .andWhere(`blocking.blockerId = :meId`, { meId: me.id }); - - const blockings = await query - .take(ps.limit) - .getMany(); - - return await Blockings.packMany(blockings, me); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + private blockingEntityService: BlockingEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.blockingsRepository.createQueryBuilder('blocking'), ps.sinceId, ps.untilId) + .andWhere('blocking.blockerId = :meId', { meId: me.id }); + + const blockings = await query + .take(ps.limit) + .getMany(); + + return await this.blockingEntityService.packMany(blockings, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index 94dcfe5023..21979884f9 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -1,8 +1,11 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelsRepository, DriveFilesRepository } from '@/models/index.js'; +import type { Channel } from '@/models/entities/Channel.js'; +import { IdService } from '@/core/IdService.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Channels, DriveFiles } from '@/models/index.js'; -import { Channel } from '@/models/entities/channel.js'; -import { genId } from '@/misc/gen-id.js'; export const meta = { tags: ['channels'], @@ -37,27 +40,41 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - let banner = null; - if (ps.bannerId != null) { - banner = await DriveFiles.findOneBy({ - id: ps.bannerId, - userId: user.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (banner == null) { - throw new ApiError(meta.errors.noSuchFile); - } - } + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + private idService: IdService, + private channelEntityService: ChannelEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + let banner = null; + if (ps.bannerId != null) { + banner = await this.driveFilesRepository.findOneBy({ + id: ps.bannerId, + userId: me.id, + }); + + if (banner == null) { + throw new ApiError(meta.errors.noSuchFile); + } + } - const channel = await Channels.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - name: ps.name, - description: ps.description || null, - bannerId: banner ? banner.id : null, - } as Channel).then(x => Channels.findOneByOrFail(x.identifiers[0])); - - return await Channels.pack(channel, user); -}); + const channel = await this.channelsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + name: ps.name, + description: ps.description ?? null, + bannerId: banner ? banner.id : null, + } as Channel).then(x => this.channelsRepository.findOneByOrFail(x.identifiers[0])); + + return await this.channelEntityService.pack(channel, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/featured.ts b/packages/backend/src/server/api/endpoints/channels/featured.ts index 73980c0fad..0c3f9509d1 100644 --- a/packages/backend/src/server/api/endpoints/channels/featured.ts +++ b/packages/backend/src/server/api/endpoints/channels/featured.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { Channels } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelsRepository } from '@/models/index.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['channels'], @@ -24,12 +27,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Channels.createQueryBuilder('channel') - .where('channel.lastNotedAt IS NOT NULL') - .orderBy('channel.lastNotedAt', 'DESC'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, - const channels = await query.take(10).getMany(); + private channelEntityService: ChannelEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.channelsRepository.createQueryBuilder('channel') + .where('channel.lastNotedAt IS NOT NULL') + .orderBy('channel.lastNotedAt', 'DESC'); - return await Promise.all(channels.map(x => Channels.pack(x, me))); -}); + const channels = await query.take(10).getMany(); + + return await Promise.all(channels.map(x => this.channelEntityService.pack(x, me))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index 895ffed0bd..6c6b498a94 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -1,8 +1,10 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelFollowingsRepository, ChannelsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Channels, ChannelFollowings } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { publishUserEvent } from '@/services/stream.js'; export const meta = { tags: ['channels'], @@ -29,21 +31,35 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const channel = await Channels.findOneBy({ - id: ps.channelId, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, - if (channel == null) { - throw new ApiError(meta.errors.noSuchChannel); - } + @Inject(DI.channelFollowingsRepository) + private channelFollowingsRepository: ChannelFollowingsRepository, + + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const channel = await Channels.findOneBy({ + id: ps.channelId, + }); - await ChannelFollowings.insert({ - id: genId(), - createdAt: new Date(), - followerId: user.id, - followeeId: channel.id, - }); + if (channel == null) { + throw new ApiError(meta.errors.noSuchChannel); + } - publishUserEvent(user.id, 'followChannel', channel); -}); + await this.channelFollowingsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + followerId: me.id, + followeeId: channel.id, + }); + + this.globalEventService.publishUserEvent(me.id, 'followChannel', channel); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/followed.ts b/packages/backend/src/server/api/endpoints/channels/followed.ts index e4aa4d1614..5a8ab26af9 100644 --- a/packages/backend/src/server/api/endpoints/channels/followed.ts +++ b/packages/backend/src/server/api/endpoints/channels/followed.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Channels, ChannelFollowings } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelFollowingsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['channels', 'account'], @@ -31,13 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(ChannelFollowings.createQueryBuilder(), ps.sinceId, ps.untilId) - .andWhere({ followerId: me.id }); - - const followings = await query - .take(ps.limit) - .getMany(); - - return await Promise.all(followings.map(x => Channels.pack(x.followeeId, me))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.channelFollowingsRepository) + private channelFollowingsRepository: ChannelFollowingsRepository, + + private channelEntityService: ChannelEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.channelFollowingsRepository.createQueryBuilder(), ps.sinceId, ps.untilId) + .andWhere({ followerId: me.id }); + + const followings = await query + .take(ps.limit) + .getMany(); + + return await Promise.all(followings.map(x => this.channelEntityService.pack(x.followeeId, me))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/owned.ts b/packages/backend/src/server/api/endpoints/channels/owned.ts index ed7e41cac2..8b8b5819e6 100644 --- a/packages/backend/src/server/api/endpoints/channels/owned.ts +++ b/packages/backend/src/server/api/endpoints/channels/owned.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Channels } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['channels', 'account'], @@ -31,13 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(Channels.createQueryBuilder(), ps.sinceId, ps.untilId) - .andWhere({ userId: me.id }); - - const channels = await query - .take(ps.limit) - .getMany(); - - return await Promise.all(channels.map(x => Channels.pack(x, me))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + private channelEntityService: ChannelEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder(), ps.sinceId, ps.untilId) + .andWhere({ userId: me.id }); + + const channels = await query + .take(ps.limit) + .getMany(); + + return await Promise.all(channels.map(x => this.channelEntityService.pack(x, me))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts index 87665a9865..54ae31790b 100644 --- a/packages/backend/src/server/api/endpoints/channels/show.ts +++ b/packages/backend/src/server/api/endpoints/channels/show.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelsRepository } from '@/models/index.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Channels } from '@/models/index.js'; export const meta = { tags: ['channels'], @@ -31,14 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const channel = await Channels.findOneBy({ - id: ps.channelId, - }); - - if (channel == null) { - throw new ApiError(meta.errors.noSuchChannel); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + private channelEntityService: ChannelEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const channel = await this.channelsRepository.findOneBy({ + id: ps.channelId, + }); + + if (channel == null) { + throw new ApiError(meta.errors.noSuchChannel); + } + + return await this.channelEntityService.pack(channel, me); + }); } - - return await Channels.pack(channel, me); -}); +} diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index deaa299013..1c7f1360b9 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -1,8 +1,11 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelsRepository, NotesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Notes, Channels } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { activeUsersChart } from '@/services/chart/index.js'; export const meta = { tags: ['notes', 'channels'], @@ -42,35 +45,50 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const channel = await Channels.findOneBy({ - id: ps.channelId, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - if (channel == null) { - throw new ApiError(meta.errors.noSuchChannel); - } + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private activeUsersChart: ActiveUsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + const channel = await this.channelsRepository.findOneBy({ + id: ps.channelId, + }); - //#region Construct query - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere('note.channelId = :channelId', { channelId: channel.id }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') - .leftJoinAndSelect('note.channel', 'channel'); - //#endregion + if (channel == null) { + throw new ApiError(meta.errors.noSuchChannel); + } - const timeline = await query.take(ps.limit).getMany(); + //#region Construct query + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('note.channelId = :channelId', { channelId: channel.id }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') + .leftJoinAndSelect('note.channel', 'channel'); + //#endregion - if (user) activeUsersChart.read(user); + const timeline = await query.take(ps.limit).getMany(); - return await Notes.packMany(timeline, user); -}); + if (me) this.activeUsersChart.read(me); + + return await this.noteEntityService.packMany(timeline, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts index e065d897a5..b464c55097 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ChannelFollowingsRepository, ChannelsRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Channels, ChannelFollowings } from '@/models/index.js'; -import { publishUserEvent } from '@/services/stream.js'; export const meta = { tags: ['channels'], @@ -28,19 +30,32 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const channel = await Channels.findOneBy({ - id: ps.channelId, - }); - - if (channel == null) { - throw new ApiError(meta.errors.noSuchChannel); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + @Inject(DI.channelFollowingsRepository) + private channelFollowingsRepository: ChannelFollowingsRepository, + + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const channel = await this.channelsRepository.findOneBy({ + id: ps.channelId, + }); + + if (channel == null) { + throw new ApiError(meta.errors.noSuchChannel); + } + + await this.channelFollowingsRepository.delete({ + followerId: me.id, + followeeId: channel.id, + }); + + this.globalEventService.publishUserEvent(me.id, 'unfollowChannel', channel); + }); } - - await ChannelFollowings.delete({ - followerId: user.id, - followeeId: channel.id, - }); - - publishUserEvent(user.id, 'unfollowChannel', channel); -}); +} diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index 13104f324f..ba62e9d371 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository, ChannelsRepository } from '@/models/index.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Channels, DriveFiles } from '@/models/index.js'; export const meta = { tags: ['channels'], @@ -48,39 +51,52 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const channel = await Channels.findOneBy({ - id: ps.channelId, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, - if (channel == null) { - throw new ApiError(meta.errors.noSuchChannel); - } + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (channel.userId !== me.id) { - throw new ApiError(meta.errors.accessDenied); - } + private channelEntityService: ChannelEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const channel = await this.channelsRepository.findOneBy({ + id: ps.channelId, + }); - // eslint:disable-next-line:no-unnecessary-initializer - let banner = undefined; - if (ps.bannerId != null) { - banner = await DriveFiles.findOneBy({ - id: ps.bannerId, - userId: me.id, - }); + if (channel == null) { + throw new ApiError(meta.errors.noSuchChannel); + } - if (banner == null) { - throw new ApiError(meta.errors.noSuchFile); - } - } else if (ps.bannerId === null) { - banner = null; - } + if (channel.userId !== me.id) { + throw new ApiError(meta.errors.accessDenied); + } - await Channels.update(channel.id, { - ...(ps.name !== undefined ? { name: ps.name } : {}), - ...(ps.description !== undefined ? { description: ps.description } : {}), - ...(banner ? { bannerId: banner.id } : {}), - }); + // eslint:disable-next-line:no-unnecessary-initializer + let banner = undefined; + if (ps.bannerId != null) { + banner = await this.driveFilesRepository.findOneBy({ + id: ps.bannerId, + userId: me.id, + }); - return await Channels.pack(channel.id, me); -}); + if (banner == null) { + throw new ApiError(meta.errors.noSuchFile); + } + } else if (ps.bannerId === null) { + banner = null; + } + + await this.channelsRepository.update(channel.id, { + ...(ps.name !== undefined ? { name: ps.name } : {}), + ...(ps.description !== undefined ? { description: ps.description } : {}), + ...(banner ? { bannerId: banner.id } : {}), + }); + + return await this.channelEntityService.pack(channel.id, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/active-users.ts b/packages/backend/src/server/api/endpoints/charts/active-users.ts index ea23794296..862ef89268 100644 --- a/packages/backend/src/server/api/endpoints/charts/active-users.ts +++ b/packages/backend/src/server/api/endpoints/charts/active-users.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { schema } from '@/core/chart/charts/entities/active-users.js'; export const meta = { tags: ['charts', 'users'], - res: getJsonSchema(activeUsersChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -22,6 +24,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await activeUsersChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private activeUsersChart: ActiveUsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.activeUsersChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/ap-request.ts b/packages/backend/src/server/api/endpoints/charts/ap-request.ts index 06dee250ee..1d5b8f05f8 100644 --- a/packages/backend/src/server/api/endpoints/charts/ap-request.ts +++ b/packages/backend/src/server/api/endpoints/charts/ap-request.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { apRequestChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import ApRequestChart from '@/core/chart/charts/ap-request.js'; +import { schema } from '@/core/chart/charts/entities/ap-request.js'; export const meta = { tags: ['charts'], - res: getJsonSchema(apRequestChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -22,6 +24,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await apRequestChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private apRequestChart: ApRequestChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.apRequestChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/drive.ts b/packages/backend/src/server/api/endpoints/charts/drive.ts index dd2c2d6838..ec28fa75de 100644 --- a/packages/backend/src/server/api/endpoints/charts/drive.ts +++ b/packages/backend/src/server/api/endpoints/charts/drive.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { driveChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import DriveChart from '@/core/chart/charts/drive.js'; +import { schema } from '@/core/chart/charts/entities/drive.js'; export const meta = { tags: ['charts', 'drive'], - res: getJsonSchema(driveChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -22,6 +24,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await driveChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private driveChart: DriveChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.driveChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/federation.ts b/packages/backend/src/server/api/endpoints/charts/federation.ts index 8c35b3c46d..6c24cbbb77 100644 --- a/packages/backend/src/server/api/endpoints/charts/federation.ts +++ b/packages/backend/src/server/api/endpoints/charts/federation.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { federationChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import FederationChart from '@/core/chart/charts/federation.js'; +import { schema } from '@/core/chart/charts/entities/federation.js'; export const meta = { tags: ['charts'], - res: getJsonSchema(federationChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -22,6 +24,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await federationChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private federationChart: FederationChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.federationChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/hashtag.ts b/packages/backend/src/server/api/endpoints/charts/hashtag.ts index 77e24a62c3..71e5bab766 100644 --- a/packages/backend/src/server/api/endpoints/charts/hashtag.ts +++ b/packages/backend/src/server/api/endpoints/charts/hashtag.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { hashtagChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import HashtagChart from '@/core/chart/charts/hashtag.js'; +import { schema } from '@/core/chart/charts/entities/hashtag.js'; export const meta = { tags: ['charts', 'hashtags'], - res: getJsonSchema(hashtagChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -23,6 +25,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await hashtagChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.tag); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private hashtagChart: HashtagChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.hashtagChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.tag); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/instance.ts b/packages/backend/src/server/api/endpoints/charts/instance.ts index 817d51ad01..a6a538ea5c 100644 --- a/packages/backend/src/server/api/endpoints/charts/instance.ts +++ b/packages/backend/src/server/api/endpoints/charts/instance.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { instanceChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import InstanceChart from '@/core/chart/charts/instance.js'; +import { schema } from '@/core/chart/charts/entities/instance.js'; export const meta = { tags: ['charts'], - res: getJsonSchema(instanceChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -23,6 +25,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await instanceChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.host); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private instanceChart: InstanceChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.instanceChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.host); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/notes.ts b/packages/backend/src/server/api/endpoints/charts/notes.ts index 951adf5408..8d03f2eaf1 100644 --- a/packages/backend/src/server/api/endpoints/charts/notes.ts +++ b/packages/backend/src/server/api/endpoints/charts/notes.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { notesChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import NotesChart from '@/core/chart/charts/notes.js'; +import { schema } from '@/core/chart/charts/entities/notes.js'; export const meta = { tags: ['charts', 'notes'], - res: getJsonSchema(notesChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -22,6 +24,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await notesChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private notesChart: NotesChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.notesChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/user/drive.ts b/packages/backend/src/server/api/endpoints/charts/user/drive.ts index f165b40224..87d56f38b7 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/drive.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/drive.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { perUserDriveChart } from '@/services/chart/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js'; +import { schema } from '@/core/chart/charts/entities/per-user-drive.js'; export const meta = { tags: ['charts', 'drive', 'users'], - res: getJsonSchema(perUserDriveChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -23,6 +25,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await perUserDriveChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private perUserDriveChart: PerUserDriveChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.perUserDriveChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/user/following.ts b/packages/backend/src/server/api/endpoints/charts/user/following.ts index f5d42e21c2..7a61544aea 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/following.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/following.ts @@ -1,11 +1,13 @@ -import define from '../../../define.js'; -import { getJsonSchema } from '@/services/chart/core.js'; -import { perUserFollowingChart } from '@/services/chart/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { getJsonSchema } from '@/core/chart/core.js'; +import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; +import { schema } from '@/core/chart/charts/entities/per-user-following.js'; export const meta = { tags: ['charts', 'users', 'following'], - res: getJsonSchema(perUserFollowingChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -23,6 +25,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await perUserFollowingChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private perUserFollowingChart: PerUserFollowingChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.perUserFollowingChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/user/notes.ts b/packages/backend/src/server/api/endpoints/charts/user/notes.ts index aefe550d43..fdc385191f 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/notes.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/notes.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { perUserNotesChart } from '@/services/chart/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import PerUserNotesChart from '@/core/chart/charts/per-user-notes.js'; +import { schema } from '@/core/chart/charts/entities/per-user-notes.js'; export const meta = { tags: ['charts', 'users', 'notes'], - res: getJsonSchema(perUserNotesChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -23,6 +25,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await perUserNotesChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private perUserNotesChart: PerUserNotesChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.perUserNotesChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts index 6bc6b56bf0..f0f3e520da 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { perUserReactionsChart } from '@/services/chart/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import PerUserReactionsChart from '@/core/chart/charts/per-user-reactions.js'; +import { schema } from '@/core/chart/charts/entities/per-user-reactions.js'; export const meta = { tags: ['charts', 'users', 'reactions'], - res: getJsonSchema(perUserReactionsChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -23,6 +25,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await perUserReactionsChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private perUserReactionsChart: PerUserReactionsChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.perUserReactionsChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/charts/users.ts b/packages/backend/src/server/api/endpoints/charts/users.ts index 338e8fd338..d09f2512e5 100644 --- a/packages/backend/src/server/api/endpoints/charts/users.ts +++ b/packages/backend/src/server/api/endpoints/charts/users.ts @@ -1,11 +1,13 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { usersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { getJsonSchema } from '@/core/chart/core.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import UsersChart from '@/core/chart/charts/users.js'; +import { schema } from '@/core/chart/charts/entities/users.js'; export const meta = { tags: ['charts', 'users'], - res: getJsonSchema(usersChart.schema), + res: getJsonSchema(schema), allowGet: true, cacheSec: 60 * 60, @@ -22,6 +24,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await usersChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private usersChart: UsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.usersChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index 5d72f5c1bf..c733d28657 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -1,8 +1,10 @@ -import define from '../../define.js'; -import { ClipNotes, Clips } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; +import { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { ApiError } from '../../error.js'; -import { genId } from '@/misc/gen-id.js'; -import { getNote } from '../../common/getters.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['account', 'notes', 'clips'], @@ -42,33 +44,47 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.findOneBy({ - id: ps.clipId, - userId: user.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); - } + @Inject(DI.clipNotesRepository) + private clipNotesRepository: ClipNotesRepository, - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); + private idService: IdService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const clip = await this.clipsRepository.findOneBy({ + id: ps.clipId, + userId: me.id, + }); - const exist = await ClipNotes.findOneBy({ - noteId: note.id, - clipId: clip.id, - }); + if (clip == null) { + throw new ApiError(meta.errors.noSuchClip); + } - if (exist != null) { - throw new ApiError(meta.errors.alreadyClipped); - } + const note = await this.getterService.getNote(ps.noteId).catch(e => { + if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw e; + }); + + const exist = await this.clipNotesRepository.findOneBy({ + noteId: note.id, + clipId: clip.id, + }); - await ClipNotes.insert({ - id: genId(), - noteId: note.id, - clipId: clip.id, - }); -}); + if (exist != null) { + throw new ApiError(meta.errors.alreadyClipped); + } + + await this.clipNotesRepository.insert({ + id: this.idService.genId(), + noteId: note.id, + clipId: clip.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index 4afe4222a1..8eca3d66d1 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { genId } from '@/misc/gen-id.js'; -import { Clips } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import { ClipsRepository } from '@/models/index.js'; +import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['clips'], @@ -27,15 +30,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - name: ps.name, - isPublic: ps.isPublic, - description: ps.description, - }).then(x => Clips.findOneByOrFail(x.identifiers[0])); - - return await Clips.pack(clip); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + + private clipEntityService: ClipEntityService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const clip = await this.clipsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + name: ps.name, + isPublic: ps.isPublic, + description: ps.description, + }).then(x => this.clipsRepository.findOneByOrFail(x.identifiers[0])); + + return await this.clipEntityService.pack(clip); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/clips/delete.ts b/packages/backend/src/server/api/endpoints/clips/delete.ts index b6c0eb702a..ea361ae9c0 100644 --- a/packages/backend/src/server/api/endpoints/clips/delete.ts +++ b/packages/backend/src/server/api/endpoints/clips/delete.ts @@ -1,6 +1,8 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ClipsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Clips } from '@/models/index.js'; export const meta = { tags: ['clips'], @@ -27,15 +29,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.findOneBy({ - id: ps.clipId, - userId: user.id, - }); - - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const clip = await this.clipsRepository.findOneBy({ + id: ps.clipId, + userId: me.id, + }); + + if (clip == null) { + throw new ApiError(meta.errors.noSuchClip); + } + + await this.clipsRepository.delete(clip.id); + }); } - - await Clips.delete(clip.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index 378811eba0..b57affd1c4 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { Clips } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ClipsRepository } from '@/models/index.js'; +import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['clips', 'account'], @@ -26,10 +29,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const clips = await Clips.findBy({ - userId: me.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, - return await Promise.all(clips.map(x => Clips.pack(x))); -}); + private clipEntityService: ClipEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const clips = await this.clipsRepository.findBy({ + userId: me.id, + }); + + return await Promise.all(clips.map(x => this.clipEntityService.pack(x))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index 4ace747efe..4282498931 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -1,10 +1,10 @@ -import define from '../../define.js'; -import { ClipNotes, Clips, Notes } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NotesRepository, ClipsRepository, ClipNotesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; export const meta = { tags: ['account', 'notes', 'clips'], @@ -44,43 +44,60 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.findOneBy({ - id: ps.clipId, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); - } + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - if (!clip.isPublic && (user == null || (clip.userId !== user.id))) { - throw new ApiError(meta.errors.noSuchClip); - } + @Inject(DI.clipNotesRepository) + private clipNotesRepository: ClipNotesRepository, - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .innerJoin(ClipNotes.metadata.targetName, 'clipNote', 'clipNote.noteId = note.id') - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') - .andWhere('clipNote.clipId = :clipId', { clipId: clip.id }); - - if (user) { - generateVisibilityQuery(query, user); - generateMutedUserQuery(query, user); - generateBlockedUserQuery(query, user); - } + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const clip = await this.clipsRepository.findOneBy({ + id: ps.clipId, + }); + + if (clip == null) { + throw new ApiError(meta.errors.noSuchClip); + } - const notes = await query - .take(ps.limit) - .getMany(); + if (!clip.isPublic && (me == null || (clip.userId !== me.id))) { + throw new ApiError(meta.errors.noSuchClip); + } - return await Notes.packMany(notes, user); -}); + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .innerJoin(this.clipNotesRepository.metadata.targetName, 'clipNote', 'clipNote.noteId = note.id') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') + .andWhere('clipNote.clipId = :clipId', { clipId: clip.id }); + + if (me) { + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + } + + const notes = await query + .take(ps.limit) + .getMany(); + + return await this.noteEntityService.packMany(notes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/clips/remove-note.ts b/packages/backend/src/server/api/endpoints/clips/remove-note.ts index 8b90e31f65..3fc60e3639 100644 --- a/packages/backend/src/server/api/endpoints/clips/remove-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/remove-note.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; -import { ClipNotes, Clips } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getNote } from '../../common/getters.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['account', 'notes', 'clips'], @@ -35,23 +37,36 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.findOneBy({ - id: ps.clipId, - userId: user.id, - }); - - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + + @Inject(DI.clipNotesRepository) + private clipNotesRepository: ClipNotesRepository, + + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const clip = await this.clipsRepository.findOneBy({ + id: ps.clipId, + userId: me.id, + }); - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); + if (clip == null) { + throw new ApiError(meta.errors.noSuchClip); + } - await ClipNotes.delete({ - noteId: note.id, - clipId: clip.id, - }); -}); + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + await this.clipNotesRepository.delete({ + noteId: note.id, + clipId: clip.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts index c3d73c168d..4e93540054 100644 --- a/packages/backend/src/server/api/endpoints/clips/show.ts +++ b/packages/backend/src/server/api/endpoints/clips/show.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ClipsRepository } from '@/models/index.js'; +import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Clips } from '@/models/index.js'; export const meta = { tags: ['clips', 'account'], @@ -33,19 +36,29 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the clip - const clip = await Clips.findOneBy({ - id: ps.clipId, - }); - - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, - if (!clip.isPublic && (me == null || (clip.userId !== me.id))) { - throw new ApiError(meta.errors.noSuchClip); - } + private clipEntityService: ClipEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the clip + const clip = await this.clipsRepository.findOneBy({ + id: ps.clipId, + }); + + if (clip == null) { + throw new ApiError(meta.errors.noSuchClip); + } - return await Clips.pack(clip); -}); + if (!clip.isPublic && (me == null || (clip.userId !== me.id))) { + throw new ApiError(meta.errors.noSuchClip); + } + + return await this.clipEntityService.pack(clip); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts index b67d844f6e..9880505d06 100644 --- a/packages/backend/src/server/api/endpoints/clips/update.ts +++ b/packages/backend/src/server/api/endpoints/clips/update.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ClipsRepository } from '@/models/index.js'; +import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { Clips } from '@/models/index.js'; export const meta = { tags: ['clips'], @@ -36,22 +39,32 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch the clip - const clip = await Clips.findOneBy({ - id: ps.clipId, - userId: user.id, - }); - - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + + private clipEntityService: ClipEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the clip + const clip = await this.clipsRepository.findOneBy({ + id: ps.clipId, + userId: me.id, + }); - await Clips.update(clip.id, { - name: ps.name, - description: ps.description, - isPublic: ps.isPublic, - }); + if (clip == null) { + throw new ApiError(meta.errors.noSuchClip); + } - return await Clips.pack(clip.id); -}); + await this.clipsRepository.update(clip.id, { + name: ps.name, + description: ps.description, + isPublic: ps.isPublic, + }); + + return await this.clipEntityService.pack(clip.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts index 82497adefa..6f40225f15 100644 --- a/packages/backend/src/server/api/endpoints/drive.ts +++ b/packages/backend/src/server/api/endpoints/drive.ts @@ -1,6 +1,7 @@ -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { DriveFiles } from '@/models/index.js'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; export const meta = { tags: ['drive', 'account'], @@ -32,14 +33,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const instance = await fetchMeta(true); - - // Calculate drive usage - const usage = await DriveFiles.calcDriveUsageOf(user.id); - - return { - capacity: 1024 * 1024 * (user.driveCapacityOverrideMb || instance.localDriveCapacityMb), - usage: usage, - }; -}); +@Injectable() +export default class extends Endpoint { + constructor( + private metaService: MetaService, + private driveFileEntityService: DriveFileEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const instance = await this.metaService.fetch(true); + + // Calculate drive usage + const usage = await this.driveFileEntityService.calcDriveUsageOf(me.id); + + return { + capacity: 1024 * 1024 * (me.driveCapacityOverrideMb ?? instance.localDriveCapacityMb), + usage: usage, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index 40e6c16c9c..56055d1340 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { DriveFiles } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -33,25 +36,36 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId) - .andWhere('file.userId = :userId', { userId: user.id }); - - if (ps.folderId) { - query.andWhere('file.folderId = :folderId', { folderId: ps.folderId }); - } else { - query.andWhere('file.folderId IS NULL'); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (ps.type) { - if (ps.type.endsWith('/*')) { - query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); - } else { - query.andWhere('file.type = :type', { type: ps.type }); - } - } + private driveFileEntityService: DriveFileEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId) + .andWhere('file.userId = :userId', { userId: me.id }); + + if (ps.folderId) { + query.andWhere('file.folderId = :folderId', { folderId: ps.folderId }); + } else { + query.andWhere('file.folderId IS NULL'); + } - const files = await query.take(ps.limit).getMany(); + if (ps.type) { + if (ps.type.endsWith('/*')) { + query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); + } else { + query.andWhere('file.type = :type', { type: ps.type }); + } + } - return await DriveFiles.packMany(files, { detail: false, self: true }); -}); + const files = await query.take(ps.limit).getMany(); + + return await this.driveFileEntityService.packMany(files, { detail: false, self: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index 415a8cc693..9f11eb8b53 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NotesRepository, DriveFilesRepository } from '@/models/index.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFiles, Notes } from '@/models/index.js'; export const meta = { tags: ['drive', 'notes'], @@ -39,22 +42,35 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch file - const file = await DriveFiles.findOneBy({ - id: ps.fileId, - userId: user.id, - }); - - if (file == null) { - throw new ApiError(meta.errors.noSuchFile); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch file + const file = await this.driveFilesRepository.findOneBy({ + id: ps.fileId, + userId: me.id, + }); - const notes = await Notes.createQueryBuilder('note') - .where(':file = ANY(note.fileIds)', { file: file.id }) - .getMany(); + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } - return await Notes.packMany(notes, user, { - detail: true, - }); -}); + const notes = await this.notesRepository.createQueryBuilder('note') + .where(':file = ANY(note.fileIds)', { file: file.id }) + .getMany(); + + return await this.noteEntityService.packMany(notes, me, { + detail: true, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index bbae9bf4e4..176031d808 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { DriveFiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -25,11 +27,19 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOneBy({ - md5: ps.md5, - userId: user.id, - }); - - return file != null; -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ + md5: ps.md5, + userId: me.id, + }); + + return file != null; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index ddcbd62889..bff83876d7 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -1,11 +1,13 @@ import ms from 'ms'; -import { addFile } from '@/services/drive/add-file.js'; -import { DriveFiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DriveFilesRepository } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import define from '../../../define.js'; -import { apiLogger } from '../../../logger.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DriveService } from '@/core/DriveService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -64,48 +66,60 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user, _, file, cleanup, ip, headers) => { - // Get 'name' parameter - let name = ps.name || file.originalname; - if (name !== undefined && name !== null) { - name = name.trim(); - if (name.length === 0) { - name = null; - } else if (name === 'blob') { - name = null; - } else if (!DriveFiles.validateFileName(name)) { - throw new ApiError(meta.errors.invalidFileName); - } - } else { - name = null; - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - const meta = await fetchMeta(); + private driveFileEntityService: DriveFileEntityService, + private metaService: MetaService, + private driveService: DriveService, + ) { + super(meta, paramDef, async (ps, me, _, file, cleanup, ip, headers) => { + // Get 'name' parameter + let name = ps.name ?? file.originalname; + if (name !== undefined && name !== null) { + name = name.trim(); + if (name.length === 0) { + name = null; + } else if (name === 'blob') { + name = null; + } else if (!this.driveFileEntityService.validateFileName(name)) { + throw new ApiError(meta.errors.invalidFileName); + } + } else { + name = null; + } - try { - // Create file - const driveFile = await addFile({ - user, - path: file.path, - name, - comment: ps.comment, - folderId: ps.folderId, - force: ps.force, - sensitive: ps.isSensitive, - requestIp: meta.enableIpLogging ? ip : null, - requestHeaders: meta.enableIpLogging ? headers : null, - }); - return await DriveFiles.pack(driveFile, { self: true }); - } catch (e) { - if (e instanceof Error || typeof e === 'string') { - apiLogger.error(e); - } - if (e instanceof IdentifiableError) { - if (e.id === '282f77bf-5816-4f72-9264-aa14d8261a21') throw new ApiError(meta.errors.inappropriate); - if (e.id === 'c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6') throw new ApiError(meta.errors.noFreeSpace); - } - throw new ApiError(); - } finally { + const meta = await this.metaService.fetch(); + + try { + // Create file + const driveFile = await this.driveService.addFile({ + user: me, + path: file.path, + name, + comment: ps.comment, + folderId: ps.folderId, + force: ps.force, + sensitive: ps.isSensitive, + requestIp: meta.enableIpLogging ? ip : null, + requestHeaders: meta.enableIpLogging ? headers : null, + }); + return await this.driveFileEntityService.pack(driveFile, { self: true }); + } catch (err) { + if (err instanceof Error || typeof err === 'string') { + console.error(err); + } + if (err instanceof IdentifiableError) { + if (err.id === '282f77bf-5816-4f72-9264-aa14d8261a21') throw new ApiError(meta.errors.inappropriate); + if (err.id === 'c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6') throw new ApiError(meta.errors.noFreeSpace); + } + throw new ApiError(); + } finally { cleanup!(); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index 6108ae7da9..9d2ea6011a 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -1,8 +1,10 @@ -import { deleteFile } from '@/services/drive/delete-file.js'; -import { publishDriveStream } from '@/services/stream.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DriveService } from '@/core/DriveService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFiles, Users } from '@/models/index.js'; export const meta = { tags: ['drive'], @@ -37,20 +39,31 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOneBy({ id: ps.fileId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (file == null) { - throw new ApiError(meta.errors.noSuchFile); - } + private driveService: DriveService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); - if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { - throw new ApiError(meta.errors.accessDenied); - } + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } - // Delete - await deleteFile(file); + if ((!me.isAdmin && !me.isModerator) && (file.userId !== me.id)) { + throw new ApiError(meta.errors.accessDenied); + } - // Publish fileDeleted event - publishDriveStream(user.id, 'fileDeleted', file.id); -}); + // Delete + await this.driveService.deleteFile(file); + + // Publish fileDeleted event + this.globalEventService.publishDriveStream(me.id, 'fileDeleted', file.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts index f2bc7348c6..6299ca8f6b 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts @@ -1,5 +1,8 @@ -import { DriveFiles } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DriveFilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -30,11 +33,21 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const files = await DriveFiles.findBy({ - md5: ps.md5, - userId: user.id, - }); - - return await DriveFiles.packMany(files, { self: true }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private driveFileEntityService: DriveFileEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const files = await this.driveFilesRepository.findBy({ + md5: ps.md5, + userId: me.id, + }); + + return await this.driveFileEntityService.packMany(files, { self: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts index 245fb45a65..e4cd5213dd 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; -import { DriveFiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -32,12 +35,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const files = await DriveFiles.findBy({ - name: ps.name, - userId: user.id, - folderId: ps.folderId ?? IsNull(), - }); - - return await Promise.all(files.map(file => DriveFiles.pack(file, { self: true }))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private driveFileEntityService: DriveFileEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const files = await this.driveFilesRepository.findBy({ + name: ps.name, + userId: me.id, + folderId: ps.folderId ?? IsNull(), + }); + + return await Promise.all(files.map(file => this.driveFileEntityService.pack(file, { self: true }))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index 2c604c54c8..bae4d7d66f 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -1,6 +1,9 @@ -import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFiles, Users } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -52,34 +55,44 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - let file: DriveFile | null = null; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (ps.fileId) { - file = await DriveFiles.findOneBy({ id: ps.fileId }); - } else if (ps.url) { - file = await DriveFiles.findOne({ - where: [{ - url: ps.url, - }, { - webpublicUrl: ps.url, - }, { - thumbnailUrl: ps.url, - }], - }); - } + private driveFileEntityService: DriveFileEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + let file: DriveFile | null = null; - if (file == null) { - throw new ApiError(meta.errors.noSuchFile); - } + if (ps.fileId) { + file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); + } else if (ps.url) { + file = await this.driveFilesRepository.findOne({ + where: [{ + url: ps.url, + }, { + webpublicUrl: ps.url, + }, { + thumbnailUrl: ps.url, + }], + }); + } - if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { - throw new ApiError(meta.errors.accessDenied); - } + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } - return await DriveFiles.pack(file, { - detail: true, - withUser: true, - self: true, - }); -}); + if ((!me.isAdmin && !me.isModerator) && (file.userId !== me.id)) { + throw new ApiError(meta.errors.accessDenied); + } + + return await this.driveFileEntityService.pack(file, { + detail: true, + withUser: true, + self: true, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index fa2ec8519c..03e3663f08 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -1,7 +1,10 @@ -import { publishDriveStream } from '@/services/stream.js'; -import { DriveFiles, DriveFolders, Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; -import define from '../../../define.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -59,54 +62,68 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOneBy({ id: ps.fileId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.driveFoldersRepository) + private driveFoldersRepository: DriveFoldersRepository, + + private driveFileEntityService: DriveFileEntityService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); + + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } - if (file == null) { - throw new ApiError(meta.errors.noSuchFile); - } + if ((!me.isAdmin && !me.isModerator) && (file.userId !== me.id)) { + throw new ApiError(meta.errors.accessDenied); + } - if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { - throw new ApiError(meta.errors.accessDenied); - } + if (ps.name) file.name = ps.name; + if (!this.driveFileEntityService.validateFileName(file.name)) { + throw new ApiError(meta.errors.invalidFileName); + } - if (ps.name) file.name = ps.name; - if (!DriveFiles.validateFileName(file.name)) { - throw new ApiError(meta.errors.invalidFileName); - } + if (ps.comment !== undefined) file.comment = ps.comment; - if (ps.comment !== undefined) file.comment = ps.comment; + if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive; - if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive; + if (ps.folderId !== undefined) { + if (ps.folderId === null) { + file.folderId = null; + } else { + const folder = await this.driveFoldersRepository.findOneBy({ + id: ps.folderId, + userId: me.id, + }); - if (ps.folderId !== undefined) { - if (ps.folderId === null) { - file.folderId = null; - } else { - const folder = await DriveFolders.findOneBy({ - id: ps.folderId, - userId: user.id, - }); + if (folder == null) { + throw new ApiError(meta.errors.noSuchFolder); + } - if (folder == null) { - throw new ApiError(meta.errors.noSuchFolder); + file.folderId = folder.id; + } } - file.folderId = folder.id; - } - } - - await DriveFiles.update(file.id, { - name: file.name, - comment: file.comment, - folderId: file.folderId, - isSensitive: file.isSensitive, - }); + await this.driveFilesRepository.update(file.id, { + name: file.name, + comment: file.comment, + folderId: file.folderId, + isSensitive: file.isSensitive, + }); - const fileObj = await DriveFiles.pack(file, { self: true }); + const fileObj = await this.driveFileEntityService.pack(file, { self: true }); - // Publish fileUpdated event - publishDriveStream(user.id, 'fileUpdated', fileObj); + // Publish fileUpdated event + this.globalEventService.publishDriveStream(me.id, 'fileUpdated', fileObj); - return fileObj; -}); + return fileObj; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index eb8071c3c9..f4f8df3c2b 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -1,9 +1,12 @@ import ms from 'ms'; -import { uploadFromUrl } from '@/services/drive/upload-from-url.js'; -import { DriveFiles } from '@/models/index.js'; -import { publishMainStream } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DriveFilesRepository } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; -import define from '../../../define.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DriveService } from '@/core/DriveService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -34,13 +37,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user, _1, _2, _3, ip, headers) => { - uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment, requestIp: ip, requestHeaders: headers }).then(file => { - DriveFiles.pack(file, { self: true }).then(packedFile => { - publishMainStream(user.id, 'urlUploadFinished', { - marker: ps.marker, - file: packedFile, +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private driveFileEntityService: DriveFileEntityService, + private driveService: DriveService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, user, _1, _2, _3, ip, headers) => { + this.driveService.uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment, requestIp: ip, requestHeaders: headers }).then(file => { + this.driveFileEntityService.pack(file, { self: true }).then(packedFile => { + this.globalEventService.publishMainStream(user.id, 'urlUploadFinished', { + marker: ps.marker, + file: packedFile, + }); + }); }); }); - }); -}); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/folders.ts b/packages/backend/src/server/api/endpoints/drive/folders.ts index d4d530ba9e..703dc83ecd 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { DriveFolders } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFoldersRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -32,17 +35,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(DriveFolders.createQueryBuilder('folder'), ps.sinceId, ps.untilId) - .andWhere('folder.userId = :userId', { userId: user.id }); - - if (ps.folderId) { - query.andWhere('folder.parentId = :parentId', { parentId: ps.folderId }); - } else { - query.andWhere('folder.parentId IS NULL'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFoldersRepository) + private driveFoldersRepository: DriveFoldersRepository, + + private driveFolderEntityService: DriveFolderEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.driveFoldersRepository.createQueryBuilder('folder'), ps.sinceId, ps.untilId) + .andWhere('folder.userId = :userId', { userId: me.id }); + + if (ps.folderId) { + query.andWhere('folder.parentId = :parentId', { parentId: ps.folderId }); + } else { + query.andWhere('folder.parentId IS NULL'); + } + + const folders = await query.take(ps.limit).getMany(); + + return await Promise.all(folders.map(folder => this.driveFolderEntityService.pack(folder))); + }); } - - const folders = await query.take(ps.limit).getMany(); - - return await Promise.all(folders.map(folder => DriveFolders.pack(folder))); -}); +} diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts index 3d7f514c85..7604eaf489 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts @@ -1,8 +1,11 @@ -import { publishDriveStream } from '@/services/stream.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFoldersRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFolders } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; export const meta = { tags: ['drive'], @@ -29,41 +32,53 @@ export const meta = { export const paramDef = { type: 'object', properties: { - name: { type: 'string', default: "Untitled", maxLength: 200 }, + name: { type: 'string', default: 'Untitled', maxLength: 200 }, parentId: { type: 'string', format: 'misskey:id', nullable: true }, }, required: [], } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // If the parent folder is specified - let parent = null; - if (ps.parentId) { - // Fetch parent folder - parent = await DriveFolders.findOneBy({ - id: ps.parentId, - userId: user.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFoldersRepository) + private driveFoldersRepository: DriveFoldersRepository, - if (parent == null) { - throw new ApiError(meta.errors.noSuchFolder); - } - } + private driveFolderEntityService: DriveFolderEntityService, + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // If the parent folder is specified + let parent = null; + if (ps.parentId) { + // Fetch parent folder + parent = await this.driveFoldersRepository.findOneBy({ + id: ps.parentId, + userId: me.id, + }); + + if (parent == null) { + throw new ApiError(meta.errors.noSuchFolder); + } + } - // Create folder - const folder = await DriveFolders.insert({ - id: genId(), - createdAt: new Date(), - name: ps.name, - parentId: parent !== null ? parent.id : null, - userId: user.id, - }).then(x => DriveFolders.findOneByOrFail(x.identifiers[0])); + // Create folder + const folder = await this.driveFoldersRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + name: ps.name, + parentId: parent !== null ? parent.id : null, + userId: me.id, + }).then(x => this.driveFoldersRepository.findOneByOrFail(x.identifiers[0])); - const folderObj = await DriveFolders.pack(folder); + const folderObj = await this.driveFolderEntityService.pack(folder); - // Publish folderCreated event - publishDriveStream(user.id, 'folderCreated', folderObj); + // Publish folderCreated event + this.globalEventService.publishDriveStream(me.id, 'folderCreated', folderObj); - return folderObj; -}); + return folderObj; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts index ab9d411ec0..dcbaecf8af 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; -import { publishDriveStream } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFoldersRepository, DriveFilesRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFolders, DriveFiles } from '@/models/index.js'; export const meta = { tags: ['drive'], @@ -34,28 +36,41 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Get folder - const folder = await DriveFolders.findOneBy({ - id: ps.folderId, - userId: user.id, - }); - - if (folder == null) { - throw new ApiError(meta.errors.noSuchFolder); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - const [childFoldersCount, childFilesCount] = await Promise.all([ - DriveFolders.countBy({ parentId: folder.id }), - DriveFiles.countBy({ folderId: folder.id }), - ]); + @Inject(DI.driveFoldersRepository) + private driveFoldersRepository: DriveFoldersRepository, - if (childFoldersCount !== 0 || childFilesCount !== 0) { - throw new ApiError(meta.errors.hasChildFilesOrFolders); - } + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Get folder + const folder = await this.driveFoldersRepository.findOneBy({ + id: ps.folderId, + userId: me.id, + }); + + if (folder == null) { + throw new ApiError(meta.errors.noSuchFolder); + } - await DriveFolders.delete(folder.id); + const [childFoldersCount, childFilesCount] = await Promise.all([ + this.driveFoldersRepository.countBy({ parentId: folder.id }), + this.driveFilesRepository.countBy({ folderId: folder.id }), + ]); - // Publish folderCreated event - publishDriveStream(user.id, 'folderDeleted', folder.id); -}); + if (childFoldersCount !== 0 || childFilesCount !== 0) { + throw new ApiError(meta.errors.hasChildFilesOrFolders); + } + + await this.driveFoldersRepository.delete(folder.id); + + // Publish folderCreated event + this.globalEventService.publishDriveStream(me.id, 'folderDeleted', folder.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/folders/find.ts b/packages/backend/src/server/api/endpoints/drive/folders/find.ts index 1feab273a1..96a87344a9 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/find.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; -import { DriveFolders } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFoldersRepository } from '@/models/index.js'; +import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -30,12 +33,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const folders = await DriveFolders.findBy({ - name: ps.name, - userId: user.id, - parentId: ps.parentId ?? IsNull(), - }); - - return await Promise.all(folders.map(folder => DriveFolders.pack(folder))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFoldersRepository) + private driveFoldersRepository: DriveFoldersRepository, + + private driveFolderEntityService: DriveFolderEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const folders = await this.driveFoldersRepository.findBy({ + name: ps.name, + userId: me.id, + parentId: ps.parentId ?? IsNull(), + }); + + return await Promise.all(folders.map(folder => this.driveFolderEntityService.pack(folder))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/folders/show.ts b/packages/backend/src/server/api/endpoints/drive/folders/show.ts index 1e7aa2b16c..4c25bc705c 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/show.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFoldersRepository } from '@/models/index.js'; +import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFolders } from '@/models/index.js'; export const meta = { tags: ['drive'], @@ -33,18 +36,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Get folder - const folder = await DriveFolders.findOneBy({ - id: ps.folderId, - userId: user.id, - }); - - if (folder == null) { - throw new ApiError(meta.errors.noSuchFolder); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFoldersRepository) + private driveFoldersRepository: DriveFoldersRepository, + + private driveFolderEntityService: DriveFolderEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Get folder + const folder = await this.driveFoldersRepository.findOneBy({ + id: ps.folderId, + userId: me.id, + }); + + if (folder == null) { + throw new ApiError(meta.errors.noSuchFolder); + } + + return await this.driveFolderEntityService.pack(folder, { + detail: true, + }); + }); } - - return await DriveFolders.pack(folder, { - detail: true, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts index 1aa2e84292..4fcd37bbbf 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts @@ -1,7 +1,10 @@ -import { publishDriveStream } from '@/services/stream.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFoldersRepository } from '@/models/index.js'; +import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFolders } from '@/models/index.js'; export const meta = { tags: ['drive'], @@ -48,71 +51,82 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch folder - const folder = await DriveFolders.findOneBy({ - id: ps.folderId, - userId: user.id, - }); - - if (folder == null) { - throw new ApiError(meta.errors.noSuchFolder); - } - - if (ps.name) folder.name = ps.name; - - if (ps.parentId !== undefined) { - if (ps.parentId === folder.id) { - throw new ApiError(meta.errors.recursiveNesting); - } else if (ps.parentId === null) { - folder.parentId = null; - } else { - // Get parent folder - const parent = await DriveFolders.findOneBy({ - id: ps.parentId, - userId: user.id, +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFoldersRepository) + private driveFoldersRepository: DriveFoldersRepository, + + private driveFolderEntityService: DriveFolderEntityService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch folder + const folder = await this.driveFoldersRepository.findOneBy({ + id: ps.folderId, + userId: me.id, }); - if (parent == null) { - throw new ApiError(meta.errors.noSuchParentFolder); + if (folder == null) { + throw new ApiError(meta.errors.noSuchFolder); } - // Check if the circular reference will occur - async function checkCircle(folderId: string): Promise { - // Fetch folder - const folder2 = await DriveFolders.findOneBy({ - id: folderId, - }); - - if (folder2!.id === folder!.id) { - return true; - } else if (folder2!.parentId) { - return await checkCircle(folder2!.parentId); - } else { - return false; - } - } + if (ps.name) folder.name = ps.name; - if (parent.parentId !== null) { - if (await checkCircle(parent.parentId)) { + if (ps.parentId !== undefined) { + if (ps.parentId === folder.id) { throw new ApiError(meta.errors.recursiveNesting); + } else if (ps.parentId === null) { + folder.parentId = null; + } else { + // Get parent folder + const parent = await this.driveFoldersRepository.findOneBy({ + id: ps.parentId, + userId: me.id, + }); + + if (parent == null) { + throw new ApiError(meta.errors.noSuchParentFolder); + } + + // Check if the circular reference will occur + const checkCircle = async (folderId: string): Promise => { + // Fetch folder + const folder2 = await this.driveFoldersRepository.findOneBy({ + id: folderId, + }); + + if (folder2!.id === folder!.id) { + return true; + } else if (folder2!.parentId) { + return await checkCircle(folder2!.parentId); + } else { + return false; + } + }; + + if (parent.parentId !== null) { + if (await checkCircle(parent.parentId)) { + throw new ApiError(meta.errors.recursiveNesting); + } + } + + folder.parentId = parent.id; } } - folder.parentId = parent.id; - } - } - - // Update - DriveFolders.update(folder.id, { - name: folder.name, - parentId: folder.parentId, - }); + // Update + this.driveFoldersRepository.update(folder.id, { + name: folder.name, + parentId: folder.parentId, + }); - const folderObj = await DriveFolders.pack(folder); + const folderObj = await this.driveFolderEntityService.pack(folder); - // Publish folderUpdated event - publishDriveStream(user.id, 'folderUpdated', folderObj); + // Publish folderUpdated event + this.globalEventService.publishDriveStream(me.id, 'folderUpdated', folderObj); - return folderObj; -}); + return folderObj; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/drive/stream.ts b/packages/backend/src/server/api/endpoints/drive/stream.ts index 99e8d024fb..aba73c2092 100644 --- a/packages/backend/src/server/api/endpoints/drive/stream.ts +++ b/packages/backend/src/server/api/endpoints/drive/stream.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { DriveFiles } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -32,19 +35,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId) - .andWhere('file.userId = :userId', { userId: user.id }); - - if (ps.type) { - if (ps.type.endsWith('/*')) { - query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); - } else { - query.andWhere('file.type = :type', { type: ps.type }); - } - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private driveFileEntityService: DriveFileEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId) + .andWhere('file.userId = :userId', { userId: me.id }); - const files = await query.take(ps.limit).getMany(); + if (ps.type) { + if (ps.type.endsWith('/*')) { + query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); + } else { + query.andWhere('file.type = :type', { type: ps.type }); + } + } - return await DriveFiles.packMany(files, { detail: false, self: true }); -}); + const files = await query.take(ps.limit).getMany(); + + return await this.driveFileEntityService.packMany(files, { detail: false, self: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/email-address/available.ts b/packages/backend/src/server/api/endpoints/email-address/available.ts index 07064ce9fa..8a497a514e 100644 --- a/packages/backend/src/server/api/endpoints/email-address/available.ts +++ b/packages/backend/src/server/api/endpoints/email-address/available.ts @@ -1,5 +1,6 @@ -import define from '../../define.js'; -import { validateEmailForAccount } from '@/services/validate-email-for-account.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmailService } from '@/core/EmailService.js'; export const meta = { tags: ['users'], @@ -31,6 +32,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - return await validateEmailForAccount(ps.emailAddress); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private emailService: EmailService, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.emailService.validateEmailForAccount(ps.emailAddress); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts index c174126779..2141dfbeb0 100644 --- a/packages/backend/src/server/api/endpoints/endpoint.ts +++ b/packages/backend/src/server/api/endpoints/endpoint.ts @@ -1,4 +1,5 @@ -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; import endpoints from '../endpoints.js'; export const meta = { @@ -16,13 +17,19 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const ep = endpoints.find(x => x.name === ps.endpoint); - if (ep == null) return null; - return { - params: Object.entries(ep.params.properties || {}).map(([k, v]) => ({ - name: k, - type: v.type.charAt(0).toUpperCase() + v.type.slice(1), - })), - }; -}); +@Injectable() +export default class extends Endpoint { + constructor( + ) { + super(meta, paramDef, async (ps) => { + const ep = endpoints.find(x => x.name === ps.endpoint); + if (ep == null) return null; + return { + params: Object.entries(ep.params.properties || {}).map(([k, v]) => ({ + name: k, + type: v.type.charAt(0).toUpperCase() + v.type.slice(1), + })), + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/endpoints.ts b/packages/backend/src/server/api/endpoints/endpoints.ts index b20da96eb3..91fc3ec98d 100644 --- a/packages/backend/src/server/api/endpoints/endpoints.ts +++ b/packages/backend/src/server/api/endpoints/endpoints.ts @@ -1,4 +1,5 @@ -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; import endpoints from '../endpoints.js'; export const meta = { @@ -29,6 +30,12 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - return endpoints.map(x => x.name); -}); +@Injectable() +export default class extends Endpoint { + constructor( + ) { + super(meta, paramDef, async () => { + return endpoints.map(x => x.name); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts index 5fe622932d..ead6b037cc 100644 --- a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts +++ b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts @@ -1,6 +1,7 @@ import ms from 'ms'; -import { createExportCustomEmojisJob } from '@/queue/index.js'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, @@ -18,6 +19,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - createExportCustomEmojisJob(user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportCustomEmojisJob(me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/federation/followers.ts b/packages/backend/src/server/api/endpoints/federation/followers.ts index 7b1197d1e5..e5222fcbfd 100644 --- a/packages/backend/src/server/api/endpoints/federation/followers.ts +++ b/packages/backend/src/server/api/endpoints/federation/followers.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Followings } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { FollowingsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['federation'], @@ -30,13 +33,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) - .andWhere(`following.followeeHost = :host`, { host: ps.host }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - const followings = await query - .take(ps.limit) - .getMany(); + private followingEntityService: FollowingEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId) + .andWhere('following.followeeHost = :host', { host: ps.host }); - return await Followings.packMany(followings, me, { populateFollowee: true }); -}); + const followings = await query + .take(ps.limit) + .getMany(); + + return await this.followingEntityService.packMany(followings, me, { populateFollowee: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/federation/following.ts b/packages/backend/src/server/api/endpoints/federation/following.ts index ed1f142d88..a20c5a31b3 100644 --- a/packages/backend/src/server/api/endpoints/federation/following.ts +++ b/packages/backend/src/server/api/endpoints/federation/following.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Followings } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { FollowingsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['federation'], @@ -30,13 +33,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) - .andWhere(`following.followerHost = :host`, { host: ps.host }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - const followings = await query - .take(ps.limit) - .getMany(); + private followingEntityService: FollowingEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId) + .andWhere('following.followerHost = :host', { host: ps.host }); - return await Followings.packMany(followings, me, { populateFollowee: true }); -}); + const followings = await query + .take(ps.limit) + .getMany(); + + return await this.followingEntityService.packMany(followings, me, { populateFollowee: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 07e5c07c6a..e7f8cefff5 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -1,7 +1,9 @@ -import config from '@/config/index.js'; -import define from '../../define.js'; -import { Instances } from '@/models/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { InstancesRepository } from '@/models/index.js'; +import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['federation'], @@ -37,82 +39,93 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Instances.createQueryBuilder('instance'); - - switch (ps.sort) { - case '+pubSub': query.orderBy('instance.followingCount', 'DESC').orderBy('instance.followersCount', 'DESC'); break; - case '-pubSub': query.orderBy('instance.followingCount', 'ASC').orderBy('instance.followersCount', 'ASC'); break; - case '+notes': query.orderBy('instance.notesCount', 'DESC'); break; - case '-notes': query.orderBy('instance.notesCount', 'ASC'); break; - case '+users': query.orderBy('instance.usersCount', 'DESC'); break; - case '-users': query.orderBy('instance.usersCount', 'ASC'); break; - case '+following': query.orderBy('instance.followingCount', 'DESC'); break; - case '-following': query.orderBy('instance.followingCount', 'ASC'); break; - case '+followers': query.orderBy('instance.followersCount', 'DESC'); break; - case '-followers': query.orderBy('instance.followersCount', 'ASC'); break; - case '+caughtAt': query.orderBy('instance.caughtAt', 'DESC'); break; - case '-caughtAt': query.orderBy('instance.caughtAt', 'ASC'); break; - case '+lastCommunicatedAt': query.orderBy('instance.lastCommunicatedAt', 'DESC'); break; - case '-lastCommunicatedAt': query.orderBy('instance.lastCommunicatedAt', 'ASC'); break; - - default: query.orderBy('instance.id', 'DESC'); break; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, + + private instanceEntityService: InstanceEntityService, + private metaService: MetaService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.instancesRepository.createQueryBuilder('instance'); + + switch (ps.sort) { + case '+pubSub': query.orderBy('instance.followingCount', 'DESC').orderBy('instance.followersCount', 'DESC'); break; + case '-pubSub': query.orderBy('instance.followingCount', 'ASC').orderBy('instance.followersCount', 'ASC'); break; + case '+notes': query.orderBy('instance.notesCount', 'DESC'); break; + case '-notes': query.orderBy('instance.notesCount', 'ASC'); break; + case '+users': query.orderBy('instance.usersCount', 'DESC'); break; + case '-users': query.orderBy('instance.usersCount', 'ASC'); break; + case '+following': query.orderBy('instance.followingCount', 'DESC'); break; + case '-following': query.orderBy('instance.followingCount', 'ASC'); break; + case '+followers': query.orderBy('instance.followersCount', 'DESC'); break; + case '-followers': query.orderBy('instance.followersCount', 'ASC'); break; + case '+caughtAt': query.orderBy('instance.caughtAt', 'DESC'); break; + case '-caughtAt': query.orderBy('instance.caughtAt', 'ASC'); break; + case '+lastCommunicatedAt': query.orderBy('instance.lastCommunicatedAt', 'DESC'); break; + case '-lastCommunicatedAt': query.orderBy('instance.lastCommunicatedAt', 'ASC'); break; + + default: query.orderBy('instance.id', 'DESC'); break; + } + + if (typeof ps.blocked === 'boolean') { + const meta = await this.metaService.fetch(true); + if (ps.blocked) { + query.andWhere('instance.host IN (:...blocks)', { blocks: meta.blockedHosts }); + } else { + query.andWhere('instance.host NOT IN (:...blocks)', { blocks: meta.blockedHosts }); + } + } + + if (typeof ps.notResponding === 'boolean') { + if (ps.notResponding) { + query.andWhere('instance.isNotResponding = TRUE'); + } else { + query.andWhere('instance.isNotResponding = FALSE'); + } + } + + if (typeof ps.suspended === 'boolean') { + if (ps.suspended) { + query.andWhere('instance.isSuspended = TRUE'); + } else { + query.andWhere('instance.isSuspended = FALSE'); + } + } + + if (typeof ps.federating === 'boolean') { + if (ps.federating) { + query.andWhere('((instance.followingCount > 0) OR (instance.followersCount > 0))'); + } else { + query.andWhere('((instance.followingCount = 0) AND (instance.followersCount = 0))'); + } + } + + if (typeof ps.subscribing === 'boolean') { + if (ps.subscribing) { + query.andWhere('instance.followersCount > 0'); + } else { + query.andWhere('instance.followersCount = 0'); + } + } + + if (typeof ps.publishing === 'boolean') { + if (ps.publishing) { + query.andWhere('instance.followingCount > 0'); + } else { + query.andWhere('instance.followingCount = 0'); + } + } + + if (ps.host) { + query.andWhere('instance.host like :host', { host: '%' + ps.host.toLowerCase() + '%' }); + } + + const instances = await query.take(ps.limit).skip(ps.offset).getMany(); + + return await this.instanceEntityService.packMany(instances); + }); } - - if (typeof ps.blocked === 'boolean') { - const meta = await fetchMeta(true); - if (ps.blocked) { - query.andWhere('instance.host IN (:...blocks)', { blocks: meta.blockedHosts }); - } else { - query.andWhere('instance.host NOT IN (:...blocks)', { blocks: meta.blockedHosts }); - } - } - - if (typeof ps.notResponding === 'boolean') { - if (ps.notResponding) { - query.andWhere('instance.isNotResponding = TRUE'); - } else { - query.andWhere('instance.isNotResponding = FALSE'); - } - } - - if (typeof ps.suspended === 'boolean') { - if (ps.suspended) { - query.andWhere('instance.isSuspended = TRUE'); - } else { - query.andWhere('instance.isSuspended = FALSE'); - } - } - - if (typeof ps.federating === 'boolean') { - if (ps.federating) { - query.andWhere('((instance.followingCount > 0) OR (instance.followersCount > 0))'); - } else { - query.andWhere('((instance.followingCount = 0) AND (instance.followersCount = 0))'); - } - } - - if (typeof ps.subscribing === 'boolean') { - if (ps.subscribing) { - query.andWhere('instance.followersCount > 0'); - } else { - query.andWhere('instance.followersCount = 0'); - } - } - - if (typeof ps.publishing === 'boolean') { - if (ps.publishing) { - query.andWhere('instance.followingCount > 0'); - } else { - query.andWhere('instance.followingCount = 0'); - } - } - - if (ps.host) { - query.andWhere('instance.host like :host', { host: '%' + ps.host.toLowerCase() + '%' }); - } - - const instances = await query.take(ps.limit).skip(ps.offset).getMany(); - - return await Instances.packMany(instances); -}); +} diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts index 2fbb8a15cb..f855b54537 100644 --- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts +++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Instances } from '@/models/index.js'; -import { toPuny } from '@/misc/convert-host.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { InstancesRepository } from '@/models/index.js'; +import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['federation'], @@ -26,9 +29,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const instance = await Instances - .findOneBy({ host: toPuny(ps.host) }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, - return instance ? await Instances.pack(instance) : null; -}); + private utilityService: UtilityService, + private instanceEntityService: InstanceEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const instance = await this.instancesRepository + .findOneBy({ host: this.utilityService.toPuny(ps.host) }); + + return instance ? await this.instanceEntityService.pack(instance) : null; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/federation/stats.ts b/packages/backend/src/server/api/endpoints/federation/stats.ts index e02c7b97e0..d07a08637f 100644 --- a/packages/backend/src/server/api/endpoints/federation/stats.ts +++ b/packages/backend/src/server/api/endpoints/federation/stats.ts @@ -1,7 +1,10 @@ import { IsNull, MoreThan, Not } from 'typeorm'; -import { Followings, Instances } from '@/models/index.js'; -import { awaitAll } from '@/prelude/await-all.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { FollowingsRepository, InstancesRepository } from '@/models/index.js'; +import { awaitAll } from '@/misc/prelude/await-all.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['federation'], @@ -21,45 +24,58 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const [topSubInstances, topPubInstances, allSubCount, allPubCount] = await Promise.all([ - Instances.find({ - where: { - followersCount: MoreThan(0), - }, - order: { - followersCount: 'DESC', - }, - take: ps.limit, - }), - Instances.find({ - where: { - followingCount: MoreThan(0), - }, - order: { - followingCount: 'DESC', - }, - take: ps.limit, - }), - Followings.count({ - where: { - followeeHost: Not(IsNull()), - }, - }), - Followings.count({ - where: { - followerHost: Not(IsNull()), - }, - }), - ]); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, - const gotSubCount = topSubInstances.map(x => x.followersCount).reduce((a, b) => a + b, 0); - const gotPubCount = topPubInstances.map(x => x.followingCount).reduce((a, b) => a + b, 0); + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - return await awaitAll({ - topSubInstances: Instances.packMany(topSubInstances), - otherFollowersCount: Math.max(0, allSubCount - gotSubCount), - topPubInstances: Instances.packMany(topPubInstances), - otherFollowingCount: Math.max(0, allPubCount - gotPubCount), - }); -}); + private instanceEntityService: InstanceEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const [topSubInstances, topPubInstances, allSubCount, allPubCount] = await Promise.all([ + this.instancesRepository.find({ + where: { + followersCount: MoreThan(0), + }, + order: { + followersCount: 'DESC', + }, + take: ps.limit, + }), + this.instancesRepository.find({ + where: { + followingCount: MoreThan(0), + }, + order: { + followingCount: 'DESC', + }, + take: ps.limit, + }), + this.followingsRepository.count({ + where: { + followeeHost: Not(IsNull()), + }, + }), + this.followingsRepository.count({ + where: { + followerHost: Not(IsNull()), + }, + }), + ]); + + const gotSubCount = topSubInstances.map(x => x.followersCount).reduce((a, b) => a + b, 0); + const gotPubCount = topPubInstances.map(x => x.followingCount).reduce((a, b) => a + b, 0); + + return await awaitAll({ + topSubInstances: this.instanceEntityService.packMany(topSubInstances), + otherFollowersCount: Math.max(0, allSubCount - gotSubCount), + topPubInstances: this.instanceEntityService.packMany(topPubInstances), + otherFollowingCount: Math.max(0, allPubCount - gotPubCount), + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts index 409cc7695e..57497bbf62 100644 --- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts +++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts @@ -1,6 +1,7 @@ -import define from '../../define.js'; -import { getRemoteUser } from '../../common/getters.js'; -import { updatePerson } from '@/remote/activitypub/models/person.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ApPersonService } from '@/core/remote/activitypub/models/ApPersonService.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['federation'], @@ -17,7 +18,15 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const user = await getRemoteUser(ps.userId); - await updatePerson(user.uri!); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private getterService: GetterService, + private apPersonService: ApPersonService, + ) { + super(meta, paramDef, async (ps) => { + const user = await this.getterService.getRemoteUser(ps.userId); + await this.apPersonService.updatePerson(user.uri!); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts index 65ad9f88d3..0400cacd02 100644 --- a/packages/backend/src/server/api/endpoints/federation/users.ts +++ b/packages/backend/src/server/api/endpoints/federation/users.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Users } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['federation'], @@ -30,13 +33,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(Users.createQueryBuilder('user'), ps.sinceId, ps.untilId) - .andWhere(`user.host = :host`, { host: ps.host }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - const users = await query - .take(ps.limit) - .getMany(); + private userEntityService: UserEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.usersRepository.createQueryBuilder('user'), ps.sinceId, ps.untilId) + .andWhere('user.host = :host', { host: ps.host }); - return await Users.packMany(users, me, { detail: true }); -}); + const users = await query + .take(ps.limit) + .getMany(); + + return await this.userEntityService.packMany(users, me, { detail: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/fetch-rss.ts b/packages/backend/src/server/api/endpoints/fetch-rss.ts index 05fa22a9e4..9e6a3cc717 100644 --- a/packages/backend/src/server/api/endpoints/fetch-rss.ts +++ b/packages/backend/src/server/api/endpoints/fetch-rss.ts @@ -1,7 +1,9 @@ import Parser from 'rss-parser'; -import { getResponse } from '@/misc/fetch.js'; -import config from '@/config/index.js'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; const rssParser = new Parser(); @@ -22,18 +24,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const res = await getResponse({ - url: ps.url, - method: 'GET', - headers: Object.assign({ - 'User-Agent': config.userAgent, - Accept: 'application/rss+xml, */*', - }), - timeout: 5000, - }); - - const text = await res.text(); - - return rssParser.parseString(text); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, + + private httpRequestService: HttpRequestService, + ) { + super(meta, paramDef, async (ps, me) => { + const res = await this.httpRequestService.getResponse({ + url: ps.url, + method: 'GET', + headers: Object.assign({ + 'User-Agent': config.userAgent, + Accept: 'application/rss+xml, */*', + }), + timeout: 5000, + }); + + const text = await res.text(); + + return rssParser.parseString(text); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 02a030cd5e..3a06c63d52 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -1,10 +1,13 @@ import ms from 'ms'; -import create from '@/services/following/create.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { Followings, Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['following', 'users'], @@ -66,39 +69,54 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const follower = user; - - // 自分自身 - if (user.id === ps.userId) { - throw new ApiError(meta.errors.followeeIsYourself); - } - - // Get followee - const followee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Check if already following - const exist = await Followings.findOneBy({ - followerId: follower.id, - followeeId: followee.id, - }); - - if (exist != null) { - throw new ApiError(meta.errors.alreadyFollowing); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + const follower = me; + + // 自分自身 + if (me.id === ps.userId) { + throw new ApiError(meta.errors.followeeIsYourself); + } + + // Get followee + const followee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check if already following + const exist = await this.followingsRepository.findOneBy({ + followerId: follower.id, + followeeId: followee.id, + }); + + if (exist != null) { + throw new ApiError(meta.errors.alreadyFollowing); + } + + try { + await this.userFollowingService.follow(follower, followee); + } catch (e) { + if (e instanceof IdentifiableError) { + if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError(meta.errors.blocking); + if (e.id === '3338392a-f764-498d-8855-db939dcf8c48') throw new ApiError(meta.errors.blocked); + } + throw e; + } + + return await this.userEntityService.pack(followee.id, me); + }); } - - try { - await create(follower, followee); - } catch (e) { - if (e instanceof IdentifiableError) { - if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError(meta.errors.blocking); - if (e.id === '3338392a-f764-498d-8855-db939dcf8c48') throw new ApiError(meta.errors.blocked); - } - throw e; - } - - return await Users.pack(followee.id, user); -}); +} diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 2f41b16e9a..07366bc821 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -1,9 +1,12 @@ import ms from 'ms'; -import deleteFollowing from '@/services/following/delete.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { Followings, Users } from '@/models/index.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['following', 'users'], @@ -53,31 +56,46 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const follower = user; - - // Check if the followee is yourself - if (user.id === ps.userId) { - throw new ApiError(meta.errors.followeeIsYourself); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + const follower = me; + + // Check if the followee is yourself + if (me.id === ps.userId) { + throw new ApiError(meta.errors.followeeIsYourself); + } + + // Get followee + const followee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check not following + const exist = await this.followingsRepository.findOneBy({ + followerId: follower.id, + followeeId: followee.id, + }); + + if (exist == null) { + throw new ApiError(meta.errors.notFollowing); + } + + await this.userFollowingService.unfollow(follower, followee); + + return await this.userEntityService.pack(followee.id, me); + }); } - - // Get followee - const followee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Check not following - const exist = await Followings.findOneBy({ - followerId: follower.id, - followeeId: followee.id, - }); - - if (exist == null) { - throw new ApiError(meta.errors.notFollowing); - } - - await deleteFollowing(follower, followee); - - return await Users.pack(followee.id, user); -}); +} diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index 18ec5affe8..8285189d66 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -1,9 +1,12 @@ import ms from 'ms'; -import deleteFollowing from '@/services/following/delete.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { Followings, Users } from '@/models/index.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['following', 'users'], @@ -53,31 +56,46 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const followee = user; - - // Check if the follower is yourself - if (user.id === ps.userId) { - throw new ApiError(meta.errors.followerIsYourself); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + const followee = me; + + // Check if the follower is yourself + if (me.id === ps.userId) { + throw new ApiError(meta.errors.followerIsYourself); + } + + // Get follower + const follower = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check not following + const exist = await this.followingsRepository.findOneBy({ + followerId: follower.id, + followeeId: followee.id, + }); + + if (exist == null) { + throw new ApiError(meta.errors.notFollowing); + } + + await this.userFollowingService.unfollow(follower, followee); + + return await this.userEntityService.pack(followee.id, me); + }); } - - // Get follower - const follower = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Check not following - const exist = await Followings.findOneBy({ - followerId: follower.id, - followeeId: followee.id, - }); - - if (exist == null) { - throw new ApiError(meta.errors.notFollowing); - } - - await deleteFollowing(follower, followee); - - return await Users.pack(followee.id, user); -}); +} diff --git a/packages/backend/src/server/api/endpoints/following/requests/accept.ts b/packages/backend/src/server/api/endpoints/following/requests/accept.ts index e5df55375e..5f082c087a 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/accept.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/accept.ts @@ -1,7 +1,8 @@ -import acceptFollowRequest from '@/services/following/requests/accept.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; export const meta = { tags: ['following', 'account'], @@ -33,17 +34,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch follower - const follower = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - await acceptFollowRequest(user, follower).catch(e => { - if (e.id === '8884c2dd-5795-4ac9-b27e-6a01d38190f9') throw new ApiError(meta.errors.noFollowRequest); - throw e; - }); - - return; -}); +@Injectable() +export default class extends Endpoint { + constructor( + private getterService: GetterService, + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch follower + const follower = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + await this.userFollowingService.acceptFollowRequest(me, follower).catch(err => { + if (err.id === '8884c2dd-5795-4ac9-b27e-6a01d38190f9') throw new ApiError(meta.errors.noFollowRequest); + throw err; + }); + + return; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts index 80d37fb075..0b79a80649 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts @@ -1,9 +1,13 @@ -import cancelFollowRequest from '@/services/following/requests/cancel.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; -import { Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { FollowingsRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../../error.js'; export const meta = { tags: ['following', 'account'], @@ -42,21 +46,33 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch followee - const followee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - try { - await cancelFollowRequest(followee, user); - } catch (e) { - if (e instanceof IdentifiableError) { - if (e.id === '17447091-ce07-46dd-b331-c1fd4f15b1e7') throw new ApiError(meta.errors.followRequestNotFound); - } - throw e; - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - return await Users.pack(followee.id, user); -}); + private userEntityService: UserEntityService, + private getterService: GetterService, + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch followee + const followee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + try { + await this.userFollowingService.cancelFollowRequest(followee, me); + } catch (err) { + if (err instanceof IdentifiableError) { + if (err.id === '17447091-ce07-46dd-b331-c1fd4f15b1e7') throw new ApiError(meta.errors.followRequestNotFound); + } + throw err; + } + + return await this.userEntityService.pack(followee.id, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/following/requests/list.ts b/packages/backend/src/server/api/endpoints/following/requests/list.ts index a8f42c481d..c36d4a077f 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/list.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts @@ -1,5 +1,8 @@ -import define from '../../../define.js'; -import { FollowRequests } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { FollowRequestsRepository } from '@/models/index.js'; +import { FollowRequestEntityService } from '@/core/entities/FollowRequestEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['following', 'account'], @@ -42,10 +45,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const reqs = await FollowRequests.findBy({ - followeeId: user.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.followRequestsRepository) + private followRequestsRepository: FollowRequestsRepository, - return await Promise.all(reqs.map(req => FollowRequests.pack(req))); -}); + private followRequestEntityService: FollowRequestEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const reqs = await this.followRequestsRepository.findBy({ + followeeId: me.id, + }); + + return await Promise.all(reqs.map(req => this.followRequestEntityService.pack(req))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/following/requests/reject.ts b/packages/backend/src/server/api/endpoints/following/requests/reject.ts index cebe604284..663c659bf3 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/reject.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/reject.ts @@ -1,7 +1,8 @@ -import { rejectFollowRequest } from '@/services/following/reject.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; export const meta = { tags: ['following', 'account'], @@ -28,14 +29,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch follower - const follower = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - await rejectFollowRequest(user, follower); - - return; -}); +@Injectable() +export default class extends Endpoint { + constructor( + private getterService: GetterService, + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch follower + const follower = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + await this.userFollowingService.rejectFollowRequest(me, follower); + + return; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/featured.ts b/packages/backend/src/server/api/endpoints/gallery/featured.ts index e6acd36911..3b892ef522 100644 --- a/packages/backend/src/server/api/endpoints/gallery/featured.ts +++ b/packages/backend/src/server/api/endpoints/gallery/featured.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { GalleryPosts } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository } from '@/models/index.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['gallery'], @@ -24,13 +27,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = GalleryPosts.createQueryBuilder('post') - .andWhere('post.createdAt > :date', { date: new Date(Date.now() - (1000 * 60 * 60 * 24 * 3)) }) - .andWhere('post.likedCount > 0') - .orderBy('post.likedCount', 'DESC'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - const posts = await query.take(10).getMany(); + private galleryPostEntityService: GalleryPostEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.galleryPostsRepository.createQueryBuilder('post') + .andWhere('post.createdAt > :date', { date: new Date(Date.now() - (1000 * 60 * 60 * 24 * 3)) }) + .andWhere('post.likedCount > 0') + .orderBy('post.likedCount', 'DESC'); - return await GalleryPosts.packMany(posts, me); -}); + const posts = await query.take(10).getMany(); + + return await this.galleryPostEntityService.packMany(posts, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/popular.ts b/packages/backend/src/server/api/endpoints/gallery/popular.ts index c4c8982fcc..551ea64835 100644 --- a/packages/backend/src/server/api/endpoints/gallery/popular.ts +++ b/packages/backend/src/server/api/endpoints/gallery/popular.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { GalleryPosts } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository } from '@/models/index.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['gallery'], @@ -24,12 +27,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = GalleryPosts.createQueryBuilder('post') - .andWhere('post.likedCount > 0') - .orderBy('post.likedCount', 'DESC'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - const posts = await query.take(10).getMany(); + private galleryPostEntityService: GalleryPostEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.galleryPostsRepository.createQueryBuilder('post') + .andWhere('post.likedCount > 0') + .orderBy('post.likedCount', 'DESC'); - return await GalleryPosts.packMany(posts, me); -}); + const posts = await query.take(10).getMany(); + + return await this.galleryPostEntityService.packMany(posts, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/posts.ts b/packages/backend/src/server/api/endpoints/gallery/posts.ts index 428ba9cc71..4afcbce816 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { GalleryPosts } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['gallery'], @@ -27,11 +30,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId) - .innerJoinAndSelect('post.user', 'user'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - const posts = await query.take(ps.limit).getMany(); + private galleryPostEntityService: GalleryPostEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId) + .innerJoinAndSelect('post.user', 'user'); - return await GalleryPosts.packMany(posts, me); -}); + const posts = await query.take(ps.limit).getMany(); + + return await this.galleryPostEntityService.packMany(posts, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index 8074a3b34f..9e8bcac131 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -1,10 +1,13 @@ import ms from 'ms'; -import define from '../../../define.js'; -import { DriveFiles, GalleryPosts } from '@/models/index.js'; -import { genId } from '../../../../../misc/gen-id.js'; -import { GalleryPost } from '@/models/entities/gallery-post.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js'; +import { GalleryPost } from '@/models/entities/GalleryPost.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import { IdService } from '@/core/IdService.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; export const meta = { tags: ['gallery'], @@ -43,28 +46,42 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const files = (await Promise.all(ps.fileIds.map(fileId => - DriveFiles.findOneBy({ - id: fileId, - userId: user.id, - }) - ))).filter((file): file is DriveFile => file != null); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - if (files.length === 0) { - throw new Error(); - } + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private galleryPostEntityService: GalleryPostEntityService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const files = (await Promise.all(ps.fileIds.map(fileId => + this.driveFilesRepository.findOneBy({ + id: fileId, + userId: me.id, + }), + ))).filter((file): file is DriveFile => file != null); - const post = await GalleryPosts.insert(new GalleryPost({ - id: genId(), - createdAt: new Date(), - updatedAt: new Date(), - title: ps.title, - description: ps.description, - userId: user.id, - isSensitive: ps.isSensitive, - fileIds: files.map(file => file.id), - })).then(x => GalleryPosts.findOneByOrFail(x.identifiers[0])); + if (files.length === 0) { + throw new Error(); + } - return await GalleryPosts.pack(post, user); -}); + const post = await this.galleryPostsRepository.insert(new GalleryPost({ + id: this.idService.genId(), + createdAt: new Date(), + updatedAt: new Date(), + title: ps.title, + description: ps.description, + userId: me.id, + isSensitive: ps.isSensitive, + fileIds: files.map(file => file.id), + })).then(x => this.galleryPostsRepository.findOneByOrFail(x.identifiers[0])); + + return await this.galleryPostEntityService.pack(post, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts index b00ee0e2ae..ad5f95c853 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { GalleryPosts } from '@/models/index.js'; export const meta = { tags: ['gallery'], @@ -27,15 +29,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const post = await GalleryPosts.findOneBy({ - id: ps.postId, - userId: user.id, - }); - - if (post == null) { - throw new ApiError(meta.errors.noSuchPost); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const post = await this.galleryPostsRepository.findOneBy({ + id: ps.postId, + userId: me.id, + }); + + if (post == null) { + throw new ApiError(meta.errors.noSuchPost); + } + + await this.galleryPostsRepository.delete(post.id); + }); } - - await GalleryPosts.delete(post.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index b858114aec..8aca98119b 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryLikesRepository, GalleryPostsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { GalleryPosts, GalleryLikes } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; export const meta = { tags: ['gallery'], @@ -40,33 +42,46 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const post = await GalleryPosts.findOneBy({ id: ps.postId }); - if (post == null) { - throw new ApiError(meta.errors.noSuchPost); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - if (post.userId === user.id) { - throw new ApiError(meta.errors.yourPost); - } + @Inject(DI.galleryLikesRepository) + private galleryLikesRepository: GalleryLikesRepository, - // if already liked - const exist = await GalleryLikes.findOneBy({ - postId: post.id, - userId: user.id, - }); + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const post = await this.galleryPostsRepository.findOneBy({ id: ps.postId }); + if (post == null) { + throw new ApiError(meta.errors.noSuchPost); + } - if (exist != null) { - throw new ApiError(meta.errors.alreadyLiked); - } + if (post.userId === me.id) { + throw new ApiError(meta.errors.yourPost); + } - // Create like - await GalleryLikes.insert({ - id: genId(), - createdAt: new Date(), - postId: post.id, - userId: user.id, - }); + // if already liked + const exist = await this.galleryLikesRepository.findOneBy({ + postId: post.id, + userId: me.id, + }); - GalleryPosts.increment({ id: post.id }, 'likedCount', 1); -}); + if (exist != null) { + throw new ApiError(meta.errors.alreadyLiked); + } + + // Create like + await this.galleryLikesRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + postId: post.id, + userId: me.id, + }); + + this.galleryPostsRepository.increment({ id: post.id }, 'likedCount', 1); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts index 4f6dafd7cb..723906d60f 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository } from '@/models/index.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { GalleryPosts } from '@/models/index.js'; export const meta = { tags: ['gallery'], @@ -31,14 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const post = await GalleryPosts.findOneBy({ - id: ps.postId, - }); - - if (post == null) { - throw new ApiError(meta.errors.noSuchPost); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, + + private galleryPostEntityService: GalleryPostEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const post = await this.galleryPostsRepository.findOneBy({ + id: ps.postId, + }); + + if (post == null) { + throw new ApiError(meta.errors.noSuchPost); + } + + return await this.galleryPostEntityService.pack(post, me); + }); } - - return await GalleryPosts.pack(post, me); -}); +} diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts index d136239e5e..d878582998 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository, GalleryLikesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { GalleryPosts, GalleryLikes } from '@/models/index.js'; export const meta = { tags: ['gallery'], @@ -33,23 +35,34 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const post = await GalleryPosts.findOneBy({ id: ps.postId }); - if (post == null) { - throw new ApiError(meta.errors.noSuchPost); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - const exist = await GalleryLikes.findOneBy({ - postId: post.id, - userId: user.id, - }); + @Inject(DI.galleryLikesRepository) + private galleryLikesRepository: GalleryLikesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const post = await this.galleryPostsRepository.findOneBy({ id: ps.postId }); + if (post == null) { + throw new ApiError(meta.errors.noSuchPost); + } - if (exist == null) { - throw new ApiError(meta.errors.notLiked); - } + const exist = await this.galleryLikesRepository.findOneBy({ + postId: post.id, + userId: me.id, + }); - // Delete like - await GalleryLikes.delete(exist.id); + if (exist == null) { + throw new ApiError(meta.errors.notLiked); + } - GalleryPosts.decrement({ id: post.id }, 'likedCount', 1); -}); + // Delete like + await this.galleryLikesRepository.delete(exist.id); + + this.galleryPostsRepository.decrement({ id: post.id }, 'likedCount', 1); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index 82fe38078e..1900afaeb6 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -1,9 +1,12 @@ import ms from 'ms'; -import define from '../../../define.js'; -import { DriveFiles, GalleryPosts } from '@/models/index.js'; -import { GalleryPost } from '@/models/entities/gallery-post.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js'; +import { GalleryPost } from '@/models/entities/GalleryPost.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; export const meta = { tags: ['gallery'], @@ -43,30 +46,43 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const files = (await Promise.all(ps.fileIds.map(fileId => - DriveFiles.findOneBy({ - id: fileId, - userId: user.id, - }) - ))).filter((file): file is DriveFile => file != null); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - if (files.length === 0) { - throw new Error(); - } + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private galleryPostEntityService: GalleryPostEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const files = (await Promise.all(ps.fileIds.map(fileId => + this.driveFilesRepository.findOneBy({ + id: fileId, + userId: me.id, + }), + ))).filter((file): file is DriveFile => file != null); - await GalleryPosts.update({ - id: ps.postId, - userId: user.id, - }, { - updatedAt: new Date(), - title: ps.title, - description: ps.description, - isSensitive: ps.isSensitive, - fileIds: files.map(file => file.id), - }); + if (files.length === 0) { + throw new Error(); + } - const post = await GalleryPosts.findOneByOrFail({ id: ps.postId }); + await this.galleryPostsRepository.update({ + id: ps.postId, + userId: me.id, + }, { + updatedAt: new Date(), + title: ps.title, + description: ps.description, + isSensitive: ps.isSensitive, + fileIds: files.map(file => file.id), + }); - return await GalleryPosts.pack(post, user); -}); + const post = await this.galleryPostsRepository.findOneByOrFail({ id: ps.postId }); + + return await this.galleryPostEntityService.pack(post, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index 56c5502978..2d9bf29dd9 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -1,7 +1,9 @@ import { MoreThan } from 'typeorm'; +import { Inject, Injectable } from '@nestjs/common'; import { USER_ONLINE_THRESHOLD } from '@/const.js'; -import { Users } from '@/models/index.js'; -import define from '../define.js'; +import { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['meta'], @@ -16,12 +18,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const count = await Users.countBy({ - lastActiveDate: MoreThan(new Date(Date.now() - USER_ONLINE_THRESHOLD)), - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + ) { + super(meta, paramDef, async () => { + const count = await this.usersRepository.countBy({ + lastActiveDate: MoreThan(new Date(Date.now() - USER_ONLINE_THRESHOLD)), + }); - return { - count, - }; -}); + return { + count, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/hashtags/list.ts b/packages/backend/src/server/api/endpoints/hashtags/list.ts index 50e36386cf..a7e7e6ba35 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/list.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/list.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { Hashtags } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { HashtagsRepository } from '@/models/index.js'; +import { HashtagEntityService } from '@/core/entities/HashtagEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['hashtags'], @@ -30,39 +33,49 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Hashtags.createQueryBuilder('tag'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.hashtagsRepository) + private hashtagsRepository: HashtagsRepository, - if (ps.attachedToUserOnly) query.andWhere('tag.attachedUsersCount != 0'); - if (ps.attachedToLocalUserOnly) query.andWhere('tag.attachedLocalUsersCount != 0'); - if (ps.attachedToRemoteUserOnly) query.andWhere('tag.attachedRemoteUsersCount != 0'); + private hashtagEntityService: HashtagEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.hashtagsRepository.createQueryBuilder('tag'); - switch (ps.sort) { - case '+mentionedUsers': query.orderBy('tag.mentionedUsersCount', 'DESC'); break; - case '-mentionedUsers': query.orderBy('tag.mentionedUsersCount', 'ASC'); break; - case '+mentionedLocalUsers': query.orderBy('tag.mentionedLocalUsersCount', 'DESC'); break; - case '-mentionedLocalUsers': query.orderBy('tag.mentionedLocalUsersCount', 'ASC'); break; - case '+mentionedRemoteUsers': query.orderBy('tag.mentionedRemoteUsersCount', 'DESC'); break; - case '-mentionedRemoteUsers': query.orderBy('tag.mentionedRemoteUsersCount', 'ASC'); break; - case '+attachedUsers': query.orderBy('tag.attachedUsersCount', 'DESC'); break; - case '-attachedUsers': query.orderBy('tag.attachedUsersCount', 'ASC'); break; - case '+attachedLocalUsers': query.orderBy('tag.attachedLocalUsersCount', 'DESC'); break; - case '-attachedLocalUsers': query.orderBy('tag.attachedLocalUsersCount', 'ASC'); break; - case '+attachedRemoteUsers': query.orderBy('tag.attachedRemoteUsersCount', 'DESC'); break; - case '-attachedRemoteUsers': query.orderBy('tag.attachedRemoteUsersCount', 'ASC'); break; - } + if (ps.attachedToUserOnly) query.andWhere('tag.attachedUsersCount != 0'); + if (ps.attachedToLocalUserOnly) query.andWhere('tag.attachedLocalUsersCount != 0'); + if (ps.attachedToRemoteUserOnly) query.andWhere('tag.attachedRemoteUsersCount != 0'); + + switch (ps.sort) { + case '+mentionedUsers': query.orderBy('tag.mentionedUsersCount', 'DESC'); break; + case '-mentionedUsers': query.orderBy('tag.mentionedUsersCount', 'ASC'); break; + case '+mentionedLocalUsers': query.orderBy('tag.mentionedLocalUsersCount', 'DESC'); break; + case '-mentionedLocalUsers': query.orderBy('tag.mentionedLocalUsersCount', 'ASC'); break; + case '+mentionedRemoteUsers': query.orderBy('tag.mentionedRemoteUsersCount', 'DESC'); break; + case '-mentionedRemoteUsers': query.orderBy('tag.mentionedRemoteUsersCount', 'ASC'); break; + case '+attachedUsers': query.orderBy('tag.attachedUsersCount', 'DESC'); break; + case '-attachedUsers': query.orderBy('tag.attachedUsersCount', 'ASC'); break; + case '+attachedLocalUsers': query.orderBy('tag.attachedLocalUsersCount', 'DESC'); break; + case '-attachedLocalUsers': query.orderBy('tag.attachedLocalUsersCount', 'ASC'); break; + case '+attachedRemoteUsers': query.orderBy('tag.attachedRemoteUsersCount', 'DESC'); break; + case '-attachedRemoteUsers': query.orderBy('tag.attachedRemoteUsersCount', 'ASC'); break; + } - query.select([ - 'tag.name', - 'tag.mentionedUsersCount', - 'tag.mentionedLocalUsersCount', - 'tag.mentionedRemoteUsersCount', - 'tag.attachedUsersCount', - 'tag.attachedLocalUsersCount', - 'tag.attachedRemoteUsersCount', - ]); + query.select([ + 'tag.name', + 'tag.mentionedUsersCount', + 'tag.mentionedLocalUsersCount', + 'tag.mentionedRemoteUsersCount', + 'tag.attachedUsersCount', + 'tag.attachedLocalUsersCount', + 'tag.attachedRemoteUsersCount', + ]); - const tags = await query.take(ps.limit).getMany(); + const tags = await query.take(ps.limit).getMany(); - return Hashtags.packMany(tags); -}); + return this.hashtagEntityService.packMany(tags); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts index c289844775..3fb77bef9b 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/search.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts @@ -1,5 +1,7 @@ -import define from '../../define.js'; -import { Hashtags } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { HashtagsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['hashtags'], @@ -27,14 +29,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const hashtags = await Hashtags.createQueryBuilder('tag') - .where('tag.name like :q', { q: ps.query.toLowerCase() + '%' }) - .orderBy('tag.count', 'DESC') - .groupBy('tag.id') - .take(ps.limit) - .skip(ps.offset) - .getMany(); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.hashtagsRepository) + private hashtagsRepository: HashtagsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const hashtags = await this.hashtagsRepository.createQueryBuilder('tag') + .where('tag.name like :q', { q: ps.query.toLowerCase() + '%' }) + .orderBy('tag.count', 'DESC') + .groupBy('tag.id') + .take(ps.limit) + .skip(ps.offset) + .getMany(); - return hashtags.map(tag => tag.name); -}); + return hashtags.map(tag => tag.name); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/hashtags/show.ts b/packages/backend/src/server/api/endpoints/hashtags/show.ts index 5b78f6ac7f..59170f6d0e 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/show.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/show.ts @@ -1,7 +1,10 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Hashtags } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { HashtagsRepository } from '@/models/index.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; +import { HashtagEntityService } from '@/core/entities/HashtagEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['hashtags'], @@ -32,11 +35,21 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const hashtag = await Hashtags.findOneBy({ name: normalizeForSearch(ps.tag) }); - if (hashtag == null) { - throw new ApiError(meta.errors.noSuchHashtag); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.hashtagsRepository) + private hashtagsRepository: HashtagsRepository, - return await Hashtags.pack(hashtag); -}); + private hashtagEntityService: HashtagEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const hashtag = await this.hashtagsRepository.findOneBy({ name: normalizeForSearch(ps.tag) }); + if (hashtag == null) { + throw new ApiError(meta.errors.noSuchHashtag); + } + + return await this.hashtagEntityService.pack(hashtag); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/hashtags/trend.ts b/packages/backend/src/server/api/endpoints/hashtags/trend.ts index 9cdbc8941c..7e483ea214 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/trend.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/trend.ts @@ -1,10 +1,12 @@ import { Brackets } from 'typeorm'; -import define from '../../define.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Notes } from '@/models/index.js'; -import { Note } from '@/models/entities/note.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NotesRepository } from '@/models/index.js'; +import type { Note } from '@/models/entities/Note.js'; import { safeForSql } from '@/misc/safe-for-sql.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DI } from '@/di-symbols.js'; /* トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要 @@ -60,94 +62,104 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const instance = await fetchMeta(true); - const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t)); - - const now = new Date(); // 5分単位で丸めた現在日時 - now.setMinutes(Math.round(now.getMinutes() / 5) * 5, 0, 0); - - const tagNotes = await Notes.createQueryBuilder('note') - .where(`note.createdAt > :date`, { date: new Date(now.getTime() - rangeA) }) - .andWhere(new Brackets(qb => { qb - .where(`note.visibility = 'public'`) - .orWhere(`note.visibility = 'home'`); - })) - .andWhere(`note.tags != '{}'`) - .select(['note.tags', 'note.userId']) - .cache(60000) // 1 min - .getMany(); - - if (tagNotes.length === 0) { - return []; - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private metaService: MetaService, + ) { + super(meta, paramDef, async () => { + const instance = await this.metaService.fetch(true); + const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t)); + + const now = new Date(); // 5分単位で丸めた現在日時 + now.setMinutes(Math.round(now.getMinutes() / 5) * 5, 0, 0); + + const tagNotes = await this.notesRepository.createQueryBuilder('note') + .where('note.createdAt > :date', { date: new Date(now.getTime() - rangeA) }) + .andWhere(new Brackets(qb => { qb + .where('note.visibility = \'public\'') + .orWhere('note.visibility = \'home\''); + })) + .andWhere('note.tags != \'{}\'') + .select(['note.tags', 'note.userId']) + .cache(60000) // 1 min + .getMany(); + + if (tagNotes.length === 0) { + return []; + } - const tags: { + const tags: { name: string; users: Note['userId'][]; }[] = []; - for (const note of tagNotes) { - for (const tag of note.tags) { - if (hiddenTags.includes(tag)) continue; - - const x = tags.find(x => x.name === tag); - if (x) { - if (!x.users.includes(note.userId)) { - x.users.push(note.userId); + for (const note of tagNotes) { + for (const tag of note.tags) { + if (hiddenTags.includes(tag)) continue; + + const x = tags.find(x => x.name === tag); + if (x) { + if (!x.users.includes(note.userId)) { + x.users.push(note.userId); + } + } else { + tags.push({ + name: tag, + users: [note.userId], + }); + } } - } else { - tags.push({ - name: tag, - users: [note.userId], - }); } - } - } - // タグを人気順に並べ替え - const hots = tags - .sort((a, b) => b.users.length - a.users.length) - .map(tag => tag.name) - .slice(0, max); - - //#region 2(または3)で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する - const countPromises: Promise[] = []; - - const range = 20; - - // 10分 - const interval = 1000 * 60 * 10; - - for (let i = 0; i < range; i++) { - countPromises.push(Promise.all(hots.map(tag => Notes.createQueryBuilder('note') - .select('count(distinct note.userId)') - .where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`) - .andWhere('note.createdAt < :lt', { lt: new Date(now.getTime() - (interval * i)) }) - .andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - (interval * (i + 1))) }) - .cache(60000) // 1 min - .getRawOne() - .then(x => parseInt(x.count, 10)) - ))); - } + // タグを人気順に並べ替え + const hots = tags + .sort((a, b) => b.users.length - a.users.length) + .map(tag => tag.name) + .slice(0, max); + + //#region 2(または3)で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する + const countPromises: Promise[] = []; + + const range = 20; + + // 10分 + const interval = 1000 * 60 * 10; + + for (let i = 0; i < range; i++) { + countPromises.push(Promise.all(hots.map(tag => this.notesRepository.createQueryBuilder('note') + .select('count(distinct note.userId)') + .where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`) + .andWhere('note.createdAt < :lt', { lt: new Date(now.getTime() - (interval * i)) }) + .andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - (interval * (i + 1))) }) + .cache(60000) // 1 min + .getRawOne() + .then(x => parseInt(x.count, 10)), + ))); + } - const countsLog = await Promise.all(countPromises); - //#endregion - - const totalCounts = await Promise.all(hots.map(tag => Notes.createQueryBuilder('note') - .select('count(distinct note.userId)') - .where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`) - .andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - rangeA) }) - .cache(60000 * 60) // 60 min - .getRawOne() - .then(x => parseInt(x.count, 10)) - )); - - const stats = hots.map((tag, i) => ({ - tag, - chart: countsLog.map(counts => counts[i]), - usersCount: totalCounts[i], - })); - - return stats; -}); + const countsLog = await Promise.all(countPromises); + //#endregion + + const totalCounts = await Promise.all(hots.map(tag => this.notesRepository.createQueryBuilder('note') + .select('count(distinct note.userId)') + .where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`) + .andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - rangeA) }) + .cache(60000 * 60) // 60 min + .getRawOne() + .then(x => parseInt(x.count, 10)), + )); + + const stats = hots.map((tag, i) => ({ + tag, + chart: countsLog.map(counts => counts[i]), + usersCount: totalCounts[i], + })); + + return stats; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index a5df21a7e3..10a88fbefa 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository } from '@/models/index.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: false, @@ -24,39 +27,49 @@ export const paramDef = { tag: { type: 'string' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] }, - state: { type: 'string', enum: ['all', 'alive'], default: "all" }, - origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" }, + state: { type: 'string', enum: ['all', 'alive'], default: 'all' }, + origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' }, }, required: ['tag', 'sort'], } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Users.createQueryBuilder('user') - .where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.usersRepository.createQueryBuilder('user') + .where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) }); - const recent = new Date(Date.now() - (1000 * 60 * 60 * 24 * 5)); + const recent = new Date(Date.now() - (1000 * 60 * 60 * 24 * 5)); - if (ps.state === 'alive') { - query.andWhere('user.updatedAt > :date', { date: recent }); - } + if (ps.state === 'alive') { + query.andWhere('user.updatedAt > :date', { date: recent }); + } - if (ps.origin === 'local') { - query.andWhere('user.host IS NULL'); - } else if (ps.origin === 'remote') { - query.andWhere('user.host IS NOT NULL'); - } + if (ps.origin === 'local') { + query.andWhere('user.host IS NULL'); + } else if (ps.origin === 'remote') { + query.andWhere('user.host IS NOT NULL'); + } - switch (ps.sort) { - case '+follower': query.orderBy('user.followersCount', 'DESC'); break; - case '-follower': query.orderBy('user.followersCount', 'ASC'); break; - case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; - case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; - case '+updatedAt': query.orderBy('user.updatedAt', 'DESC'); break; - case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break; - } + switch (ps.sort) { + case '+follower': query.orderBy('user.followersCount', 'DESC'); break; + case '-follower': query.orderBy('user.followersCount', 'ASC'); break; + case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; + case '+updatedAt': query.orderBy('user.updatedAt', 'DESC'); break; + case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break; + } - const users = await query.take(ps.limit).getMany(); + const users = await query.take(ps.limit).getMany(); - return await Users.packMany(users, me, { detail: true }); -}); + return await this.userEntityService.packMany(users, me, { detail: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index 22aedfeee8..815b3168b4 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -1,5 +1,8 @@ -import { Users } from '@/models/index.js'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], @@ -20,12 +23,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user, token) => { - const isSecure = token == null; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - // ここで渡ってきている user はキャッシュされていて古い可能性もあるので id だけ渡す - return await Users.pack(user.id, user, { - detail: true, - includeSecrets: isSecure, - }); -}); + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, user, token) => { + const isSecure = token == null; + + // ここで渡ってきている user はキャッシュされていて古い可能性もあるので id だけ渡す + return await this.userEntityService.pack(user.id, user, { + detail: true, + includeSecrets: isSecure, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts index 35806b2bc3..bcf3931b04 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -1,6 +1,8 @@ import * as speakeasy from 'speakeasy'; -import define from '../../../define.js'; -import { UserProfiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserProfilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -17,27 +19,35 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const token = ps.token.replace(/\s/g, ''); - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - if (profile.twoFactorTempSecret == null) { - throw new Error('二段階認証の設定が開始されていません'); - } - - const verified = (speakeasy as any).totp.verify({ - secret: profile.twoFactorTempSecret, - encoding: 'base32', - token: token, - }); - - if (!verified) { - throw new Error('not verified'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const token = ps.token.replace(/\s/g, ''); + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + + if (profile.twoFactorTempSecret == null) { + throw new Error('二段階認証の設定が開始されていません'); + } + + const verified = (speakeasy as any).totp.verify({ + secret: profile.twoFactorTempSecret, + encoding: 'base32', + token: token, + }); + + if (!verified) { + throw new Error('not verified'); + } + + await this.userProfilesRepository.update(me.id, { + twoFactorSecret: profile.twoFactorTempSecret, + twoFactorEnabled: true, + }); + }); } - - await UserProfiles.update(user.id, { - twoFactorSecret: profile.twoFactorTempSecret, - twoFactorEnabled: true, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index 1afb34bfda..f2f4c2044e 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -1,19 +1,16 @@ -import bcrypt from 'bcryptjs'; import { promisify } from 'node:util'; +import bcrypt from 'bcryptjs'; import * as cbor from 'cbor'; -import define from '../../../define.js'; -import { - UserProfiles, - UserSecurityKeys, - AttestationChallenges, - Users, -} from '@/models/index.js'; -import config from '@/config/index.js'; -import { procedures, hash } from '../../../2fa.js'; -import { publishMainStream } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; +import { AttestationChallengesRepository, UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; const cborDecodeFirst = promisify(cbor.decodeFirst) as any; -const rpIdHashReal = hash(Buffer.from(config.hostname, 'utf-8')); export const meta = { requireCredential: true, @@ -34,110 +31,135 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); - - if (!same) { - throw new Error('incorrect password'); - } - - if (!profile.twoFactorEnabled) { - throw new Error('2fa not enabled'); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, - const clientData = JSON.parse(ps.clientDataJSON); + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, - if (clientData.type !== 'webauthn.create') { - throw new Error('not a creation attestation'); - } - if (clientData.origin !== config.scheme + '://' + config.host) { - throw new Error('origin mismatch'); - } - - const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, 'utf-8')); - - const attestation = await cborDecodeFirst(ps.attestationObject); - - const rpIdHash = attestation.authData.slice(0, 32); - if (!rpIdHashReal.equals(rpIdHash)) { - throw new Error('rpIdHash mismatch'); - } + @Inject(DI.userSecurityKeysRepository) + private userSecurityKeysRepository: UserSecurityKeysRepository, - const flags = attestation.authData[32]; + @Inject(DI.attestationChallengesRepository) + private attestationChallengesRepository: AttestationChallengesRepository, - // eslint:disable-next-line:no-bitwise - if (!(flags & 1)) { - throw new Error('user not present'); - } - - const authData = Buffer.from(attestation.authData); - const credentialIdLength = authData.readUInt16BE(53); - const credentialId = authData.slice(55, 55 + credentialIdLength); - const publicKeyData = authData.slice(55 + credentialIdLength); - const publicKey: Map = await cborDecodeFirst(publicKeyData); - if (publicKey.get(3) !== -7) { - throw new Error('alg mismatch'); - } - - if (!(procedures as any)[attestation.fmt]) { - throw new Error('unsupported fmt'); - } - - const verificationData = (procedures as any)[attestation.fmt].verify({ - attStmt: attestation.attStmt, - authenticatorData: authData, - clientDataHash: clientDataJSONHash, - credentialId, - publicKey, - rpIdHash, - }); - if (!verificationData.valid) throw new Error('signature invalid'); - - const attestationChallenge = await AttestationChallenges.findOneBy({ - userId: user.id, - id: ps.challengeId, - registrationChallenge: true, - challenge: hash(clientData.challenge).toString('hex'), - }); - - if (!attestationChallenge) { - throw new Error('non-existent challenge'); - } - - await AttestationChallenges.delete({ - userId: user.id, - id: ps.challengeId, - }); - - // Expired challenge (> 5min old) - if ( - new Date().getTime() - attestationChallenge.createdAt.getTime() >= - 5 * 60 * 1000 + private userEntityService: UserEntityService, + private globalEventService: GlobalEventService, + private twoFactorAuthenticationService: TwoFactorAuthenticationService, ) { - throw new Error('expired challenge'); + super(meta, paramDef, async (ps, me) => { + const rpIdHashReal = this.twoFactorAuthenticationService.hash(Buffer.from(this.config.hostname, 'utf-8')); + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + if (!profile.twoFactorEnabled) { + throw new Error('2fa not enabled'); + } + + const clientData = JSON.parse(ps.clientDataJSON); + + if (clientData.type !== 'webauthn.create') { + throw new Error('not a creation attestation'); + } + if (clientData.origin !== this.config.scheme + '://' + this.config.host) { + throw new Error('origin mismatch'); + } + + const clientDataJSONHash = this.twoFactorAuthenticationService.hash(Buffer.from(ps.clientDataJSON, 'utf-8')); + + const attestation = await cborDecodeFirst(ps.attestationObject); + + const rpIdHash = attestation.authData.slice(0, 32); + if (!rpIdHashReal.equals(rpIdHash)) { + throw new Error('rpIdHash mismatch'); + } + + const flags = attestation.authData[32]; + + // eslint:disable-next-line:no-bitwise + if (!(flags & 1)) { + throw new Error('user not present'); + } + + const authData = Buffer.from(attestation.authData); + const credentialIdLength = authData.readUInt16BE(53); + const credentialId = authData.slice(55, 55 + credentialIdLength); + const publicKeyData = authData.slice(55 + credentialIdLength); + const publicKey: Map = await cborDecodeFirst(publicKeyData); + if (publicKey.get(3) !== -7) { + throw new Error('alg mismatch'); + } + + const procedures = this.twoFactorAuthenticationService.getProcedures(); + + if (!(procedures as any)[attestation.fmt]) { + throw new Error('unsupported fmt'); + } + + const verificationData = (procedures as any)[attestation.fmt].verify({ + attStmt: attestation.attStmt, + authenticatorData: authData, + clientDataHash: clientDataJSONHash, + credentialId, + publicKey, + rpIdHash, + }); + if (!verificationData.valid) throw new Error('signature invalid'); + + const attestationChallenge = await this.attestationChallengesRepository.findOneBy({ + userId: me.id, + id: ps.challengeId, + registrationChallenge: true, + challenge: this.twoFactorAuthenticationService.hash(clientData.challenge).toString('hex'), + }); + + if (!attestationChallenge) { + throw new Error('non-existent challenge'); + } + + await this.attestationChallengesRepository.delete({ + userId: me.id, + id: ps.challengeId, + }); + + // Expired challenge (> 5min old) + if ( + new Date().getTime() - attestationChallenge.createdAt.getTime() >= + 5 * 60 * 1000 + ) { + throw new Error('expired challenge'); + } + + const credentialIdString = credentialId.toString('hex'); + + await this.userSecurityKeysRepository.insert({ + userId: me.id, + id: credentialIdString, + lastUsed: new Date(), + name: ps.name, + publicKey: verificationData.publicKey.toString('hex'), + }); + + // Publish meUpdated event + this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { + detail: true, + includeSecrets: true, + })); + + return { + id: credentialIdString, + name: ps.name, + }; + }); } - - const credentialIdString = credentialId.toString('hex'); - - await UserSecurityKeys.insert({ - userId: user.id, - id: credentialIdString, - lastUsed: new Date(), - name: ps.name, - publicKey: verificationData.publicKey.toString('hex'), - }); - - // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, { - detail: true, - includeSecrets: true, - })); - - return { - id: credentialIdString, - name: ps.name, - }; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts index 4bfa24f97f..3eb9f43c2b 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { UserProfiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserProfilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -16,8 +18,16 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - await UserProfiles.update(user.id, { - usePasswordLessLogin: ps.value, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + await this.userProfilesRepository.update(me.id, { + usePasswordLessLogin: ps.value, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index e906b82043..df37db4c6a 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -1,10 +1,12 @@ -import bcrypt from 'bcryptjs'; -import define from '../../../define.js'; -import { UserProfiles, AttestationChallenges } from '@/models/index.js'; import { promisify } from 'node:util'; import * as crypto from 'node:crypto'; -import { genId } from '@/misc/gen-id.js'; -import { hash } from '../../../2fa.js'; +import bcrypt from 'bcryptjs'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserProfilesRepository, AttestationChallengesRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; +import { DI } from '@/di-symbols.js'; const randomBytes = promisify(crypto.randomBytes); @@ -23,39 +25,53 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + @Inject(DI.attestationChallengesRepository) + private attestationChallengesRepository: AttestationChallengesRepository, - if (!same) { - throw new Error('incorrect password'); - } + private idService: IdService, + private twoFactorAuthenticationService: TwoFactorAuthenticationService, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); - if (!profile.twoFactorEnabled) { - throw new Error('2fa not enabled'); - } + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } - // 32 byte challenge - const entropy = await randomBytes(32); - const challenge = entropy.toString('base64') - .replace(/=/g, '') - .replace(/\+/g, '-') - .replace(/\//g, '_'); - - const challengeId = genId(); - - await AttestationChallenges.insert({ - userId: user.id, - id: challengeId, - challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'), - createdAt: new Date(), - registrationChallenge: true, - }); - - return { - challengeId, - challenge, - }; -}); + if (!profile.twoFactorEnabled) { + throw new Error('2fa not enabled'); + } + + // 32 byte challenge + const entropy = await randomBytes(32); + const challenge = entropy.toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + + const challengeId = this.idService.genId(); + + await this.attestationChallengesRepository.insert({ + userId: me.id, + id: challengeId, + challenge: this.twoFactorAuthenticationService.hash(Buffer.from(challenge, 'utf-8')).toString('hex'), + createdAt: new Date(), + registrationChallenge: true, + }); + + return { + challengeId, + challenge, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index 33f5717728..e20911f35e 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -1,9 +1,11 @@ import bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; import * as QRCode from 'qrcode'; -import config from '@/config/index.js'; -import { UserProfiles } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserProfilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; +import { Config } from '@/config.js'; export const meta = { requireCredential: true, @@ -20,39 +22,50 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); - if (!same) { - throw new Error('incorrect password'); - } + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + // Generate user's secret key + const secret = speakeasy.generateSecret({ + length: 32, + }); - // Generate user's secret key - const secret = speakeasy.generateSecret({ - length: 32, - }); - - await UserProfiles.update(user.id, { - twoFactorTempSecret: secret.base32, - }); - - // Get the data URL of the authenticator URL - const url = speakeasy.otpauthURL({ - secret: secret.base32, - encoding: 'base32', - label: user.username, - issuer: config.host, - }); - const dataUrl = await QRCode.toDataURL(url); - - return { - qr: dataUrl, - url, - secret: secret.base32, - label: user.username, - issuer: config.host, - }; -}); + await this.userProfilesRepository.update(me.id, { + twoFactorTempSecret: secret.base32, + }); + + // Get the data URL of the authenticator URL + const url = speakeasy.otpauthURL({ + secret: secret.base32, + encoding: 'base32', + label: me.username, + issuer: this.config.host, + }); + const dataUrl = await QRCode.toDataURL(url); + + return { + qr: dataUrl, + url, + secret: secret.base32, + label: me.username, + issuer: this.config.host, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index eb2f75308d..1889dd7893 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -1,7 +1,11 @@ import bcrypt from 'bcryptjs'; -import define from '../../../define.js'; -import { UserProfiles, UserSecurityKeys, Users } from '@/models/index.js'; -import { publishMainStream } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -19,27 +23,41 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); - - if (!same) { - throw new Error('incorrect password'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userSecurityKeysRepository) + private userSecurityKeysRepository: UserSecurityKeysRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private userEntityService: UserEntityService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + // Make sure we only delete the user's own creds + await this.userSecurityKeysRepository.delete({ + userId: me.id, + id: ps.credentialId, + }); + + // Publish meUpdated event + this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { + detail: true, + includeSecrets: true, + })); + + return {}; + }); } - - // Make sure we only delete the user's own creds - await UserSecurityKeys.delete({ - userId: user.id, - id: ps.credentialId, - }); - - // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, { - detail: true, - includeSecrets: true, - })); - - return {}; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index 45e7a98639..4607e5d981 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -1,6 +1,8 @@ import bcrypt from 'bcryptjs'; -import define from '../../../define.js'; -import { UserProfiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserProfilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -17,18 +19,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); - if (!same) { - throw new Error('incorrect password'); - } + if (!same) { + throw new Error('incorrect password'); + } - await UserProfiles.update(user.id, { - twoFactorSecret: null, - twoFactorEnabled: false, - }); -}); + await this.userProfilesRepository.update(me.id, { + twoFactorSecret: null, + twoFactorEnabled: false, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index eca9558847..8d5851659b 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -1,5 +1,7 @@ -import define from '../../define.js'; -import { AccessTokens } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AccessTokensRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -16,25 +18,33 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = AccessTokens.createQueryBuilder('token') - .where('token.userId = :userId', { userId: user.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.accessTokensRepository.createQueryBuilder('token') + .where('token.userId = :userId', { userId: me.id }); - switch (ps.sort) { - case '+createdAt': query.orderBy('token.createdAt', 'DESC'); break; - case '-createdAt': query.orderBy('token.createdAt', 'ASC'); break; - case '+lastUsedAt': query.orderBy('token.lastUsedAt', 'DESC'); break; - case '-lastUsedAt': query.orderBy('token.lastUsedAt', 'ASC'); break; - default: query.orderBy('token.id', 'ASC'); break; - } + switch (ps.sort) { + case '+createdAt': query.orderBy('token.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('token.createdAt', 'ASC'); break; + case '+lastUsedAt': query.orderBy('token.lastUsedAt', 'DESC'); break; + case '-lastUsedAt': query.orderBy('token.lastUsedAt', 'ASC'); break; + default: query.orderBy('token.id', 'ASC'); break; + } - const tokens = await query.getMany(); + const tokens = await query.getMany(); - return await Promise.all(tokens.map(token => ({ - id: token.id, - name: token.name, - createdAt: token.createdAt, - lastUsedAt: token.lastUsedAt, - permission: token.permission, - }))); -}); + return await Promise.all(tokens.map(token => ({ + id: token.id, + name: token.name, + createdAt: token.createdAt, + lastUsedAt: token.lastUsedAt, + permission: token.permission, + }))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts index 68bd103a6d..a5592d20de 100644 --- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts +++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { AccessTokens, Apps } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AccessTokensRepository } from '@/models/index.js'; +import { AppEntityService } from '@/core/entities/AppEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -12,26 +15,36 @@ export const paramDef = { properties: { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, offset: { type: 'integer', default: 0 }, - sort: { type: 'string', enum: ['desc', 'asc'], default: "desc" }, + sort: { type: 'string', enum: ['desc', 'asc'], default: 'desc' }, }, required: [], } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Get tokens - const tokens = await AccessTokens.find({ - where: { - userId: user.id, - }, - take: ps.limit, - skip: ps.offset, - order: { - id: ps.sort === 'asc' ? 1 : -1, - }, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, - return await Promise.all(tokens.map(token => Apps.pack(token.appId, user, { - detail: true, - }))); -}); + private appEntityService: AppEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Get tokens + const tokens = await this.accessTokensRepository.find({ + where: { + userId: me.id, + }, + take: ps.limit, + skip: ps.offset, + order: { + id: ps.sort === 'asc' ? 1 : -1, + }, + }); + + return await Promise.all(tokens.map(token => this.appEntityService.pack(token.appId, me, { + detail: true, + }))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index f9f6a33a80..cc5b712ecf 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -1,6 +1,8 @@ import bcrypt from 'bcryptjs'; -import define from '../../define.js'; -import { UserProfiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserProfilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -18,21 +20,29 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - // Compare password - const same = await bcrypt.compare(ps.currentPassword, profile.password!); - - if (!same) { - throw new Error('incorrect password'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + + // Compare password + const same = await bcrypt.compare(ps.currentPassword, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + // Generate hash of password + const salt = await bcrypt.genSalt(8); + const hash = await bcrypt.hash(ps.newPassword, salt); + + await this.userProfilesRepository.update(me.id, { + password: hash, + }); + }); } - - // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(ps.newPassword, salt); - - await UserProfiles.update(user.id, { - password: hash, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index ede4a9d03b..a1804599df 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -1,7 +1,9 @@ import bcrypt from 'bcryptjs'; -import { UserProfiles, Users } from '@/models/index.js'; -import { deleteAccount } from '@/services/delete-account.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DeleteAccountService } from '@/core/DeleteAccountService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -18,19 +20,32 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - const userDetailed = await Users.findOneByOrFail({ id: user.id }); - if (userDetailed.isDeleted) { - return; - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, - if (!same) { - throw new Error('incorrect password'); - } + private deleteAccountService: DeleteAccountService, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + const userDetailed = await this.usersRepository.findOneByOrFail({ id: me.id }); + if (userDetailed.isDeleted) { + return; + } + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); - await deleteAccount(user); -}); + if (!same) { + throw new Error('incorrect password'); + } + + await this.deleteAccountService.deleteAccount(me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/export-blocking.ts b/packages/backend/src/server/api/endpoints/i/export-blocking.ts index aed4c2e0a3..770708e685 100644 --- a/packages/backend/src/server/api/endpoints/i/export-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/export-blocking.ts @@ -1,6 +1,7 @@ -import define from '../../define.js'; -import { createExportBlockingJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, @@ -18,6 +19,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - createExportBlockingJob(user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportBlockingJob(me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/export-following.ts b/packages/backend/src/server/api/endpoints/i/export-following.ts index 058d77b3c2..fcaa59b12d 100644 --- a/packages/backend/src/server/api/endpoints/i/export-following.ts +++ b/packages/backend/src/server/api/endpoints/i/export-following.ts @@ -1,6 +1,7 @@ -import define from '../../define.js'; -import { createExportFollowingJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, @@ -21,6 +22,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - createExportFollowingJob(user, ps.excludeMuting, ps.excludeInactive); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportFollowingJob(me, ps.excludeMuting, ps.excludeInactive); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/export-mute.ts b/packages/backend/src/server/api/endpoints/i/export-mute.ts index c0216fac0c..37bef0a117 100644 --- a/packages/backend/src/server/api/endpoints/i/export-mute.ts +++ b/packages/backend/src/server/api/endpoints/i/export-mute.ts @@ -1,6 +1,7 @@ -import define from '../../define.js'; -import { createExportMuteJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, @@ -18,6 +19,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - createExportMuteJob(user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportMuteJob(me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/export-notes.ts b/packages/backend/src/server/api/endpoints/i/export-notes.ts index 4b85a45554..9d2505e403 100644 --- a/packages/backend/src/server/api/endpoints/i/export-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/export-notes.ts @@ -1,6 +1,7 @@ -import define from '../../define.js'; -import { createExportNotesJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, @@ -18,6 +19,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - createExportNotesJob(user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportNotesJob(me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts index fa5c1f5e5a..0f8e4bca76 100644 --- a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts @@ -1,6 +1,7 @@ -import define from '../../define.js'; -import { createExportUserListsJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, @@ -18,6 +19,13 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - createExportUserListsJob(user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportUserListsJob(me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts index 3c420e4d0f..350abd9f7b 100644 --- a/packages/backend/src/server/api/endpoints/i/favorites.ts +++ b/packages/backend/src/server/api/endpoints/i/favorites.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { NoteFavorites } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteFavoritesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteFavoriteEntityService } from '@/core/entities/NoteFavoriteEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'notes', 'favorites'], @@ -31,14 +34,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(NoteFavorites.createQueryBuilder('favorite'), ps.sinceId, ps.untilId) - .andWhere(`favorite.userId = :meId`, { meId: user.id }) - .leftJoinAndSelect('favorite.note', 'note'); - - const favorites = await query - .take(ps.limit) - .getMany(); - - return await NoteFavorites.packMany(favorites, user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.noteFavoritesRepository) + private noteFavoritesRepository: NoteFavoritesRepository, + + private noteFavoriteEntityService: NoteFavoriteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.noteFavoritesRepository.createQueryBuilder('favorite'), ps.sinceId, ps.untilId) + .andWhere('favorite.userId = :meId', { meId: me.id }) + .leftJoinAndSelect('favorite.note', 'note'); + + const favorites = await query + .take(ps.limit) + .getMany(); + + return await this.noteFavoriteEntityService.packMany(favorites, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts index a38383f30e..ff6bcc01ab 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; -import { GalleryLikes } from '@/models/index.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryLikesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { GalleryLikeEntityService } from '@/core/entities/GalleryLikeEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'gallery'], @@ -27,7 +30,7 @@ export const meta = { ref: 'GalleryPost', }, }, - } + }, }, } as const; @@ -42,14 +45,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(GalleryLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId) - .andWhere(`like.userId = :meId`, { meId: user.id }) - .leftJoinAndSelect('like.post', 'post'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryLikesRepository) + private galleryLikesRepository: GalleryLikesRepository, - const likes = await query - .take(ps.limit) - .getMany(); + private galleryLikeEntityService: GalleryLikeEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.galleryLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId) + .andWhere('like.userId = :meId', { meId: me.id }) + .leftJoinAndSelect('like.post', 'post'); - return await GalleryLikes.packMany(likes, user); -}); + const likes = await query + .take(ps.limit) + .getMany(); + + return await this.galleryLikeEntityService.packMany(likes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts index b4edb5f73d..927be51f79 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; -import { GalleryPosts } from '@/models/index.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'gallery'], @@ -31,13 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId) - .andWhere(`post.userId = :meId`, { meId: user.id }); - - const posts = await query - .take(ps.limit) - .getMany(); - - return await GalleryPosts.packMany(posts, user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, + + private galleryPostEntityService: GalleryPostEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId) + .andWhere('post.userId = :meId', { meId: me.id }); + + const posts = await query + .take(ps.limit) + .getMany(); + + return await this.galleryPostEntityService.packMany(posts, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts index e7d7518c5b..0695abdd85 100644 --- a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts +++ b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts @@ -1,5 +1,7 @@ -import define from '../../define.js'; -import { MutedNotes } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MutedNotesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], @@ -27,11 +29,19 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - return { - count: await MutedNotes.countBy({ - userId: user.id, - reason: 'word', - }), - }; -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.mutedNotesRepository) + private mutedNotesRepository: MutedNotesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + return { + count: await this.mutedNotesRepository.countBy({ + userId: me.id, + reason: 'word', + }), + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index 0bcbf37ddd..bfba1fc36f 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -1,8 +1,10 @@ -import define from '../../define.js'; -import { createImportBlockingJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { DriveFiles } from '@/models/index.js'; export const meta = { secure: true, @@ -49,13 +51,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOneBy({ id: ps.fileId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (file == null) throw new ApiError(meta.errors.noSuchFile); - //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); - if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile); - if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); - createImportBlockingJob(user, file.id); -}); + if (file == null) throw new ApiError(meta.errors.noSuchFile); + //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); + if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile); + if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + + this.queueService.createImportBlockingJob(me, file.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index ee2abbea19..c7cb2e0330 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -1,8 +1,10 @@ -import define from '../../define.js'; -import { createImportFollowingJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { DriveFiles } from '@/models/index.js'; export const meta = { secure: true, @@ -48,13 +50,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOneBy({ id: ps.fileId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (file == null) throw new ApiError(meta.errors.noSuchFile); - //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); - if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile); - if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); - createImportFollowingJob(user, file.id); -}); + if (file == null) throw new ApiError(meta.errors.noSuchFile); + //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); + if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile); + if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + + this.queueService.createImportFollowingJob(me, file.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index b3b3b39238..060c37c13f 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -1,8 +1,10 @@ -import define from '../../define.js'; -import { createImportMutingJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { DriveFiles } from '@/models/index.js'; export const meta = { secure: true, @@ -49,13 +51,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOneBy({ id: ps.fileId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (file == null) throw new ApiError(meta.errors.noSuchFile); - //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); - if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile); - if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); - createImportMutingJob(user, file.id); -}); + if (file == null) throw new ApiError(meta.errors.noSuchFile); + //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); + if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile); + if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + + this.queueService.createImportMutingJob(me, file.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index 64f5ec05fd..a5e17283e5 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -1,8 +1,10 @@ -import define from '../../define.js'; -import { createImportUserListsJob } from '@/queue/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; +import { DriveFilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { DriveFiles } from '@/models/index.js'; export const meta = { secure: true, @@ -48,13 +50,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOneBy({ id: ps.fileId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - if (file == null) throw new ApiError(meta.errors.noSuchFile); - //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); - if (file.size > 30000) throw new ApiError(meta.errors.tooBigFile); - if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); - createImportUserListsJob(user, file.id); -}); + if (file == null) throw new ApiError(meta.errors.noSuchFile); + //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); + if (file.size > 30000) throw new ApiError(meta.errors.tooBigFile); + if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + + this.queueService.createImportUserListsJob(me, file.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index 2b343dabdd..96927dad49 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -1,10 +1,13 @@ import { Brackets } from 'typeorm'; -import { Notifications, Followings, Mutings, Users, UserProfiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotificationsRepository } from '@/models/index.js'; import { notificationTypes } from '@/types.js'; -import read from '@/services/note/read.js'; -import { readNotification } from '../../common/read-notification.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteReadService } from '@/core/NoteReadService.js'; +import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js'; +import { NotificationService } from '@/core/NotificationService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'notifications'], @@ -49,96 +52,121 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // includeTypes が空の場合はクエリしない - if (ps.includeTypes && ps.includeTypes.length === 0) { - return []; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.notificationsRepository) + private notificationsRepository: NotificationsRepository, + + private notificationEntityService: NotificationEntityService, + private notificationService: NotificationService, + private queryService: QueryService, + private noteReadService: NoteReadService, + ) { + super(meta, paramDef, async (ps, me) => { + // includeTypes が空の場合はクエリしない + if (ps.includeTypes && ps.includeTypes.length === 0) { + return []; + } + // excludeTypes に全指定されている場合はクエリしない + if (notificationTypes.every(type => ps.excludeTypes?.includes(type))) { + return []; + } + const followingQuery = this.followingsRepository.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); + + const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') + .select('muting.muteeId') + .where('muting.muterId = :muterId', { muterId: me.id }); + + const mutingInstanceQuery = this.userProfilesRepository.createQueryBuilder('user_profile') + .select('user_profile.mutedInstances') + .where('user_profile.userId = :muterId', { muterId: me.id }); + + const suspendedQuery = this.usersRepository.createQueryBuilder('users') + .select('users.id') + .where('users.isSuspended = TRUE'); + + const query = this.queryService.makePaginationQuery(this.notificationsRepository.createQueryBuilder('notification'), ps.sinceId, ps.untilId) + .andWhere('notification.notifieeId = :meId', { meId: me.id }) + .leftJoinAndSelect('notification.notifier', 'notifier') + .leftJoinAndSelect('notification.note', 'note') + .leftJoinAndSelect('notifier.avatar', 'notifierAvatar') + .leftJoinAndSelect('notifier.banner', 'notifierBanner') + .leftJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + + // muted users + query.andWhere(new Brackets(qb => { qb + .where(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`) + .orWhere('notification.notifierId IS NULL'); + })); + query.setParameters(mutingQuery.getParameters()); + + // muted instances + query.andWhere(new Brackets(qb => { qb + .andWhere('notifier.host IS NULL') + .orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`); + })); + query.setParameters(mutingInstanceQuery.getParameters()); + + // suspended users + query.andWhere(new Brackets(qb => { qb + .where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`) + .orWhere('notification.notifierId IS NULL'); + })); + + if (ps.following) { + query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: me.id }); + query.setParameters(followingQuery.getParameters()); + } + + if (ps.includeTypes && ps.includeTypes.length > 0) { + query.andWhere('notification.type IN (:...includeTypes)', { includeTypes: ps.includeTypes }); + } else if (ps.excludeTypes && ps.excludeTypes.length > 0) { + query.andWhere('notification.type NOT IN (:...excludeTypes)', { excludeTypes: ps.excludeTypes }); + } + + if (ps.unreadOnly) { + query.andWhere('notification.isRead = false'); + } + + const notifications = await query.take(ps.limit).getMany(); + + // Mark all as read + if (notifications.length > 0 && ps.markAsRead) { + this.notificationService.readNotification(me.id, notifications.map(x => x.id)); + } + + const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!); + + if (notes.length > 0) { + this.noteReadService.read(me.id, notes); + } + + return await this.notificationEntityService.packMany(notifications, me.id); + }); } - // excludeTypes に全指定されている場合はクエリしない - if (notificationTypes.every(type => ps.excludeTypes?.includes(type))) { - return []; - } - const followingQuery = Followings.createQueryBuilder('following') - .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: user.id }); - - const mutingQuery = Mutings.createQueryBuilder('muting') - .select('muting.muteeId') - .where('muting.muterId = :muterId', { muterId: user.id }); - - const mutingInstanceQuery = UserProfiles.createQueryBuilder('user_profile') - .select('user_profile.mutedInstances') - .where('user_profile.userId = :muterId', { muterId: user.id }); - - const suspendedQuery = Users.createQueryBuilder('users') - .select('users.id') - .where('users.isSuspended = TRUE'); - - const query = makePaginationQuery(Notifications.createQueryBuilder('notification'), ps.sinceId, ps.untilId) - .andWhere('notification.notifieeId = :meId', { meId: user.id }) - .leftJoinAndSelect('notification.notifier', 'notifier') - .leftJoinAndSelect('notification.note', 'note') - .leftJoinAndSelect('notifier.avatar', 'notifierAvatar') - .leftJoinAndSelect('notifier.banner', 'notifierBanner') - .leftJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - - // muted users - query.andWhere(new Brackets(qb => { qb - .where(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`) - .orWhere('notification.notifierId IS NULL'); - })); - query.setParameters(mutingQuery.getParameters()); - - // muted instances - query.andWhere(new Brackets(qb => { qb - .andWhere('notifier.host IS NULL') - .orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`); - })); - query.setParameters(mutingInstanceQuery.getParameters()); - - // suspended users - query.andWhere(new Brackets(qb => { qb - .where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`) - .orWhere('notification.notifierId IS NULL'); - })); - - if (ps.following) { - query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: user.id }); - query.setParameters(followingQuery.getParameters()); - } - - if (ps.includeTypes && ps.includeTypes.length > 0) { - query.andWhere('notification.type IN (:...includeTypes)', { includeTypes: ps.includeTypes }); - } else if (ps.excludeTypes && ps.excludeTypes.length > 0) { - query.andWhere('notification.type NOT IN (:...excludeTypes)', { excludeTypes: ps.excludeTypes }); - } - - if (ps.unreadOnly) { - query.andWhere('notification.isRead = false'); - } - - const notifications = await query.take(ps.limit).getMany(); - - // Mark all as read - if (notifications.length > 0 && ps.markAsRead) { - readNotification(user.id, notifications.map(x => x.id)); - } - - const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!); - - if (notes.length > 0) { - read(user.id, notes); - } - - return await Notifications.packMany(notifications, user.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/page-likes.ts b/packages/backend/src/server/api/endpoints/i/page-likes.ts index 71e326e2f0..9a909eedf4 100644 --- a/packages/backend/src/server/api/endpoints/i/page-likes.ts +++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { PageLikes } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { PageLikesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { PageLikeEntityService } from '@/core/entities/PageLikeEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'pages'], @@ -26,7 +29,7 @@ export const meta = { ref: 'Page', }, }, - } + }, }, } as const; @@ -41,14 +44,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(PageLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId) - .andWhere(`like.userId = :meId`, { meId: user.id }) - .leftJoinAndSelect('like.page', 'page'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pageLikesRepository) + private pageLikesRepository: PageLikesRepository, - const likes = await query - .take(ps.limit) - .getMany(); + private pageLikeEntityService: PageLikeEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.pageLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId) + .andWhere('like.userId = :meId', { meId: me.id }) + .leftJoinAndSelect('like.page', 'page'); - return PageLikes.packMany(likes, user); -}); + const likes = await query + .take(ps.limit) + .getMany(); + + return this.pageLikeEntityService.packMany(likes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts index f28aed3fd4..7c4e4a6c7d 100644 --- a/packages/backend/src/server/api/endpoints/i/pages.ts +++ b/packages/backend/src/server/api/endpoints/i/pages.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Pages } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { PagesRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { PageEntityService } from '@/core/entities/PageEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'pages'], @@ -31,13 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId) - .andWhere(`page.userId = :meId`, { meId: user.id }); - - const pages = await query - .take(ps.limit) - .getMany(); - - return await Pages.packMany(pages); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + + private pageEntityService: PageEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.pagesRepository.createQueryBuilder('page'), ps.sinceId, ps.untilId) + .andWhere('page.userId = :meId', { meId: me.id }); + + const pages = await query + .take(ps.limit) + .getMany(); + + return await this.pageEntityService.packMany(pages); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts index 67b7026be1..f31b0dc35e 100644 --- a/packages/backend/src/server/api/endpoints/i/pin.ts +++ b/packages/backend/src/server/api/endpoints/i/pin.ts @@ -1,7 +1,9 @@ -import { addPinned } from '@/services/i/pin.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UsersRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { NotePiningService } from '@/core/NotePiningService.js'; import { ApiError } from '../../error.js'; -import { Users } from '@/models/index.js'; export const meta = { tags: ['account', 'notes'], @@ -46,15 +48,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - await addPinned(user, ps.noteId).catch(e => { - if (e.id === '70c4e51f-5bea-449c-a030-53bee3cce202') throw new ApiError(meta.errors.noSuchNote); - if (e.id === '15a018eb-58e5-4da1-93be-330fcc5e4e1a') throw new ApiError(meta.errors.pinLimitExceeded); - if (e.id === '23f0cf4e-59a3-4276-a91d-61a5891c1514') throw new ApiError(meta.errors.alreadyPinned); - throw e; - }); - - return await Users.pack(user.id, user, { - detail: true, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private userEntityService: UserEntityService, + private notePiningService: NotePiningService, + ) { + super(meta, paramDef, async (ps, me) => { + await this.notePiningService.addPinned(me, ps.noteId).catch(err => { + if (err.id === '70c4e51f-5bea-449c-a030-53bee3cce202') throw new ApiError(meta.errors.noSuchNote); + if (err.id === '15a018eb-58e5-4da1-93be-330fcc5e4e1a') throw new ApiError(meta.errors.pinLimitExceeded); + if (err.id === '23f0cf4e-59a3-4276-a91d-61a5891c1514') throw new ApiError(meta.errors.alreadyPinned); + throw err; + }); + + return await this.userEntityService.pack(me.id, me, { + detail: true, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts index 7ff6409caf..36c3566f55 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts @@ -1,6 +1,8 @@ -import { publishMainStream } from '@/services/stream.js'; -import define from '../../define.js'; -import { MessagingMessages, UserGroupJoinings } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'messaging'], @@ -17,25 +19,38 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Update documents - await MessagingMessages.update({ - recipientId: user.id, - isRead: false, - }, { - isRead: true, - }); - - const joinings = await UserGroupJoinings.findBy({ userId: user.id }); - - await Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder().update() - .set({ - reads: (() => `array_append("reads", '${user.id}')`) as any, - }) - .where(`groupId = :groupId`, { groupId: j.userGroupId }) - .andWhere('userId != :userId', { userId: user.id }) - .andWhere('NOT (:userId = ANY(reads))', { userId: user.id }) - .execute())); - - publishMainStream(user.id, 'readAllMessagingMessages'); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Update documents + await this.messagingMessagesRepository.update({ + recipientId: me.id, + isRead: false, + }, { + isRead: true, + }); + + const joinings = await this.userGroupJoiningsRepository.findBy({ userId: me.id }); + + await Promise.all(joinings.map(j => this.messagingMessagesRepository.createQueryBuilder().update() + .set({ + reads: (() => `array_append("reads", '${me.id}')`) as any, + }) + .where('groupId = :groupId', { groupId: j.userGroupId }) + .andWhere('userId != :userId', { userId: me.id }) + .andWhere('NOT (:userId = ANY(reads))', { userId: me.id }) + .execute())); + + this.globalEventService.publishMainStream(me.id, 'readAllMessagingMessages'); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts index 49f3deb331..b4bb83c6eb 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts @@ -1,6 +1,8 @@ -import { publishMainStream } from '@/services/stream.js'; -import define from '../../define.js'; -import { NoteUnreads } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteUnreadsRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], @@ -17,13 +19,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Remove documents - await NoteUnreads.delete({ - userId: user.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.noteUnreadsRepository) + private noteUnreadsRepository: NoteUnreadsRepository, - // 全て既読になったイベントを発行 - publishMainStream(user.id, 'readAllUnreadMentions'); - publishMainStream(user.id, 'readAllUnreadSpecifiedNotes'); -}); + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Remove documents + await this.noteUnreadsRepository.delete({ + userId: me.id, + }); + + // 全て既読になったイベントを発行 + this.globalEventService.publishMainStream(me.id, 'readAllUnreadMentions'); + this.globalEventService.publishMainStream(me.id, 'readAllUnreadSpecifiedNotes'); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts index 45b6e98c86..5a7909674f 100644 --- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts +++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts @@ -1,8 +1,12 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { genId } from '@/misc/gen-id.js'; -import { AnnouncementReads, Announcements, Users } from '@/models/index.js'; -import { publishMainStream } from '@/services/stream.js'; export const meta = { tags: ['account'], @@ -29,33 +33,48 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Check if announcement exists - const announcement = await Announcements.findOneBy({ id: ps.announcementId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.announcementsRepository) + private announcementsRepository: AnnouncementsRepository, - if (announcement == null) { - throw new ApiError(meta.errors.noSuchAnnouncement); - } + @Inject(DI.announcementReadsRepository) + private announcementReadsRepository: AnnouncementReadsRepository, - // Check if already read - const read = await AnnouncementReads.findOneBy({ - announcementId: ps.announcementId, - userId: user.id, - }); + private userEntityService: UserEntityService, + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Check if announcement exists + const announcement = await this.announcementsRepository.findOneBy({ id: ps.announcementId }); - if (read != null) { - return; - } + if (announcement == null) { + throw new ApiError(meta.errors.noSuchAnnouncement); + } + + // Check if already read + const read = await this.announcementReadsRepository.findOneBy({ + announcementId: ps.announcementId, + userId: me.id, + }); + + if (read != null) { + return; + } - // Create read - await AnnouncementReads.insert({ - id: genId(), - createdAt: new Date(), - announcementId: ps.announcementId, - userId: user.id, - }); + // Create read + await this.announcementReadsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + announcementId: ps.announcementId, + userId: me.id, + }); - if (!await Users.getHasUnreadAnnouncement(user.id)) { - publishMainStream(user.id, 'readAllAnnouncements'); + if (!await this.userEntityService.getHasUnreadAnnouncement(me.id)) { + this.globalEventService.publishMainStream(me.id, 'readAllAnnouncements'); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts index af929b04e8..7796fd97cb 100644 --- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -1,8 +1,10 @@ import bcrypt from 'bcryptjs'; -import { publishInternalEvent, publishMainStream, publishUserEvent } from '@/services/stream.js'; -import generateUserToken from '../../common/generate-native-user-token.js'; -import define from '../../define.js'; -import { Users, UserProfiles } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import generateUserToken from '@/misc/generate-native-user-token.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -19,31 +21,44 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const freshUser = await Users.findOneByOrFail({ id: user.id }); - const oldToken = freshUser.token; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const freshUser = await this.usersRepository.findOneByOrFail({ id: me.id }); + const oldToken = freshUser.token; - if (!same) { - throw new Error('incorrect password'); - } + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); - const newToken = generateUserToken(); + if (!same) { + throw new Error('incorrect password'); + } - await Users.update(user.id, { - token: newToken, - }); + const newToken = generateUserToken(); - // Publish event - publishInternalEvent('userTokenRegenerated', { id: user.id, oldToken, newToken }); - publishMainStream(user.id, 'myTokenRegenerated'); + await this.usersRepository.update(me.id, { + token: newToken, + }); - // Terminate streaming - setTimeout(() => { - publishUserEvent(user.id, 'terminate', {}); - }, 5000); -}); + // Publish event + this.globalEventService.publishInternalEvent('userTokenRegenerated', { id: me.id, oldToken, newToken }); + this.globalEventService.publishMainStream(me.id, 'myTokenRegenerated'); + + // Terminate streaming + setTimeout(() => { + this.globalEventService.publishUserEvent(me.id, 'terminate', {}); + }, 5000); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts index d0b16dbc48..3b4db5fae3 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -18,19 +20,27 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }) - .andWhere('item.scope = :scope', { scope: ps.scope }); - - const items = await query.getMany(); - - const res = {} as Record; - - for (const item of items) { - res[item.key] = item.value; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const items = await query.getMany(); + + const res = {} as Record; + + for (const item of items) { + res[item.key] = item.value; + } + + return res; + }); } - - return res; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts index cc5d5a8c6f..d24dff95b0 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -28,21 +30,29 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }) - .andWhere('item.key = :key', { key: ps.key }) - .andWhere('item.scope = :scope', { scope: ps.scope }); - - const item = await query.getOne(); - - if (item == null) { - throw new ApiError(meta.errors.noSuchKey); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }) + .andWhere('item.key = :key', { key: ps.key }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const item = await query.getOne(); + + if (item == null) { + throw new ApiError(meta.errors.noSuchKey); + } + + return { + updatedAt: item.updatedAt, + value: item.value, + }; + }); } - - return { - updatedAt: item.updatedAt, - value: item.value, - }; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts index a79319744c..98d94a4c02 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -28,18 +30,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }) - .andWhere('item.key = :key', { key: ps.key }) - .andWhere('item.scope = :scope', { scope: ps.scope }); - - const item = await query.getOne(); - - if (item == null) { - throw new ApiError(meta.errors.noSuchKey); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }) + .andWhere('item.key = :key', { key: ps.key }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const item = await query.getOne(); + + if (item == null) { + throw new ApiError(meta.errors.noSuchKey); + } + + return item.value; + }); } - - return item.value; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts index ac209c06a6..d1a05d9d06 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -18,19 +20,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }) - .andWhere('item.scope = :scope', { scope: ps.scope }); - - const items = await query.getMany(); - - const res = {} as Record; - - for (const item of items) { - const type = typeof item.value; - res[item.key] = +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const items = await query.getMany(); + + const res = {} as Record; + + for (const item of items) { + const type = typeof item.value; + res[item.key] = item.value === null ? 'null' : Array.isArray(item.value) ? 'array' : type === 'number' ? 'number' : @@ -38,7 +46,9 @@ export default define(meta, paramDef, async (ps, user) => { type === 'boolean' ? 'boolean' : type === 'object' ? 'object' : null as never; - } + } - return res; -}); + return res; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys.ts b/packages/backend/src/server/api/endpoints/i/registry/keys.ts index 5ea1a9d344..6df5f4ecc3 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -18,14 +20,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .select('item.key') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }) - .andWhere('item.scope = :scope', { scope: ps.scope }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .select('item.key') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }) + .andWhere('item.scope = :scope', { scope: ps.scope }); - const items = await query.getMany(); + const items = await query.getMany(); - return items.map(x => x.key); -}); + return items.map(x => x.key); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/remove.ts b/packages/backend/src/server/api/endpoints/i/registry/remove.ts index 92473654c6..b5870f099d 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/remove.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/remove.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -28,18 +30,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }) - .andWhere('item.key = :key', { key: ps.key }) - .andWhere('item.scope = :scope', { scope: ps.scope }); - - const item = await query.getOne(); - - if (item == null) { - throw new ApiError(meta.errors.noSuchKey); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }) + .andWhere('item.key = :key', { key: ps.key }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const item = await query.getOne(); + + if (item == null) { + throw new ApiError(meta.errors.noSuchKey); + } + + await this.registryItemsRepository.remove(item); + }); } - - await RegistryItems.remove(item); -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts index de4b313e25..58085ddbc5 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -14,20 +16,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .select('item.scope') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }); - - const items = await query.getMany(); - - const res = [] as string[][]; - - for (const item of items) { - if (res.some(scope => scope.join('.') === item.scope.join('.'))) continue; - res.push(item.scope); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .select('item.scope') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }); + + const items = await query.getMany(); + + const res = [] as string[][]; + + for (const item of items) { + if (res.some(scope => scope.join('.') === item.scope.join('.'))) continue; + res.push(item.scope); + } + + return res; + }); } - - return res; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/registry/set.ts b/packages/backend/src/server/api/endpoints/i/registry/set.ts index d380b428a3..585aac2e01 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/set.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/set.ts @@ -1,7 +1,9 @@ -import { publishMainStream } from '@/services/stream.js'; -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { RegistryItemsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -22,37 +24,48 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = RegistryItems.createQueryBuilder('item') - .where('item.domain IS NULL') - .andWhere('item.userId = :userId', { userId: user.id }) - .andWhere('item.key = :key', { key: ps.key }) - .andWhere('item.scope = :scope', { scope: ps.scope }); - - const existingItem = await query.getOne(); - - if (existingItem) { - await RegistryItems.update(existingItem.id, { - updatedAt: new Date(), - value: ps.value, - }); - } else { - await RegistryItems.insert({ - id: genId(), - createdAt: new Date(), - updatedAt: new Date(), - userId: user.id, - domain: null, - scope: ps.scope, - key: ps.key, - value: ps.value, +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.registryItemsRepository) + private registryItemsRepository: RegistryItemsRepository, + + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registryItemsRepository.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: me.id }) + .andWhere('item.key = :key', { key: ps.key }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const existingItem = await query.getOne(); + + if (existingItem) { + await this.registryItemsRepository.update(existingItem.id, { + updatedAt: new Date(), + value: ps.value, + }); + } else { + await this.registryItemsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + updatedAt: new Date(), + userId: me.id, + domain: null, + scope: ps.scope, + key: ps.key, + value: ps.value, + }); + } + + // TODO: サードパーティアプリが傍受出来てしまうのでどうにかする + this.globalEventService.publishMainStream(me.id, 'registryUpdated', { + scope: ps.scope, + key: ps.key, + value: ps.value, + }); }); } - - // TODO: サードパーティアプリが傍受出来てしまうのでどうにかする - publishMainStream(user.id, 'registryUpdated', { - scope: ps.scope, - key: ps.key, - value: ps.value, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts index c692453794..86a82e6a6c 100644 --- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -1,6 +1,8 @@ -import define from '../../define.js'; -import { AccessTokens } from '@/models/index.js'; -import { publishUserEvent } from '@/services/stream.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AccessTokensRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -17,16 +19,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const token = await AccessTokens.findOneBy({ id: ps.tokenId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, - if (token) { - await AccessTokens.delete({ - id: ps.tokenId, - userId: user.id, - }); + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const token = await this.accessTokensRepository.findOneBy({ id: ps.tokenId }); + + if (token) { + await this.accessTokensRepository.delete({ + id: ps.tokenId, + userId: me.id, + }); - // Terminate streaming - publishUserEvent(user.id, 'terminate'); + // Terminate streaming + this.globalEventService.publishUserEvent(me.id, 'terminate'); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/signin-history.ts b/packages/backend/src/server/api/endpoints/i/signin-history.ts index ca37411662..410cd72065 100644 --- a/packages/backend/src/server/api/endpoints/i/signin-history.ts +++ b/packages/backend/src/server/api/endpoints/i/signin-history.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { Signins } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { SigninsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { SigninEntityService } from '@/core/entities/SigninEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, @@ -19,11 +22,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(Signins.createQueryBuilder('signin'), ps.sinceId, ps.untilId) - .andWhere(`signin.userId = :meId`, { meId: user.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.signinsRepository) + private signinsRepository: SigninsRepository, - const history = await query.take(ps.limit).getMany(); + private signinEntityService: SigninEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.signinsRepository.createQueryBuilder('signin'), ps.sinceId, ps.untilId) + .andWhere('signin.userId = :meId', { meId: me.id }); - return await Promise.all(history.map(record => Signins.pack(record))); -}); + const history = await query.take(ps.limit).getMany(); + + return await Promise.all(history.map(record => this.signinEntityService.pack(record))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/unpin.ts b/packages/backend/src/server/api/endpoints/i/unpin.ts index 9912689da5..9a735e1168 100644 --- a/packages/backend/src/server/api/endpoints/i/unpin.ts +++ b/packages/backend/src/server/api/endpoints/i/unpin.ts @@ -1,7 +1,9 @@ -import { removePinned } from '@/services/i/pin.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UsersRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { NotePiningService } from '@/core/NotePiningService.js'; import { ApiError } from '../../error.js'; -import { Users } from '@/models/index.js'; export const meta = { tags: ['account', 'notes'], @@ -34,13 +36,21 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - await removePinned(user, ps.noteId).catch(e => { - if (e.id === 'b302d4cf-c050-400a-bbb3-be208681f40c') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - return await Users.pack(user.id, user, { - detail: true, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private userEntityService: UserEntityService, + private notePiningService: NotePiningService, + ) { + super(meta, paramDef, async (ps, me) => { + await this.notePiningService.removePinned(me, ps.noteId).catch(err => { + if (err.id === 'b302d4cf-c050-400a-bbb3-be208681f40c') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + return await this.userEntityService.pack(me.id, me, { + detail: true, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 3318078523..719cc14f09 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -1,13 +1,15 @@ -import { publishMainStream } from '@/services/stream.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; import rndstr from 'rndstr'; -import config from '@/config/index.js'; import ms from 'ms'; import bcrypt from 'bcryptjs'; -import { Users, UserProfiles } from '@/models/index.js'; -import { sendEmail } from '@/services/send-email.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { EmailService } from '@/core/EmailService.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ApiError } from '../../error.js'; -import { validateEmailForAccount } from '@/services/validate-email-for-account.js'; export const meta = { requireCredential: true, @@ -44,50 +46,68 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); - - if (!same) { - throw new ApiError(meta.errors.incorrectPassword); - } - - if (ps.email != null) { - const available = await validateEmailForAccount(ps.email); - if (!available) { - throw new ApiError(meta.errors.unavailable); - } - } - - await UserProfiles.update(user.id, { - email: ps.email, - emailVerified: false, - emailVerifyCode: null, - }); - - const iObj = await Users.pack(user.id, user, { - detail: true, - includeSecrets: true, - }); - - // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', iObj); - - if (ps.email != null) { - const code = rndstr('a-z0-9', 16); - - await UserProfiles.update(user.id, { - emailVerifyCode: code, +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private userEntityService: UserEntityService, + private emailService: EmailService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new ApiError(meta.errors.incorrectPassword); + } + + if (ps.email != null) { + const available = await this.emailService.validateEmailForAccount(ps.email); + if (!available) { + throw new ApiError(meta.errors.unavailable); + } + } + + await this.userProfilesRepository.update(me.id, { + email: ps.email, + emailVerified: false, + emailVerifyCode: null, + }); + + const iObj = await this.userEntityService.pack(me.id, me, { + detail: true, + includeSecrets: true, + }); + + // Publish meUpdated event + this.globalEventService.publishMainStream(me.id, 'meUpdated', iObj); + + if (ps.email != null) { + const code = rndstr('a-z0-9', 16); + + await this.userProfilesRepository.update(me.id, { + emailVerifyCode: code, + }); + + const link = `${this.config.url}/verify-email/${code}`; + + this.emailService.sendEmail(ps.email, 'Email verification', + `To verify email, please click this link:
${link}`, + `To verify email, please click this link: ${link}`); + } + + return iObj; }); - - const link = `${config.url}/verify-email/${code}`; - - sendEmail(ps.email, 'Email verification', - `To verify email, please click this link:
${link}`, - `To verify email, please click this link: ${link}`); } - - return iObj; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 3c2f1cea0d..4b904d4696 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -1,19 +1,23 @@ import RE2 from 're2'; import * as mfm from 'mfm-js'; -import { publishMainStream, publishUserEvent } from '@/services/stream.js'; -import acceptAllFollowRequests from '@/services/following/requests/accept-all.js'; -import { publishToFollowers } from '@/services/i/update.js'; +import { Inject, Injectable } from '@nestjs/common'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractHashtags } from '@/misc/extract-hashtags.js'; -import { updateUsertags } from '@/services/update-hashtag.js'; -import { Users, DriveFiles, UserProfiles, Pages } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import { UserProfile } from '@/models/entities/user-profile.js'; +import { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/index.js'; +import type { User } from '@/models/entities/User.js'; +import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/entities/User.js'; +import type { UserProfile } from '@/models/entities/UserProfile.js'; import { notificationTypes } from '@/types.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { langmap } from '@/misc/langmap.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { AccountUpdateService } from '@/core/AccountUpdateService.js'; +import { HashtagService } from '@/core/HashtagService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import define from '../../define.js'; export const meta = { tags: ['account'], @@ -70,10 +74,10 @@ export const meta = { export const paramDef = { type: 'object', properties: { - name: { ...Users.nameSchema, nullable: true }, - description: { ...Users.descriptionSchema, nullable: true }, - location: { ...Users.locationSchema, nullable: true }, - birthday: { ...Users.birthdaySchema, nullable: true }, + name: { ...nameSchema, nullable: true }, + description: { ...descriptionSchema, nullable: true }, + location: { ...locationSchema, nullable: true }, + birthday: { ...birthdaySchema, nullable: true }, lang: { type: 'string', enum: [null, ...Object.keys(langmap)], nullable: true }, avatarId: { type: 'string', format: 'misskey:id', nullable: true }, bannerId: { type: 'string', format: 'misskey:id', nullable: true }, @@ -122,134 +126,157 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, _user, token) => { - const user = await Users.findOneByOrFail({ id: _user.id }); - const isSecure = token == null; - - const updates = {} as Partial; - const profileUpdates = {} as Partial; - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - if (ps.name !== undefined) updates.name = ps.name; - if (ps.description !== undefined) profileUpdates.description = ps.description; - if (ps.lang !== undefined) profileUpdates.lang = ps.lang; - if (ps.location !== undefined) profileUpdates.location = ps.location; - if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday; - if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility; - if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId; - if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId; - if (ps.mutedWords !== undefined) { - // validate regular expression syntax - ps.mutedWords.filter(x => !Array.isArray(x)).forEach(x => { - const regexp = x.match(/^\/(.+)\/(.*)$/); - if (!regexp) throw new ApiError(meta.errors.invalidRegexp); - - try { - new RE2(regexp[1], regexp[2]); - } catch (err) { - throw new ApiError(meta.errors.invalidRegexp); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + + private userEntityService: UserEntityService, + private globalEventService: GlobalEventService, + private userFollowingService: UserFollowingService, + private accountUpdateService: AccountUpdateService, + private hashtagService: HashtagService, + ) { + super(meta, paramDef, async (ps, _user, token) => { + const user = await this.usersRepository.findOneByOrFail({ id: _user.id }); + const isSecure = token == null; + + const updates = {} as Partial; + const profileUpdates = {} as Partial; + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + if (ps.name !== undefined) updates.name = ps.name; + if (ps.description !== undefined) profileUpdates.description = ps.description; + if (ps.lang !== undefined) profileUpdates.lang = ps.lang; + if (ps.location !== undefined) profileUpdates.location = ps.location; + if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday; + if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility; + if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId; + if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId; + if (ps.mutedWords !== undefined) { + // validate regular expression syntax + ps.mutedWords.filter(x => !Array.isArray(x)).forEach(x => { + const regexp = x.match(/^\/(.+)\/(.*)$/); + if (!regexp) throw new ApiError(meta.errors.invalidRegexp); + + try { + new RE2(regexp[1], regexp[2]); + } catch (err) { + throw new ApiError(meta.errors.invalidRegexp); + } + }); + + profileUpdates.mutedWords = ps.mutedWords; + profileUpdates.enableWordMute = ps.mutedWords.length > 0; + } + if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances; + if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes as typeof notificationTypes[number][]; + if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked; + if (typeof ps.isExplorable === 'boolean') updates.isExplorable = ps.isExplorable; + if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus; + if (typeof ps.publicReactions === 'boolean') profileUpdates.publicReactions = ps.publicReactions; + if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot; + if (typeof ps.showTimelineReplies === 'boolean') updates.showTimelineReplies = ps.showTimelineReplies; + if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot; + if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; + if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle; + if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat; + if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; + if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail; + if (typeof ps.alwaysMarkNsfw === 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw; + if (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive; + if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes; + + if (ps.avatarId) { + const avatar = await this.driveFilesRepository.findOneBy({ id: ps.avatarId }); + + if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar); + if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage); } - }); - - profileUpdates.mutedWords = ps.mutedWords; - profileUpdates.enableWordMute = ps.mutedWords.length > 0; - } - if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances; - if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes as typeof notificationTypes[number][]; - if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked; - if (typeof ps.isExplorable === 'boolean') updates.isExplorable = ps.isExplorable; - if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus; - if (typeof ps.publicReactions === 'boolean') profileUpdates.publicReactions = ps.publicReactions; - if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot; - if (typeof ps.showTimelineReplies === 'boolean') updates.showTimelineReplies = ps.showTimelineReplies; - if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot; - if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; - if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle; - if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat; - if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; - if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail; - if (typeof ps.alwaysMarkNsfw === 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw; - if (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive; - if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes; - - if (ps.avatarId) { - const avatar = await DriveFiles.findOneBy({ id: ps.avatarId }); - - if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar); - if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage); - } - if (ps.bannerId) { - const banner = await DriveFiles.findOneBy({ id: ps.bannerId }); + if (ps.bannerId) { + const banner = await this.driveFilesRepository.findOneBy({ id: ps.bannerId }); - if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner); - if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage); - } + if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner); + if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage); + } - if (ps.pinnedPageId) { - const page = await Pages.findOneBy({ id: ps.pinnedPageId }); + if (ps.pinnedPageId) { + const page = await this.pagesRepository.findOneBy({ id: ps.pinnedPageId }); - if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage); + if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage); - profileUpdates.pinnedPageId = page.id; - } else if (ps.pinnedPageId === null) { - profileUpdates.pinnedPageId = null; - } + profileUpdates.pinnedPageId = page.id; + } else if (ps.pinnedPageId === null) { + profileUpdates.pinnedPageId = null; + } - if (ps.fields) { - profileUpdates.fields = ps.fields - .filter(x => typeof x.name === 'string' && x.name !== '' && typeof x.value === 'string' && x.value !== '') - .map(x => { - return { name: x.name, value: x.value }; - }); - } + if (ps.fields) { + profileUpdates.fields = ps.fields + .filter(x => typeof x.name === 'string' && x.name !== '' && typeof x.value === 'string' && x.value !== '') + .map(x => { + return { name: x.name, value: x.value }; + }); + } - //#region emojis/tags + //#region emojis/tags - let emojis = [] as string[]; - let tags = [] as string[]; + let emojis = [] as string[]; + let tags = [] as string[]; - const newName = updates.name === undefined ? user.name : updates.name; - const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description; + const newName = updates.name === undefined ? user.name : updates.name; + const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description; - if (newName != null) { - const tokens = mfm.parseSimple(newName); - emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!)); - } + if (newName != null) { + const tokens = mfm.parseSimple(newName); + emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!)); + } - if (newDescription != null) { - const tokens = mfm.parse(newDescription); - emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!)); - tags = extractHashtags(tokens!).map(tag => normalizeForSearch(tag)).splice(0, 32); - } + if (newDescription != null) { + const tokens = mfm.parse(newDescription); + emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!)); + tags = extractHashtags(tokens!).map(tag => normalizeForSearch(tag)).splice(0, 32); + } - updates.emojis = emojis; - updates.tags = tags; + updates.emojis = emojis; + updates.tags = tags; - // ハッシュタグ更新 - updateUsertags(user, tags); - //#endregion + // ハッシュタグ更新 + this.hashtagService.updateUsertags(user, tags); + //#endregion - if (Object.keys(updates).length > 0) await Users.update(user.id, updates); - if (Object.keys(profileUpdates).length > 0) await UserProfiles.update(user.id, profileUpdates); + if (Object.keys(updates).length > 0) await this.usersRepository.update(user.id, updates); + if (Object.keys(profileUpdates).length > 0) await this.userProfilesRepository.update(user.id, profileUpdates); - const iObj = await Users.pack(user.id, user, { - detail: true, - includeSecrets: isSecure, - }); + const iObj = await this.userEntityService.pack(user.id, user, { + detail: true, + includeSecrets: isSecure, + }); - // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', iObj); - publishUserEvent(user.id, 'updateUserProfile', await UserProfiles.findOneBy({ userId: user.id })); + // Publish meUpdated event + this.globalEventService.publishMainStream(user.id, 'meUpdated', iObj); + this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', await this.userProfilesRepository.findOneBy({ userId: user.id })); - // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 - if (user.isLocked && ps.isLocked === false) { - acceptAllFollowRequests(user); - } + // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 + if (user.isLocked && ps.isLocked === false) { + this.userFollowingService.acceptAllFollowRequests(user); + } - // フォロワーにUpdateを配信 - publishToFollowers(user.id); + // フォロワーにUpdateを配信 + this.accountUpdateService.publishToFollowers(user.id); - return iObj; -}); + return iObj; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts index 1d7e4a16b3..6dd1626bb8 100644 --- a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts +++ b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { UserGroupInvitations } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserGroupInvitationsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UserGroupInvitationEntityService } from '@/core/entities/UserGroupInvitationEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'groups'], @@ -42,14 +45,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(UserGroupInvitations.createQueryBuilder('invitation'), ps.sinceId, ps.untilId) - .andWhere(`invitation.userId = :meId`, { meId: user.id }) - .leftJoinAndSelect('invitation.userGroup', 'user_group'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupInvitationsRepository) + private userGroupInvitationsRepository: UserGroupInvitationsRepository, - const invitations = await query - .take(ps.limit) - .getMany(); + private userGroupInvitationEntityService: UserGroupInvitationEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.userGroupInvitationsRepository.createQueryBuilder('invitation'), ps.sinceId, ps.untilId) + .andWhere('invitation.userId = :meId', { meId: me.id }) + .leftJoinAndSelect('invitation.userGroup', 'user_group'); - return await UserGroupInvitations.packMany(invitations); -}); + const invitations = await query + .take(ps.limit) + .getMany(); + + return await this.userGroupInvitationEntityService.packMany(invitations); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts index 2e2fd00b8c..016b1b5d6a 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -1,8 +1,10 @@ -import define from '../../../define.js'; -import { genId } from '@/misc/gen-id.js'; -import { Webhooks } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; -import { webhookEventTypes } from '@/models/entities/webhook.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import { WebhooksRepository } from '@/models/index.js'; +import { webhookEventTypes } from '@/models/entities/Webhook.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['webhooks'], @@ -25,19 +27,32 @@ export const paramDef = { required: ['name', 'url', 'secret', 'on'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const webhook = await Webhooks.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - name: ps.name, - url: ps.url, - secret: ps.secret, - on: ps.on, - }).then(x => Webhooks.findOneByOrFail(x.identifiers[0])); - - publishInternalEvent('webhookCreated', webhook); - - return webhook; -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.webhooksRepository) + private webhooksRepository: WebhooksRepository, + + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const webhook = await this.webhooksRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + name: ps.name, + url: ps.url, + secret: ps.secret, + on: ps.on, + }).then(x => this.webhooksRepository.findOneByOrFail(x.identifiers[0])); + + this.globalEventService.publishInternalEvent('webhookCreated', webhook); + + return webhook; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts index 2821eaa5f1..53b553b43e 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { WebhooksRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { Webhooks } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['webhooks'], @@ -27,18 +29,30 @@ export const paramDef = { required: ['webhookId'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const webhook = await Webhooks.findOneBy({ - id: ps.webhookId, - userId: user.id, - }); - - if (webhook == null) { - throw new ApiError(meta.errors.noSuchWebhook); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.webhooksRepository) + private webhooksRepository: WebhooksRepository, + + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const webhook = await this.webhooksRepository.findOneBy({ + id: ps.webhookId, + userId: me.id, + }); + + if (webhook == null) { + throw new ApiError(meta.errors.noSuchWebhook); + } + + await this.webhooksRepository.delete(webhook.id); + + this.globalEventService.publishInternalEvent('webhookDeleted', webhook); + }); } - - await Webhooks.delete(webhook.id); - - publishInternalEvent('webhookDeleted', webhook); -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts index 54e4563732..8e4aff45dd 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -1,5 +1,7 @@ -import define from '../../../define.js'; -import { Webhooks } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { WebhooksRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['webhooks', 'account'], @@ -16,10 +18,18 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const webhooks = await Webhooks.findBy({ - userId: me.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.webhooksRepository) + private webhooksRepository: WebhooksRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const webhooks = await this.webhooksRepository.findBy({ + userId: me.id, + }); - return webhooks; -}); + return webhooks; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts index 02fa1edb5e..622c2ade98 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -1,6 +1,8 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { WebhooksRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { Webhooks } from '@/models/index.js'; export const meta = { tags: ['webhooks'], @@ -27,15 +29,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const webhook = await Webhooks.findOneBy({ - id: ps.webhookId, - userId: user.id, - }); - - if (webhook == null) { - throw new ApiError(meta.errors.noSuchWebhook); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.webhooksRepository) + private webhooksRepository: WebhooksRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const webhook = await this.webhooksRepository.findOneBy({ + id: ps.webhookId, + userId: me.id, + }); + + if (webhook == null) { + throw new ApiError(meta.errors.noSuchWebhook); + } + + return webhook; + }); } - - return webhook; -}); +} diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts index f87b9753fb..3a0ef1a526 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts @@ -1,8 +1,10 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { WebhooksRepository } from '@/models/index.js'; +import { webhookEventTypes } from '@/models/entities/Webhook.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { Webhooks } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; -import { webhookEventTypes } from '@/models/entities/webhook.js'; export const meta = { tags: ['webhooks'], @@ -36,24 +38,36 @@ export const paramDef = { required: ['webhookId', 'name', 'url', 'secret', 'on', 'active'], } as const; +// TODO: ロジックをサービスに切り出す + // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const webhook = await Webhooks.findOneBy({ - id: ps.webhookId, - userId: user.id, - }); - - if (webhook == null) { - throw new ApiError(meta.errors.noSuchWebhook); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.webhooksRepository) + private webhooksRepository: WebhooksRepository, + + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const webhook = await this.webhooksRepository.findOneBy({ + id: ps.webhookId, + userId: me.id, + }); - await Webhooks.update(webhook.id, { - name: ps.name, - url: ps.url, - secret: ps.secret, - on: ps.on, - active: ps.active, - }); + if (webhook == null) { + throw new ApiError(meta.errors.noSuchWebhook); + } - publishInternalEvent('webhookUpdated', webhook); -}); + await this.webhooksRepository.update(webhook.id, { + name: ps.name, + url: ps.url, + secret: ps.secret, + on: ps.on, + active: ps.active, + }); + + this.globalEventService.publishInternalEvent('webhookUpdated', webhook); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/messaging/history.ts b/packages/backend/src/server/api/endpoints/messaging/history.ts index ea0600d0e4..da3ba59df9 100644 --- a/packages/backend/src/server/api/endpoints/messaging/history.ts +++ b/packages/backend/src/server/api/endpoints/messaging/history.ts @@ -1,7 +1,10 @@ -import define from '../../define.js'; -import { MessagingMessage } from '@/models/entities/messaging-message.js'; -import { MessagingMessages, Mutings, UserGroupJoinings } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; +import { MutingsRepository, UserGroupJoiningsRepository, MessagingMessagesRepository } from '@/models/index.js'; +import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['messaging'], @@ -31,61 +34,77 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const mute = await Mutings.findBy({ - muterId: user.id, - }); - - const groups = ps.group ? await UserGroupJoinings.findBy({ - userId: user.id, - }).then(xs => xs.map(x => x.userGroupId)) : []; - - if (ps.group && groups.length === 0) { - return []; - } - - const history: MessagingMessage[] = []; - - for (let i = 0; i < ps.limit; i++) { - const found = ps.group - ? history.map(m => m.groupId!) - : history.map(m => (m.userId === user.id) ? m.recipientId! : m.userId!); - - const query = MessagingMessages.createQueryBuilder('message') - .orderBy('message.createdAt', 'DESC'); - - if (ps.group) { - query.where(`message.groupId IN (:...groups)`, { groups: groups }); - - if (found.length > 0) { - query.andWhere(`message.groupId NOT IN (:...found)`, { found: found }); - } - } else { - query.where(new Brackets(qb => { qb - .where(`message.userId = :userId`, { userId: user.id }) - .orWhere(`message.recipientId = :userId`, { userId: user.id }); - })); - query.andWhere(`message.groupId IS NULL`); - - if (found.length > 0) { - query.andWhere(`message.userId NOT IN (:...found)`, { found: found }); - query.andWhere(`message.recipientId NOT IN (:...found)`, { found: found }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private messagingMessageEntityService: MessagingMessageEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const mute = await this.mutingsRepository.findBy({ + muterId: me.id, + }); + + const groups = ps.group ? await this.userGroupJoiningsRepository.findBy({ + userId: me.id, + }).then(xs => xs.map(x => x.userGroupId)) : []; + + if (ps.group && groups.length === 0) { + return []; } - if (mute.length > 0) { - query.andWhere(`message.userId NOT IN (:...mute)`, { mute: mute.map(m => m.muteeId) }); - query.andWhere(`message.recipientId NOT IN (:...mute)`, { mute: mute.map(m => m.muteeId) }); + const history: MessagingMessage[] = []; + + for (let i = 0; i < ps.limit; i++) { + const found = ps.group + ? history.map(m => m.groupId!) + : history.map(m => (m.userId === me.id) ? m.recipientId! : m.userId!); + + const query = this.messagingMessagesRepository.createQueryBuilder('message') + .orderBy('message.createdAt', 'DESC'); + + if (ps.group) { + query.where('message.groupId IN (:...groups)', { groups: groups }); + + if (found.length > 0) { + query.andWhere('message.groupId NOT IN (:...found)', { found: found }); + } + } else { + query.where(new Brackets(qb => { qb + .where('message.userId = :userId', { userId: me.id }) + .orWhere('message.recipientId = :userId', { userId: me.id }); + })); + query.andWhere('message.groupId IS NULL'); + + if (found.length > 0) { + query.andWhere('message.userId NOT IN (:...found)', { found: found }); + query.andWhere('message.recipientId NOT IN (:...found)', { found: found }); + } + + if (mute.length > 0) { + query.andWhere('message.userId NOT IN (:...mute)', { mute: mute.map(m => m.muteeId) }); + query.andWhere('message.recipientId NOT IN (:...mute)', { mute: mute.map(m => m.muteeId) }); + } + } + + const message = await query.getOne(); + + if (message) { + history.push(message); + } else { + break; + } } - } - const message = await query.getOne(); - - if (message) { - history.push(message); - } else { - break; - } + return await Promise.all(history.map(h => this.messagingMessageEntityService.pack(h.id, me))); + }); } - - return await Promise.all(history.map(h => MessagingMessages.pack(h.id, user))); -}); +} diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts index dbf1f6c868..6579b03987 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts @@ -1,10 +1,15 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { MessagingMessages, UserGroups, UserGroupJoinings, Users } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; -import { readUserMessagingMessage, readGroupMessagingMessage, deliverReadActivity } from '../../common/read-messaging-message.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UsersRepository } from '@/models/index.js'; +import { UserGroupsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; +import { MessagingService } from '@/core/MessagingService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['messaging'], @@ -69,73 +74,93 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - if (ps.userId != null) { - // Fetch recipient (user) - const recipient = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId) - .andWhere(new Brackets(qb => { qb - .where(new Brackets(qb => { qb - .where('message.userId = :meId') - .andWhere('message.recipientId = :recipientId'); - })) - .orWhere(new Brackets(qb => { qb - .where('message.userId = :recipientId') - .andWhere('message.recipientId = :meId'); - })); - })) - .setParameter('meId', user.id) - .setParameter('recipientId', recipient.id); - - const messages = await query.take(ps.limit).getMany(); - - // Mark all as read - if (ps.markAsRead) { - readUserMessagingMessage(user.id, recipient.id, messages.filter(m => m.recipientId === user.id).map(x => x.id)); - - // リモートユーザーとのメッセージだったら既読配信 - if (Users.isLocalUser(user) && Users.isRemoteUser(recipient)) { - deliverReadActivity(user, recipient, messages); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + @Inject(DI.userGroupsRepository) + private userGroupRepository: UserGroupsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private messagingMessageEntityService: MessagingMessageEntityService, + private messagingService: MessagingService, + private userEntityService: UserEntityService, + private queryService: QueryService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + if (ps.userId != null) { + // Fetch recipient (user) + const recipient = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + const query = this.queryService.makePaginationQuery(this.messagingMessagesRepository.createQueryBuilder('message'), ps.sinceId, ps.untilId) + .andWhere(new Brackets(qb => { qb + .where(new Brackets(qb => { qb + .where('message.userId = :meId') + .andWhere('message.recipientId = :recipientId'); + })) + .orWhere(new Brackets(qb => { qb + .where('message.userId = :recipientId') + .andWhere('message.recipientId = :meId'); + })); + })) + .setParameter('meId', me.id) + .setParameter('recipientId', recipient.id); + + const messages = await query.take(ps.limit).getMany(); + + // Mark all as read + if (ps.markAsRead) { + this.messagingService.readUserMessagingMessage(me.id, recipient.id, messages.filter(m => m.recipientId === me.id).map(x => x.id)); + + // リモートユーザーとのメッセージだったら既読配信 + if (this.userEntityService.isLocalUser(me) && this.userEntityService.isRemoteUser(recipient)) { + this.messagingService.deliverReadActivity(me, recipient, messages); + } + } + + return await Promise.all(messages.map(message => this.messagingMessageEntityService.pack(message, me, { + populateRecipient: false, + }))); + } else if (ps.groupId != null) { + // Fetch recipient (group) + const recipientGroup = await this.userGroupRepository.findOneBy({ id: ps.groupId }); + + if (recipientGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } + + // check joined + const joining = await this.userGroupJoiningsRepository.findOneBy({ + userId: me.id, + userGroupId: recipientGroup.id, + }); + + if (joining == null) { + throw new ApiError(meta.errors.groupAccessDenied); + } + + const query = this.queryService.makePaginationQuery(this.messagingMessagesRepository.createQueryBuilder('message'), ps.sinceId, ps.untilId) + .andWhere('message.groupId = :groupId', { groupId: recipientGroup.id }); + + const messages = await query.take(ps.limit).getMany(); + + // Mark all as read + if (ps.markAsRead) { + this.messagingService.readGroupMessagingMessage(me.id, recipientGroup.id, messages.map(x => x.id)); + } + + return await Promise.all(messages.map(message => this.messagingMessageEntityService.pack(message, me, { + populateGroup: false, + }))); } - } - - return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, { - populateRecipient: false, - }))); - } else if (ps.groupId != null) { - // Fetch recipient (group) - const recipientGroup = await UserGroups.findOneBy({ id: ps.groupId }); - - if (recipientGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); - } - - // check joined - const joining = await UserGroupJoinings.findOneBy({ - userId: user.id, - userGroupId: recipientGroup.id, }); - - if (joining == null) { - throw new ApiError(meta.errors.groupAccessDenied); - } - - const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId) - .andWhere(`message.groupId = :groupId`, { groupId: recipientGroup.id }); - - const messages = await query.take(ps.limit).getMany(); - - // Mark all as read - if (ps.markAsRead) { - readGroupMessagingMessage(user.id, recipientGroup.id, messages.map(x => x.id)); - } - - return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, { - populateGroup: false, - }))); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts index 405af5ec17..e02afcbcfd 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts @@ -1,10 +1,12 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { BlockingsRepository, UserGroupJoiningsRepository, DriveFilesRepository, UserGroupsRepository } from '@/models/index.js'; +import type { User } from '@/models/entities/User.js'; +import type { UserGroup } from '@/models/entities/UserGroup.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { MessagingService } from '@/core/MessagingService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; -import { MessagingMessages, DriveFiles, UserGroups, UserGroupJoinings, Blockings } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { createMessage } from '@/services/messages/create.js'; export const meta = { tags: ['messaging'], @@ -87,65 +89,85 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - let recipientUser: User | null; - let recipientGroup: UserGroup | null; - - if (ps.userId != null) { - // Myself - if (ps.userId === user.id) { - throw new ApiError(meta.errors.recipientIsYourself); - } - - // Fetch recipient (user) - recipientUser = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private getterService: GetterService, + private messagingService: MessagingService, + ) { + super(meta, paramDef, async (ps, me) => { + let recipientUser: User | null; + let recipientGroup: UserGroup | null; + + if (ps.userId != null) { + // Myself + if (ps.userId === me.id) { + throw new ApiError(meta.errors.recipientIsYourself); + } + + // Fetch recipient (user) + recipientUser = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check blocking + const block = await this.blockingsRepository.findOneBy({ + blockerId: recipientUser.id, + blockeeId: me.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } else if (ps.groupId != null) { + // Fetch recipient (group) + recipientGroup = await this.userGroupsRepository.findOneBy({ id: ps.groupId! }); + + if (recipientGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } + + // check joined + const joining = await this.userGroupJoiningsRepository.findOneBy({ + userId: me.id, + userGroupId: recipientGroup.id, + }); + + if (joining == null) { + throw new ApiError(meta.errors.groupAccessDenied); + } + } + + let file = null; + if (ps.fileId != null) { + file = await this.driveFilesRepository.findOneBy({ + id: ps.fileId, + userId: me.id, + }); + + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } + } + + // テキストが無いかつ添付ファイルも無かったらエラー + if (ps.text == null && file == null) { + throw new ApiError(meta.errors.contentRequired); + } + + return await this.messagingService.createMessage(me, recipientUser, recipientGroup, ps.text, file); }); - - // Check blocking - const block = await Blockings.findOneBy({ - blockerId: recipientUser.id, - blockeeId: user.id, - }); - if (block) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } else if (ps.groupId != null) { - // Fetch recipient (group) - recipientGroup = await UserGroups.findOneBy({ id: ps.groupId! }); - - if (recipientGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); - } - - // check joined - const joining = await UserGroupJoinings.findOneBy({ - userId: user.id, - userGroupId: recipientGroup.id, - }); - - if (joining == null) { - throw new ApiError(meta.errors.groupAccessDenied); - } } - - let file = null; - if (ps.fileId != null) { - file = await DriveFiles.findOneBy({ - id: ps.fileId, - userId: user.id, - }); - - if (file == null) { - throw new ApiError(meta.errors.noSuchFile); - } - } - - // テキストが無いかつ添付ファイルも無かったらエラー - if (ps.text == null && file == null) { - throw new ApiError(meta.errors.contentRequired); - } - - return await createMessage(user, recipientUser, recipientGroup, ps.text, file); -}); +} diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts index f66d75873c..5baecb9114 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts @@ -1,8 +1,10 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MessagingMessagesRepository } from '@/models/index.js'; +import { MessagingService } from '@/core/MessagingService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { MessagingMessages } from '@/models/index.js'; -import { deleteMessage } from '@/services/messages/delete.js'; export const meta = { tags: ['messaging'], @@ -35,15 +37,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const message = await MessagingMessages.findOneBy({ - id: ps.messageId, - userId: user.id, - }); - - if (message == null) { - throw new ApiError(meta.errors.noSuchMessage); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + private messagingService: MessagingService, + ) { + super(meta, paramDef, async (ps, me) => { + const message = await this.messagingMessagesRepository.findOneBy({ + id: ps.messageId, + userId: me.id, + }); + + if (message == null) { + throw new ApiError(meta.errors.noSuchMessage); + } + + await this.messagingService.deleteMessage(message); + }); } - - await deleteMessage(message); -}); +} diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts index db12ae922c..6e66cafe1e 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts @@ -1,7 +1,9 @@ -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MessagingMessagesRepository } from '@/models/index.js'; +import { MessagingService } from '@/core/MessagingService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { MessagingMessages } from '@/models/index.js'; -import { readUserMessagingMessage, readGroupMessagingMessage } from '../../../common/read-messaging-message.js'; export const meta = { tags: ['messaging'], @@ -28,22 +30,32 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const message = await MessagingMessages.findOneBy({ id: ps.messageId }); - - if (message == null) { - throw new ApiError(meta.errors.noSuchMessage); - } - - if (message.recipientId) { - await readUserMessagingMessage(user.id, message.userId, [message.id]).catch(e => { - if (e.id === 'e140a4bf-49ce-4fb6-b67c-b78dadf6b52f') throw new ApiError(meta.errors.noSuchMessage); - throw e; - }); - } else if (message.groupId) { - await readGroupMessagingMessage(user.id, message.groupId, [message.id]).catch(e => { - if (e.id === '930a270c-714a-46b2-b776-ad27276dc569') throw new ApiError(meta.errors.noSuchMessage); - throw e; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + private messagingService: MessagingService, + ) { + super(meta, paramDef, async (ps, me) => { + const message = await this.messagingMessagesRepository.findOneBy({ id: ps.messageId }); + + if (message == null) { + throw new ApiError(meta.errors.noSuchMessage); + } + + if (message.recipientId) { + await this.messagingService.readUserMessagingMessage(me.id, message.userId, [message.id]).catch(err => { + if (err.id === 'e140a4bf-49ce-4fb6-b67c-b78dadf6b52f') throw new ApiError(meta.errors.noSuchMessage); + throw err; + }); + } else if (message.groupId) { + await this.messagingService.readGroupMessagingMessage(me.id, message.groupId, [message.id]).catch(err => { + if (err.id === '930a270c-714a-46b2-b776-ad27276dc569') throw new ApiError(meta.errors.noSuchMessage); + throw err; + }); + } }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 5b624842c3..9a6258d7dd 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -1,10 +1,14 @@ import { IsNull, MoreThan } from 'typeorm'; -import config from '@/config/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Ads, Emojis, Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { AdsRepository, EmojisRepository, UsersRepository } from '@/models/index.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import define from '../define.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['meta'], @@ -26,7 +30,6 @@ export const meta = { version: { type: 'string', optional: false, nullable: false, - example: config.version, }, name: { type: 'string', @@ -304,111 +307,132 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const instance = await fetchMeta(true); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - const emojis = await Emojis.find({ - where: { - host: IsNull(), - }, - order: { - category: 'ASC', - name: 'ASC', - }, - cache: { - id: 'meta_emojis', - milliseconds: 3600000, // 1 hour - }, - }); + @Inject(DI.adsRepository) + private adsRepository: AdsRepository, - const ads = await Ads.find({ - where: { - expiresAt: MoreThan(new Date()), - }, - }); + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, - const response: any = { - maintainerName: instance.maintainerName, - maintainerEmail: instance.maintainerEmail, + private userEntityService: UserEntityService, + private emojiEntityService: EmojiEntityService, + private metaService: MetaService, + ) { + super(meta, paramDef, async (ps, me) => { + const instance = await this.metaService.fetch(true); - version: config.version, + const emojis = await this.emojisRepository.find({ + where: { + host: IsNull(), + }, + order: { + category: 'ASC', + name: 'ASC', + }, + cache: { + id: 'meta_emojis', + milliseconds: 3600000, // 1 hour + }, + }); - name: instance.name, - uri: config.url, - description: instance.description, - langs: instance.langs, - tosUrl: instance.ToSUrl, - repositoryUrl: instance.repositoryUrl, - feedbackUrl: instance.feedbackUrl, - disableRegistration: instance.disableRegistration, - disableLocalTimeline: instance.disableLocalTimeline, - disableGlobalTimeline: instance.disableGlobalTimeline, - driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, - driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, - emailRequiredForSignup: instance.emailRequiredForSignup, - enableHcaptcha: instance.enableHcaptcha, - hcaptchaSiteKey: instance.hcaptchaSiteKey, - enableRecaptcha: instance.enableRecaptcha, - recaptchaSiteKey: instance.recaptchaSiteKey, - swPublickey: instance.swPublicKey, - themeColor: instance.themeColor, - mascotImageUrl: instance.mascotImageUrl, - bannerUrl: instance.bannerUrl, - errorImageUrl: instance.errorImageUrl, - iconUrl: instance.iconUrl, - backgroundImageUrl: instance.backgroundImageUrl, - logoImageUrl: instance.logoImageUrl, - maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため - emojis: await Emojis.packMany(emojis), - defaultLightTheme: instance.defaultLightTheme, - defaultDarkTheme: instance.defaultDarkTheme, - ads: ads.map(ad => ({ - id: ad.id, - url: ad.url, - place: ad.place, - ratio: ad.ratio, - imageUrl: ad.imageUrl, - })), - enableEmail: instance.enableEmail, + const ads = await this.adsRepository.find({ + where: { + expiresAt: MoreThan(new Date()), + }, + }); - enableTwitterIntegration: instance.enableTwitterIntegration, - enableGithubIntegration: instance.enableGithubIntegration, - enableDiscordIntegration: instance.enableDiscordIntegration, + const response: any = { + maintainerName: instance.maintainerName, + maintainerEmail: instance.maintainerEmail, - enableServiceWorker: instance.enableServiceWorker, + version: this.config.version, - translatorAvailable: instance.deeplAuthKey != null, + name: instance.name, + uri: this.config.url, + description: instance.description, + langs: instance.langs, + tosUrl: instance.ToSUrl, + repositoryUrl: instance.repositoryUrl, + feedbackUrl: instance.feedbackUrl, + disableRegistration: instance.disableRegistration, + disableLocalTimeline: instance.disableLocalTimeline, + disableGlobalTimeline: instance.disableGlobalTimeline, + driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, + driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, + emailRequiredForSignup: instance.emailRequiredForSignup, + enableHcaptcha: instance.enableHcaptcha, + hcaptchaSiteKey: instance.hcaptchaSiteKey, + enableRecaptcha: instance.enableRecaptcha, + recaptchaSiteKey: instance.recaptchaSiteKey, + swPublickey: instance.swPublicKey, + themeColor: instance.themeColor, + mascotImageUrl: instance.mascotImageUrl, + bannerUrl: instance.bannerUrl, + errorImageUrl: instance.errorImageUrl, + iconUrl: instance.iconUrl, + backgroundImageUrl: instance.backgroundImageUrl, + logoImageUrl: instance.logoImageUrl, + maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため + emojis: await this.emojiEntityService.packMany(emojis), + defaultLightTheme: instance.defaultLightTheme, + defaultDarkTheme: instance.defaultDarkTheme, + ads: ads.map(ad => ({ + id: ad.id, + url: ad.url, + place: ad.place, + ratio: ad.ratio, + imageUrl: ad.imageUrl, + })), + enableEmail: instance.enableEmail, - ...(ps.detail ? { - pinnedPages: instance.pinnedPages, - pinnedClipId: instance.pinnedClipId, - cacheRemoteFiles: instance.cacheRemoteFiles, - requireSetup: (await Users.countBy({ - host: IsNull(), - })) === 0, - } : {}), - }; + enableTwitterIntegration: instance.enableTwitterIntegration, + enableGithubIntegration: instance.enableGithubIntegration, + enableDiscordIntegration: instance.enableDiscordIntegration, - if (ps.detail) { - const proxyAccount = instance.proxyAccountId ? await Users.pack(instance.proxyAccountId).catch(() => null) : null; + enableServiceWorker: instance.enableServiceWorker, - response.proxyAccountName = proxyAccount ? proxyAccount.username : null; - response.features = { - registration: !instance.disableRegistration, - localTimeLine: !instance.disableLocalTimeline, - globalTimeLine: !instance.disableGlobalTimeline, - emailRequiredForSignup: instance.emailRequiredForSignup, - elasticsearch: config.elasticsearch ? true : false, - hcaptcha: instance.enableHcaptcha, - recaptcha: instance.enableRecaptcha, - objectStorage: instance.useObjectStorage, - twitter: instance.enableTwitterIntegration, - github: instance.enableGithubIntegration, - discord: instance.enableDiscordIntegration, - serviceWorker: instance.enableServiceWorker, - miauth: true, - }; - } + translatorAvailable: instance.deeplAuthKey != null, + + ...(ps.detail ? { + pinnedPages: instance.pinnedPages, + pinnedClipId: instance.pinnedClipId, + cacheRemoteFiles: instance.cacheRemoteFiles, + requireSetup: (await this.usersRepository.countBy({ + host: IsNull(), + })) === 0, + } : {}), + }; - return response; -}); + if (ps.detail) { + const proxyAccount = instance.proxyAccountId ? await this.userEntityService.pack(instance.proxyAccountId).catch(() => null) : null; + + response.proxyAccountName = proxyAccount ? proxyAccount.username : null; + response.features = { + registration: !instance.disableRegistration, + localTimeLine: !instance.disableLocalTimeline, + globalTimeLine: !instance.disableGlobalTimeline, + emailRequiredForSignup: instance.emailRequiredForSignup, + elasticsearch: this.config.elasticsearch ? true : false, + hcaptcha: instance.enableHcaptcha, + recaptcha: instance.enableRecaptcha, + objectStorage: instance.useObjectStorage, + twitter: instance.enableTwitterIntegration, + github: instance.enableGithubIntegration, + discord: instance.enableDiscordIntegration, + serviceWorker: instance.enableServiceWorker, + miauth: true, + }; + } + + return response; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts index 73ecdaeb03..d8eb89c0e6 100644 --- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts +++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts @@ -1,7 +1,9 @@ -import define from '../../define.js'; -import { AccessTokens } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AccessTokensRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['auth'], @@ -37,28 +39,38 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Generate access token - const accessToken = secureRndstr(32, true); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, - const now = new Date(); + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + // Generate access token + const accessToken = secureRndstr(32, true); - // Insert access token doc - await AccessTokens.insert({ - id: genId(), - createdAt: now, - lastUsedAt: now, - session: ps.session, - userId: user.id, - token: accessToken, - hash: accessToken, - name: ps.name, - description: ps.description, - iconUrl: ps.iconUrl, - permission: ps.permission, - }); + const now = new Date(); - return { - token: accessToken, - }; -}); + // Insert access token doc + await this.accessTokensRepository.insert({ + id: this.idService.genId(), + createdAt: now, + lastUsedAt: now, + session: ps.session, + userId: me.id, + token: accessToken, + hash: accessToken, + name: ps.name, + description: ps.description, + iconUrl: ps.iconUrl, + permission: ps.permission, + }); + + return { + token: accessToken, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 7e857e6731..cbdd001185 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -1,10 +1,12 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import { MutingsRepository } from '@/models/index.js'; +import type { Muting } from '@/models/entities/Muting.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { genId } from '@/misc/gen-id.js'; -import { Mutings, NoteWatchings } from '@/models/index.js'; -import { Muting } from '@/models/entities/muting.js'; -import { publishUserEvent } from '@/services/stream.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['account'], @@ -48,47 +50,54 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const muter = user; - - // 自分自身 - if (user.id === ps.userId) { - throw new ApiError(meta.errors.muteeIsYourself); - } - - // Get mutee - const mutee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Check if already muting - const exist = await Mutings.findOneBy({ - muterId: muter.id, - muteeId: mutee.id, - }); - - if (exist != null) { - throw new ApiError(meta.errors.alreadyMuting); - } - - if (ps.expiresAt && ps.expiresAt <= Date.now()) { - return; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, + + private globalEventService: GlobalEventService, + private getterService: GetterService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const muter = me; + + // 自分自身 + if (me.id === ps.userId) { + throw new ApiError(meta.errors.muteeIsYourself); + } + + // Get mutee + const mutee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check if already muting + const exist = await this.mutingsRepository.findOneBy({ + muterId: muter.id, + muteeId: mutee.id, + }); + + if (exist != null) { + throw new ApiError(meta.errors.alreadyMuting); + } + + if (ps.expiresAt && ps.expiresAt <= Date.now()) { + return; + } + + // Create mute + await this.mutingsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, + muterId: muter.id, + muteeId: mutee.id, + } as Muting); + + this.globalEventService.publishUserEvent(me.id, 'mute', mutee); + }); } - - // Create mute - await Mutings.insert({ - id: genId(), - createdAt: new Date(), - expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, - muterId: muter.id, - muteeId: mutee.id, - } as Muting); - - publishUserEvent(user.id, 'mute', mutee); - - NoteWatchings.delete({ - userId: muter.id, - noteUserId: mutee.id, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts index 0b173dbe24..c7098059d5 100644 --- a/packages/backend/src/server/api/endpoints/mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/mute/delete.ts @@ -1,8 +1,10 @@ -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MutingsRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { Mutings } from '@/models/index.js'; -import { publishUserEvent } from '@/services/stream.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['account'], @@ -41,34 +43,45 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const muter = user; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, - // Check if the mutee is yourself - if (user.id === ps.userId) { - throw new ApiError(meta.errors.muteeIsYourself); - } + private globalEventService: GlobalEventService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const muter = me; - // Get mutee - const mutee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); + // Check if the mutee is yourself + if (me.id === ps.userId) { + throw new ApiError(meta.errors.muteeIsYourself); + } - // Check not muting - const exist = await Mutings.findOneBy({ - muterId: muter.id, - muteeId: mutee.id, - }); + // Get mutee + const mutee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); - if (exist == null) { - throw new ApiError(meta.errors.notMuting); - } + // Check not muting + const exist = await this.mutingsRepository.findOneBy({ + muterId: muter.id, + muteeId: mutee.id, + }); - // Delete mute - await Mutings.delete({ - id: exist.id, - }); + if (exist == null) { + throw new ApiError(meta.errors.notMuting); + } - publishUserEvent(user.id, 'unmute', mutee); -}); + // Delete mute + await this.mutingsRepository.delete({ + id: exist.id, + }); + + this.globalEventService.publishUserEvent(me.id, 'unmute', mutee); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/mute/list.ts b/packages/backend/src/server/api/endpoints/mute/list.ts index 31283cf4c1..11c05eb795 100644 --- a/packages/backend/src/server/api/endpoints/mute/list.ts +++ b/packages/backend/src/server/api/endpoints/mute/list.ts @@ -1,6 +1,9 @@ -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { Mutings } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MutingsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { MutingEntityService } from '@/core/entities/MutingEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], @@ -31,13 +34,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(Mutings.createQueryBuilder('muting'), ps.sinceId, ps.untilId) - .andWhere(`muting.muterId = :meId`, { meId: me.id }); - - const mutings = await query - .take(ps.limit) - .getMany(); - - return await Mutings.packMany(mutings, me); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, + + private mutingEntityService: MutingEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.mutingsRepository.createQueryBuilder('muting'), ps.sinceId, ps.untilId) + .andWhere('muting.muterId = :meId', { meId: me.id }); + + const mutings = await query + .take(ps.limit) + .getMany(); + + return await this.mutingEntityService.packMany(mutings, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/my/apps.ts b/packages/backend/src/server/api/endpoints/my/apps.ts index 85b75c15df..90cd53a133 100644 --- a/packages/backend/src/server/api/endpoints/my/apps.ts +++ b/packages/backend/src/server/api/endpoints/my/apps.ts @@ -1,5 +1,8 @@ -import define from '../../define.js'; -import { Apps } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { AppsRepository } from '@/models/index.js'; +import { AppEntityService } from '@/core/entities/AppEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account', 'app'], @@ -27,18 +30,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = { - userId: user.id, - }; - - const apps = await Apps.find({ - where: query, - take: ps.limit, - skip: ps.offset, - }); - - return await Promise.all(apps.map(app => Apps.pack(app, user, { - detail: true, - }))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.appsRepository) + private appsRepository: AppsRepository, + + private appEntityService: AppEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = { + userId: me.id, + }; + + const apps = await this.appsRepository.find({ + where: query, + take: ps.limit, + skip: ps.offset, + }); + + return await Promise.all(apps.map(app => this.appEntityService.pack(app, me, { + detail: true, + }))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts index 015b0338e3..288e195316 100644 --- a/packages/backend/src/server/api/endpoints/notes.ts +++ b/packages/backend/src/server/api/endpoints/notes.ts @@ -1,6 +1,9 @@ -import { Notes } from '@/models/index.js'; -import define from '../define.js'; -import { makePaginationQuery } from '../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -32,48 +35,59 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere('note.visibility = \'public\'') - .andWhere('note.localOnly = FALSE') - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - if (ps.local) { - query.andWhere('note.userHost IS NULL'); + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere('note.visibility = \'public\'') + .andWhere('note.localOnly = FALSE') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + + if (ps.local) { + query.andWhere('note.userHost IS NULL'); + } + + if (ps.reply !== undefined) { + query.andWhere(ps.reply ? 'note.replyId IS NOT NULL' : 'note.replyId IS NULL'); + } + + if (ps.renote !== undefined) { + query.andWhere(ps.renote ? 'note.renoteId IS NOT NULL' : 'note.renoteId IS NULL'); + } + + if (ps.withFiles !== undefined) { + query.andWhere(ps.withFiles ? 'note.fileIds != \'{}\'' : 'note.fileIds = \'{}\''); + } + + if (ps.poll !== undefined) { + query.andWhere(ps.poll ? 'note.hasPoll = TRUE' : 'note.hasPoll = FALSE'); + } + + // TODO + //if (bot != undefined) { + // query.isBot = bot; + //} + + const notes = await query.take(ps.limit).getMany(); + + return await this.noteEntityService.packMany(notes); + }); } - - if (ps.reply !== undefined) { - query.andWhere(ps.reply ? 'note.replyId IS NOT NULL' : 'note.replyId IS NULL'); - } - - if (ps.renote !== undefined) { - query.andWhere(ps.renote ? 'note.renoteId IS NOT NULL' : 'note.renoteId IS NULL'); - } - - if (ps.withFiles !== undefined) { - query.andWhere(ps.withFiles ? 'note.fileIds != \'{}\'' : 'note.fileIds = \'{}\''); - } - - if (ps.poll !== undefined) { - query.andWhere(ps.poll ? 'note.hasPoll = TRUE' : 'note.hasPoll = FALSE'); - } - - // TODO - //if (bot != undefined) { - // query.isBot = bot; - //} - - const notes = await query.take(ps.limit).getMany(); - - return await Notes.packMany(notes); -}); +} diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index efc109105c..86f90e049f 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -1,10 +1,10 @@ import { Brackets } from 'typeorm'; -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -34,38 +34,49 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere(new Brackets(qb => { qb - .where('note.replyId = :noteId', { noteId: ps.noteId }) - .orWhere(new Brackets(qb => { qb - .where('note.renoteId = :noteId', { noteId: ps.noteId }) +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere(new Brackets(qb => { qb - .where('note.text IS NOT NULL') - .orWhere('note.fileIds != \'{}\'') - .orWhere('note.hasPoll = TRUE'); - })); - })); - })) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + .where('note.replyId = :noteId', { noteId: ps.noteId }) + .orWhere(new Brackets(qb => { qb + .where('note.renoteId = :noteId', { noteId: ps.noteId }) + .andWhere(new Brackets(qb => { qb + .where('note.text IS NOT NULL') + .orWhere('note.fileIds != \'{}\'') + .orWhere('note.hasPoll = TRUE'); + })); + })); + })) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - generateVisibilityQuery(query, user); - if (user) { - generateMutedUserQuery(query, user); - generateBlockedUserQuery(query, user); - } + this.queryService.generateVisibilityQuery(query, me); + if (me) { + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + } - const notes = await query.take(ps.limit).getMany(); + const notes = await query.take(ps.limit).getMany(); - return await Notes.packMany(notes, user); -}); + return await this.noteEntityService.packMany(notes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index e79f8563e8..7d893f32a1 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -1,8 +1,11 @@ import { In } from 'typeorm'; -import { ClipNotes, Clips } from '@/models/index.js'; -import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['clips', 'notes'], @@ -37,20 +40,34 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - const clipNotes = await ClipNotes.findBy({ - noteId: note.id, - }); - - const clips = await Clips.findBy({ - id: In(clipNotes.map(x => x.clipId)), - isPublic: true, - }); - - return await Promise.all(clips.map(x => Clips.pack(x))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + + @Inject(DI.clipNotesRepository) + private clipNotesRepository: ClipNotesRepository, + + private clipEntityService: ClipEntityService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + const clipNotes = await this.clipNotesRepository.findBy({ + noteId: note.id, + }); + + const clips = await this.clipsRepository.findBy({ + id: In(clipNotes.map(x => x.clipId)), + isPublic: true, + }); + + return await Promise.all(clips.map(x => this.clipEntityService.pack(x))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index b731d18248..2f8324ed62 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -1,8 +1,11 @@ -import { Note } from '@/models/entities/note.js'; -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { Note } from '@/models/entities/Note.js'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getNote } from '../../common/getters.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['notes'], @@ -39,36 +42,47 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - const conversation: Note[] = []; - let i = 0; + private noteEntityService: NoteEntityService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); - async function get(id: any) { - i++; - const p = await Notes.findOneBy({ id }); - if (p == null) return; + const conversation: Note[] = []; + let i = 0; - if (i > ps.offset!) { - conversation.push(p); - } + const get = async (id: any) => { + i++; + const p = await this.notesRepository.findOneBy({ id }); + if (p == null) return; - if (conversation.length === ps.limit) { - return; - } + if (i > ps.offset!) { + conversation.push(p); + } - if (p.replyId) { - await get(p.replyId); - } - } + if (conversation.length === ps.limit) { + return; + } - if (note.replyId) { - await get(note.replyId); - } + if (p.replyId) { + await get(p.replyId); + } + }; - return await Notes.packMany(conversation, user); -}); + if (note.replyId) { + await get(note.replyId); + } + + return await this.noteEntityService.packMany(conversation, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index a133294169..30b7a889fc 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -1,15 +1,18 @@ import ms from 'ms'; import { In } from 'typeorm'; -import create from '@/services/note/create.js'; -import { User } from '@/models/entities/user.js'; -import { Users, DriveFiles, Notes, Channels, Blockings } from '@/models/index.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { Note } from '@/models/entities/note.js'; -import { Channel } from '@/models/entities/channel.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { User } from '@/models/entities/User.js'; +import { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesRepository, ChannelsRepository } from '@/models/index.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { Note } from '@/models/entities/Note.js'; +import type { Channel } from '@/models/entities/Channel.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { NoteCreateService } from '@/core/NoteCreateService.js'; +import { DI } from '@/di-symbols.js'; import { noteVisibilities } from '../../../../types.js'; import { ApiError } from '../../error.js'; -import define from '../../define.js'; export const meta = { tags: ['notes'], @@ -161,115 +164,138 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - let visibleUsers: User[] = []; - if (ps.visibleUserIds) { - visibleUsers = await Users.findBy({ - id: In(ps.visibleUserIds), - }); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - let files: DriveFile[] = []; - const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; - if (fileIds != null) { - files = await DriveFiles.createQueryBuilder('file') - .where('file.userId = :userId AND file.id IN (:...fileIds)', { - userId: user.id, - fileIds, - }) - .orderBy('array_position(ARRAY[:...fileIds], "id"::text)') - .setParameters({ fileIds }) - .getMany(); - } + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - let renote: Note | null = null; - if (ps.renoteId != null) { - // Fetch renote to note - renote = await Notes.findOneBy({ id: ps.renoteId }); + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, - if (renote == null) { - throw new ApiError(meta.errors.noSuchRenoteTarget); - } else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) { - throw new ApiError(meta.errors.cannotReRenote); - } + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, - // Check blocking - if (renote.userId !== user.id) { - const block = await Blockings.findOneBy({ - blockerId: renote.userId, - blockeeId: user.id, - }); - if (block) { - throw new ApiError(meta.errors.youHaveBeenBlocked); + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + private noteEntityService: NoteEntityService, + private noteCreateService: NoteCreateService, + ) { + super(meta, paramDef, async (ps, me) => { + let visibleUsers: User[] = []; + if (ps.visibleUserIds) { + visibleUsers = await this.usersRepository.findBy({ + id: In(ps.visibleUserIds), + }); } - } - } - let reply: Note | null = null; - if (ps.replyId != null) { - // Fetch reply - reply = await Notes.findOneBy({ id: ps.replyId }); + let files: DriveFile[] = []; + const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; + if (fileIds != null) { + files = await this.driveFilesRepository.createQueryBuilder('file') + .where('file.userId = :userId AND file.id IN (:...fileIds)', { + userId: me.id, + fileIds, + }) + .orderBy('array_position(ARRAY[:...fileIds], "id"::text)') + .setParameters({ fileIds }) + .getMany(); + } - if (reply == null) { - throw new ApiError(meta.errors.noSuchReplyTarget); - } else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) { - throw new ApiError(meta.errors.cannotReplyToPureRenote); - } + let renote: Note | null = null; + if (ps.renoteId != null) { + // Fetch renote to note + renote = await this.notesRepository.findOneBy({ id: ps.renoteId }); - // Check blocking - if (reply.userId !== user.id) { - const block = await Blockings.findOneBy({ - blockerId: reply.userId, - blockeeId: user.id, - }); - if (block) { - throw new ApiError(meta.errors.youHaveBeenBlocked); + if (renote == null) { + throw new ApiError(meta.errors.noSuchRenoteTarget); + } else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) { + throw new ApiError(meta.errors.cannotReRenote); + } + + // Check blocking + if (renote.userId !== me.id) { + const block = await this.blockingsRepository.findOneBy({ + blockerId: renote.userId, + blockeeId: me.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } } - } - } - if (ps.poll) { - if (typeof ps.poll.expiresAt === 'number') { - if (ps.poll.expiresAt < Date.now()) { - throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); + let reply: Note | null = null; + if (ps.replyId != null) { + // Fetch reply + reply = await this.notesRepository.findOneBy({ id: ps.replyId }); + + if (reply == null) { + throw new ApiError(meta.errors.noSuchReplyTarget); + } else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) { + throw new ApiError(meta.errors.cannotReplyToPureRenote); + } + + // Check blocking + if (reply.userId !== me.id) { + const block = await this.blockingsRepository.findOneBy({ + blockerId: reply.userId, + blockeeId: me.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } } - } else if (typeof ps.poll.expiredAfter === 'number') { - ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter; - } - } - let channel: Channel | null = null; - if (ps.channelId != null) { - channel = await Channels.findOneBy({ id: ps.channelId }); + if (ps.poll) { + if (typeof ps.poll.expiresAt === 'number') { + if (ps.poll.expiresAt < Date.now()) { + throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); + } + } else if (typeof ps.poll.expiredAfter === 'number') { + ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter; + } + } - if (channel == null) { - throw new ApiError(meta.errors.noSuchChannel); - } - } + let channel: Channel | null = null; + if (ps.channelId != null) { + channel = await this.channelsRepository.findOneBy({ id: ps.channelId }); - // 投稿を作成 - const note = await create(user, { - createdAt: new Date(), - files: files, - poll: ps.poll ? { - choices: ps.poll.choices, - multiple: ps.poll.multiple || false, - expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, - } : undefined, - text: ps.text || undefined, - reply, - renote, - cw: ps.cw, - localOnly: ps.localOnly, - visibility: ps.visibility, - visibleUsers, - channel, - apMentions: ps.noExtractMentions ? [] : undefined, - apHashtags: ps.noExtractHashtags ? [] : undefined, - apEmojis: ps.noExtractEmojis ? [] : undefined, - }); + if (channel == null) { + throw new ApiError(meta.errors.noSuchChannel); + } + } + + // 投稿を作成 + const note = await this.noteCreateService.create(me, { + createdAt: new Date(), + files: files, + poll: ps.poll ? { + choices: ps.poll.choices, + multiple: ps.poll.multiple || false, + expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, + } : undefined, + text: ps.text ?? undefined, + reply, + renote, + cw: ps.cw, + localOnly: ps.localOnly, + visibility: ps.visibility, + visibleUsers, + channel, + apMentions: ps.noExtractMentions ? [] : undefined, + apHashtags: ps.noExtractHashtags ? [] : undefined, + apEmojis: ps.noExtractEmojis ? [] : undefined, + }); - return { - createdNote: await Notes.pack(note, user), - }; -}); + return { + createdNote: await this.noteEntityService.pack(note, me), + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index c23ceeb5bf..4769c8bdf1 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -1,9 +1,11 @@ import ms from 'ms'; -import deleteNote from '@/services/note/delete.js'; -import { Users } from '@/models/index.js'; -import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteDeleteService } from '@/core/NoteDeleteService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['notes'], @@ -42,16 +44,27 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - if ((!user.isAdmin && !user.isModerator) && (note.userId !== user.id)) { - throw new ApiError(meta.errors.accessDenied); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private getterService: GetterService, + private noteDeleteService: NoteDeleteService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); - // この操作を行うのが投稿者とは限らない(例えばモデレーター)ため - await deleteNote(await Users.findOneByOrFail({ id: note.userId }), note); -}); + if ((!me.isAdmin && !me.isModerator) && (note.userId !== me.id)) { + throw new ApiError(meta.errors.accessDenied); + } + + // この操作を行うのが投稿者とは限らない(例えばモデレーター)ため + await this.noteDeleteService.delete(await this.usersRepository.findOneByOrFail({ id: note.userId }), note); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index 097371a425..bfdd1acd22 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -1,8 +1,10 @@ -import { NoteFavorites } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NoteFavoritesRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { getNote } from '../../../common/getters.js'; export const meta = { tags: ['notes', 'favorites'], @@ -35,28 +37,39 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Get favoritee - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - // if already favorited - const exist = await NoteFavorites.findOneBy({ - noteId: note.id, - userId: user.id, - }); - - if (exist != null) { - throw new ApiError(meta.errors.alreadyFavorited); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.noteFavoritesRepository) + private noteFavoritesRepository: NoteFavoritesRepository, + + private idService: IdService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + // Get favoritee + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + // if already favorited + const exist = await this.noteFavoritesRepository.findOneBy({ + noteId: note.id, + userId: me.id, + }); - // Create favorite - await NoteFavorites.insert({ - id: genId(), - createdAt: new Date(), - noteId: note.id, - userId: user.id, - }); -}); + if (exist != null) { + throw new ApiError(meta.errors.alreadyFavorited); + } + + // Create favorite + await this.noteFavoritesRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + noteId: note.id, + userId: me.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index 82ef4fa197..6b3a02b101 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -1,7 +1,9 @@ -import { NoteFavorites } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { DI } from '@/di-symbols.js'; +import { NoteFavoritesRepository } from '@/models/index.js'; import { ApiError } from '../../../error.js'; -import { getNote } from '../../../common/getters.js'; export const meta = { tags: ['notes', 'favorites'], @@ -34,23 +36,33 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Get favoritee - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - // if already favorited - const exist = await NoteFavorites.findOneBy({ - noteId: note.id, - userId: user.id, - }); - - if (exist == null) { - throw new ApiError(meta.errors.notFavorited); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.noteFavoritesRepository) + private noteFavoritesRepository: NoteFavoritesRepository, + + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + // Get favoritee + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + // if already favorited + const exist = await this.noteFavoritesRepository.findOneBy({ + noteId: note.id, + userId: me.id, + }); - // Delete favorite - await NoteFavorites.delete(exist.id); -}); + if (exist == null) { + throw new ApiError(meta.errors.notFavorited); + } + + // Delete favorite + await this.noteFavoritesRepository.delete(exist.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index dd9cc581aa..9985f9d257 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -1,7 +1,9 @@ -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -29,39 +31,50 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const max = 30; - const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - const query = Notes.createQueryBuilder('note') - .addSelect('note.score') - .where('note.userHost IS NULL') - .andWhere('note.score > 0') - .andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) }) - .andWhere('note.visibility = \'public\'') - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const max = 30; + const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで - if (user) generateMutedUserQuery(query, user); - if (user) generateBlockedUserQuery(query, user); + const query = this.notesRepository.createQueryBuilder('note') + .addSelect('note.score') + .where('note.userHost IS NULL') + .andWhere('note.score > 0') + .andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) }) + .andWhere('note.visibility = \'public\'') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - let notes = await query - .orderBy('note.score', 'DESC') - .take(max) - .getMany(); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); - notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + let notes = await query + .orderBy('note.score', 'DESC') + .take(max) + .getMany(); - notes = notes.slice(ps.offset, ps.offset + ps.limit); + notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); - return await Notes.packMany(notes, user); -}); + notes = notes.slice(ps.offset, ps.offset + ps.limit); + + return await this.noteEntityService.packMany(notes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 925318f544..73b5afa40a 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -1,13 +1,12 @@ -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Notes } from '@/models/index.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateRepliesQuery } from '../../common/generate-replies-query.js'; -import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; export const meta = { tags: ['notes'], @@ -49,50 +48,63 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const m = await fetchMeta(); - if (m.disableGlobalTimeline) { - if (user == null || (!user.isAdmin && !user.isModerator)) { - throw new ApiError(meta.errors.gtlDisabled); - } - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - //#region Construct query - const query = makePaginationQuery(Notes.createQueryBuilder('note'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere('note.visibility = \'public\'') - .andWhere('note.channelId IS NULL') - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private metaService: MetaService, + private activeUsersChart: ActiveUsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + const m = await this.metaService.fetch(); + if (m.disableGlobalTimeline) { + if (me == null || (!me.isAdmin && !me.isModerator)) { + throw new ApiError(meta.errors.gtlDisabled); + } + } - generateRepliesQuery(query, user); - if (user) { - generateMutedUserQuery(query, user); - generateMutedNoteQuery(query, user); - generateBlockedUserQuery(query, user); - } + //#region Construct query + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('note.visibility = \'public\'') + .andWhere('note.channelId IS NULL') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); - } - //#endregion + this.queryService.generateRepliesQuery(query, me); + if (me) { + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateMutedNoteQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + } - const timeline = await query.take(ps.limit).getMany(); + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + //#endregion - process.nextTick(() => { - if (user) { - activeUsersChart.read(user); - } - }); + const timeline = await query.take(ps.limit).getMany(); - return await Notes.packMany(timeline, user); -}); + process.nextTick(() => { + if (me) { + this.activeUsersChart.read(me); + } + }); + + return await this.noteEntityService.packMany(timeline, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 2dc98c4c9f..c6458223eb 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -1,16 +1,13 @@ import { Brackets } from 'typeorm'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Followings, Notes } from '@/models/index.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { MetaService } from '@/core/MetaService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateRepliesQuery } from '../../common/generate-replies-query.js'; -import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js'; -import { generateChannelQuery } from '../../common/generate-channel-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; export const meta = { tags: ['notes'], @@ -57,83 +54,99 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const m = await fetchMeta(); - if (m.disableLocalTimeline && (!user.isAdmin && !user.isModerator)) { - throw new ApiError(meta.errors.stlDisabled); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private metaService: MetaService, + private activeUsersChart: ActiveUsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + const m = await this.metaService.fetch(); + if (m.disableLocalTimeline && (!me.isAdmin && !me.isModerator)) { + throw new ApiError(meta.errors.stlDisabled); + } + + //#region Construct query + const followingQuery = this.followingsRepository.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); + + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere(new Brackets(qb => { + qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: me.id }) + .orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)'); + })) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') + .setParameters(followingQuery.getParameters()); + + this.queryService.generateChannelQuery(query, me); + this.queryService.generateRepliesQuery(query, me); + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateMutedNoteQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + + if (ps.includeMyRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.userId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.includeRenotedMyNotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.includeLocalRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserHost IS NOT NULL'); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + //#endregion + + const timeline = await query.take(ps.limit).getMany(); + + process.nextTick(() => { + this.activeUsersChart.read(me); + }); + + return await this.noteEntityService.packMany(timeline, me); + }); } - - //#region Construct query - const followingQuery = Followings.createQueryBuilder('following') - .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: user.id }); - - const query = makePaginationQuery(Notes.createQueryBuilder('note'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere(new Brackets(qb => { - qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id }) - .orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)'); - })) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') - .setParameters(followingQuery.getParameters()); - - generateChannelQuery(query, user); - generateRepliesQuery(query, user); - generateVisibilityQuery(query, user); - generateMutedUserQuery(query, user); - generateMutedNoteQuery(query, user); - generateBlockedUserQuery(query, user); - - if (ps.includeMyRenotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.userId != :meId', { meId: user.id }); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } - - if (ps.includeRenotedMyNotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.renoteUserId != :meId', { meId: user.id }); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } - - if (ps.includeLocalRenotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.renoteUserHost IS NOT NULL'); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } - - if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); - } - //#endregion - - const timeline = await query.take(ps.limit).getMany(); - - process.nextTick(() => { - activeUsersChart.read(user); - }); - - return await Notes.packMany(timeline, user); -}); +} diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index aac2a3749c..7b8859639d 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -1,16 +1,14 @@ import { Brackets } from 'typeorm'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Notes, Users } from '@/models/index.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateRepliesQuery } from '../../common/generate-replies-query.js'; -import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js'; -import { generateChannelQuery } from '../../common/generate-channel-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; export const meta = { tags: ['notes'], @@ -56,64 +54,77 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const m = await fetchMeta(); - if (m.disableLocalTimeline) { - if (user == null || (!user.isAdmin && !user.isModerator)) { - throw new ApiError(meta.errors.ltlDisabled); - } - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - //#region Construct query - const query = makePaginationQuery(Notes.createQueryBuilder('note'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)') - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private metaService: MetaService, + private activeUsersChart: ActiveUsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + const m = await this.metaService.fetch(); + if (m.disableLocalTimeline) { + if (me == null || (!me.isAdmin && !me.isModerator)) { + throw new ApiError(meta.errors.ltlDisabled); + } + } - generateChannelQuery(query, user); - generateRepliesQuery(query, user); - generateVisibilityQuery(query, user); - if (user) generateMutedUserQuery(query, user); - if (user) generateMutedNoteQuery(query, user); - if (user) generateBlockedUserQuery(query, user); + //#region Construct query + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); - } + this.queryService.generateChannelQuery(query, me); + this.queryService.generateRepliesQuery(query, me); + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateMutedNoteQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); - if (ps.fileType != null) { - query.andWhere('note.fileIds != \'{}\''); - query.andWhere(new Brackets(qb => { - for (const type of ps.fileType!) { - const i = ps.fileType!.indexOf(type); - qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } - })); - if (ps.excludeNsfw) { - query.andWhere('note.cw IS NULL'); - query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)'); - } - } - //#endregion + if (ps.fileType != null) { + query.andWhere('note.fileIds != \'{}\''); + query.andWhere(new Brackets(qb => { + for (const type of ps.fileType!) { + const i = ps.fileType!.indexOf(type); + qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); + } + })); - const timeline = await query.take(ps.limit).getMany(); + if (ps.excludeNsfw) { + query.andWhere('note.cw IS NULL'); + query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)'); + } + } + //#endregion + + const timeline = await query.take(ps.limit).getMany(); - process.nextTick(() => { - if (user) { - activeUsersChart.read(user); - } - }); + process.nextTick(() => { + if (me) { + this.activeUsersChart.read(me); + } + }); - return await Notes.packMany(timeline, user); -}); + return await this.noteEntityService.packMany(timeline, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index 9b41544523..9b2dabc88b 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -1,12 +1,12 @@ import { Brackets } from 'typeorm'; -import read from '@/services/note/read.js'; -import { Notes, Followings } from '@/models/index.js'; -import define from '../../define.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; -import { generateMutedNoteThreadQuery } from '../../common/generate-muted-note-thread-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { NoteReadService } from '@/core/NoteReadService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -37,45 +37,60 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const followingQuery = Followings.createQueryBuilder('following') - .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: user.id }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere(new Brackets(qb => { qb - .where(`'{"${user.id}"}' <@ note.mentions`) - .orWhere(`'{"${user.id}"}' <@ note.visibleUserIds`); - })) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - generateVisibilityQuery(query, user); - generateMutedUserQuery(query, user); - generateMutedNoteThreadQuery(query, user); - generateBlockedUserQuery(query, user); + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private noteReadService: NoteReadService, + ) { + super(meta, paramDef, async (ps, me) => { + const followingQuery = this.followingsRepository.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); - if (ps.visibility) { - query.andWhere('note.visibility = :visibility', { visibility: ps.visibility }); - } + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere(new Brackets(qb => { qb + .where(`'{"${me.id}"}' <@ note.mentions`) + .orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`); + })) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - if (ps.following) { - query.andWhere(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id }); - query.setParameters(followingQuery.getParameters()); - } + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateMutedNoteThreadQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + + if (ps.visibility) { + query.andWhere('note.visibility = :visibility', { visibility: ps.visibility }); + } - const mentions = await query.take(ps.limit).getMany(); + if (ps.following) { + query.andWhere(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: me.id }); + query.setParameters(followingQuery.getParameters()); + } - read(user.id, mentions); + const mentions = await query.take(ps.limit).getMany(); - return await Notes.packMany(mentions, user); -}); + this.noteReadService.read(me.id, mentions); + + return await this.noteEntityService.packMany(mentions, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index 5a04d68f3e..11bfdbba0f 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -1,6 +1,9 @@ import { Brackets, In } from 'typeorm'; -import { Polls, Mutings, Notes, PollVotes } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository, MutingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -28,56 +31,75 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = Polls.createQueryBuilder('poll') - .where('poll.userHost IS NULL') - .andWhere('poll.userId != :meId', { meId: user.id }) - .andWhere('poll.noteVisibility = \'public\'') - .andWhere(new Brackets(qb => { qb - .where('poll.expiresAt IS NULL') - .orWhere('poll.expiresAt > :now', { now: new Date() }); - })); - - //#region exclude arleady voted polls - const votedQuery = PollVotes.createQueryBuilder('vote') - .select('vote.noteId') - .where('vote.userId = :meId', { meId: user.id }); - - query - .andWhere(`poll.noteId NOT IN (${ votedQuery.getQuery() })`); - - query.setParameters(votedQuery.getParameters()); - //#endregion - - //#region mute - const mutingQuery = Mutings.createQueryBuilder('muting') - .select('muting.muteeId') - .where('muting.muterId = :muterId', { muterId: user.id }); - - query - .andWhere(`poll.userId NOT IN (${ mutingQuery.getQuery() })`); - - query.setParameters(mutingQuery.getParameters()); - //#endregion - - const polls = await query - .orderBy('poll.noteId', 'DESC') - .take(ps.limit) - .skip(ps.offset) - .getMany(); - - if (polls.length === 0) return []; - - const notes = await Notes.find({ - where: { - id: In(polls.map(poll => poll.noteId)), - }, - order: { - createdAt: 'DESC', - }, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + @Inject(DI.pollVotesRepository) + private pollVotesRepository: PollVotesRepository, + + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, + + private noteEntityService: NoteEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.pollsRepository.createQueryBuilder('poll') + .where('poll.userHost IS NULL') + .andWhere('poll.userId != :meId', { meId: me.id }) + .andWhere('poll.noteVisibility = \'public\'') + .andWhere(new Brackets(qb => { qb + .where('poll.expiresAt IS NULL') + .orWhere('poll.expiresAt > :now', { now: new Date() }); + })); + + //#region exclude arleady voted polls + const votedQuery = this.pollVotesRepository.createQueryBuilder('vote') + .select('vote.noteId') + .where('vote.userId = :meId', { meId: me.id }); + + query + .andWhere(`poll.noteId NOT IN (${ votedQuery.getQuery() })`); + + query.setParameters(votedQuery.getParameters()); + //#endregion + + //#region mute + const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') + .select('muting.muteeId') + .where('muting.muterId = :muterId', { muterId: me.id }); + + query + .andWhere(`poll.userId NOT IN (${ mutingQuery.getQuery() })`); + + query.setParameters(mutingQuery.getParameters()); + //#endregion + + const polls = await query + .orderBy('poll.noteId', 'DESC') + .take(ps.limit) + .skip(ps.offset) + .getMany(); + + if (polls.length === 0) return []; + + const notes = await this.notesRepository.find({ + where: { + id: In(polls.map(poll => poll.noteId)), + }, + order: { + createdAt: 'DESC', + }, + }); - return await Notes.packMany(notes, user, { - detail: true, - }); -}); + return await this.noteEntityService.packMany(notes, me, { + detail: true, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 45a832cbd2..76f07528d7 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -1,16 +1,17 @@ import { Not } from 'typeorm'; -import { publishNoteStream } from '@/services/stream.js'; -import { createNotification } from '@/services/create-notification.js'; -import { deliver } from '@/queue/index.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderVote from '@/remote/activitypub/renderer/vote.js'; -import { deliverQuestionUpdate } from '@/services/note/polls/update.js'; -import { PollVotes, NoteWatchings, Users, Polls, Blockings } from '@/models/index.js'; -import { IRemoteUser } from '@/models/entities/user.js'; -import { genId } from '@/misc/gen-id.js'; -import { getNote } from '../../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, BlockingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; +import type { IRemoteUser } from '@/models/entities/User.js'; +import { IdService } from '@/core/IdService.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { QueueService } from '@/core/QueueService.js'; +import { PollService } from '@/core/PollService.js'; +import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { CreateNotificationService } from '@/core/CreateNotificationService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import define from '../../../define.js'; export const meta = { tags: ['notes'], @@ -67,103 +68,116 @@ export const paramDef = { required: ['noteId', 'choice'], } as const; -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const createdAt = new Date(); +// TODO: ロジックをサービスに切り出す - // Get votee - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + @Inject(DI.pollVotesRepository) + private pollVotesRepository: PollVotesRepository, + + private idService: IdService, + private getterService: GetterService, + private queueService: QueueService, + private pollService: PollService, + private apRendererService: ApRendererService, + private globalEventService: GlobalEventService, + private createNotificationService: CreateNotificationService, + ) { + super(meta, paramDef, async (ps, me) => { + const createdAt = new Date(); + + // Get votee + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); - if (!note.hasPoll) { - throw new ApiError(meta.errors.noPoll); - } + if (!note.hasPoll) { + throw new ApiError(meta.errors.noPoll); + } - // Check blocking - if (note.userId !== user.id) { - const block = await Blockings.findOneBy({ - blockerId: note.userId, - blockeeId: user.id, - }); - if (block) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } + // Check blocking + if (note.userId !== me.id) { + const block = await this.blockingsRepository.findOneBy({ + blockerId: note.userId, + blockeeId: me.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } - const poll = await Polls.findOneByOrFail({ noteId: note.id }); + const poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id }); - if (poll.expiresAt && poll.expiresAt < createdAt) { - throw new ApiError(meta.errors.alreadyExpired); - } + if (poll.expiresAt && poll.expiresAt < createdAt) { + throw new ApiError(meta.errors.alreadyExpired); + } - if (poll.choices[ps.choice] == null) { - throw new ApiError(meta.errors.invalidChoice); - } + if (poll.choices[ps.choice] == null) { + throw new ApiError(meta.errors.invalidChoice); + } - // if already voted - const exist = await PollVotes.findBy({ - noteId: note.id, - userId: user.id, - }); + // if already voted + const exist = await this.pollVotesRepository.findBy({ + noteId: note.id, + userId: me.id, + }); - if (exist.length) { - if (poll.multiple) { - if (exist.some(x => x.choice === ps.choice)) { - throw new ApiError(meta.errors.alreadyVoted); + if (exist.length) { + if (poll.multiple) { + if (exist.some(x => x.choice === ps.choice)) { + throw new ApiError(meta.errors.alreadyVoted); + } + } else { + throw new ApiError(meta.errors.alreadyVoted); + } } - } else { - throw new ApiError(meta.errors.alreadyVoted); - } - } - // Create vote - const vote = await PollVotes.insert({ - id: genId(), - createdAt, - noteId: note.id, - userId: user.id, - choice: ps.choice, - }).then(x => PollVotes.findOneByOrFail(x.identifiers[0])); - - // Increment votes count - const index = ps.choice + 1; // In SQL, array index is 1 based - await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`); - - publishNoteStream(note.id, 'pollVoted', { - choice: ps.choice, - userId: user.id, - }); - - // Notify - createNotification(note.userId, 'pollVote', { - notifierId: user.id, - noteId: note.id, - choice: ps.choice, - }); - - // Fetch watchers - NoteWatchings.findBy({ - noteId: note.id, - userId: Not(user.id), - }).then(watchers => { - for (const watcher of watchers) { - createNotification(watcher.userId, 'pollVote', { - notifierId: user.id, + // Create vote + const vote = await this.pollVotesRepository.insert({ + id: this.idService.genId(), + createdAt, noteId: note.id, + userId: me.id, choice: ps.choice, + }).then(x => this.pollVotesRepository.findOneByOrFail(x.identifiers[0])); + + // Increment votes count + const index = ps.choice + 1; // In SQL, array index is 1 based + await this.pollsRepository.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`); + + this.globalEventService.publishNoteStream(note.id, 'pollVoted', { + choice: ps.choice, + userId: me.id, }); - } - }); - // リモート投票の場合リプライ送信 - if (note.userHost != null) { - const pollOwner = await Users.findOneByOrFail({ id: note.userId }) as IRemoteUser; + // Notify + this.createNotificationService.createNotification(note.userId, 'pollVote', { + notifierId: me.id, + noteId: note.id, + choice: ps.choice, + }); - deliver(user, renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox); - } + // リモート投票の場合リプライ送信 + if (note.userHost != null) { + const pollOwner = await this.usersRepository.findOneByOrFail({ id: note.userId }) as IRemoteUser; + + this.queueService.deliver(me, this.apRendererService.renderActivity(await this.apRendererService.renderVote(me, vote, note, poll, pollOwner)), pollOwner.inbox); + } - // リモートフォロワーにUpdate配信 - deliverQuestionUpdate(note.id); -}); + // リモートフォロワーにUpdate配信 + this.pollService.deliverQuestionUpdate(note.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index 15a62d394d..d57950f012 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -1,8 +1,12 @@ -import { DeepPartial, FindOptionsWhere } from 'typeorm'; -import { NoteReactions } from '@/models/index.js'; -import { NoteReaction } from '@/models/entities/note-reaction.js'; -import define from '../../define.js'; +import { DeepPartial } from 'typeorm'; +import { Inject, Injectable } from '@nestjs/common'; +import { NoteReactionsRepository } from '@/models/index.js'; +import type { NoteReaction } from '@/models/entities/NoteReaction.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import type { FindOptionsWhere } from 'typeorm'; export const meta = { tags: ['notes', 'reactions'], @@ -45,28 +49,38 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = { - noteId: ps.noteId, - } as FindOptionsWhere; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.noteReactionsRepository) + private noteReactionsRepository: NoteReactionsRepository, - if (ps.type) { - // ローカルリアクションはホスト名が . とされているが - // DB 上ではそうではないので、必要に応じて変換 - const suffix = '@.:'; - const type = ps.type.endsWith(suffix) ? ps.type.slice(0, ps.type.length - suffix.length) + ':' : ps.type; - query.reaction = type; - } + private noteReactionEntityService: NoteReactionEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = { + noteId: ps.noteId, + } as FindOptionsWhere; - const reactions = await NoteReactions.find({ - where: query, - take: ps.limit, - skip: ps.offset, - order: { - id: -1, - }, - relations: ['user', 'user.avatar', 'user.banner', 'note'], - }); + if (ps.type) { + // ローカルリアクションはホスト名が . とされているが + // DB 上ではそうではないので、必要に応じて変換 + const suffix = '@.:'; + const type = ps.type.endsWith(suffix) ? ps.type.slice(0, ps.type.length - suffix.length) + ':' : ps.type; + query.reaction = type; + } - return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, user))); -}); + const reactions = await this.noteReactionsRepository.find({ + where: query, + take: ps.limit, + skip: ps.offset, + order: { + id: -1, + }, + relations: ['user', 'user.avatar', 'user.banner', 'note'], + }); + + return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts index 07e52a9266..2af734307d 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts @@ -1,6 +1,7 @@ -import createReaction from '@/services/note/reaction/create.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { ReactionService } from '@/core/ReactionService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -41,15 +42,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - await createReaction(user, note, ps.reaction).catch(e => { - if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted); - if (e.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError(meta.errors.youHaveBeenBlocked); - throw e; - }); - return; -}); +@Injectable() +export default class extends Endpoint { + constructor( + private getterService: GetterService, + private reactionService: ReactionService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + await this.reactionService.create(me, note, ps.reaction).catch(err => { + if (err.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted); + if (err.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError(meta.errors.youHaveBeenBlocked); + throw err; + }); + return; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts index c13cafa21d..31ed962922 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts @@ -1,7 +1,8 @@ import ms from 'ms'; -import deleteReaction from '@/services/note/reaction/delete.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { ReactionService } from '@/core/ReactionService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -41,13 +42,21 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - await deleteReaction(user, note).catch(e => { - if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') throw new ApiError(meta.errors.notReacted); - throw e; - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private getterService: GetterService, + private reactionService: ReactionService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + await this.reactionService.delete(me, note).catch(err => { + if (err.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') throw new ApiError(meta.errors.notReacted); + throw err; + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 28be360763..57b7aeae0d 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -1,11 +1,11 @@ -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['notes'], @@ -43,31 +43,43 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere('note.renoteId = :renoteId', { renoteId: note.id }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); - generateVisibilityQuery(query, user); - if (user) generateMutedUserQuery(query, user); - if (user) generateBlockedUserQuery(query, user); + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere('note.renoteId = :renoteId', { renoteId: note.id }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - const renotes = await query.take(ps.limit).getMany(); + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); - return await Notes.packMany(renotes, user); -}); + const renotes = await query.take(ps.limit).getMany(); + + return await this.noteEntityService.packMany(renotes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index ab0018f58e..7020d0c681 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -1,9 +1,9 @@ -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -33,26 +33,37 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere('note.replyId = :replyId', { replyId: ps.noteId }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - - generateVisibilityQuery(query, user); - if (user) generateMutedUserQuery(query, user); - if (user) generateBlockedUserQuery(query, user); - - const timeline = await query.take(ps.limit).getMany(); - - return await Notes.packMany(timeline, user); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere('note.replyId = :replyId', { replyId: ps.noteId }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); + + const timeline = await query.take(ps.limit).getMany(); + + return await this.noteEntityService.packMany(timeline, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index 777de7221c..0727c9af6c 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -1,12 +1,12 @@ import { Brackets } from 'typeorm'; -import { Notes } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; import { safeForSql } from '@/misc/safe-for-sql.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes', 'hashtags'], @@ -66,75 +66,86 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - generateVisibilityQuery(query, me); - if (me) generateMutedUserQuery(query, me); - if (me) generateBlockedUserQuery(query, me); + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - try { - if (ps.tag) { - if (!safeForSql(ps.tag)) throw 'Injection'; - query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`); - } else { - query.andWhere(new Brackets(qb => { - for (const tags of ps.query!) { - qb.orWhere(new Brackets(qb => { - for (const tag of tags) { - if (!safeForSql(tag)) throw 'Injection'; - qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`); + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); + + try { + if (ps.tag) { + if (!safeForSql(ps.tag)) throw 'Injection'; + query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`); + } else { + query.andWhere(new Brackets(qb => { + for (const tags of ps.query!) { + qb.orWhere(new Brackets(qb => { + for (const tag of tags) { + if (!safeForSql(tag)) throw 'Injection'; + qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`); + } + })); } })); } - })); - } - } catch (e) { - if (e === 'Injection') return []; - throw e; - } + } catch (e) { + if (e === 'Injection') return []; + throw e; + } - if (ps.reply != null) { - if (ps.reply) { - query.andWhere('note.replyId IS NOT NULL'); - } else { - query.andWhere('note.replyId IS NULL'); - } - } + if (ps.reply != null) { + if (ps.reply) { + query.andWhere('note.replyId IS NOT NULL'); + } else { + query.andWhere('note.replyId IS NULL'); + } + } - if (ps.renote != null) { - if (ps.renote) { - query.andWhere('note.renoteId IS NOT NULL'); - } else { - query.andWhere('note.renoteId IS NULL'); - } - } + if (ps.renote != null) { + if (ps.renote) { + query.andWhere('note.renoteId IS NOT NULL'); + } else { + query.andWhere('note.renoteId IS NULL'); + } + } - if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); - } + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } - if (ps.poll != null) { - if (ps.poll) { - query.andWhere('note.hasPoll = TRUE'); - } else { - query.andWhere('note.hasPoll = FALSE'); - } - } + if (ps.poll != null) { + if (ps.poll) { + query.andWhere('note.hasPoll = TRUE'); + } else { + query.andWhere('note.hasPoll = FALSE'); + } + } - // Search notes - const notes = await query.take(ps.limit).getMany(); + // Search notes + const notes = await query.take(ps.limit).getMany(); - return await Notes.packMany(notes, me); -}); + return await this.noteEntityService.packMany(notes, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index 4e2cdae801..484cfc1128 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -1,12 +1,11 @@ import { In } from 'typeorm'; -import { Notes } from '@/models/index.js'; -import config from '@/config/index.js'; -import es from '../../../../db/elasticsearch.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -46,97 +45,51 @@ export const paramDef = { required: ['query'], } as const; -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - if (es == null) { - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId); - - if (ps.userId) { - query.andWhere('note.userId = :userId', { userId: ps.userId }); - } else if (ps.channelId) { - query.andWhere('note.channelId = :channelId', { channelId: ps.channelId }); - } - - query - .andWhere('note.text ILIKE :q', { q: `%${ps.query}%` }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - - generateVisibilityQuery(query, me); - if (me) generateMutedUserQuery(query, me); - if (me) generateBlockedUserQuery(query, me); - - const notes = await query.take(ps.limit).getMany(); - - return await Notes.packMany(notes, me); - } else { - const userQuery = ps.userId != null ? [{ - term: { - userId: ps.userId, - }, - }] : []; - - const hostQuery = ps.userId == null ? - ps.host === null ? [{ - bool: { - must_not: { - exists: { - field: 'userHost', - }, - }, - }, - }] : ps.host !== undefined ? [{ - term: { - userHost: ps.host, - }, - }] : [] - : []; +// TODO: ロジックをサービスに切り出す - const result = await es.search({ - index: config.elasticsearch.index || 'misskey_note', - body: { - size: ps.limit, - from: ps.offset, - query: { - bool: { - must: [{ - simple_query_string: { - fields: ['text'], - query: ps.query.toLowerCase(), - default_operator: 'and', - }, - }, ...hostQuery, ...userQuery], - }, - }, - sort: [{ - _doc: 'desc', - }], - }, - }); - - const hits = result.body.hits.hits.map((hit: any) => hit._id); - - if (hits.length === 0) return []; - - // Fetch found notes - const notes = await Notes.find({ - where: { - id: In(hits), - }, - order: { - id: -1, - }, +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId); + + if (ps.userId) { + query.andWhere('note.userId = :userId', { userId: ps.userId }); + } else if (ps.channelId) { + query.andWhere('note.channelId = :channelId', { channelId: ps.channelId }); + } + + query + .andWhere('note.text ILIKE :q', { q: `%${ps.query}%` }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); + + const notes = await query.take(ps.limit).getMany(); + + return await this.noteEntityService.packMany(notes, me); }); - - return await Notes.packMany(notes, me); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index 5cd74bd2ca..c3f5b9dfb0 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -1,7 +1,10 @@ -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['notes'], @@ -32,13 +35,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - return await Notes.pack(note, user, { - detail: true, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + return await this.noteEntityService.pack(note, me, { + detail: true, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index 01afa5add2..7756d39f7c 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -1,5 +1,7 @@ -import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository, NoteThreadMutingsRepository, NoteFavoritesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -35,36 +37,42 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await Notes.findOneByOrFail({ id: ps.noteId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - const [favorite, watching, threadMuting] = await Promise.all([ - NoteFavorites.count({ - where: { - userId: user.id, - noteId: note.id, - }, - take: 1, - }), - NoteWatchings.count({ - where: { - userId: user.id, - noteId: note.id, - }, - take: 1, - }), - NoteThreadMutings.count({ - where: { - userId: user.id, - threadId: note.threadId || note.id, - }, - take: 1, - }), - ]); + @Inject(DI.noteThreadMutingsRepository) + private noteThreadMutingsRepository: NoteThreadMutingsRepository, + + @Inject(DI.noteFavoritesRepository) + private noteFavoritesRepository: NoteFavoritesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.notesRepository.findOneByOrFail({ id: ps.noteId }); + + const [favorite, threadMuting] = await Promise.all([ + this.noteFavoritesRepository.count({ + where: { + userId: me.id, + noteId: note.id, + }, + take: 1, + }), + this.noteThreadMutingsRepository.count({ + where: { + userId: me.id, + threadId: note.threadId || note.id, + }, + take: 1, + }), + ]); - return { - isFavorited: favorite !== 0, - isWatching: watching !== 0, - isMutedThread: threadMuting !== 0, - }; -}); + return { + isFavorited: favorite !== 0, + isMutedThread: threadMuting !== 0, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index cf360526d3..1c83adddff 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -1,8 +1,10 @@ -import { Notes, NoteThreadMutings } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import readNote from '@/services/note/read.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository, NoteThreadMutingsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { NoteReadService } from '@/core/NoteReadService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -30,26 +32,41 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - const mutedNotes = await Notes.find({ - where: [{ - id: note.threadId || note.id, - }, { - threadId: note.threadId || note.id, - }], - }); - - await readNote(user.id, mutedNotes); - - await NoteThreadMutings.insert({ - id: genId(), - createdAt: new Date(), - threadId: note.threadId || note.id, - userId: user.id, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.noteThreadMutingsRepository) + private noteThreadMutingsRepository: NoteThreadMutingsRepository, + + private getterService: GetterService, + private noteReadService: NoteReadService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + const mutedNotes = await this.notesRepository.find({ + where: [{ + id: note.threadId || note.id, + }, { + threadId: note.threadId || note.id, + }], + }); + + await this.noteReadService.read(me.id, mutedNotes); + + await this.noteThreadMutingsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + threadId: note.threadId || note.id, + userId: me.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts index ac310d0fe6..1f896734d1 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -1,6 +1,8 @@ -import { NoteThreadMutings } from '@/models/index.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NoteThreadMutingsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -28,14 +30,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - await NoteThreadMutings.delete({ - threadId: note.threadId || note.id, - userId: user.id, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.noteThreadMutingsRepository) + private noteThreadMutingsRepository: NoteThreadMutingsRepository, + + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + await this.noteThreadMutingsRepository.delete({ + threadId: note.threadId || note.id, + userId: me.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 22f4925175..53a1ae1348 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -1,14 +1,12 @@ import { Brackets } from 'typeorm'; -import { Notes, Followings } from '@/models/index.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateRepliesQuery } from '../../common/generate-replies-query.js'; -import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js'; -import { generateChannelQuery } from '../../common/generate-channel-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -47,85 +45,100 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const hasFollowing = (await Followings.count({ - where: { - followerId: user.id, - }, - take: 1, - })) !== 0; - - //#region Construct query - const followingQuery = Followings.createQueryBuilder('following') - .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: user.id }); - - const query = makePaginationQuery(Notes.createQueryBuilder('note'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere(new Brackets(qb => { qb - .where('note.userId = :meId', { meId: user.id }); - if (hasFollowing) qb.orWhere(`note.userId IN (${ followingQuery.getQuery() })`); - })) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') - .setParameters(followingQuery.getParameters()); - - generateChannelQuery(query, user); - generateRepliesQuery(query, user); - generateVisibilityQuery(query, user); - generateMutedUserQuery(query, user); - generateMutedNoteQuery(query, user); - generateBlockedUserQuery(query, user); - - if (ps.includeMyRenotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.userId != :meId', { meId: user.id }); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, - if (ps.includeRenotedMyNotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.renoteUserId != :meId', { meId: user.id }); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - if (ps.includeLocalRenotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.renoteUserHost IS NOT NULL'); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private activeUsersChart: ActiveUsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + const hasFollowing = (await this.followingsRepository.count({ + where: { + followerId: me.id, + }, + take: 1, + })) !== 0; - if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); - } - //#endregion + //#region Construct query + const followingQuery = this.followingsRepository.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); + + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere(new Brackets(qb => { qb + .where('note.userId = :meId', { meId: me.id }); + if (hasFollowing) qb.orWhere(`note.userId IN (${ followingQuery.getQuery() })`); + })) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') + .setParameters(followingQuery.getParameters()); - const timeline = await query.take(ps.limit).getMany(); + this.queryService.generateChannelQuery(query, me); + this.queryService.generateRepliesQuery(query, me); + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateMutedNoteQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); - process.nextTick(() => { - activeUsersChart.read(user); - }); + if (ps.includeMyRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.userId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } - return await Notes.packMany(timeline, user); -}); + if (ps.includeRenotedMyNotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.includeLocalRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserHost IS NOT NULL'); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + //#endregion + + const timeline = await query.take(ps.limit).getMany(); + + process.nextTick(() => { + this.activeUsersChart.read(me); + }); + + return await this.noteEntityService.packMany(timeline, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index 5e40e7106f..c24f1e401e 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -1,12 +1,15 @@ import { URLSearchParams } from 'node:url'; import fetch from 'node-fetch'; -import config from '@/config/index.js'; -import { getAgentByUrl } from '@/misc/fetch.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Notes } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; import { ApiError } from '../../error.js'; -import { getNote } from '../../common/getters.js'; -import define from '../../define.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['notes'], @@ -37,58 +40,74 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - if (!(await Notes.isVisibleForMe(note, user ? user.id : null))) { - return 204; // TODO: 良い感じのエラー返す - } - - if (note.text == null) { - return 204; - } - - const instance = await fetchMeta(); - - if (instance.deeplAuthKey == null) { - return 204; // TODO: 良い感じのエラー返す - } - - let targetLang = ps.targetLang; - if (targetLang.includes('-')) targetLang = targetLang.split('-')[0]; - - const params = new URLSearchParams(); - params.append('auth_key', instance.deeplAuthKey); - params.append('text', note.text); - params.append('target_lang', targetLang); - - const endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate'; - - const res = await fetch(endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': config.userAgent, - Accept: 'application/json, */*', - }, - body: params, - // TODO - //timeout: 10000, - agent: getAgentByUrl, - }); - - const json = (await res.json()) as { +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private getterService: GetterService, + private metaService: MetaService, + private httpRequestService: HttpRequestService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + if (!(await this.noteEntityService.isVisibleForMe(note, me ? me.id : null))) { + return 204; // TODO: 良い感じのエラー返す + } + + if (note.text == null) { + return 204; + } + + const instance = await this.metaService.fetch(); + + if (instance.deeplAuthKey == null) { + return 204; // TODO: 良い感じのエラー返す + } + + let targetLang = ps.targetLang; + if (targetLang.includes('-')) targetLang = targetLang.split('-')[0]; + + const params = new URLSearchParams(); + params.append('auth_key', instance.deeplAuthKey); + params.append('text', note.text); + params.append('target_lang', targetLang); + + const endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate'; + + const res = await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': config.userAgent, + Accept: 'application/json, */*', + }, + body: params, + // TODO + //timeout: 10000, + agent: (url) => this.httpRequestService.getAgentByUrl(url), + }); + + const json = (await res.json()) as { translations: { detected_source_language: string; text: string; }[]; }; - return { - sourceLang: json.translations[0].detected_source_language, - text: json.translations[0].text, - }; -}); + return { + sourceLang: json.translations[0].detected_source_language, + text: json.translations[0].text, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index 3fba0efe0c..c0048888b4 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -1,9 +1,11 @@ import ms from 'ms'; -import deleteNote from '@/services/note/delete.js'; -import { Notes, Users } from '@/models/index.js'; -import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteDeleteService } from '@/core/NoteDeleteService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['notes'], @@ -36,18 +38,32 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - const renotes = await Notes.findBy({ - userId: user.id, - renoteId: note.id, - }); - - for (const note of renotes) { - deleteNote(await Users.findOneByOrFail({ id: user.id }), note); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private getterService: GetterService, + private noteDeleteService: NoteDeleteService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + const renotes = await this.notesRepository.findBy({ + userId: me.id, + renoteId: note.id, + }); + + for (const note of renotes) { + this.noteDeleteService.delete(await this.usersRepository.findOneByOrFail({ id: me.id }), note); + } + }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index e603a8f625..87a464578c 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -1,10 +1,12 @@ import { Brackets } from 'typeorm'; -import { UserLists, UserListJoinings, Notes } from '@/models/index.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import ActiveUsersChart from '@/core/chart/charts/active-users.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; export const meta = { tags: ['notes', 'lists'], @@ -52,72 +54,90 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const list = await UserLists.findOneBy({ - id: ps.listId, - userId: user.id, - }); - - if (list == null) { - throw new ApiError(meta.errors.noSuchList); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private activeUsersChart: ActiveUsersChart, + ) { + super(meta, paramDef, async (ps, me) => { + const list = await this.userListsRepository.findOneBy({ + id: ps.listId, + userId: me.id, + }); + + if (list == null) { + throw new ApiError(meta.errors.noSuchList); + } + + //#region Construct query + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .innerJoin(this.userListJoiningsRepository.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') + .andWhere('userListJoining.userListId = :userListId', { userListId: list.id }); + + this.queryService.generateVisibilityQuery(query, me); + + if (ps.includeMyRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.userId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.includeRenotedMyNotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.includeLocalRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserHost IS NOT NULL'); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + //#endregion + + const timeline = await query.take(ps.limit).getMany(); + + this.activeUsersChart.read(me); + + return await this.noteEntityService.packMany(timeline, me); + }); } - - //#region Construct query - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .innerJoin(UserListJoinings.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId') - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') - .andWhere('userListJoining.userListId = :userListId', { userListId: list.id }); - - generateVisibilityQuery(query, user); - - if (ps.includeMyRenotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.userId != :meId', { meId: user.id }); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } - - if (ps.includeRenotedMyNotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.renoteUserId != :meId', { meId: user.id }); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } - - if (ps.includeLocalRenotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.renoteUserHost IS NOT NULL'); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } - - if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); - } - //#endregion - - const timeline = await query.take(ps.limit).getMany(); - - activeUsersChart.read(user); - - return await Notes.packMany(timeline, user); -}); +} diff --git a/packages/backend/src/server/api/endpoints/notes/watching/create.ts b/packages/backend/src/server/api/endpoints/notes/watching/create.ts deleted file mode 100644 index 7d482b0732..0000000000 --- a/packages/backend/src/server/api/endpoints/notes/watching/create.ts +++ /dev/null @@ -1,38 +0,0 @@ -import watch from '@/services/note/watch.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; -import { ApiError } from '../../../error.js'; - -export const meta = { - tags: ['notes'], - - requireCredential: true, - - kind: 'write:account', - - errors: { - noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: 'ea0e37a6-90a3-4f58-ba6b-c328ca206fc7', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - noteId: { type: 'string', format: 'misskey:id' }, - }, - required: ['noteId'], -} as const; - -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - await watch(user.id, note); -}); diff --git a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts deleted file mode 100644 index 2c1a2e5fbd..0000000000 --- a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts +++ /dev/null @@ -1,38 +0,0 @@ -import unwatch from '@/services/note/unwatch.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; -import { ApiError } from '../../../error.js'; - -export const meta = { - tags: ['notes'], - - requireCredential: true, - - kind: 'write:account', - - errors: { - noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '09b3695c-f72c-4731-a428-7cff825fc82e', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - noteId: { type: 'string', format: 'misskey:id' }, - }, - required: ['noteId'], -} as const; - -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - await unwatch(user.id, note); -}); diff --git a/packages/backend/src/server/api/endpoints/notifications/create.ts b/packages/backend/src/server/api/endpoints/notifications/create.ts index 80d513d8da..3427a3eb5c 100644 --- a/packages/backend/src/server/api/endpoints/notifications/create.ts +++ b/packages/backend/src/server/api/endpoints/notifications/create.ts @@ -1,5 +1,6 @@ -import { createNotification } from '@/services/create-notification.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { CreateNotificationService } from '@/core/CreateNotificationService.js'; export const meta = { tags: ['notifications'], @@ -23,11 +24,18 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user, token) => { - createNotification(user.id, 'app', { - appAccessTokenId: token ? token.id : null, - customBody: ps.body, - customHeader: ps.header, - customIcon: ps.icon, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private createNotificationService: CreateNotificationService, + ) { + super(meta, paramDef, async (ps, user, token) => { + this.createNotificationService.createNotification(user.id, 'app', { + appAccessTokenId: token ? token.id : null, + customBody: ps.body, + customHeader: ps.header, + customIcon: ps.icon, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts index d169afbb35..3d1eb2b39c 100644 --- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts @@ -1,7 +1,9 @@ -import { publishMainStream } from '@/services/stream.js'; -import { pushNotification } from '@/services/push-notification.js'; -import { Notifications } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotificationsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { PushNotificationService } from '@/core/PushNotificationService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notifications', 'account'], @@ -18,16 +20,27 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Update documents - await Notifications.update({ - notifieeId: user.id, - isRead: false, - }, { - isRead: true, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notificationsRepository) + private notificationsRepository: NotificationsRepository, - // 全ての通知を読みましたよというイベントを発行 - publishMainStream(user.id, 'readAllNotifications'); - pushNotification(user.id, 'readAllNotifications', undefined); -}); + private globalEventService: GlobalEventService, + private pushNotificationService: PushNotificationService, + ) { + super(meta, paramDef, async (ps, me) => { + // Update documents + await this.notificationsRepository.update({ + notifieeId: me.id, + isRead: false, + }, { + isRead: true, + }); + + // 全ての通知を読みましたよというイベントを発行 + this.globalEventService.publishMainStream(me.id, 'readAllNotifications'); + this.pushNotificationService.pushNotification(me.id, 'readAllNotifications', undefined); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts index 7bce525a55..cdf8d09f9e 100644 --- a/packages/backend/src/server/api/endpoints/notifications/read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/read.ts @@ -1,5 +1,6 @@ -import define from '../../define.js'; -import { readNotification } from '../../common/read-notification.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NotificationService } from '@/core/NotificationService.js'; export const meta = { tags: ['notifications', 'account'], @@ -43,7 +44,14 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - if ('notificationId' in ps) return readNotification(user.id, [ps.notificationId]); - return readNotification(user.id, ps.notificationIds); -}); +@Injectable() +export default class extends Endpoint { + constructor( + private notificationService: NotificationService, + ) { + super(meta, paramDef, async (ps, me) => { + if ('notificationId' in ps) return this.notificationService.readNotification(me.id, [ps.notificationId]); + return this.notificationService.readNotification(me.id, ps.notificationIds); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts index 6dd3ede85a..1b0299c3c6 100644 --- a/packages/backend/src/server/api/endpoints/page-push.ts +++ b/packages/backend/src/server/api/endpoints/page-push.ts @@ -1,6 +1,10 @@ -import { publishMainStream } from '@/services/stream.js'; -import { Users, Pages } from '@/models/index.js'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PagesRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../error.js'; export const meta = { @@ -27,19 +31,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOneBy({ id: ps.pageId }); - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + + private userEntityService: UserEntityService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); + if (page == null) { + throw new ApiError(meta.errors.noSuchPage); + } - publishMainStream(page.userId, 'pageEvent', { - pageId: ps.pageId, - event: ps.event, - var: ps.var, - userId: user.id, - user: await Users.pack(user.id, { id: page.userId }, { - detail: true, - }), - }); -}); + this.globalEventService.publishMainStream(page.userId, 'pageEvent', { + pageId: ps.pageId, + event: ps.event, + var: ps.var, + userId: me.id, + user: await this.userEntityService.pack(me.id, { id: page.userId }, { + detail: true, + }), + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index b008cde84e..ac80849aa0 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -1,8 +1,11 @@ import ms from 'ms'; -import { Pages, DriveFiles } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { Page } from '@/models/entities/page.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DriveFilesRepository, PagesRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { Page } from '@/models/entities/Page.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { PageEntityService } from '@/core/entities/PageEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -59,45 +62,59 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - let eyeCatchingImage = null; - if (ps.eyeCatchingImageId != null) { - eyeCatchingImage = await DriveFiles.findOneBy({ - id: ps.eyeCatchingImageId, - userId: user.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, - if (eyeCatchingImage == null) { - throw new ApiError(meta.errors.noSuchFile); - } - } + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private pageEntityService: PageEntityService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + let eyeCatchingImage = null; + if (ps.eyeCatchingImageId != null) { + eyeCatchingImage = await this.driveFilesRepository.findOneBy({ + id: ps.eyeCatchingImageId, + userId: me.id, + }); - await Pages.findBy({ - userId: user.id, - name: ps.name, - }).then(result => { - if (result.length > 0) { - throw new ApiError(meta.errors.nameAlreadyExists); - } - }); + if (eyeCatchingImage == null) { + throw new ApiError(meta.errors.noSuchFile); + } + } - const page = await Pages.insert(new Page({ - id: genId(), - createdAt: new Date(), - updatedAt: new Date(), - title: ps.title, - name: ps.name, - summary: ps.summary, - content: ps.content, - variables: ps.variables, - script: ps.script, - eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null, - userId: user.id, - visibility: 'public', - alignCenter: ps.alignCenter, - hideTitleWhenPinned: ps.hideTitleWhenPinned, - font: ps.font, - })).then(x => Pages.findOneByOrFail(x.identifiers[0])); + await this.pagesRepository.findBy({ + userId: me.id, + name: ps.name, + }).then(result => { + if (result.length > 0) { + throw new ApiError(meta.errors.nameAlreadyExists); + } + }); - return await Pages.pack(page); -}); + const page = await this.pagesRepository.insert(new Page({ + id: this.idService.genId(), + createdAt: new Date(), + updatedAt: new Date(), + title: ps.title, + name: ps.name, + summary: ps.summary, + content: ps.content, + variables: ps.variables, + script: ps.script, + eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null, + userId: me.id, + visibility: 'public', + alignCenter: ps.alignCenter, + hideTitleWhenPinned: ps.hideTitleWhenPinned, + font: ps.font, + })).then(x => this.pagesRepository.findOneByOrFail(x.identifiers[0])); + + return await this.pageEntityService.pack(page); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index a7708e6585..4e97755761 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -1,5 +1,7 @@ -import { Pages } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PagesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -33,14 +35,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOneBy({ id: ps.pageId }); - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); + if (page == null) { + throw new ApiError(meta.errors.noSuchPage); + } + if (page.userId !== me.id) { + throw new ApiError(meta.errors.accessDenied); + } + + await this.pagesRepository.delete(page.id); + }); } - if (page.userId !== user.id) { - throw new ApiError(meta.errors.accessDenied); - } - - await Pages.delete(page.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/pages/featured.ts b/packages/backend/src/server/api/endpoints/pages/featured.ts index 5a149a626e..3e3dbb0832 100644 --- a/packages/backend/src/server/api/endpoints/pages/featured.ts +++ b/packages/backend/src/server/api/endpoints/pages/featured.ts @@ -1,5 +1,8 @@ -import { Pages } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PagesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { PageEntityService } from '@/core/entities/PageEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['pages'], @@ -24,13 +27,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Pages.createQueryBuilder('page') - .where('page.visibility = \'public\'') - .andWhere('page.likedCount > 0') - .orderBy('page.likedCount', 'DESC'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, - const pages = await query.take(10).getMany(); + private pageEntityService: PageEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.pagesRepository.createQueryBuilder('page') + .where('page.visibility = \'public\'') + .andWhere('page.likedCount > 0') + .orderBy('page.likedCount', 'DESC'); - return await Pages.packMany(pages, me); -}); + const pages = await query.take(10).getMany(); + + return await this.pageEntityService.packMany(pages, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index 269b539f74..f3c55fed8b 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -1,6 +1,8 @@ -import { Pages, PageLikes } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PagesRepository, PageLikesRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -40,33 +42,46 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOneBy({ id: ps.pageId }); - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, - if (page.userId === user.id) { - throw new ApiError(meta.errors.yourPage); - } + @Inject(DI.pageLikesRepository) + private pageLikesRepository: PageLikesRepository, - // if already liked - const exist = await PageLikes.findOneBy({ - pageId: page.id, - userId: user.id, - }); + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); + if (page == null) { + throw new ApiError(meta.errors.noSuchPage); + } - if (exist != null) { - throw new ApiError(meta.errors.alreadyLiked); - } + if (page.userId === me.id) { + throw new ApiError(meta.errors.yourPage); + } - // Create like - await PageLikes.insert({ - id: genId(), - createdAt: new Date(), - pageId: page.id, - userId: user.id, - }); + // if already liked + const exist = await this.pageLikesRepository.findOneBy({ + pageId: page.id, + userId: me.id, + }); - Pages.increment({ id: page.id }, 'likedCount', 1); -}); + if (exist != null) { + throw new ApiError(meta.errors.alreadyLiked); + } + + // Create like + await this.pageLikesRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + pageId: page.id, + userId: me.id, + }); + + this.pagesRepository.increment({ id: page.id }, 'likedCount', 1); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts index 5d37e86b91..6d73889d39 100644 --- a/packages/backend/src/server/api/endpoints/pages/show.ts +++ b/packages/backend/src/server/api/endpoints/pages/show.ts @@ -1,7 +1,10 @@ import { IsNull } from 'typeorm'; -import { Pages, Users } from '@/models/index.js'; -import { Page } from '@/models/entities/page.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, PagesRepository } from '@/models/index.js'; +import type { Page } from '@/models/entities/Page.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { PageEntityService } from '@/core/entities/PageEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -44,27 +47,40 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - let page: Page | null = null; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (ps.pageId) { - page = await Pages.findOneBy({ id: ps.pageId }); - } else if (ps.name && ps.username) { - const author = await Users.findOneBy({ - host: IsNull(), - usernameLower: ps.username.toLowerCase(), - }); - if (author) { - page = await Pages.findOneBy({ - name: ps.name, - userId: author.id, - }); - } - } + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } + private pageEntityService: PageEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + let page: Page | null = null; + + if (ps.pageId) { + page = await this.pagesRepository.findOneBy({ id: ps.pageId }); + } else if (ps.name && ps.username) { + const author = await this.usersRepository.findOneBy({ + host: IsNull(), + usernameLower: ps.username.toLowerCase(), + }); + if (author) { + page = await this.pagesRepository.findOneBy({ + name: ps.name, + userId: author.id, + }); + } + } - return await Pages.pack(page, user); -}); + if (page == null) { + throw new ApiError(meta.errors.noSuchPage); + } + + return await this.pageEntityService.pack(page, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts index 6b3a2bec10..88386739be 100644 --- a/packages/backend/src/server/api/endpoints/pages/unlike.ts +++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts @@ -1,5 +1,7 @@ -import { Pages, PageLikes } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PagesRepository, PageLikesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -33,23 +35,34 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOneBy({ id: ps.pageId }); - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, - const exist = await PageLikes.findOneBy({ - pageId: page.id, - userId: user.id, - }); + @Inject(DI.pageLikesRepository) + private pageLikesRepository: PageLikesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); + if (page == null) { + throw new ApiError(meta.errors.noSuchPage); + } - if (exist == null) { - throw new ApiError(meta.errors.notLiked); - } + const exist = await this.pageLikesRepository.findOneBy({ + pageId: page.id, + userId: me.id, + }); - // Delete like - await PageLikes.delete(exist.id); + if (exist == null) { + throw new ApiError(meta.errors.notLiked); + } - Pages.decrement({ id: page.id }, 'likedCount', 1); -}); + // Delete like + await this.pageLikesRepository.delete(exist.id); + + this.pagesRepository.decrement({ id: page.id }, 'likedCount', 1); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index d241f585aa..8980ac4906 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -1,7 +1,9 @@ import ms from 'ms'; import { Not } from 'typeorm'; -import { Pages, DriveFiles } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PagesRepository, DriveFilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -65,52 +67,63 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOneBy({ id: ps.pageId }); - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } - if (page.userId !== user.id) { - throw new ApiError(meta.errors.accessDenied); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, - let eyeCatchingImage = null; - if (ps.eyeCatchingImageId != null) { - eyeCatchingImage = await DriveFiles.findOneBy({ - id: ps.eyeCatchingImageId, - userId: user.id, - }); + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); + if (page == null) { + throw new ApiError(meta.errors.noSuchPage); + } + if (page.userId !== me.id) { + throw new ApiError(meta.errors.accessDenied); + } - if (eyeCatchingImage == null) { - throw new ApiError(meta.errors.noSuchFile); - } - } + let eyeCatchingImage = null; + if (ps.eyeCatchingImageId != null) { + eyeCatchingImage = await this.driveFilesRepository.findOneBy({ + id: ps.eyeCatchingImageId, + userId: me.id, + }); - await Pages.findBy({ - id: Not(ps.pageId), - userId: user.id, - name: ps.name, - }).then(result => { - if (result.length > 0) { - throw new ApiError(meta.errors.nameAlreadyExists); - } - }); + if (eyeCatchingImage == null) { + throw new ApiError(meta.errors.noSuchFile); + } + } - await Pages.update(page.id, { - updatedAt: new Date(), - title: ps.title, - name: ps.name === undefined ? page.name : ps.name, - summary: ps.name === undefined ? page.summary : ps.summary, - content: ps.content, - variables: ps.variables, - script: ps.script, - alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter, - hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned, - font: ps.font === undefined ? page.font : ps.font, - eyeCatchingImageId: ps.eyeCatchingImageId === null - ? null - : ps.eyeCatchingImageId === undefined - ? page.eyeCatchingImageId - : eyeCatchingImage!.id, - }); -}); + await this.pagesRepository.findBy({ + id: Not(ps.pageId), + userId: me.id, + name: ps.name, + }).then(result => { + if (result.length > 0) { + throw new ApiError(meta.errors.nameAlreadyExists); + } + }); + + await this.pagesRepository.update(page.id, { + updatedAt: new Date(), + title: ps.title, + name: ps.name === undefined ? page.name : ps.name, + summary: ps.name === undefined ? page.summary : ps.summary, + content: ps.content, + variables: ps.variables, + script: ps.script, + alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter, + hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned, + font: ps.font === undefined ? page.font : ps.font, + eyeCatchingImageId: ps.eyeCatchingImageId === null + ? null + : ps.eyeCatchingImageId === undefined + ? page.eyeCatchingImageId + : eyeCatchingImage!.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/ping.ts b/packages/backend/src/server/api/endpoints/ping.ts index 2891a0860a..4bb62b298e 100644 --- a/packages/backend/src/server/api/endpoints/ping.ts +++ b/packages/backend/src/server/api/endpoints/ping.ts @@ -1,4 +1,5 @@ -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; export const meta = { requireCredential: false, @@ -24,8 +25,14 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - return { - pong: Date.now(), - }; -}); +@Injectable() +export default class extends Endpoint { + constructor( + ) { + super(meta, paramDef, async () => { + return { + pong: Date.now(), + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 41595b47d9..573331e0d8 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -1,9 +1,12 @@ import { IsNull } from 'typeorm'; -import { Users } from '@/models/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; import * as Acct from '@/misc/acct.js'; -import { User } from '@/models/entities/user.js'; -import define from '../define.js'; +import type { User } from '@/models/entities/User.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MetaService } from '@/core/MetaService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users'], @@ -28,13 +31,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const meta = await fetchMeta(); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - const users = await Promise.all(meta.pinnedUsers.map(acct => Acct.parse(acct)).map(acct => Users.findOneBy({ - usernameLower: acct.username.toLowerCase(), - host: acct.host ?? IsNull(), - }))); + private metaService: MetaService, + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const meta = await this.metaService.fetch(); - return await Users.packMany(users.filter(x => x !== undefined) as User[], me, { detail: true }); -}); + const users = await Promise.all(meta.pinnedthis.usersRepository.map(acct => Acct.parse(acct)).map(acct => this.usersRepository.findOneBy({ + usernameLower: acct.username.toLowerCase(), + host: acct.host ?? IsNull(), + }))); + + return await this.userEntityService.packMany(users.filter(x => x !== undefined) as User[], me, { detail: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index c6a940c65e..7c8188ce3c 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -1,8 +1,10 @@ -import { PromoReads } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PromoReadsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getNote } from '../../common/getters.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['notes'], @@ -27,25 +29,36 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - const exist = await PromoReads.findOneBy({ - noteId: note.id, - userId: user.id, - }); - - if (exist != null) { - return; - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.promoReadsRepository) + private promoReadsRepository: PromoReadsRepository, + + private idService: IdService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.getterService.getNote(ps.noteId).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + + const exist = await this.promoReadsRepository.findOneBy({ + noteId: note.id, + userId: me.id, + }); - await PromoReads.insert({ - id: genId(), - createdAt: new Date(), - noteId: note.id, - userId: user.id, - }); -}); + if (exist != null) { + return; + } + + await this.promoReadsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + noteId: note.id, + userId: me.id, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 511a6bbb53..4766239533 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -1,13 +1,14 @@ import rndstr from 'rndstr'; import ms from 'ms'; import { IsNull } from 'typeorm'; -import { publishMainStream } from '@/services/stream.js'; -import config from '@/config/index.js'; -import { Users, UserProfiles, PasswordResetRequests } from '@/models/index.js'; -import { sendEmail } from '@/services/send-email.js'; -import { genId } from '@/misc/gen-id.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { PasswordResetRequestsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import { EmailService } from '@/core/EmailService.js'; import { ApiError } from '../error.js'; -import define from '../define.js'; export const meta = { tags: ['reset password'], @@ -36,41 +37,61 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - const user = await Users.findOneBy({ - usernameLower: ps.username.toLowerCase(), - host: IsNull(), - }); - - // 合致するユーザーが登録されていなかったら無視 - if (user == null) { - return; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.passwordResetRequestsRepository) + private passwordResetRequestsRepository: PasswordResetRequestsRepository, + + private idService: IdService, + private emailService: EmailService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ + usernameLower: ps.username.toLowerCase(), + host: IsNull(), + }); + + // 合致するユーザーが登録されていなかったら無視 + if (user == null) { + return; + } + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + // 合致するメアドが登録されていなかったら無視 + if (profile.email !== ps.email) { + return; + } + + // メアドが認証されていなかったら無視 + if (!profile.emailVerified) { + return; + } + + const token = rndstr('a-z0-9', 64); + + await this.passwordResetRequestsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: profile.userId, + token, + }); + + const link = `${this.config.url}/reset-password/${token}`; + + this.emailService.sendEmail(ps.email, 'Password reset requested', + `To reset password, please click this link:
${link}`, + `To reset password, please click this link: ${link}`); + }); } - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - // 合致するメアドが登録されていなかったら無視 - if (profile.email !== ps.email) { - return; - } - - // メアドが認証されていなかったら無視 - if (!profile.emailVerified) { - return; - } - - const token = rndstr('a-z0-9', 64); - - await PasswordResetRequests.insert({ - id: genId(), - createdAt: new Date(), - userId: profile.userId, - token, - }); - - const link = `${config.url}/reset-password/${token}`; - - sendEmail(ps.email, 'Password reset requested', - `To reset password, please click this link:
${link}`, - `To reset password, please click this link: ${link}`); -}); +} diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts index 140f96d579..526efbc2f6 100644 --- a/packages/backend/src/server/api/endpoints/reset-db.ts +++ b/packages/backend/src/server/api/endpoints/reset-db.ts @@ -1,5 +1,9 @@ -import { resetDb } from '@/db/postgre.js'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import Redis from 'ioredis'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; +import { resetDb } from '@/misc/reset-db.js'; import { ApiError } from '../error.js'; export const meta = { @@ -21,10 +25,22 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test'; - - await resetDb(); - - await new Promise(resolve => setTimeout(resolve, 1000)); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.redis) + private redisClient: Redis.Redis, + ) { + super(meta, paramDef, async (ps, me) => { + if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test'; + + await redisClient.flushdb(); + await resetDb(this.db); + + await new Promise(resolve => setTimeout(resolve, 1000)); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 797169c2c3..48edde5196 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -1,7 +1,9 @@ import bcrypt from 'bcryptjs'; -import { publishMainStream } from '@/services/stream.js'; -import { Users, UserProfiles, PasswordResetRequests } from '@/models/index.js'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../error.js'; export const meta = { @@ -26,23 +28,34 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const req = await PasswordResetRequests.findOneByOrFail({ - token: ps.token, - }); - - // 発行してから30分以上経過していたら無効 - if (Date.now() - req.createdAt.getTime() > 1000 * 60 * 30) { - throw new Error(); // TODO +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.passwordResetRequestsRepository) + private passwordResetRequestsRepository: PasswordResetRequestsRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const req = await this.passwordResetRequestsRepository.findOneByOrFail({ + token: ps.token, + }); + + // 発行してから30分以上経過していたら無効 + if (Date.now() - req.createdAt.getTime() > 1000 * 60 * 30) { + throw new Error(); // TODO + } + + // Generate hash of password + const salt = await bcrypt.genSalt(8); + const hash = await bcrypt.hash(ps.password, salt); + + await this.userProfilesRepository.update(req.userId, { + password: hash, + }); + + this.passwordResetRequestsRepository.delete(req.id); + }); } - - // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(ps.password, salt); - - await UserProfiles.update(req.userId, { - password: hash, - }); - - PasswordResetRequests.delete(req.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts index 99f3730e97..8989a3073d 100644 --- a/packages/backend/src/server/api/endpoints/server-info.ts +++ b/packages/backend/src/server/api/endpoints/server-info.ts @@ -1,6 +1,7 @@ import * as os from 'node:os'; import si from 'systeminformation'; -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; export const meta = { requireCredential: false, @@ -15,22 +16,28 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const memStats = await si.mem(); - const fsStats = await si.fsSize(); +@Injectable() +export default class extends Endpoint { + constructor( + ) { + super(meta, paramDef, async () => { + const memStats = await si.mem(); + const fsStats = await si.fsSize(); - return { - machine: os.hostname(), - cpu: { - model: os.cpus()[0].model, - cores: os.cpus().length, - }, - mem: { - total: memStats.total, - }, - fs: { - total: fsStats[0].size, - used: fsStats[0].used, - }, - }; -}); + return { + machine: os.hostname(), + cpu: { + model: os.cpus()[0].model, + cores: os.cpus().length, + }, + mem: { + total: memStats.total, + }, + fs: { + total: fsStats[0].size, + used: fsStats[0].used, + }, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index cc94f8bf26..17af75578b 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -1,7 +1,8 @@ -import { Instances, NoteReactions, Notes, Users } from '@/models/index.js'; -import define from '../define.js'; -import { } from '@/services/chart/index.js'; +import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; +import { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: false, @@ -51,34 +52,51 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async () => { - const [ - notesCount, - originalNotesCount, - usersCount, - originalUsersCount, - reactionsCount, - //originalReactionsCount, - instances, - ] = await Promise.all([ - Notes.count({ cache: 3600000 }), // 1 hour - Notes.count({ where: { userHost: IsNull() }, cache: 3600000 }), - Users.count({ cache: 3600000 }), - Users.count({ where: { host: IsNull() }, cache: 3600000 }), - NoteReactions.count({ cache: 3600000 }), // 1 hour - //NoteReactions.count({ where: { userHost: IsNull() }, cache: 3600000 }), - Instances.count({ cache: 3600000 }), - ]); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - return { - notesCount, - originalNotesCount, - usersCount, - originalUsersCount, - reactionsCount, - //originalReactionsCount, - instances, - driveUsageLocal: 0, - driveUsageRemote: 0, - }; -}); + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, + + @Inject(DI.noteReactionsRepository) + private noteReactionsRepository: NoteReactionsRepository, + ) { + super(meta, paramDef, async () => { + const [ + notesCount, + originalNotesCount, + usersCount, + originalUsersCount, + reactionsCount, + //originalReactionsCount, + instances, + ] = await Promise.all([ + this.notesRepository.count({ cache: 3600000 }), // 1 hour + this.notesRepository.count({ where: { userHost: IsNull() }, cache: 3600000 }), + this.usersRepository.count({ cache: 3600000 }), + this.usersRepository.count({ where: { host: IsNull() }, cache: 3600000 }), + this.noteReactionsRepository.count({ cache: 3600000 }), // 1 hour + //this.noteReactionsRepository.count({ where: { userHost: IsNull() }, cache: 3600000 }), + this.instancesRepository.count({ cache: 3600000 }), + ]); + + return { + notesCount, + originalNotesCount, + usersCount, + originalUsersCount, + reactionsCount, + //originalReactionsCount, + instances, + driveUsageLocal: 0, + driveUsageRemote: 0, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index 437f8874ff..73a084c2ad 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -1,7 +1,9 @@ -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { genId } from '@/misc/gen-id.js'; -import { SwSubscriptions } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { IdService } from '@/core/IdService.js'; +import { SwSubscriptionsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], @@ -38,35 +40,46 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // if already subscribed - const exist = await SwSubscriptions.findOneBy({ - userId: user.id, - endpoint: ps.endpoint, - auth: ps.auth, - publickey: ps.publickey, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.swSubscriptionsRepository) + private swSubscriptionsRepository: SwSubscriptionsRepository, - const instance = await fetchMeta(true); + private idService: IdService, + private metaService: MetaService, + ) { + super(meta, paramDef, async (ps, me) => { + // if already subscribed + const exist = await this.swSubscriptionsRepository.findOneBy({ + userId: me.id, + endpoint: ps.endpoint, + auth: ps.auth, + publickey: ps.publickey, + }); - if (exist != null) { - return { - state: 'already-subscribed' as const, - key: instance.swPublicKey, - }; - } + const instance = await this.metaService.fetch(true); + + if (exist != null) { + return { + state: 'already-subscribed' as const, + key: instance.swPublicKey, + }; + } - await SwSubscriptions.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - endpoint: ps.endpoint, - auth: ps.auth, - publickey: ps.publickey, - }); + await this.swSubscriptionsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + endpoint: ps.endpoint, + auth: ps.auth, + publickey: ps.publickey, + }); - return { - state: 'subscribed' as const, - key: instance.swPublicKey, - }; -}); + return { + state: 'subscribed' as const, + key: instance.swPublicKey, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index c19e06b879..feb6730154 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -1,5 +1,7 @@ -import { SwSubscriptions } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { SwSubscriptionsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], @@ -18,9 +20,17 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - await SwSubscriptions.delete({ - userId: user.id, - endpoint: ps.endpoint, - }); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.swSubscriptionsRepository) + private swSubscriptionsRepository: SwSubscriptionsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + await this.swSubscriptionsRepository.delete({ + userId: me.id, + endpoint: ps.endpoint, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/test.ts b/packages/backend/src/server/api/endpoints/test.ts index 9949237a7e..39ea1f2171 100644 --- a/packages/backend/src/server/api/endpoints/test.ts +++ b/packages/backend/src/server/api/endpoints/test.ts @@ -1,4 +1,5 @@ -import define from '../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; export const meta = { tags: ['non-productive'], @@ -21,6 +22,12 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - return ps; -}); +@Injectable() +export default class extends Endpoint { + constructor( + ) { + super(meta, paramDef, async (ps, me) => { + return ps; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index 3e41aeaed8..56474d6988 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -1,6 +1,9 @@ import { IsNull } from 'typeorm'; -import { Users, UsedUsernames } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsedUsernamesRepository, UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { localUsernameSchema } from '@/models/entities/User.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users'], @@ -22,22 +25,33 @@ export const meta = { export const paramDef = { type: 'object', properties: { - username: Users.localUsernameSchema, + username: localUsernameSchema, }, required: ['username'], } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { - // Get exist - const exist = await Users.countBy({ - host: IsNull(), - usernameLower: ps.username.toLowerCase(), - }); - - const exist2 = await UsedUsernames.countBy({ username: ps.username.toLowerCase() }); - - return { - available: exist === 0 && exist2 === 0, - }; -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.usedUsernamesRepository) + private usedUsernamesRepository: UsedUsernamesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + // Get exist + const exist = await this.usersRepository.countBy({ + host: IsNull(), + usernameLower: ps.username.toLowerCase(), + }); + + const exist2 = await this.usedUsernamesRepository.countBy({ username: ps.username.toLowerCase() }); + + return { + available: exist === 0 && exist2 === 0, + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 3a8211374b..3d05ec2e1d 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -1,7 +1,9 @@ -import { Users } from '@/models/index.js'; -import define from '../define.js'; -import { generateMutedUserQueryForUsers } from '../common/generate-muted-user-query.js'; -import { generateBlockQueryForUsers } from '../common/generate-block-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users'], @@ -38,43 +40,54 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Users.createQueryBuilder('user'); - query.where('user.isExplorable = TRUE'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - switch (ps.state) { - case 'admin': query.andWhere('user.isAdmin = TRUE'); break; - case 'moderator': query.andWhere('user.isModerator = TRUE'); break; - case 'adminOrModerator': query.andWhere('user.isAdmin = TRUE OR user.isModerator = TRUE'); break; - case 'alive': query.andWhere('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; - } + private userEntityService: UserEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.usersRepository.createQueryBuilder('user'); + query.where('user.isExplorable = TRUE'); - switch (ps.origin) { - case 'local': query.andWhere('user.host IS NULL'); break; - case 'remote': query.andWhere('user.host IS NOT NULL'); break; - } + switch (ps.state) { + case 'admin': query.andWhere('user.isAdmin = TRUE'); break; + case 'moderator': query.andWhere('user.isModerator = TRUE'); break; + case 'adminOrModerator': query.andWhere('user.isAdmin = TRUE OR user.isModerator = TRUE'); break; + case 'alive': query.andWhere('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; + } - if (ps.hostname) { - query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() }); - } + switch (ps.origin) { + case 'local': query.andWhere('user.host IS NULL'); break; + case 'remote': query.andWhere('user.host IS NOT NULL'); break; + } - switch (ps.sort) { - case '+follower': query.orderBy('user.followersCount', 'DESC'); break; - case '-follower': query.orderBy('user.followersCount', 'ASC'); break; - case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; - case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; - case '+updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'DESC'); break; - case '-updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'ASC'); break; - default: query.orderBy('user.id', 'ASC'); break; - } + if (ps.hostname) { + query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() }); + } + + switch (ps.sort) { + case '+follower': query.orderBy('user.followersCount', 'DESC'); break; + case '-follower': query.orderBy('user.followersCount', 'ASC'); break; + case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; + case '+updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'DESC'); break; + case '-updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'ASC'); break; + default: query.orderBy('user.id', 'ASC'); break; + } - if (me) generateMutedUserQueryForUsers(query, me); - if (me) generateBlockQueryForUsers(query, me); + if (me) this.queryService.generateMutedUserQueryForUsers(query, me); + if (me) this.queryService.generateBlockQueryForUsers(query, me); - query.take(ps.limit); - query.skip(ps.offset); + query.take(ps.limit); + query.skip(ps.offset); - const users = await query.getMany(); + const users = await query.getMany(); - return await Users.packMany(users, me, { detail: true }); -}); + return await this.userEntityService.packMany(users, me, { detail: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index 09fdf27c23..2d5545cbab 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -1,6 +1,9 @@ -import { Clips } from '@/models/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { ClipsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users', 'clips'], @@ -30,14 +33,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(Clips.createQueryBuilder('clip'), ps.sinceId, ps.untilId) - .andWhere('clip.userId = :userId', { userId: ps.userId }) - .andWhere('clip.isPublic = true'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, - const clips = await query - .take(ps.limit) - .getMany(); + private clipEntityService: ClipEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.clipsRepository.createQueryBuilder('clip'), ps.sinceId, ps.untilId) + .andWhere('clip.userId = :userId', { userId: ps.userId }) + .andWhere('clip.isPublic = true'); - return await Clips.packMany(clips); -}); + const clips = await query + .take(ps.limit) + .getMany(); + + return await this.clipEntityService.packMany(clips); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 7f9f980764..08bcdd9f88 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -1,9 +1,12 @@ import { IsNull } from 'typeorm'; -import { Users, Followings, UserProfiles } from '@/models/index.js'; -import { toPunyNullable } from '@/misc/convert-host.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; export const meta = { tags: ['users'], @@ -66,42 +69,60 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy(ps.userId != null - ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) ?? IsNull() }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user == null) { - throw new ApiError(meta.errors.noSuchUser); - } + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - if (profile.ffVisibility === 'private') { - if (me == null || (me.id !== user.id)) { - throw new ApiError(meta.errors.forbidden); - } - } else if (profile.ffVisibility === 'followers') { - if (me == null) { - throw new ApiError(meta.errors.forbidden); - } else if (me.id !== user.id) { - const following = await Followings.findOneBy({ - followeeId: user.id, - followerId: me.id, - }); - if (following == null) { - throw new ApiError(meta.errors.forbidden); + private utilityService: UtilityService, + private followingEntityService: FollowingEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy(ps.userId != null + ? { id: ps.userId } + : { usernameLower: ps.username!.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() }); + + if (user == null) { + throw new ApiError(meta.errors.noSuchUser); } - } - } - const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) - .andWhere('following.followeeId = :userId', { userId: user.id }) - .innerJoinAndSelect('following.follower', 'follower'); + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); - const followings = await query - .take(ps.limit) - .getMany(); + if (profile.ffVisibility === 'private') { + if (me == null || (me.id !== user.id)) { + throw new ApiError(meta.errors.forbidden); + } + } else if (profile.ffVisibility === 'followers') { + if (me == null) { + throw new ApiError(meta.errors.forbidden); + } else if (me.id !== user.id) { + const following = await this.followingsRepository.findOneBy({ + followeeId: user.id, + followerId: me.id, + }); + if (following == null) { + throw new ApiError(meta.errors.forbidden); + } + } + } + + const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId) + .andWhere('following.followeeId = :userId', { userId: user.id }) + .innerJoinAndSelect('following.follower', 'follower'); - return await Followings.packMany(followings, me, { populateFollower: true }); -}); + const followings = await query + .take(ps.limit) + .getMany(); + + return await this.followingEntityService.packMany(followings, me, { populateFollower: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 0aaa810f76..225ab5210a 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -1,9 +1,12 @@ import { IsNull } from 'typeorm'; -import { Users, Followings, UserProfiles } from '@/models/index.js'; -import { toPunyNullable } from '@/misc/convert-host.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; export const meta = { tags: ['users'], @@ -66,42 +69,60 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy(ps.userId != null - ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) ?? IsNull() }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user == null) { - throw new ApiError(meta.errors.noSuchUser); - } + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - if (profile.ffVisibility === 'private') { - if (me == null || (me.id !== user.id)) { - throw new ApiError(meta.errors.forbidden); - } - } else if (profile.ffVisibility === 'followers') { - if (me == null) { - throw new ApiError(meta.errors.forbidden); - } else if (me.id !== user.id) { - const following = await Followings.findOneBy({ - followeeId: user.id, - followerId: me.id, - }); - if (following == null) { - throw new ApiError(meta.errors.forbidden); + private utilityService: UtilityService, + private followingEntityService: FollowingEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy(ps.userId != null + ? { id: ps.userId } + : { usernameLower: ps.username!.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() }); + + if (user == null) { + throw new ApiError(meta.errors.noSuchUser); } - } - } - const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) - .andWhere('following.followerId = :userId', { userId: user.id }) - .innerJoinAndSelect('following.followee', 'followee'); + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); - const followings = await query - .take(ps.limit) - .getMany(); + if (profile.ffVisibility === 'private') { + if (me == null || (me.id !== user.id)) { + throw new ApiError(meta.errors.forbidden); + } + } else if (profile.ffVisibility === 'followers') { + if (me == null) { + throw new ApiError(meta.errors.forbidden); + } else if (me.id !== user.id) { + const following = await this.followingsRepository.findOneBy({ + followeeId: user.id, + followerId: me.id, + }); + if (following == null) { + throw new ApiError(meta.errors.forbidden); + } + } + } + + const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId) + .andWhere('following.followerId = :userId', { userId: user.id }) + .innerJoinAndSelect('following.followee', 'followee'); - return await Followings.packMany(followings, me, { populateFollowee: true }); -}); + const followings = await query + .take(ps.limit) + .getMany(); + + return await this.followingEntityService.packMany(followings, me, { populateFollowee: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts index 35bf2df598..2d28d6ca07 100644 --- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts @@ -1,6 +1,9 @@ -import define from '../../../define.js'; -import { GalleryPosts } from '@/models/index.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GalleryPostsRepository } from '@/models/index.js'; +import { QueryService } from '@/core/QueryService.js'; +import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users', 'gallery'], @@ -30,13 +33,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId) - .andWhere(`post.userId = :userId`, { userId: ps.userId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.galleryPostsRepository) + private galleryPostsRepository: GalleryPostsRepository, - const posts = await query - .take(ps.limit) - .getMany(); + private galleryPostEntityService: GalleryPostEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId) + .andWhere('post.userId = :userId', { userId: ps.userId }); - return await GalleryPosts.packMany(posts, user); -}); + const posts = await query + .take(ps.limit) + .getMany(); + + return await this.galleryPostEntityService.packMany(posts, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index 56965d3066..3eeca7562f 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -1,9 +1,12 @@ import { Not, In, IsNull } from 'typeorm'; -import { maximum } from '@/prelude/array.js'; -import { Notes, Users } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { maximum } from '@/misc/prelude/array.js'; +import { NotesRepository, UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['users'], @@ -51,64 +54,78 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Lookup user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Fetch recent notes - const recentNotes = await Notes.find({ - where: { - userId: user.id, - replyId: Not(IsNull()), - }, - order: { - id: -1, - }, - take: 1000, - select: ['replyId'], - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + // Lookup user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Fetch recent notes + const recentNotes = await this.notesRepository.find({ + where: { + userId: user.id, + replyId: Not(IsNull()), + }, + order: { + id: -1, + }, + take: 1000, + select: ['replyId'], + }); + + // 投稿が少なかったら中断 + if (recentNotes.length === 0) { + return []; + } + + // TODO ミュートを考慮 + const replyTargetNotes = await this.notesRepository.find({ + where: { + id: In(recentNotes.map(p => p.replyId)), + }, + select: ['userId'], + }); - // 投稿が少なかったら中断 - if (recentNotes.length === 0) { - return []; - } + const repliedUsers: any = {}; - // TODO ミュートを考慮 - const replyTargetNotes = await Notes.find({ - where: { - id: In(recentNotes.map(p => p.replyId)), - }, - select: ['userId'], - }); - - const repliedUsers: any = {}; - - // Extract replies from recent notes - for (const userId of replyTargetNotes.map(x => x.userId.toString())) { - if (repliedUsers[userId]) { - repliedUsers[userId]++; - } else { - repliedUsers[userId] = 1; - } - } + // Extract replies from recent notes + for (const userId of replyTargetNotes.map(x => x.userId.toString())) { + if (repliedUsers[userId]) { + repliedUsers[userId]++; + } else { + repliedUsers[userId] = 1; + } + } - // Calc peak - const peak = maximum(Object.values(repliedUsers)); + // Calc peak + const peak = maximum(Object.values(repliedUsers)); - // Sort replies by frequency - const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]); + // Sort replies by frequency + const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]); - // Extract top replied users - const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit); + // Extract top replied users + const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit); - // Make replies object (includes weights) - const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ - user: await Users.pack(user, me, { detail: true }), - weight: repliedUsers[user] / peak, - }))); + // Make replies object (includes weights) + const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ + user: await this.userEntityService.pack(user, me, { detail: true }), + weight: repliedUsers[user] / peak, + }))); - return repliesObj; -}); + return repliesObj; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts index 4a6362a3c6..5d7ad84ae0 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -1,8 +1,11 @@ -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { UserGroupJoining } from '@/models/entities/user-group-joining.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import type { UserGroup } from '@/models/entities/UserGroup.js'; +import type { UserGroupJoining } from '@/models/entities/UserGroupJoining.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['groups'], @@ -29,21 +32,35 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const userGroup = await UserGroups.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - name: ps.name, - } as UserGroup).then(x => UserGroups.findOneByOrFail(x.identifiers[0])); - - // Push the owner - await UserGroupJoinings.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - userGroupId: userGroup.id, - } as UserGroupJoining); - - return await UserGroups.pack(userGroup); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private userGroupEntityService: UserGroupEntityService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const userGroup = await this.userGroupsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + name: ps.name, + } as UserGroup).then(x => this.userGroupsRepository.findOneByOrFail(x.identifiers[0])); + + // Push the owner + await this.userGroupJoiningsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + userGroupId: userGroup.id, + } as UserGroupJoining); + + return await this.userGroupEntityService.pack(userGroup); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts index 2ff1f9aec1..50156b049e 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts @@ -1,5 +1,7 @@ -import { UserGroups } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -29,15 +31,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const userGroup = await UserGroups.findOneBy({ - id: ps.groupId, - userId: user.id, - }); - - if (userGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const userGroup = await this.userGroupsRepository.findOneBy({ + id: ps.groupId, + userId: me.id, + }); + + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } + + await this.userGroupsRepository.delete(userGroup.id); + }); } - - await UserGroups.delete(userGroup.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts index 220fff5f3e..0490fd41a0 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts @@ -1,8 +1,10 @@ -import { UserGroupJoinings, UserGroupInvitations } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { UserGroupJoining } from '@/models/entities/user-group-joining.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupInvitationsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import type { UserGroupJoining } from '@/models/entities/UserGroupJoining.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../../error.js'; -import define from '../../../../define.js'; export const meta = { tags: ['groups', 'users'], @@ -31,27 +33,40 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch the invitation - const invitation = await UserGroupInvitations.findOneBy({ - id: ps.invitationId, - }); - - if (invitation == null) { - throw new ApiError(meta.errors.noSuchInvitation); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupInvitationsRepository) + private userGroupInvitationsRepository: UserGroupInvitationsRepository, - if (invitation.userId !== user.id) { - throw new ApiError(meta.errors.noSuchInvitation); - } + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the invitation + const invitation = await this.userGroupInvitationsRepository.findOneBy({ + id: ps.invitationId, + }); - // Push the user - await UserGroupJoinings.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - userGroupId: invitation.userGroupId, - } as UserGroupJoining); + if (invitation == null) { + throw new ApiError(meta.errors.noSuchInvitation); + } - UserGroupInvitations.delete(invitation.id); -}); + if (invitation.userId !== me.id) { + throw new ApiError(meta.errors.noSuchInvitation); + } + + // Push the user + await this.userGroupJoiningsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + userGroupId: invitation.userGroupId, + } as UserGroupJoining); + + this.userGroupInvitationsRepository.delete(invitation.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts index 8d1d3db734..26efc1ecf3 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts @@ -1,5 +1,7 @@ -import { UserGroupInvitations } from '@/models/index.js'; -import define from '../../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupInvitationsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../../error.js'; export const meta = { @@ -29,19 +31,27 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch the invitation - const invitation = await UserGroupInvitations.findOneBy({ - id: ps.invitationId, - }); - - if (invitation == null) { - throw new ApiError(meta.errors.noSuchInvitation); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupInvitationsRepository) + private userGroupInvitationsRepository: UserGroupInvitationsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the invitation + const invitation = await this.userGroupInvitationsRepository.findOneBy({ + id: ps.invitationId, + }); + + if (invitation == null) { + throw new ApiError(meta.errors.noSuchInvitation); + } + + if (invitation.userId !== me.id) { + throw new ApiError(meta.errors.noSuchInvitation); + } + + await this.userGroupInvitationsRepository.delete(invitation.id); + }); } - - if (invitation.userId !== user.id) { - throw new ApiError(meta.errors.noSuchInvitation); - } - - await UserGroupInvitations.delete(invitation.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index 1a8d320f3a..4ae32a6bda 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -1,10 +1,12 @@ -import { UserGroups, UserGroupJoinings, UserGroupInvitations } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { UserGroupInvitation } from '@/models/entities/user-group-invitation.js'; -import { createNotification } from '@/services/create-notification.js'; -import { getUser } from '../../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository, UserGroupJoiningsRepository, UserGroupInvitationsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import type { UserGroupInvitation } from '@/models/entities/UserGroupInvitation.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { CreateNotificationService } from '@/core/CreateNotificationService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import define from '../../../define.js'; export const meta = { tags: ['groups', 'users'], @@ -52,51 +54,69 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the group - const userGroup = await UserGroups.findOneBy({ - id: ps.groupId, - userId: me.id, - }); - - if (userGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + + @Inject(DI.userGroupInvitationsRepository) + private userGroupInvitationsRepository: UserGroupInvitationsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private idService: IdService, + private getterService: GetterService, + private createNotificationService: CreateNotificationService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the group + const userGroup = await this.userGroupsRepository.findOneBy({ + id: ps.groupId, + userId: me.id, + }); + + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } + + // Fetch the user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + const joining = await this.userGroupJoiningsRepository.findOneBy({ + userGroupId: userGroup.id, + userId: user.id, + }); + + if (joining) { + throw new ApiError(meta.errors.alreadyAdded); + } + + const existInvitation = await this.userGroupInvitationsRepository.findOneBy({ + userGroupId: userGroup.id, + userId: user.id, + }); + + if (existInvitation) { + throw new ApiError(meta.errors.alreadyInvited); + } + + const invitation = await this.userGroupInvitationsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: user.id, + userGroupId: userGroup.id, + } as UserGroupInvitation).then(x => this.userGroupInvitationsRepository.findOneByOrFail(x.identifiers[0])); + + // 通知を作成 + this.createNotificationService.createNotification(user.id, 'groupInvited', { + notifierId: me.id, + userGroupInvitationId: invitation.id, + }); + }); } - - // Fetch the user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - const joining = await UserGroupJoinings.findOneBy({ - userGroupId: userGroup.id, - userId: user.id, - }); - - if (joining) { - throw new ApiError(meta.errors.alreadyAdded); - } - - const existInvitation = await UserGroupInvitations.findOneBy({ - userGroupId: userGroup.id, - userId: user.id, - }); - - if (existInvitation) { - throw new ApiError(meta.errors.alreadyInvited); - } - - const invitation = await UserGroupInvitations.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - userGroupId: userGroup.id, - } as UserGroupInvitation).then(x => UserGroupInvitations.findOneByOrFail(x.identifiers[0])); - - // 通知を作成 - createNotification(user.id, 'groupInvited', { - notifierId: me.id, - userGroupInvitationId: invitation.id, - }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts index 16c6e544e5..e7e69f257d 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -1,6 +1,9 @@ import { Not, In } from 'typeorm'; -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['groups', 'account'], @@ -29,17 +32,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const ownedGroups = await UserGroups.findBy({ - userId: me.id, - }); - - const joinings = await UserGroupJoinings.findBy({ - userId: me.id, - ...(ownedGroups.length > 0 ? { - userGroupId: Not(In(ownedGroups.map(x => x.id))), - } : {}), - }); - - return await Promise.all(joinings.map(x => UserGroups.pack(x.userGroupId))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private userGroupEntityService: UserGroupEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const ownedGroups = await this.userGroupsRepository.findBy({ + userId: me.id, + }); + + const joinings = await this.userGroupJoiningsRepository.findBy({ + userId: me.id, + ...(ownedGroups.length > 0 ? { + userGroupId: Not(In(ownedGroups.map(x => x.id))), + } : {}), + }); + + return await Promise.all(joinings.map(x => this.userGroupEntityService.pack(x.userGroupId))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts index 83dc757db1..0a63dbb7f1 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts @@ -1,5 +1,7 @@ -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -35,19 +37,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the group - const userGroup = await UserGroups.findOneBy({ - id: ps.groupId, - }); - - if (userGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, - if (me.id === userGroup.userId) { - throw new ApiError(meta.errors.youAreOwner); - } + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the group + const userGroup = await this.userGroupsRepository.findOneBy({ + id: ps.groupId, + }); + + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } - await UserGroupJoinings.delete({ userGroupId: userGroup.id, userId: me.id }); -}); + if (me.id === userGroup.userId) { + throw new ApiError(meta.errors.youAreOwner); + } + + await this.userGroupJoiningsRepository.delete({ userGroupId: userGroup.id, userId: me.id }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts index d77cf1a52e..c9ae39561f 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -1,5 +1,8 @@ -import { UserGroups } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['groups', 'account'], @@ -28,10 +31,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const userGroups = await UserGroups.findBy({ - userId: me.id, - }); - - return await Promise.all(userGroups.map(x => UserGroups.pack(x))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + + private userGroupEntityService: UserGroupEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const userGroups = await this.userGroupsRepository.findBy({ + userId: me.id, + }); + + return await Promise.all(userGroups.map(x => this.userGroupEntityService.pack(x))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index ba67a1e5c9..e6f60eef0a 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -1,7 +1,9 @@ -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; export const meta = { tags: ['groups', 'users'], @@ -43,27 +45,40 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the group - const userGroup = await UserGroups.findOneBy({ - id: ps.groupId, - userId: me.id, - }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, - if (userGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); - } + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, - // Fetch the user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the group + const userGroup = await this.userGroupsRepository.findOneBy({ + id: ps.groupId, + userId: me.id, + }); - if (user.id === userGroup.userId) { - throw new ApiError(meta.errors.isOwner); - } + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } + + // Fetch the user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); - // Pull the user - await UserGroupJoinings.delete({ userGroupId: userGroup.id, userId: user.id }); -}); + if (user.id === userGroup.userId) { + throw new ApiError(meta.errors.isOwner); + } + + // Pull the user + await this.userGroupJoiningsRepository.delete({ userGroupId: userGroup.id, userId: user.id }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts index 21e3d9da26..1cebfcd204 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/show.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts @@ -1,5 +1,8 @@ -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -35,24 +38,37 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the group - const userGroup = await UserGroups.findOneBy({ - id: ps.groupId, - }); - - if (userGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, - const joining = await UserGroupJoinings.findOneBy({ - userId: me.id, - userGroupId: userGroup.id, - }); + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, - if (joining == null && userGroup.userId !== me.id) { - throw new ApiError(meta.errors.noSuchGroup); - } + private userGroupEntityService: UserGroupEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the group + const userGroup = await this.userGroupsRepository.findOneBy({ + id: ps.groupId, + }); + + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } - return await UserGroups.pack(userGroup); -}); + const joining = await this.userGroupJoiningsRepository.findOneBy({ + userId: me.id, + userGroupId: userGroup.id, + }); + + if (joining == null && userGroup.userId !== me.id) { + throw new ApiError(meta.errors.noSuchGroup); + } + + return await this.userGroupEntityService.pack(userGroup); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index 6456e70dd5..a8b2533b73 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -1,7 +1,10 @@ -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; export const meta = { tags: ['groups', 'users'], @@ -49,35 +52,49 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the group - const userGroup = await UserGroups.findOneBy({ - id: ps.groupId, - userId: me.id, - }); - - if (userGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + + @Inject(DI.userGroupJoiningsRepository) + private userGroupJoiningsRepository: UserGroupJoiningsRepository, + + private userGroupEntityService: UserGroupEntityService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the group + const userGroup = await this.userGroupsRepository.findOneBy({ + id: ps.groupId, + userId: me.id, + }); + + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } + + // Fetch the user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + const joining = await this.userGroupJoiningsRepository.findOneBy({ + userGroupId: userGroup.id, + userId: user.id, + }); + + if (joining == null) { + throw new ApiError(meta.errors.noSuchGroupMember); + } + + await this.userGroupsRepository.update(userGroup.id, { + userId: ps.userId, + }); + + return await this.userGroupEntityService.pack(userGroup.id); + }); } - - // Fetch the user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - const joining = await UserGroupJoinings.findOneBy({ - userGroupId: userGroup.id, - userId: user.id, - }); - - if (joining == null) { - throw new ApiError(meta.errors.noSuchGroupMember); - } - - await UserGroups.update(userGroup.id, { - userId: ps.userId, - }); - - return await UserGroups.pack(userGroup.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts index 0a96165fc4..b679625c85 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/update.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts @@ -1,5 +1,8 @@ -import { UserGroups } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserGroupsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -36,20 +39,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the group - const userGroup = await UserGroups.findOneBy({ - id: ps.groupId, - userId: me.id, - }); - - if (userGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userGroupsRepository) + private userGroupsRepository: UserGroupsRepository, + + private userGroupEntityService: UserGroupEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the group + const userGroup = await this.userGroupsRepository.findOneBy({ + id: ps.groupId, + userId: me.id, + }); - await UserGroups.update(userGroup.id, { - name: ps.name, - }); + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } - return await UserGroups.pack(userGroup.id); -}); + await this.userGroupsRepository.update(userGroup.id, { + name: ps.name, + }); + + return await this.userGroupEntityService.pack(userGroup.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index 783e63f5de..aa64ca1229 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -1,7 +1,10 @@ -import { UserLists } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { UserList } from '@/models/entities/user-list.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import type { UserList } from '@/models/entities/UserList.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['lists'], @@ -28,13 +31,24 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const userList = await UserLists.insert({ - id: genId(), - createdAt: new Date(), - userId: user.id, - name: ps.name, - } as UserList).then(x => UserLists.findOneByOrFail(x.identifiers[0])); - - return await UserLists.pack(userList); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + private userListEntityService: UserListEntityService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const userList = await this.userListsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + name: ps.name, + } as UserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); + + return await this.userListEntityService.pack(userList); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts index 5a7613c98a..0f4125a39f 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts @@ -1,5 +1,7 @@ -import { UserLists } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -29,15 +31,23 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const userList = await UserLists.findOneBy({ - id: ps.listId, - userId: user.id, - }); - - if (userList == null) { - throw new ApiError(meta.errors.noSuchList); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const userList = await this.userListsRepository.findOneBy({ + id: ps.listId, + userId: me.id, + }); + + if (userList == null) { + throw new ApiError(meta.errors.noSuchList); + } + + await this.userListsRepository.delete(userList.id); + }); } - - await UserLists.delete(userList.id); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts index 889052fa30..919de22377 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -1,5 +1,8 @@ -import { UserLists } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['lists', 'account'], @@ -28,10 +31,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const userLists = await UserLists.findBy({ - userId: me.id, - }); - - return await Promise.all(userLists.map(x => UserLists.pack(x))); -}); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + private userListEntityService: UserListEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const userLists = await this.userListsRepository.findBy({ + userId: me.id, + }); + + return await Promise.all(userLists.map(x => this.userListEntityService.pack(x))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index d3d1d6555c..89d97be93e 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -1,8 +1,11 @@ -import { publishUserListStream } from '@/services/stream.js'; -import { UserLists, UserListJoinings, Users } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; export const meta = { tags: ['lists', 'users'], @@ -38,25 +41,40 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the list - const userList = await UserLists.findOneBy({ - id: ps.listId, - userId: me.id, - }); - - if (userList == null) { - throw new ApiError(meta.errors.noSuchList); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the list + const userList = await this.userListsRepository.findOneBy({ + id: ps.listId, + userId: me.id, + }); - // Fetch the user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); + if (userList == null) { + throw new ApiError(meta.errors.noSuchList); + } - // Pull the user - await UserListJoinings.delete({ userListId: userList.id, userId: user.id }); + // Fetch the user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); - publishUserListStream(userList.id, 'userRemoved', await Users.pack(user)); -}); + // Pull the user + await this.userListJoiningsRepository.delete({ userListId: userList.id, userId: user.id }); + + this.globalEventService.publishUserListStream(userList.id, 'userRemoved', await this.userEntityService.pack(user)); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 12b7b86342..77ad772b13 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -1,8 +1,10 @@ -import { pushUserToUserList } from '@/services/user-list/push.js'; -import { UserLists, UserListJoinings, Blockings } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/common/GetterService.js'; +import { UserListService } from '@/core/UserListService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; export const meta = { tags: ['lists', 'users'], @@ -50,43 +52,60 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the list - const userList = await UserLists.findOneBy({ - id: ps.listId, - userId: me.id, - }); - - if (userList == null) { - throw new ApiError(meta.errors.noSuchList); - } - - // Fetch the user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // Check blocking - if (user.id !== me.id) { - const block = await Blockings.findOneBy({ - blockerId: user.id, - blockeeId: me.id, +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + private getterService: GetterService, + private userListService: UserListService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the list + const userList = await this.userListsRepository.findOneBy({ + id: ps.listId, + userId: me.id, + }); + + if (userList == null) { + throw new ApiError(meta.errors.noSuchList); + } + + // Fetch the user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check blocking + if (user.id !== me.id) { + const block = await this.blockingsRepository.findOneBy({ + blockerId: user.id, + blockeeId: me.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } + + const exist = await this.userListJoiningsRepository.findOneBy({ + userListId: userList.id, + userId: user.id, + }); + + if (exist) { + throw new ApiError(meta.errors.alreadyAdded); + } + + // Push the user + await this.userListService.push(user, userList); }); - if (block) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } - - const exist = await UserListJoinings.findOneBy({ - userListId: userList.id, - userId: user.id, - }); - - if (exist) { - throw new ApiError(meta.errors.alreadyAdded); } - - // Push the user - await pushUserToUserList(user, userList); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index fd0612f735..62e730b2f7 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -1,5 +1,8 @@ -import { UserLists } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -35,16 +38,26 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Fetch the list - const userList = await UserLists.findOneBy({ - id: ps.listId, - userId: me.id, - }); - - if (userList == null) { - throw new ApiError(meta.errors.noSuchList); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + private userListEntityService: UserListEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the list + const userList = await this.userListsRepository.findOneBy({ + id: ps.listId, + userId: me.id, + }); + + if (userList == null) { + throw new ApiError(meta.errors.noSuchList); + } + + return await this.userListEntityService.pack(userList); + }); } - - return await UserLists.pack(userList); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts index 65e708b959..c6669d24d1 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -1,5 +1,8 @@ -import { UserLists } from '@/models/index.js'; -import define from '../../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserListsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -36,20 +39,30 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Fetch the list - const userList = await UserLists.findOneBy({ - id: ps.listId, - userId: user.id, - }); - - if (userList == null) { - throw new ApiError(meta.errors.noSuchList); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + private userListEntityService: UserListEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the list + const userList = await this.userListsRepository.findOneBy({ + id: ps.listId, + userId: me.id, + }); - await UserLists.update(userList.id, { - name: ps.name, - }); + if (userList == null) { + throw new ApiError(meta.errors.noSuchList); + } - return await UserLists.pack(userList.id); -}); + await this.userListsRepository.update(userList.id, { + name: ps.name, + }); + + return await this.userListEntityService.pack(userList.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 9fa56fe83a..bb8104584c 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -1,12 +1,12 @@ import { Brackets } from 'typeorm'; -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { NotesRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['users', 'notes'], @@ -53,70 +53,82 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Lookup user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - //#region Construct query - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere('note.userId = :userId', { userId: user.id }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - - generateVisibilityQuery(query, me); - if (me) { - generateMutedUserQuery(query, me, user); - generateBlockedUserQuery(query, me); - } - - if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + // Lookup user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + //#region Construct query + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('note.userId = :userId', { userId: user.id }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); + + this.queryService.generateVisibilityQuery(query, me); + if (me) { + this.queryService.generateMutedUserQuery(query, me, user); + this.queryService.generateBlockedUserQuery(query, me); + } - if (ps.fileType != null) { - query.andWhere('note.fileIds != \'{}\''); - query.andWhere(new Brackets(qb => { - for (const type of ps.fileType!) { - const i = ps.fileType!.indexOf(type); - qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } - })); - if (ps.excludeNsfw) { - query.andWhere('note.cw IS NULL'); - query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)'); - } - } + if (ps.fileType != null) { + query.andWhere('note.fileIds != \'{}\''); + query.andWhere(new Brackets(qb => { + for (const type of ps.fileType!) { + const i = ps.fileType!.indexOf(type); + qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); + } + })); + + if (ps.excludeNsfw) { + query.andWhere('note.cw IS NULL'); + query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)'); + } + } - if (!ps.includeReplies) { - query.andWhere('note.replyId IS NULL'); - } + if (!ps.includeReplies) { + query.andWhere('note.replyId IS NULL'); + } - if (ps.includeMyRenotes === false) { - query.andWhere(new Brackets(qb => { - qb.orWhere('note.userId != :userId', { userId: user.id }); - qb.orWhere('note.renoteId IS NULL'); - qb.orWhere('note.text IS NOT NULL'); - qb.orWhere('note.fileIds != \'{}\''); - qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); - })); - } + if (ps.includeMyRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.userId != :userId', { userId: user.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } - //#endregion + //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit).getMany(); - return await Notes.packMany(timeline, me); -}); + return await this.noteEntityService.packMany(timeline, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index b1d28af845..96c7ef1e70 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -1,6 +1,9 @@ -import { Pages } from '@/models/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { PageEntityService } from '@/core/entities/PageEntityService.js'; +import { PagesRepository } from '@/models'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users', 'pages'], @@ -30,14 +33,25 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId) - .andWhere('page.userId = :userId', { userId: ps.userId }) - .andWhere('page.visibility = \'public\''); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, - const pages = await query - .take(ps.limit) - .getMany(); + private pageEntityService: PageEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.pagesRepository.createQueryBuilder('page'), ps.sinceId, ps.untilId) + .andWhere('page.userId = :userId', { userId: ps.userId }) + .andWhere('page.visibility = \'public\''); - return await Pages.packMany(pages); -}); + const pages = await query + .take(ps.limit) + .getMany(); + + return await this.pageEntityService.packMany(pages); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index 9668bd21b8..6b4d882b7c 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -1,7 +1,9 @@ -import { NoteReactions, UserProfiles } from '@/models/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UserProfilesRepository, NoteReactionsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -44,23 +46,37 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const profile = await UserProfiles.findOneByOrFail({ userId: ps.userId }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, - if (me == null || (me.id !== ps.userId && !profile.publicReactions)) { - throw new ApiError(meta.errors.reactionsNotPublic); - } + @Inject(DI.noteReactionsRepository) + private noteReactionsRepository: NoteReactionsRepository, + + private noteReactionEntityService: NoteReactionEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId }); - const query = makePaginationQuery(NoteReactions.createQueryBuilder('reaction'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere('reaction.userId = :userId', { userId: ps.userId }) - .leftJoinAndSelect('reaction.note', 'note'); + if (me == null || (me.id !== ps.userId && !profile.publicReactions)) { + throw new ApiError(meta.errors.reactionsNotPublic); + } - generateVisibilityQuery(query, me); + const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('reaction.userId = :userId', { userId: ps.userId }) + .leftJoinAndSelect('reaction.note', 'note'); - const reactions = await query - .take(ps.limit) - .getMany(); + this.queryService.generateVisibilityQuery(query, me); - return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, me, { withNote: true }))); -}); + const reactions = await query + .take(ps.limit) + .getMany(); + + return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me, { withNote: true }))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index e7654e1714..e50a5706d9 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -1,8 +1,10 @@ import ms from 'ms'; -import { Users, Followings } from '@/models/index.js'; -import define from '../../define.js'; -import { generateMutedUserQueryForUsers } from '../../common/generate-muted-user-query.js'; -import { generateBlockedUserQuery, generateBlockQueryForUsers } from '../../common/generate-block-query.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users'], @@ -34,29 +36,43 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const query = Users.createQueryBuilder('user') - .where('user.isLocked = FALSE') - .andWhere('user.isExplorable = TRUE') - .andWhere('user.host IS NULL') - .andWhere('user.updatedAt >= :date', { date: new Date(Date.now() - ms('7days')) }) - .andWhere('user.id != :meId', { meId: me.id }) - .orderBy('user.followersCount', 'DESC'); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - generateMutedUserQueryForUsers(query, me); - generateBlockQueryForUsers(query, me); - generateBlockedUserQuery(query, me); + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.usersRepository.createQueryBuilder('user') + .where('user.isLocked = FALSE') + .andWhere('user.isExplorable = TRUE') + .andWhere('user.host IS NULL') + .andWhere('user.updatedAt >= :date', { date: new Date(Date.now() - ms('7days')) }) + .andWhere('user.id != :meId', { meId: me.id }) + .orderBy('user.followersCount', 'DESC'); - const followingQuery = Followings.createQueryBuilder('following') - .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: me.id }); + this.queryService.generateMutedUserQueryForUsers(query, me); + this.queryService.generateBlockQueryForUsers(query, me); + this.queryService.generateBlockedUserQuery(query, me); - query - .andWhere(`user.id NOT IN (${ followingQuery.getQuery() })`); + const followingQuery = this.followingsRepository.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); - query.setParameters(followingQuery.getParameters()); + query + .andWhere(`user.id NOT IN (${ followingQuery.getQuery() })`); - const users = await query.take(ps.limit).skip(ps.offset).getMany(); + query.setParameters(followingQuery.getParameters()); - return await Users.packMany(users, me, { detail: true }); -}); + const users = await query.take(ps.limit).skip(ps.offset).getMany(); + + return await this.userEntityService.packMany(users, me, { detail: true }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index 233a6a90b4..aea75ae799 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -1,5 +1,8 @@ -import { Users } from '@/models/index.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users'], @@ -112,10 +115,20 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const ids = Array.isArray(ps.userId) ? ps.userId : [ps.userId]; +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - const relations = await Promise.all(ids.map(id => Users.getRelation(me.id, id))); + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const ids = Array.isArray(ps.userId) ? ps.userId : [ps.userId]; - return Array.isArray(ps.userId) ? relations : relations[0]; -}); + const relations = await Promise.all(ids.map(id => this.userEntityService.getRelation(me.id, id))); + + return Array.isArray(ps.userId) ? relations : relations[0]; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index a9987eafa9..5c211a9017 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -1,12 +1,14 @@ import * as sanitizeHtml from 'sanitize-html'; -import { publishAdminStream } from '@/services/stream.js'; -import { AbuseUserReports, Users } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { sendEmail } from '@/services/send-email.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { getUser } from '../../common/getters.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { EmailService } from '@/core/EmailService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import define from '../../define.js'; +import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['users'], @@ -46,55 +48,72 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - // Lookup user - const user = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - if (user.id === me.id) { - throw new ApiError(meta.errors.cannotReportYourself); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, - if (user.isAdmin) { - throw new ApiError(meta.errors.cannotReportAdmin); - } + @Inject(DI.abuseUserReportsRepository) + private abuseUserReportsRepository: AbuseUserReportsRepository, - const report = await AbuseUserReports.insert({ - id: genId(), - createdAt: new Date(), - targetUserId: user.id, - targetUserHost: user.host, - reporterId: me.id, - reporterHost: null, - comment: ps.comment, - }).then(x => AbuseUserReports.findOneByOrFail(x.identifiers[0])); - - // Publish event to moderators - setImmediate(async () => { - const moderators = await Users.find({ - where: [{ - isAdmin: true, - }, { - isModerator: true, - }], - }); + private idService: IdService, + private metaService: MetaService, + private emailService: EmailService, + private getterService: GetterService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps, me) => { + // Lookup user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + if (user.id === me.id) { + throw new ApiError(meta.errors.cannotReportYourself); + } + + if (user.isAdmin) { + throw new ApiError(meta.errors.cannotReportAdmin); + } - for (const moderator of moderators) { - publishAdminStream(moderator.id, 'newAbuseUserReport', { - id: report.id, - targetUserId: report.targetUserId, - reporterId: report.reporterId, - comment: report.comment, + const report = await this.abuseUserReportsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + targetUserId: user.id, + targetUserHost: user.host, + reporterId: me.id, + reporterHost: null, + comment: ps.comment, + }).then(x => this.abuseUserReportsRepository.findOneByOrFail(x.identifiers[0])); + + // Publish event to moderators + setImmediate(async () => { + const moderators = await this.usersRepository.find({ + where: [{ + isAdmin: true, + }, { + isModerator: true, + }], + }); + + for (const moderator of moderators) { + this.globalEventService.publishAdminStream(moderator.id, 'newAbuseUserReport', { + id: report.id, + targetUserId: report.targetUserId, + reporterId: report.reporterId, + comment: report.comment, + }); + } + + const meta = await this.metaService.fetch(); + if (meta.email) { + this.emailService.sendEmail(meta.email, 'New abuse report', + sanitizeHtml(ps.comment), + sanitizeHtml(ps.comment)); + } }); - } - - const meta = await fetchMeta(); - if (meta.email) { - sendEmail(meta.email, 'New abuse report', - sanitizeHtml(ps.comment), - sanitizeHtml(ps.comment)); - } - }); -}); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index 6e5bc46bb5..1747dc93f6 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -1,8 +1,11 @@ import { Brackets } from 'typeorm'; -import { Followings, Users } from '@/models/index.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { USER_ACTIVE_THRESHOLD } from '@/const.js'; -import { User } from '@/models/entities/user.js'; -import define from '../../define.js'; +import type { User } from '@/models/entities/User.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users'], @@ -39,78 +42,91 @@ export const paramDef = { // TODO: avatar,bannerをJOINしたいけどエラーになる // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 - - if (ps.host) { - const q = Users.createQueryBuilder('user') - .where('user.isSuspended = FALSE') - .andWhere('user.host LIKE :host', { host: ps.host.toLowerCase() + '%' }); - - if (ps.username) { - q.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }); - } - - q.andWhere('user.updatedAt IS NOT NULL'); - q.orderBy('user.updatedAt', 'DESC'); - - const users = await q.take(ps.limit).getMany(); - - return await Users.packMany(users, me, { detail: ps.detail }); - } else if (ps.username) { - let users: User[] = []; - - if (me) { - const followingQuery = Followings.createQueryBuilder('following') - .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: me.id }); - - const query = Users.createQueryBuilder('user') - .where(`user.id IN (${ followingQuery.getQuery() })`) - .andWhere('user.id != :meId', { meId: me.id }) - .andWhere('user.isSuspended = FALSE') - .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) - .andWhere(new Brackets(qb => { qb - .where('user.updatedAt IS NULL') - .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); - })); - - query.setParameters(followingQuery.getParameters()); - - users = await query - .orderBy('user.usernameLower', 'ASC') - .take(ps.limit) - .getMany(); - - if (users.length < ps.limit) { - const otherQuery = await Users.createQueryBuilder('user') - .where(`user.id NOT IN (${ followingQuery.getQuery() })`) - .andWhere('user.id != :meId', { meId: me.id }) - .andWhere('user.isSuspended = FALSE') - .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) - .andWhere('user.updatedAt IS NOT NULL'); - - otherQuery.setParameters(followingQuery.getParameters()); - - const otherUsers = await otherQuery - .orderBy('user.updatedAt', 'DESC') - .take(ps.limit - users.length) - .getMany(); - - users = users.concat(otherUsers); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 + + if (ps.host) { + const q = this.usersRepository.createQueryBuilder('user') + .where('user.isSuspended = FALSE') + .andWhere('user.host LIKE :host', { host: ps.host.toLowerCase() + '%' }); + + if (ps.username) { + q.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }); + } + + q.andWhere('user.updatedAt IS NOT NULL'); + q.orderBy('user.updatedAt', 'DESC'); + + const users = await q.take(ps.limit).getMany(); + + return await this.userEntityService.packMany(users, me, { detail: ps.detail }); + } else if (ps.username) { + let users: User[] = []; + + if (me) { + const followingQuery = this.followingsRepository.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); + + const query = this.usersRepository.createQueryBuilder('user') + .where(`user.id IN (${ followingQuery.getQuery() })`) + .andWhere('user.id != :meId', { meId: me.id }) + .andWhere('user.isSuspended = FALSE') + .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) + .andWhere(new Brackets(qb => { qb + .where('user.updatedAt IS NULL') + .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); + })); + + query.setParameters(followingQuery.getParameters()); + + users = await query + .orderBy('user.usernameLower', 'ASC') + .take(ps.limit) + .getMany(); + + if (users.length < ps.limit) { + const otherQuery = await this.usersRepository.createQueryBuilder('user') + .where(`user.id NOT IN (${ followingQuery.getQuery() })`) + .andWhere('user.id != :meId', { meId: me.id }) + .andWhere('user.isSuspended = FALSE') + .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) + .andWhere('user.updatedAt IS NOT NULL'); + + otherQuery.setParameters(followingQuery.getParameters()); + + const otherUsers = await otherQuery + .orderBy('user.updatedAt', 'DESC') + .take(ps.limit - users.length) + .getMany(); + + users = users.concat(otherUsers); + } + } else { + users = await this.usersRepository.createQueryBuilder('user') + .where('user.isSuspended = FALSE') + .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) + .andWhere('user.updatedAt IS NOT NULL') + .orderBy('user.updatedAt', 'DESC') + .take(ps.limit - users.length) + .getMany(); + } + + return await this.userEntityService.packMany(users, me, { detail: !!ps.detail }); } - } else { - users = await Users.createQueryBuilder('user') - .where('user.isSuspended = FALSE') - .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) - .andWhere('user.updatedAt IS NOT NULL') - .orderBy('user.updatedAt', 'DESC') - .take(ps.limit - users.length) - .getMany(); - } - - return await Users.packMany(users, me, { detail: !!ps.detail }); - } - return []; -}); + return []; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 01729de667..9879b1b68b 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -1,7 +1,10 @@ import { Brackets } from 'typeorm'; -import { UserProfiles, Users } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { User } from '@/models/entities/User.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['users'], @@ -34,89 +37,102 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 - - const isUsername = ps.query.startsWith('@'); - - let users: User[] = []; - - if (isUsername) { - const usernameQuery = Users.createQueryBuilder('user') - .where('user.usernameLower LIKE :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) - .andWhere(new Brackets(qb => { qb - .where('user.updatedAt IS NULL') - .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); - })) - .andWhere('user.isSuspended = FALSE'); - - if (ps.origin === 'local') { - usernameQuery.andWhere('user.host IS NULL'); - } else if (ps.origin === 'remote') { - usernameQuery.andWhere('user.host IS NOT NULL'); - } - - users = await usernameQuery - .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') - .take(ps.limit) - .skip(ps.offset) - .getMany(); - } else { - const nameQuery = Users.createQueryBuilder('user') - .where(new Brackets(qb => { - qb.where('user.name ILIKE :query', { query: '%' + ps.query + '%' }); - - // Also search username if it qualifies as username - if (Users.validateLocalUsername(ps.query)) { - qb.orWhere('user.usernameLower LIKE :username', { username: '%' + ps.query.toLowerCase() + '%' }); +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 + + const isUsername = ps.query.startsWith('@'); + + let users: User[] = []; + + if (isUsername) { + const usernameQuery = this.usersRepository.createQueryBuilder('user') + .where('user.usernameLower LIKE :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) + .andWhere(new Brackets(qb => { qb + .where('user.updatedAt IS NULL') + .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); + })) + .andWhere('user.isSuspended = FALSE'); + + if (ps.origin === 'local') { + usernameQuery.andWhere('user.host IS NULL'); + } else if (ps.origin === 'remote') { + usernameQuery.andWhere('user.host IS NOT NULL'); + } + + users = await usernameQuery + .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') + .take(ps.limit) + .skip(ps.offset) + .getMany(); + } else { + const nameQuery = this.usersRepository.createQueryBuilder('user') + .where(new Brackets(qb => { + qb.where('user.name ILIKE :query', { query: '%' + ps.query + '%' }); + + // Also search username if it qualifies as username + if (this.userEntityService.validateLocalUsername(ps.query)) { + qb.orWhere('user.usernameLower LIKE :username', { username: '%' + ps.query.toLowerCase() + '%' }); + } + })) + .andWhere(new Brackets(qb => { qb + .where('user.updatedAt IS NULL') + .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); + })) + .andWhere('user.isSuspended = FALSE'); + + if (ps.origin === 'local') { + nameQuery.andWhere('user.host IS NULL'); + } else if (ps.origin === 'remote') { + nameQuery.andWhere('user.host IS NOT NULL'); + } + + users = await nameQuery + .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') + .take(ps.limit) + .skip(ps.offset) + .getMany(); + + if (users.length < ps.limit) { + const profQuery = this.userProfilesRepository.createQueryBuilder('prof') + .select('prof.userId') + .where('prof.description ILIKE :query', { query: '%' + ps.query + '%' }); + + if (ps.origin === 'local') { + profQuery.andWhere('prof.userHost IS NULL'); + } else if (ps.origin === 'remote') { + profQuery.andWhere('prof.userHost IS NOT NULL'); + } + + const query = this.usersRepository.createQueryBuilder('user') + .where(`user.id IN (${ profQuery.getQuery() })`) + .andWhere(new Brackets(qb => { qb + .where('user.updatedAt IS NULL') + .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); + })) + .andWhere('user.isSuspended = FALSE') + .setParameters(profQuery.getParameters()); + + users = users.concat(await query + .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') + .take(ps.limit) + .skip(ps.offset) + .getMany(), + ); } - })) - .andWhere(new Brackets(qb => { qb - .where('user.updatedAt IS NULL') - .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); - })) - .andWhere('user.isSuspended = FALSE'); - - if (ps.origin === 'local') { - nameQuery.andWhere('user.host IS NULL'); - } else if (ps.origin === 'remote') { - nameQuery.andWhere('user.host IS NOT NULL'); - } - - users = await nameQuery - .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') - .take(ps.limit) - .skip(ps.offset) - .getMany(); - - if (users.length < ps.limit) { - const profQuery = UserProfiles.createQueryBuilder('prof') - .select('prof.userId') - .where('prof.description ILIKE :query', { query: '%' + ps.query + '%' }); - - if (ps.origin === 'local') { - profQuery.andWhere('prof.userHost IS NULL'); - } else if (ps.origin === 'remote') { - profQuery.andWhere('prof.userHost IS NOT NULL'); } - const query = Users.createQueryBuilder('user') - .where(`user.id IN (${ profQuery.getQuery() })`) - .andWhere(new Brackets(qb => { qb - .where('user.updatedAt IS NULL') - .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); - })) - .andWhere('user.isSuspended = FALSE') - .setParameters(profQuery.getParameters()); - - users = users.concat(await query - .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') - .take(ps.limit) - .skip(ps.offset) - .getMany(), - ); - } + return await this.userEntityService.packMany(users, me, { detail: ps.detail }); + }); } - - return await Users.packMany(users, me, { detail: ps.detail }); -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 846d83b49f..98f5f03063 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -1,10 +1,14 @@ -import { FindOptionsWhere, In, IsNull } from 'typeorm'; -import { resolveUser } from '@/remote/resolve-user.js'; -import { Users } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import define from '../../define.js'; -import { apiLogger } from '../../logger.js'; +import { In, IsNull } from 'typeorm'; +import { Inject, Injectable } from '@nestjs/common'; +import { UsersRepository } from '@/models/index.js'; +import type { User } from '@/models/entities/User.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import { ApiLoggerService } from '../../ApiLoggerService.js'; +import type { FindOptionsWhere } from 'typeorm'; export const meta = { tags: ['users'], @@ -78,53 +82,65 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - let user; - - const isAdminOrModerator = me && (me.isAdmin || me.isModerator); - - if (ps.userIds) { - if (ps.userIds.length === 0) { - return []; - } - - const users = await Users.findBy(isAdminOrModerator ? { - id: In(ps.userIds), - } : { - id: In(ps.userIds), - isSuspended: false, - }); - - // リクエストされた通りに並べ替え - const _users: User[] = []; - for (const id of ps.userIds) { - _users.push(users.find(x => x.id === id)!); - } - - return await Promise.all(_users.map(u => Users.pack(u, me, { - detail: true, - }))); - } else { - // Lookup user - if (typeof ps.host === 'string' && typeof ps.username === 'string') { - user = await resolveUser(ps.username, ps.host).catch(e => { - apiLogger.warn(`failed to resolve remote user: ${e}`); - throw new ApiError(meta.errors.failedToResolveRemoteUser); - }); - } else { - const q: FindOptionsWhere = ps.userId != null - ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: IsNull() }; - - user = await Users.findOneBy(q); - } - - if (user == null || (!isAdminOrModerator && user.isSuspended)) { - throw new ApiError(meta.errors.noSuchUser); - } - - return await Users.pack(user, me, { - detail: true, +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private userEntityService: UserEntityService, + private resolveUserService: ResolveUserService, + private apiLoggerService: ApiLoggerService, + ) { + super(meta, paramDef, async (ps, me) => { + let user; + + const isAdminOrModerator = me && (me.isAdmin || me.isModerator); + + if (ps.userIds) { + if (ps.userIds.length === 0) { + return []; + } + + const users = await this.usersRepository.findBy(isAdminOrModerator ? { + id: In(ps.userIds), + } : { + id: In(ps.userIds), + isSuspended: false, + }); + + // リクエストされた通りに並べ替え + const _users: User[] = []; + for (const id of ps.userIds) { + _users.push(users.find(x => x.id === id)!); + } + + return await Promise.all(_users.map(u => this.userEntityService.pack(u, me, { + detail: true, + }))); + } else { + // Lookup user + if (typeof ps.host === 'string' && typeof ps.username === 'string') { + user = await this.resolveUserService.resolveUser(ps.username, ps.host).catch(err => { + this.apiLoggerService.logger.warn(`failed to resolve remote user: ${err}`); + throw new ApiError(meta.errors.failedToResolveRemoteUser); + }); + } else { + const q: FindOptionsWhere = ps.userId != null + ? { id: ps.userId } + : { usernameLower: ps.username!.toLowerCase(), host: IsNull() }; + + user = await this.usersRepository.findOneBy(q); + } + + if (user == null || (!isAdminOrModerator && user.isSuspended)) { + throw new ApiError(meta.errors.noSuchUser); + } + + return await this.userEntityService.pack(user, me, { + detail: true, + }); + } }); } -}); +} diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index 47f322ee9b..71f4ca0cfa 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -1,6 +1,8 @@ -import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index.js'; -import { awaitAll } from '@/prelude/await-all.js'; -import define from '../../define.js'; +import { Inject, Injectable } from '@nestjs/common'; +import { awaitAll } from '@/misc/prelude/await-all.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -116,78 +118,109 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); - if (user == null) { - throw new ApiError(meta.errors.noSuchUser); - } +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.noteReactionsRepository) + private noteReactionsRepository: NoteReactionsRepository, + + @Inject(DI.pageLikesRepository) + private pageLikesRepository: PageLikesRepository, - const result = await awaitAll({ - notesCount: Notes.createQueryBuilder('note') - .where('note.userId = :userId', { userId: user.id }) - .getCount(), - repliesCount: Notes.createQueryBuilder('note') - .where('note.userId = :userId', { userId: user.id }) - .andWhere('note.replyId IS NOT NULL') - .getCount(), - renotesCount: Notes.createQueryBuilder('note') - .where('note.userId = :userId', { userId: user.id }) - .andWhere('note.renoteId IS NOT NULL') - .getCount(), - repliedCount: Notes.createQueryBuilder('note') - .where('note.replyUserId = :userId', { userId: user.id }) - .getCount(), - renotedCount: Notes.createQueryBuilder('note') - .where('note.renoteUserId = :userId', { userId: user.id }) - .getCount(), - pollVotesCount: PollVotes.createQueryBuilder('vote') - .where('vote.userId = :userId', { userId: user.id }) - .getCount(), - pollVotedCount: PollVotes.createQueryBuilder('vote') - .innerJoin('vote.note', 'note') - .where('note.userId = :userId', { userId: user.id }) - .getCount(), - localFollowingCount: Followings.createQueryBuilder('following') - .where('following.followerId = :userId', { userId: user.id }) - .andWhere('following.followeeHost IS NULL') - .getCount(), - remoteFollowingCount: Followings.createQueryBuilder('following') - .where('following.followerId = :userId', { userId: user.id }) - .andWhere('following.followeeHost IS NOT NULL') - .getCount(), - localFollowersCount: Followings.createQueryBuilder('following') - .where('following.followeeId = :userId', { userId: user.id }) - .andWhere('following.followerHost IS NULL') - .getCount(), - remoteFollowersCount: Followings.createQueryBuilder('following') - .where('following.followeeId = :userId', { userId: user.id }) - .andWhere('following.followerHost IS NOT NULL') - .getCount(), - sentReactionsCount: NoteReactions.createQueryBuilder('reaction') - .where('reaction.userId = :userId', { userId: user.id }) - .getCount(), - receivedReactionsCount: NoteReactions.createQueryBuilder('reaction') - .innerJoin('reaction.note', 'note') - .where('note.userId = :userId', { userId: user.id }) - .getCount(), - noteFavoritesCount: NoteFavorites.createQueryBuilder('favorite') - .where('favorite.userId = :userId', { userId: user.id }) - .getCount(), - pageLikesCount: PageLikes.createQueryBuilder('like') - .where('like.userId = :userId', { userId: user.id }) - .getCount(), - pageLikedCount: PageLikes.createQueryBuilder('like') - .innerJoin('like.page', 'page') - .where('page.userId = :userId', { userId: user.id }) - .getCount(), - driveFilesCount: DriveFiles.createQueryBuilder('file') - .where('file.userId = :userId', { userId: user.id }) - .getCount(), - driveUsage: DriveFiles.calcDriveUsageOf(user), - }); - - result.followingCount = result.localFollowingCount + result.remoteFollowingCount; - result.followersCount = result.localFollowersCount + result.remoteFollowersCount; - - return result; -}); + @Inject(DI.noteFavoritesRepository) + private noteFavoritesRepository: NoteFavoritesRepository, + + @Inject(DI.pollVotesRepository) + private pollVotesRepository: PollVotesRepository, + + private driveFileEntityService: DriveFileEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + if (user == null) { + throw new ApiError(meta.errors.noSuchUser); + } + + const result = await awaitAll({ + notesCount: this.notesRepository.createQueryBuilder('note') + .where('note.userId = :userId', { userId: user.id }) + .getCount(), + repliesCount: this.notesRepository.createQueryBuilder('note') + .where('note.userId = :userId', { userId: user.id }) + .andWhere('note.replyId IS NOT NULL') + .getCount(), + renotesCount: this.notesRepository.createQueryBuilder('note') + .where('note.userId = :userId', { userId: user.id }) + .andWhere('note.renoteId IS NOT NULL') + .getCount(), + repliedCount: this.notesRepository.createQueryBuilder('note') + .where('note.replyUserId = :userId', { userId: user.id }) + .getCount(), + renotedCount: this.notesRepository.createQueryBuilder('note') + .where('note.renoteUserId = :userId', { userId: user.id }) + .getCount(), + pollVotesCount: this.pollVotesRepository.createQueryBuilder('vote') + .where('vote.userId = :userId', { userId: user.id }) + .getCount(), + pollVotedCount: this.pollVotesRepository.createQueryBuilder('vote') + .innerJoin('vote.note', 'note') + .where('note.userId = :userId', { userId: user.id }) + .getCount(), + localFollowingCount: this.followingsRepository.createQueryBuilder('following') + .where('following.followerId = :userId', { userId: user.id }) + .andWhere('following.followeeHost IS NULL') + .getCount(), + remoteFollowingCount: this.followingsRepository.createQueryBuilder('following') + .where('following.followerId = :userId', { userId: user.id }) + .andWhere('following.followeeHost IS NOT NULL') + .getCount(), + localFollowersCount: this.followingsRepository.createQueryBuilder('following') + .where('following.followeeId = :userId', { userId: user.id }) + .andWhere('following.followerHost IS NULL') + .getCount(), + remoteFollowersCount: this.followingsRepository.createQueryBuilder('following') + .where('following.followeeId = :userId', { userId: user.id }) + .andWhere('following.followerHost IS NOT NULL') + .getCount(), + sentReactionsCount: this.noteReactionsRepository.createQueryBuilder('reaction') + .where('reaction.userId = :userId', { userId: user.id }) + .getCount(), + receivedReactionsCount: this.noteReactionsRepository.createQueryBuilder('reaction') + .innerJoin('reaction.note', 'note') + .where('note.userId = :userId', { userId: user.id }) + .getCount(), + noteFavoritesCount: this.noteFavoritesRepository.createQueryBuilder('favorite') + .where('favorite.userId = :userId', { userId: user.id }) + .getCount(), + pageLikesCount: this.pageLikesRepository.createQueryBuilder('like') + .where('like.userId = :userId', { userId: user.id }) + .getCount(), + pageLikedCount: this.pageLikesRepository.createQueryBuilder('like') + .innerJoin('like.page', 'page') + .where('page.userId = :userId', { userId: user.id }) + .getCount(), + driveFilesCount: this.driveFilesRepository.createQueryBuilder('file') + .where('file.userId = :userId', { userId: user.id }) + .getCount(), + driveUsage: this.driveFileEntityService.calcDriveUsageOf(user), + }); + + result.followingCount = result.localFollowingCount + result.remoteFollowingCount; + result.followersCount = result.localFollowersCount + result.remoteFollowersCount; + + return result; + }); + } +} -- cgit v1.2.3-freya From a2eac9fff67f811ed4ac1a80a88fd1f0eafae6c8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 19 Sep 2022 03:11:50 +0900 Subject: test --- packages/backend/src/core/AiService.ts | 10 +- packages/backend/src/core/AntennaService.ts | 28 ++-- packages/backend/src/core/AppLockService.ts | 10 +- packages/backend/src/core/CaptchaService.ts | 6 +- .../backend/src/core/CreateNotificationService.ts | 8 +- packages/backend/src/core/CustomEmojiService.ts | 26 ++-- packages/backend/src/core/DownloadService.ts | 20 +-- packages/backend/src/core/DriveService.ts | 82 ++++++------ packages/backend/src/core/EmailService.ts | 8 +- .../backend/src/core/FederatedInstanceService.ts | 10 +- .../src/core/FetchInstanceMetadataService.ts | 52 ++++---- packages/backend/src/core/FileInfoService.ts | 26 ++-- packages/backend/src/core/HttpRequestService.ts | 14 +- packages/backend/src/core/IdService.ts | 6 +- packages/backend/src/core/InstanceActorService.ts | 10 +- packages/backend/src/core/LoggerService.ts | 6 +- packages/backend/src/core/MessagingService.ts | 4 +- packages/backend/src/core/MetaService.ts | 16 +-- packages/backend/src/core/NoteCreateService.ts | 36 +++--- packages/backend/src/core/NoteDeleteService.ts | 14 +- packages/backend/src/core/NotificationService.ts | 8 +- packages/backend/src/core/RelayService.ts | 12 +- packages/backend/src/core/UserBlockingService.ts | 16 +-- packages/backend/src/core/UserFollowingService.ts | 38 +++--- .../backend/src/core/UserKeypairStoreService.ts | 6 +- packages/backend/src/core/WebhookService.ts | 24 ++-- packages/backend/src/core/chart/core.ts | 12 +- .../backend/src/core/entities/NoteEntityService.ts | 12 +- .../backend/src/core/entities/UserEntityService.ts | 6 +- .../backend/src/core/remote/ResolveUserService.ts | 34 ++--- .../backend/src/core/remote/WebfingerService.ts | 4 +- .../core/remote/activitypub/ApAudienceService.ts | 14 +- .../core/remote/activitypub/ApDbResolverService.ts | 12 +- .../src/core/remote/activitypub/ApInboxService.ts | 144 ++++++++++----------- .../core/remote/activitypub/ApRendererService.ts | 6 +- .../core/remote/activitypub/ApRequestService.ts | 32 ++--- .../remote/activitypub/models/ApImageService.ts | 6 +- .../remote/activitypub/models/ApNoteService.ts | 22 ++-- .../remote/activitypub/models/ApPersonService.ts | 26 ++-- .../remote/activitypub/models/ApQuestionService.ts | 6 +- packages/backend/src/daemons/JanitorService.ts | 6 +- packages/backend/src/daemons/QueueStatsService.ts | 6 +- packages/backend/src/daemons/ServerStatsService.ts | 6 +- .../backend/src/queue/QueueProcessorService.ts | 16 +-- .../CheckExpiredMutingsProcessorService.ts | 8 +- .../processors/CleanChartsProcessorService.ts | 8 +- .../src/queue/processors/CleanProcessorService.ts | 8 +- .../processors/CleanRemoteFilesProcessorService.ts | 8 +- .../processors/DeleteAccountProcessorService.ts | 10 +- .../processors/DeleteDriveFilesProcessorService.ts | 8 +- .../queue/processors/DeleteFileProcessorService.ts | 4 +- .../queue/processors/DeliverProcessorService.ts | 20 +-- .../EndedPollNotificationProcessorService.ts | 4 +- .../processors/ExportBlockingProcessorService.ts | 14 +- .../ExportCustomEmojisProcessorService.ts | 16 +-- .../processors/ExportFollowingProcessorService.ts | 14 +- .../processors/ExportMutingProcessorService.ts | 14 +- .../processors/ExportNotesProcessorService.ts | 14 +- .../processors/ExportUserListsProcessorService.ts | 14 +- .../processors/ImportBlockingProcessorService.ts | 12 +- .../ImportCustomEmojisProcessorService.ts | 14 +- .../processors/ImportFollowingProcessorService.ts | 12 +- .../processors/ImportMutingProcessorService.ts | 12 +- .../processors/ImportUserListsProcessorService.ts | 10 +- .../src/queue/processors/InboxProcessorService.ts | 6 +- .../processors/ResyncChartsProcessorService.ts | 8 +- .../queue/processors/TickChartsProcessorService.ts | 8 +- .../processors/WebhookDeliverProcessorService.ts | 6 +- .../backend/src/server/ActivityPubServerService.ts | 64 ++++----- packages/backend/src/server/FileServerService.ts | 14 +- .../backend/src/server/MediaProxyServerService.ts | 10 +- packages/backend/src/server/ServerService.ts | 12 +- packages/backend/src/server/api/ApiCallService.ts | 28 ++-- .../backend/src/server/api/AuthenticateService.ts | 6 +- .../backend/src/server/api/RateLimiterService.ts | 8 +- .../src/server/api/endpoints/admin/suspend-user.ts | 8 +- .../backend/src/server/api/endpoints/ap/show.ts | 12 +- .../server/api/integration/DiscordServerService.ts | 14 +- .../server/api/integration/GithubServerService.ts | 14 +- .../server/api/integration/TwitterServerService.ts | 14 +- .../backend/src/server/web/ClientServerService.ts | 4 +- .../backend/src/server/web/UrlPreviewService.ts | 16 +-- 82 files changed, 671 insertions(+), 671 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index 1cfc3382a9..e6102a1b91 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -15,7 +15,7 @@ let isSupportedCpu: undefined | boolean = undefined; @Injectable() export class AiService { - #model: nsfw.NSFWJS; + private model: nsfw.NSFWJS; constructor( @Inject(DI.config) @@ -26,7 +26,7 @@ export class AiService { public async detectSensitive(path: string): Promise { try { if (isSupportedCpu === undefined) { - const cpuFlags = await this.#getCpuFlags(); + const cpuFlags = await this.getCpuFlags(); isSupportedCpu = REQUIRED_CPU_FLAGS.every(required => cpuFlags.includes(required)); } @@ -37,12 +37,12 @@ export class AiService { const tf = await import('@tensorflow/tfjs-node'); - if (this.#model == null) this.#model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 }); + if (this.model == null) this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 }); const buffer = await fs.promises.readFile(path); const image = await tf.node.decodeImage(buffer, 3) as any; try { - const predictions = await this.#model.classify(image); + const predictions = await this.model.classify(image); return predictions; } finally { image.dispose(); @@ -53,7 +53,7 @@ export class AiService { } } - async #getCpuFlags(): Promise { + private async getCpuFlags(): Promise { const str = await si.cpuFlags(); return str.split(/\s+/); } diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 8993880a06..e0af033952 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -16,9 +16,9 @@ import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class AntennaService implements OnApplicationShutdown { - #antennasFetched: boolean; - #antennas: Antenna[]; - #blockingCache: Cache; + private antennasFetched: boolean; + private antennas: Antenna[]; + private blockingCache: Cache; constructor( @Inject(DI.redisSubscriber) @@ -49,9 +49,9 @@ export class AntennaService implements OnApplicationShutdown { private idService: IdService, private globalEventServie: GlobalEventService, ) { - this.#antennasFetched = false; - this.#antennas = []; - this.#blockingCache = new Cache(1000 * 60 * 5); + this.antennasFetched = false; + this.antennas = []; + this.blockingCache = new Cache(1000 * 60 * 5); this.redisSubscriber.on('message', this.onRedisMessage); } @@ -67,13 +67,13 @@ export class AntennaService implements OnApplicationShutdown { const { type, body } = obj.message; switch (type) { case 'antennaCreated': - this.#antennas.push(body); + this.antennas.push(body); break; case 'antennaUpdated': - this.#antennas[this.#antennas.findIndex(a => a.id === body.id)] = body; + this.antennas[this.antennas.findIndex(a => a.id === body.id)] = body; break; case 'antennaDeleted': - this.#antennas = this.#antennas.filter(a => a.id !== body.id); + this.antennas = this.antennas.filter(a => a.id !== body.id); break; default: break; @@ -137,7 +137,7 @@ export class AntennaService implements OnApplicationShutdown { if (note.visibility === 'specified') return false; // アンテナ作成者がノート作成者にブロックされていたらスキップ - const blockings = await this.#blockingCache.fetch(noteUser.id, () => this.blockingsRepository.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId))); + const blockings = await this.blockingCache.fetch(noteUser.id, () => this.blockingsRepository.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId))); if (blockings.some(blocking => blocking === antenna.userId)) return false; if (note.visibility === 'followers') { @@ -218,11 +218,11 @@ export class AntennaService implements OnApplicationShutdown { } public async getAntennas() { - if (!this.#antennasFetched) { - this.#antennas = await this.antennasRepository.find(); - this.#antennasFetched = true; + if (!this.antennasFetched) { + this.antennas = await this.antennasRepository.find(); + this.antennasFetched = true; } - return this.#antennas; + return this.antennas; } } diff --git a/packages/backend/src/core/AppLockService.ts b/packages/backend/src/core/AppLockService.ts index f3c345493b..04b3d8b112 100644 --- a/packages/backend/src/core/AppLockService.ts +++ b/packages/backend/src/core/AppLockService.ts @@ -11,13 +11,13 @@ const retryDelay = 100; @Injectable() export class AppLockService { - #lock: (key: string, timeout?: number) => Promise<() => void>; + private lock: (key: string, timeout?: number) => Promise<() => void>; constructor( @Inject(DI.redis) private redisClient: Redis.Redis, ) { - this.#lock = promisify(redisLock(this.redisClient, retryDelay)); + this.lock = promisify(redisLock(this.redisClient, retryDelay)); } /** @@ -27,14 +27,14 @@ export class AppLockService { * @returns Unlock function */ public getApLock(uri: string, timeout = 30 * 1000): Promise<() => void> { - return this.#lock(`ap-object:${uri}`, timeout); + return this.lock(`ap-object:${uri}`, timeout); } public getFetchInstanceMetadataLock(host: string, timeout = 30 * 1000): Promise<() => void> { - return this.#lock(`instance:${host}`, timeout); + return this.lock(`instance:${host}`, timeout); } public getChartInsertLock(lockKey: string, timeout = 30 * 1000): Promise<() => void> { - return this.#lock(`chart-insert:${lockKey}`, timeout); + return this.lock(`chart-insert:${lockKey}`, timeout); } } diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index 891e5315c2..b1b52fd6a9 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -19,7 +19,7 @@ export class CaptchaService { ) { } - async #getCaptchaResponse(url: string, secret: string, response: string): Promise { + private async getCaptchaResponse(url: string, secret: string, response: string): Promise { const params = new URLSearchParams({ secret, response, @@ -46,7 +46,7 @@ export class CaptchaService { } public async verifyRecaptcha(secret: string, response: string): Promise { - const result = await this.#getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(e => { + const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(e => { throw `recaptcha-request-failed: ${e}`; }); @@ -57,7 +57,7 @@ export class CaptchaService { } public async verifyHcaptcha(secret: string, response: string): Promise { - const result = await this.#getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(e => { + const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(e => { throw `hcaptcha-request-failed: ${e}`; }); diff --git a/packages/backend/src/core/CreateNotificationService.ts b/packages/backend/src/core/CreateNotificationService.ts index 10aa2c5df5..525fac6d92 100644 --- a/packages/backend/src/core/CreateNotificationService.ts +++ b/packages/backend/src/core/CreateNotificationService.ts @@ -78,8 +78,8 @@ export class CreateNotificationService { this.globalEventServie.publishMainStream(notifieeId, 'unreadNotification', packed); this.pushNotificationService.pushNotification(notifieeId, 'notification', packed); - if (type === 'follow') this.#emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! })); - if (type === 'receiveFollowRequest') this.#emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! })); + if (type === 'follow') this.emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! })); + if (type === 'receiveFollowRequest') this.emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! })); }, 2000); return notification; @@ -90,7 +90,7 @@ export class CreateNotificationService { // TODO: locale ファイルをクライアント用とサーバー用で分けたい - async #emailNotificationFollow(userId: User['id'], follower: User) { + private async emailNotificationFollow(userId: User['id'], follower: User) { /* const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); if (!userProfile.email || !userProfile.emailNotificationTypes.includes('follow')) return; @@ -101,7 +101,7 @@ export class CreateNotificationService { */ } - async #emailNotificationReceiveFollowRequest(userId: User['id'], follower: User) { + private async emailNotificationReceiveFollowRequest(userId: User['id'], follower: User) { /* const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); if (!userProfile.email || !userProfile.emailNotificationTypes.includes('receiveFollowRequest')) return; diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 83e28baea4..32dad70d1c 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -23,7 +23,7 @@ type PopulatedEmoji = { @Injectable() export class CustomEmojiService { - #cache: Cache; + private cache: Cache; constructor( @Inject(DI.config) @@ -40,7 +40,7 @@ export class CustomEmojiService { private utilityService: UtilityService, private reactionService: ReactionService, ) { - this.#cache = new Cache(1000 * 60 * 60 * 12); + this.cache = new Cache(1000 * 60 * 60 * 12); } public async add(data: { @@ -67,7 +67,7 @@ export class CustomEmojiService { return emoji; } - #normalizeHost(src: string | undefined, noteUserHost: string | null): string | null { + private normalizeHost(src: string | undefined, noteUserHost: string | null): string | null { // クエリに使うホスト let host = src === '.' ? null // .はローカルホスト (ここがマッチするのはリアクションのみ) : src === undefined ? noteUserHost // ノートなどでホスト省略表記の場合はローカルホスト (ここがリアクションにマッチすることはない) @@ -79,14 +79,14 @@ export class CustomEmojiService { return host; } - #parseEmojiStr(emojiName: string, noteUserHost: string | null) { + private parseEmojiStr(emojiName: string, noteUserHost: string | null) { const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/); if (!match) return { name: null, host: null }; const name = match[1]; // ホスト正規化 - const host = this.utilityService.toPunyNullable(this.#normalizeHost(match[2], noteUserHost)); + const host = this.utilityService.toPunyNullable(this.normalizeHost(match[2], noteUserHost)); return { name, host }; } @@ -98,7 +98,7 @@ export class CustomEmojiService { * @returns 絵文字情報, nullは未マッチを意味する */ public async populateEmoji(emojiName: string, noteUserHost: string | null): Promise { - const { name, host } = this.#parseEmojiStr(emojiName, noteUserHost); + const { name, host } = this.parseEmojiStr(emojiName, noteUserHost); if (name == null) return null; const queryOrNull = async () => (await this.emojisRepository.findOneBy({ @@ -106,7 +106,7 @@ export class CustomEmojiService { host: host ?? IsNull(), })) ?? null; - const emoji = await this.#cache.fetch(`${name} ${host}`, queryOrNull); + const emoji = await this.cache.fetch(`${name} ${host}`, queryOrNull); if (emoji == null) return null; @@ -132,20 +132,20 @@ export class CustomEmojiService { let emojis: { name: string | null; host: string | null; }[] = []; for (const note of notes) { emojis = emojis.concat(note.emojis - .map(e => this.#parseEmojiStr(e, note.userHost))); + .map(e => this.parseEmojiStr(e, note.userHost))); if (note.renote) { emojis = emojis.concat(note.renote.emojis - .map(e => this.#parseEmojiStr(e, note.renote!.userHost))); + .map(e => this.parseEmojiStr(e, note.renote!.userHost))); if (note.renote.user) { emojis = emojis.concat(note.renote.user.emojis - .map(e => this.#parseEmojiStr(e, note.renote!.userHost))); + .map(e => this.parseEmojiStr(e, note.renote!.userHost))); } } const customReactions = Object.keys(note.reactions).map(x => this.reactionService.decodeReaction(x)).filter(x => x.name != null) as typeof emojis; emojis = emojis.concat(customReactions); if (note.user) { emojis = emojis.concat(note.user.emojis - .map(e => this.#parseEmojiStr(e, note.userHost))); + .map(e => this.parseEmojiStr(e, note.userHost))); } } return emojis.filter(x => x.name != null) as { name: string; host: string | null; }[]; @@ -155,7 +155,7 @@ export class CustomEmojiService { * 与えられた絵文字のリストをデータベースから取得し、キャッシュに追加します */ public async prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise { - const notCachedEmojis = emojis.filter(emoji => this.#cache.get(`${emoji.name} ${emoji.host}`) == null); + const notCachedEmojis = emojis.filter(emoji => this.cache.get(`${emoji.name} ${emoji.host}`) == null); const emojisQuery: any[] = []; const hosts = new Set(notCachedEmojis.map(e => e.host)); for (const host of hosts) { @@ -169,7 +169,7 @@ export class CustomEmojiService { select: ['name', 'host', 'originalUrl', 'publicUrl'], }) : []; for (const emoji of _emojis) { - this.#cache.set(`${emoji.name} ${emoji.host}`, emoji); + this.cache.set(`${emoji.name} ${emoji.host}`, emoji); } } } diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts index 9c5c45ed2c..81939d5f51 100644 --- a/packages/backend/src/core/DownloadService.ts +++ b/packages/backend/src/core/DownloadService.ts @@ -18,7 +18,7 @@ const pipeline = util.promisify(stream.pipeline); @Injectable() export class DownloadService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -27,11 +27,11 @@ export class DownloadService { private httpRequestService: HttpRequestService, private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('download'); + this.logger = this.loggerService.getLogger('download'); } public async downloadUrl(url: string, path: string): Promise { - this.#logger.info(`Downloading ${chalk.cyan(url)} ...`); + this.logger.info(`Downloading ${chalk.cyan(url)} ...`); const timeout = 30 * 1000; const operationTimeout = 60 * 1000; @@ -60,8 +60,8 @@ export class DownloadService { }, }).on('response', (res: Got.Response) => { if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !this.config.proxy && res.ip) { - if (this.#isPrivateIp(res.ip)) { - this.#logger.warn(`Blocked address: ${res.ip}`); + if (this.isPrivateIp(res.ip)) { + this.logger.warn(`Blocked address: ${res.ip}`); req.destroy(); } } @@ -70,13 +70,13 @@ export class DownloadService { if (contentLength != null) { const size = Number(contentLength); if (size > maxSize) { - this.#logger.warn(`maxSize exceeded (${size} > ${maxSize}) on response`); + this.logger.warn(`maxSize exceeded (${size} > ${maxSize}) on response`); req.destroy(); } } }).on('downloadProgress', (progress: Got.Progress) => { if (progress.transferred > maxSize) { - this.#logger.warn(`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`); + this.logger.warn(`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`); req.destroy(); } }); @@ -91,14 +91,14 @@ export class DownloadService { } } - this.#logger.succ(`Download finished: ${chalk.cyan(url)}`); + this.logger.succ(`Download finished: ${chalk.cyan(url)}`); } public async downloadTextFile(url: string): Promise { // Create temp file const [path, cleanup] = await createTemp(); - this.#logger.info(`text file: Temp file is ${path}`); + this.logger.info(`text file: Temp file is ${path}`); try { // write content at URL to temp file @@ -112,7 +112,7 @@ export class DownloadService { } } - #isPrivateIp(ip: string): boolean { + private isPrivateIp(ip: string): boolean { for (const net of this.config.allowedPrivateNetworks ?? []) { const cidr = new IPCIDR(net); if (cidr.contains(ip)) { diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index e601122fca..467f3c1cde 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -74,8 +74,8 @@ type UploadFromUrlArgs = { @Injectable() export class DriveService { - #registerLogger: Logger; - #downloaderLogger: Logger; + private registerLogger: Logger; + private downloaderLogger: Logger; constructor( @Inject(DI.config) @@ -110,8 +110,8 @@ export class DriveService { private instanceChart: InstanceChart, ) { const logger = new Logger('drive', 'blue'); - this.#registerLogger = logger.createSubLogger('register', 'yellow'); - this.#downloaderLogger = logger.createSubLogger('downloader'); + this.registerLogger = logger.createSubLogger('register', 'yellow'); + this.downloaderLogger = logger.createSubLogger('downloader'); } /*** @@ -122,7 +122,7 @@ export class DriveService { * @param hash Hash for original * @param size Size for original */ - async #save(file: DriveFile, path: string, name: string, type: string, hash: string, size: number): Promise { + private async save(file: DriveFile, path: string, name: string, type: string, hash: string, size: number): Promise { // thunbnail, webpublic を必要なら生成 const alts = await this.generateAlts(path, type, !file.uri); @@ -161,25 +161,25 @@ export class DriveService { //#endregion //#region Uploads - this.#registerLogger.info(`uploading original: ${key}`); + this.registerLogger.info(`uploading original: ${key}`); const uploads = [ - this.#upload(key, fs.createReadStream(path), type, name), + this.upload(key, fs.createReadStream(path), type, name), ]; if (alts.webpublic) { webpublicKey = `${meta.objectStoragePrefix}/webpublic-${uuid()}.${alts.webpublic.ext}`; webpublicUrl = `${ baseUrl }/${ webpublicKey }`; - this.#registerLogger.info(`uploading webpublic: ${webpublicKey}`); - uploads.push(this.#upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, name)); + this.registerLogger.info(`uploading webpublic: ${webpublicKey}`); + uploads.push(this.upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, name)); } if (alts.thumbnail) { thumbnailKey = `${meta.objectStoragePrefix}/thumbnail-${uuid()}.${alts.thumbnail.ext}`; thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`; - this.#registerLogger.info(`uploading thumbnail: ${thumbnailKey}`); - uploads.push(this.#upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type)); + this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`); + uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type)); } await Promise.all(uploads); @@ -211,12 +211,12 @@ export class DriveService { if (alts.thumbnail) { thumbnailUrl = this.internalStorageService.saveFromBuffer(thumbnailAccessKey, alts.thumbnail.data); - this.#registerLogger.info(`thumbnail stored: ${thumbnailAccessKey}`); + this.registerLogger.info(`thumbnail stored: ${thumbnailAccessKey}`); } if (alts.webpublic) { webpublicUrl = this.internalStorageService.saveFromBuffer(webpublicAccessKey, alts.webpublic.data); - this.#registerLogger.info(`web stored: ${webpublicAccessKey}`); + this.registerLogger.info(`web stored: ${webpublicAccessKey}`); } file.storedInternal = true; @@ -251,7 +251,7 @@ export class DriveService { thumbnail, }; } catch (err) { - this.#registerLogger.warn(`GenerateVideoThumbnail failed: ${err}`); + this.registerLogger.warn(`GenerateVideoThumbnail failed: ${err}`); return { webpublic: null, thumbnail: null, @@ -260,7 +260,7 @@ export class DriveService { } if (!['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml'].includes(type)) { - this.#registerLogger.debug('web image and thumbnail not created (not an required file)'); + this.registerLogger.debug('web image and thumbnail not created (not an required file)'); return { webpublic: null, thumbnail: null, @@ -290,7 +290,7 @@ export class DriveService { metadata.height && metadata.height <= 2048 ); } catch (err) { - this.#registerLogger.warn(`sharp failed: ${err}`); + this.registerLogger.warn(`sharp failed: ${err}`); return { webpublic: null, thumbnail: null, @@ -301,7 +301,7 @@ export class DriveService { let webpublic: IImage | null = null; if (generateWeb && !satisfyWebpublic) { - this.#registerLogger.info('creating web image'); + this.registerLogger.info('creating web image'); try { if (['image/jpeg', 'image/webp'].includes(type)) { @@ -311,14 +311,14 @@ export class DriveService { } else if (['image/svg+xml'].includes(type)) { webpublic = await this.imageProcessingService.convertSharpToPng(img, 2048, 2048); } else { - this.#registerLogger.debug('web image not created (not an required image)'); + this.registerLogger.debug('web image not created (not an required image)'); } } catch (err) { - this.#registerLogger.warn('web image not created (an error occured)', err as Error); + this.registerLogger.warn('web image not created (an error occured)', err as Error); } } else { - if (satisfyWebpublic) this.#registerLogger.info('web image not created (original satisfies webpublic)'); - else this.#registerLogger.info('web image not created (from remote)'); + if (satisfyWebpublic) this.registerLogger.info('web image not created (original satisfies webpublic)'); + else this.registerLogger.info('web image not created (from remote)'); } // #endregion webpublic @@ -329,10 +329,10 @@ export class DriveService { if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(type)) { thumbnail = await this.imageProcessingService.convertSharpToWebp(img, 498, 280); } else { - this.#registerLogger.debug('thumbnail not created (not an required file)'); + this.registerLogger.debug('thumbnail not created (not an required file)'); } } catch (err) { - this.#registerLogger.warn('thumbnail not created (an error occured)', err as Error); + this.registerLogger.warn('thumbnail not created (an error occured)', err as Error); } // #endregion thumbnail @@ -345,7 +345,7 @@ export class DriveService { /** * Upload to ObjectStorage */ - async #upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { + private async upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { if (type === 'image/apng') type = 'image/png'; if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream'; @@ -369,10 +369,10 @@ export class DriveService { }); const result = await upload.promise(); - if (result) this.#registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`); + if (result) this.registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`); } - async #deleteOldFile(user: IRemoteUser) { + private async deleteOldFile(user: IRemoteUser) { const q = this.driveFilesRepository.createQueryBuilder('file') .where('file.userId = :userId', { userId: user.id }) .andWhere('file.isLink = FALSE'); @@ -430,7 +430,7 @@ export class DriveService { sensitiveThresholdForPorn: 0.75, enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos, }); - this.#registerLogger.info(`${JSON.stringify(info)}`); + this.registerLogger.info(`${JSON.stringify(info)}`); // 現状 false positive が多すぎて実用に耐えない //if (info.porn && instance.disallowUploadWhenPredictedAsPorn) { @@ -448,7 +448,7 @@ export class DriveService { }); if (much) { - this.#registerLogger.info(`file with same hash is found: ${much.id}`); + this.registerLogger.info(`file with same hash is found: ${much.id}`); return much; } } @@ -463,11 +463,11 @@ export class DriveService { if (this.userEntityService.isLocalUser(user) && u?.driveCapacityOverrideMb != null) { driveCapacity = 1024 * 1024 * u.driveCapacityOverrideMb; - this.#registerLogger.debug('drive capacity override applied'); - this.#registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`); + this.registerLogger.debug('drive capacity override applied'); + this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`); } - this.#registerLogger.debug(`drive usage is ${usage} (max: ${driveCapacity})`); + this.registerLogger.debug(`drive usage is ${usage} (max: ${driveCapacity})`); // If usage limit exceeded if (usage + info.size > driveCapacity) { @@ -475,7 +475,7 @@ export class DriveService { throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.'); } else { // (アバターまたはバナーを含まず)最も古いファイルを削除する - this.#deleteOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as IRemoteUser); + this.deleteOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as IRemoteUser); } } } @@ -566,22 +566,22 @@ export class DriveService { } catch (err) { // duplicate key error (when already registered) if (isDuplicateKeyValueError(err)) { - this.#registerLogger.info(`already registered ${file.uri}`); + this.registerLogger.info(`already registered ${file.uri}`); file = await this.driveFilesRepository.findOneBy({ uri: file.uri!, userId: user ? user.id : IsNull(), }) as DriveFile; } else { - this.#registerLogger.error(err as Error); + this.registerLogger.error(err as Error); throw err; } } } else { - file = await (this.#save(file, path, detectedName, info.type.mime, info.md5, info.size)); + file = await (this.save(file, path, detectedName, info.type.mime, info.md5, info.size)); } - this.#registerLogger.succ(`drive file has been created ${file.id}`); + this.registerLogger.succ(`drive file has been created ${file.id}`); if (user) { this.driveFileEntityService.pack(file, { self: true }).then(packedFile => { @@ -624,7 +624,7 @@ export class DriveService { } } - this.#deletePostProcess(file, isExpired); + this.deletePostProcess(file, isExpired); } public async deleteFileSync(file: DriveFile, isExpired = false) { @@ -654,10 +654,10 @@ export class DriveService { await Promise.all(promises); } - this.#deletePostProcess(file, isExpired); + this.deletePostProcess(file, isExpired); } - async #deletePostProcess(file: DriveFile, isExpired = false) { + private async deletePostProcess(file: DriveFile, isExpired = false) { // リモートファイル期限切れ削除後は直リンクにする if (isExpired && file.userHost !== null && file.uri != null) { this.driveFilesRepository.update(file.id, { @@ -725,10 +725,10 @@ export class DriveService { await this.downloadService.downloadUrl(url, path); const driveFile = await this.addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive, requestIp, requestHeaders }); - this.#downloaderLogger.succ(`Got: ${driveFile.id}`); + this.downloaderLogger.succ(`Got: ${driveFile.id}`); return driveFile!; } catch (err) { - this.#downloaderLogger.error(`Failed to create drive file: ${err}`, { + this.downloaderLogger.error(`Failed to create drive file: ${err}`, { url: url, e: err, }); diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index a593922acc..521ab7fd81 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -10,7 +10,7 @@ import { LoggerService } from '@/core/LoggerService.js'; @Injectable() export class EmailService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -22,7 +22,7 @@ export class EmailService { private metaService: MetaService, private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('email'); + this.logger = this.loggerService.getLogger('email'); } public async sendEmail(to: string, subject: string, html: string, text: string) { @@ -134,9 +134,9 @@ export class EmailService { `, }); - this.#logger.info(`Message sent: ${info.messageId}`); + this.logger.info(`Message sent: ${info.messageId}`); } catch (err) { - this.#logger.error(err as Error); + this.logger.error(err as Error); throw err; } } diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index 24bedd8192..a4894a4376 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -8,7 +8,7 @@ import { UtilityService } from './UtilityService.js'; @Injectable() export class FederatedInstanceService { - #cache: Cache; + private cache: Cache; constructor( @Inject(DI.instancesRepository) @@ -17,13 +17,13 @@ export class FederatedInstanceService { private utilityService: UtilityService, private idService: IdService, ) { - this.#cache = new Cache(1000 * 60 * 60); + this.cache = new Cache(1000 * 60 * 60); } public async registerOrFetchInstanceDoc(host: string): Promise { host = this.utilityService.toPuny(host); - const cached = this.#cache.get(host); + const cached = this.cache.get(host); if (cached) return cached; const index = await this.instancesRepository.findOneBy({ host }); @@ -36,10 +36,10 @@ export class FederatedInstanceService { lastCommunicatedAt: new Date(), }).then(x => this.instancesRepository.findOneByOrFail(x.identifiers[0])); - this.#cache.set(host, i); + this.cache.set(host, i); return i; } else { - this.#cache.set(host, index); + this.cache.set(host, index); return index; } } diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 4414d83942..376617914e 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -32,7 +32,7 @@ type NodeInfo = { @Injectable() export class FetchInstanceMetadataService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.instancesRepository) @@ -42,7 +42,7 @@ export class FetchInstanceMetadataService { private httpRequestService: HttpRequestService, private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('metadata', 'cyan'); + this.logger = this.loggerService.getLogger('metadata', 'cyan'); } public async fetchInstanceMetadata(instance: Instance, force = false): Promise { @@ -57,24 +57,24 @@ export class FetchInstanceMetadataService { } } - this.#logger.info(`Fetching metadata of ${instance.host} ...`); + this.logger.info(`Fetching metadata of ${instance.host} ...`); try { const [info, dom, manifest] = await Promise.all([ - this.#fetchNodeinfo(instance).catch(() => null), - this.#fetchDom(instance).catch(() => null), - this.#fetchManifest(instance).catch(() => null), + this.fetchNodeinfo(instance).catch(() => null), + this.fetchDom(instance).catch(() => null), + this.fetchManifest(instance).catch(() => null), ]); const [favicon, icon, themeColor, name, description] = await Promise.all([ - this.#fetchFaviconUrl(instance, dom).catch(() => null), - this.#fetchIconUrl(instance, dom, manifest).catch(() => null), - this.#getThemeColor(info, dom, manifest).catch(() => null), - this.#getSiteName(info, dom, manifest).catch(() => null), - this.#getDescription(info, dom, manifest).catch(() => null), + this.fetchFaviconUrl(instance, dom).catch(() => null), + this.fetchIconUrl(instance, dom, manifest).catch(() => null), + this.getThemeColor(info, dom, manifest).catch(() => null), + this.getSiteName(info, dom, manifest).catch(() => null), + this.getDescription(info, dom, manifest).catch(() => null), ]); - this.#logger.succ(`Successfuly fetched metadata of ${instance.host}`); + this.logger.succ(`Successfuly fetched metadata of ${instance.host}`); const updates = { infoUpdatedAt: new Date(), @@ -96,16 +96,16 @@ export class FetchInstanceMetadataService { await this.instancesRepository.update(instance.id, updates); - this.#logger.succ(`Successfuly updated metadata of ${instance.host}`); + this.logger.succ(`Successfuly updated metadata of ${instance.host}`); } catch (e) { - this.#logger.error(`Failed to update metadata of ${instance.host}: ${e}`); + this.logger.error(`Failed to update metadata of ${instance.host}: ${e}`); } finally { unlock(); } } - async #fetchNodeinfo(instance: Instance): Promise { - this.#logger.info(`Fetching nodeinfo of ${instance.host} ...`); + private async fetchNodeinfo(instance: Instance): Promise { + this.logger.info(`Fetching nodeinfo of ${instance.host} ...`); try { const wellknown = await this.httpRequestService.getJson('https://' + instance.host + '/.well-known/nodeinfo') @@ -137,18 +137,18 @@ export class FetchInstanceMetadataService { throw err.statusCode ?? err.message; }); - this.#logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`); + this.logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`); return info as NodeInfo; } catch (err) { - this.#logger.error(`Failed to fetch nodeinfo of ${instance.host}: ${err}`); + this.logger.error(`Failed to fetch nodeinfo of ${instance.host}: ${err}`); throw err; } } - async #fetchDom(instance: Instance): Promise { - this.#logger.info(`Fetching HTML of ${instance.host} ...`); + private async fetchDom(instance: Instance): Promise { + this.logger.info(`Fetching HTML of ${instance.host} ...`); const url = 'https://' + instance.host; @@ -160,7 +160,7 @@ export class FetchInstanceMetadataService { return doc; } - async #fetchManifest(instance: Instance): Promise | null> { + private async fetchManifest(instance: Instance): Promise | null> { const url = 'https://' + instance.host; const manifestUrl = url + '/manifest.json'; @@ -170,7 +170,7 @@ export class FetchInstanceMetadataService { return manifest; } - async #fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] | null): Promise { + private async fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] | null): Promise { const url = 'https://' + instance.host; if (doc) { @@ -197,7 +197,7 @@ export class FetchInstanceMetadataService { return null; } - async #fetchIconUrl(instance: Instance, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { + private async fetchIconUrl(instance: Instance, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) { const url = 'https://' + instance.host; return (new URL(manifest.icons[0].src, url)).href; @@ -225,7 +225,7 @@ export class FetchInstanceMetadataService { return null; } - async #getThemeColor(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { + private async getThemeColor(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { const themeColor = info?.metadata?.themeColor ?? doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') ?? manifest?.theme_color; if (themeColor) { @@ -236,7 +236,7 @@ export class FetchInstanceMetadataService { return null; } - async #getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { + private async getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (info && info.metadata) { if (info.metadata.nodeName || info.metadata.name) { return info.metadata.nodeName ?? info.metadata.name; @@ -258,7 +258,7 @@ export class FetchInstanceMetadataService { return null; } - async #getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { + private async getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (info && info.metadata) { if (info.metadata.nodeDescription || info.metadata.description) { return info.metadata.nodeDescription ?? info.metadata.description; diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts index 45c94dafdc..fd8a4fdd3a 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -61,7 +61,7 @@ export class FileInfoService { const warnings = [] as string[]; const size = await this.getFileSize(path); - const md5 = await this.#calcHash(path); + const md5 = await this.calcHash(path); let type = await this.detectType(path); @@ -71,7 +71,7 @@ export class FileInfoService { let orientation: number | undefined; if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/bmp', 'image/tiff', 'image/svg+xml', 'image/vnd.adobe.photoshop'].includes(type.mime)) { - const imageSize = await this.#detectImageSize(path).catch(e => { + const imageSize = await this.detectImageSize(path).catch(e => { warnings.push(`detectImageSize failed: ${e}`); return undefined; }); @@ -98,7 +98,7 @@ export class FileInfoService { let blurhash: string | undefined; if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/svg+xml'].includes(type.mime)) { - blurhash = await this.#getBlurhash(path).catch(e => { + blurhash = await this.getBlurhash(path).catch(e => { warnings.push(`getBlurhash failed: ${e}`); return undefined; }); @@ -108,7 +108,7 @@ export class FileInfoService { let porn = false; if (!opts.skipSensitiveDetection) { - await this.#detectSensitivity( + await this.detectSensitivity( path, type.mime, opts.sensitiveThreshold ?? 0.5, @@ -135,7 +135,7 @@ export class FileInfoService { }; } - async #detectSensitivity(source: string, mime: string, sensitiveThreshold: number, sensitiveThresholdForPorn: number, analyzeVideo: boolean): Promise<[sensitive: boolean, porn: boolean]> { + private async detectSensitivity(source: string, mime: string, sensitiveThreshold: number, sensitiveThresholdForPorn: number, analyzeVideo: boolean): Promise<[sensitive: boolean, porn: boolean]> { let sensitive = false; let porn = false; @@ -204,7 +204,7 @@ export class FileInfoService { let frameIndex = 0; let targetIndex = 0; let nextIndex = 1; - for await (const path of this.#asyncIterateFrames(outDir, command)) { + for await (const path of this.asyncIterateFrames(outDir, command)) { try { const index = frameIndex++; if (index !== targetIndex) { @@ -230,7 +230,7 @@ export class FileInfoService { return [sensitive, porn]; } - async *#asyncIterateFrames(cwd: string, command: FFmpeg.FfmpegCommand): AsyncGenerator { + private async *asyncIterateFrames(cwd: string, command: FFmpeg.FfmpegCommand): AsyncGenerator { const watcher = new FSWatcher({ cwd, disableGlobbing: true, @@ -245,7 +245,7 @@ export class FileInfoService { const current = `${i}.png`; const next = `${i + 1}.png`; const framePath = join(cwd, current); - if (await this.#exists(join(cwd, next))) { + if (await this.exists(join(cwd, next))) { yield framePath; } else if (!finished) { // eslint-disable-line @typescript-eslint/no-unnecessary-condition watcher.add(next); @@ -261,7 +261,7 @@ export class FileInfoService { command.once('error', reject); }); yield framePath; - } else if (await this.#exists(framePath)) { + } else if (await this.exists(framePath)) { yield framePath; } else { return; @@ -269,7 +269,7 @@ export class FileInfoService { } } - #exists(path: string): Promise { + private exists(path: string): Promise { return fs.promises.access(path).then(() => true, () => false); } @@ -333,7 +333,7 @@ export class FileInfoService { /** * Calculate MD5 hash */ - async #calcHash(path: string): Promise { + private async calcHash(path: string): Promise { const hash = crypto.createHash('md5').setEncoding('hex'); await pipeline(fs.createReadStream(path), hash); return hash.read(); @@ -342,7 +342,7 @@ export class FileInfoService { /** * Detect dimensions of image */ - async #detectImageSize(path: string): Promise<{ + private async detectImageSize(path: string): Promise<{ width: number; height: number; wUnits: string; @@ -358,7 +358,7 @@ export class FileInfoService { /** * Calculate average color of image */ - #getBlurhash(path: string): Promise { + private getBlurhash(path: string): Promise { return new Promise((resolve, reject) => { sharp(path) .raw() diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index 21cde12536..f4c00cd259 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -15,12 +15,12 @@ export class HttpRequestService { /** * Get http non-proxy agent */ - #http: http.Agent; + private http: http.Agent; /** * Get https non-proxy agent */ - #https: https.Agent; + private https: https.Agent; /** * Get http proxy or non-proxy agent @@ -42,13 +42,13 @@ export class HttpRequestService { lookup: false, // nativeのdns.lookupにfallbackしない }); - this.#http = new http.Agent({ + this.http = new http.Agent({ keepAlive: true, keepAliveMsecs: 30 * 1000, lookup: cache.lookup, } as http.AgentOptions); - this.#https = new https.Agent({ + this.https = new https.Agent({ keepAlive: true, keepAliveMsecs: 30 * 1000, lookup: cache.lookup, @@ -65,7 +65,7 @@ export class HttpRequestService { scheduling: 'lifo', proxy: config.proxy, }) - : this.#http; + : this.http; this.httpsAgent = config.proxy ? new HttpsProxyAgent({ @@ -76,7 +76,7 @@ export class HttpRequestService { scheduling: 'lifo', proxy: config.proxy, }) - : this.#https; + : this.https; } /** @@ -86,7 +86,7 @@ export class HttpRequestService { */ public getAgentByUrl(url: URL, bypassProxy = false): http.Agent | https.Agent { if (bypassProxy || (this.config.proxyBypassHosts || []).includes(url.hostname)) { - return url.protocol === 'http:' ? this.#http : this.#https; + return url.protocol === 'http:' ? this.http : this.https; } else { return url.protocol === 'http:' ? this.httpAgent : this.httpsAgent; } diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts index b3b0d63627..345b72bac5 100644 --- a/packages/backend/src/core/IdService.ts +++ b/packages/backend/src/core/IdService.ts @@ -9,19 +9,19 @@ import { genObjectId } from '@/misc/id/object-id.js'; @Injectable() export class IdService { - #metohd: string; + private metohd: string; constructor( @Inject(DI.config) private config: Config, ) { - this.#metohd = config.id.toLowerCase(); + this.metohd = config.id.toLowerCase(); } public genId(date?: Date): string { if (!date || (date > new Date())) date = new Date(); - switch (this.#metohd) { + switch (this.metohd) { case 'aid': return genAid(date); case 'meid': return genMeid(date); case 'meidg': return genMeidg(date); diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts index 3a93a49c7b..57d55870b1 100644 --- a/packages/backend/src/core/InstanceActorService.ts +++ b/packages/backend/src/core/InstanceActorService.ts @@ -10,7 +10,7 @@ const ACTOR_USERNAME = 'instance.actor' as const; @Injectable() export class InstanceActorService { - #cache: Cache; + private cache: Cache; constructor( @Inject(DI.usersRepository) @@ -18,11 +18,11 @@ export class InstanceActorService { private createSystemUserService: CreateSystemUserService, ) { - this.#cache = new Cache(Infinity); + this.cache = new Cache(Infinity); } public async getInstanceActor(): Promise { - const cached = this.#cache.get(null); + const cached = this.cache.get(null); if (cached) return cached; const user = await this.usersRepository.findOneBy({ @@ -31,11 +31,11 @@ export class InstanceActorService { }) as ILocalUser | undefined; if (user) { - this.#cache.set(null, user); + this.cache.set(null, user); return user; } else { const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as ILocalUser; - this.#cache.set(null, created); + this.cache.set(null, created); return created; } } diff --git a/packages/backend/src/core/LoggerService.ts b/packages/backend/src/core/LoggerService.ts index d844b38841..558e3016dc 100644 --- a/packages/backend/src/core/LoggerService.ts +++ b/packages/backend/src/core/LoggerService.ts @@ -6,14 +6,14 @@ import Logger from '@/logger.js'; @Injectable() export class LoggerService { - #syslogClient; + private syslogClient; constructor( @Inject(DI.config) private config: Config, ) { if (this.config.syslog) { - this.#syslogClient = new SyslogPro.RFC5424({ + this.syslogClient = new SyslogPro.RFC5424({ applacationName: 'Misskey', timestamp: true, encludeStructuredData: true, @@ -28,6 +28,6 @@ export class LoggerService { } public getLogger(domain: string, color?: string | undefined, store?: boolean) { - return new Logger(domain, color, store, this.#syslogClient); + return new Logger(domain, color, store, this.syslogClient); } } diff --git a/packages/backend/src/core/MessagingService.ts b/packages/backend/src/core/MessagingService.ts index 669089e1e5..1819b32a45 100644 --- a/packages/backend/src/core/MessagingService.ts +++ b/packages/backend/src/core/MessagingService.ts @@ -142,10 +142,10 @@ export class MessagingService { public async deleteMessage(message: MessagingMessage) { await this.messagingMessagesRepository.delete(message.id); - this.#postDeleteMessage(message); + this.postDeleteMessage(message); } - async #postDeleteMessage(message: MessagingMessage) { + private async postDeleteMessage(message: MessagingMessage) { if (message.recipientId) { const user = await this.usersRepository.findOneByOrFail({ id: message.userId }); const recipient = await this.usersRepository.findOneByOrFail({ id: message.recipientId }); diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index b5bd423765..4099e340be 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -7,24 +7,24 @@ import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class MetaService implements OnApplicationShutdown { - #cache: Meta | undefined; - #intervalId: NodeJS.Timer; + private cache: Meta | undefined; + private intervalId: NodeJS.Timer; constructor( @Inject(DI.db) private db: DataSource, ) { if (process.env.NODE_ENV !== 'test') { - this.#intervalId = setInterval(() => { + this.intervalId = setInterval(() => { this.fetch(true).then(meta => { - this.#cache = meta; + this.cache = meta; }); }, 1000 * 10); } } async fetch(noCache = false): Promise { - if (!noCache && this.#cache) return this.#cache; + if (!noCache && this.cache) return this.cache; return await this.db.transaction(async transactionalEntityManager => { // 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する @@ -37,7 +37,7 @@ export class MetaService implements OnApplicationShutdown { const meta = metas[0]; if (meta) { - this.#cache = meta; + this.cache = meta; return meta; } else { // metaが空のときfetchMetaが同時に呼ばれるとここが同時に呼ばれてしまうことがあるのでフェイルセーフなupsertを使う @@ -51,13 +51,13 @@ export class MetaService implements OnApplicationShutdown { ) .then((x) => transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0])); - this.#cache = saved; + this.cache = saved; return saved; } }); } public onApplicationShutdown(signal?: string | undefined) { - clearInterval(this.#intervalId); + clearInterval(this.intervalId); } } diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 83c080893d..5acc07fba6 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -277,7 +277,7 @@ export class NoteCreateService { emojis = data.apEmojis ?? extractCustomEmojisFromMfm(combinedTokens); - mentionedUsers = data.apMentions ?? await this.#extractMentionedUsers(user, combinedTokens); + mentionedUsers = data.apMentions ?? await this.extractMentionedUsers(user, combinedTokens); } tags = tags.filter(tag => Array.from(tag ?? '').length <= 128).splice(0, 32); @@ -300,14 +300,14 @@ export class NoteCreateService { } } - const note = await this.#insertNote(user, data, tags, emojis, mentionedUsers); + const note = await this.insertNote(user, data, tags, emojis, mentionedUsers); - setImmediate(() => this.#postNoteCreated(note, user, data, silent, tags!, mentionedUsers!)); + setImmediate(() => this.postNoteCreated(note, user, data, silent, tags!, mentionedUsers!)); return note; } - async #insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) { + private async insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) { const insert = new Note({ id: this.idService.genId(data.createdAt!), createdAt: data.createdAt!, @@ -403,7 +403,7 @@ export class NoteCreateService { } } - async #postNoteCreated(note: Note, user: { + private async postNoteCreated(note: Note, user: { id: User['id']; username: User['username']; host: User['host']; @@ -428,7 +428,7 @@ export class NoteCreateService { } // Increment notes count (user) - this.#incNotesCountOfUser(user); + this.incNotesCountOfUser(user); // Word mute mutedWordsCache.fetch(null, () => this.userProfilesRepository.find({ @@ -473,12 +473,12 @@ export class NoteCreateService { } if (data.reply) { - this.#saveReply(data.reply, note); + this.saveReply(data.reply, note); } // この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき if (data.renote && (await this.noteEntityService.countSameRenotes(user.id, data.renote.id, note.id) === 0)) { - this.#incRenoteCount(data.renote); + this.incRenoteCount(data.renote); } if (data.poll && data.poll.expiresAt) { @@ -536,7 +536,7 @@ export class NoteCreateService { const nm = new NotificationManager(this.mutingsRepository, this.createNotificationService, user, note); const nmRelatedPromises = []; - await this.#createMentionedEvents(mentionedUsers, note, nm); + await this.createMentionedEvents(mentionedUsers, note, nm); // If has in reply to note if (data.reply) { @@ -590,7 +590,7 @@ export class NoteCreateService { //#region AP deliver if (this.userEntityService.isLocalUser(user)) { (async () => { - const noteActivity = await this.#renderNoteOrRenoteActivity(data, note); + const noteActivity = await this.renderNoteOrRenoteActivity(data, note); const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity); // メンションされたリモートユーザーに配送 @@ -644,10 +644,10 @@ export class NoteCreateService { } // Register to search database - this.#index(note); + this.index(note); } - #incRenoteCount(renote: Note) { + private incRenoteCount(renote: Note) { this.notesRepository.createQueryBuilder().update() .set({ renoteCount: () => '"renoteCount" + 1', @@ -657,7 +657,7 @@ export class NoteCreateService { .execute(); } - async #createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) { + private async createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) { for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) { const threadMuted = await this.noteThreadMutingsRepository.findOneBy({ userId: u.id, @@ -686,11 +686,11 @@ export class NoteCreateService { } } - #saveReply(reply: Note, note: Note) { + private saveReply(reply: Note, note: Note) { this.notesRepository.increment({ id: reply.id }, 'repliesCount', 1); } - async #renderNoteOrRenoteActivity(data: Option, note: Note) { + private async renderNoteOrRenoteActivity(data: Option, note: Note) { if (data.localOnly) return null; const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0) @@ -700,7 +700,7 @@ export class NoteCreateService { return this.apRendererService.renderActivity(content); } - #index(note: Note) { + private index(note: Note) { if (note.text == null || this.config.elasticsearch == null) return; /* es!.index({ @@ -714,7 +714,7 @@ export class NoteCreateService { });*/ } - #incNotesCountOfUser(user: { id: User['id']; }) { + private incNotesCountOfUser(user: { id: User['id']; }) { this.usersRepository.createQueryBuilder().update() .set({ updatedAt: new Date(), @@ -724,7 +724,7 @@ export class NoteCreateService { .execute(); } - async #extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.MfmNode[]): Promise { + private async extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.MfmNode[]): Promise { if (tokens == null) return []; const mentions = extractMentions(tokens); diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index 9153418beb..6365286f84 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -79,16 +79,16 @@ export class NoteDeleteService { ? this.apRendererService.renderUndo(this.apRendererService.renderAnnounce(renote.uri ?? `${this.config.url}/notes/${renote.id}`, note), user) : this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${note.id}`), user)); - this.#deliverToConcerned(user, note, content); + this.deliverToConcerned(user, note, content); } // also deliever delete activity to cascaded notes - const cascadingNotes = (await this.#findCascadingNotes(note)).filter(note => !note.localOnly); // filter out local-only notes + const cascadingNotes = (await this.findCascadingNotes(note)).filter(note => !note.localOnly); // filter out local-only notes for (const cascadingNote of cascadingNotes) { if (!cascadingNote.user) continue; if (!this.userEntityService.isLocalUser(cascadingNote.user)) continue; const content = this.apRendererService.renderActivity(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${cascadingNote.id}`), cascadingNote.user)); - this.#deliverToConcerned(cascadingNote.user, cascadingNote, content); + this.deliverToConcerned(cascadingNote.user, cascadingNote, content); } //#endregion @@ -110,7 +110,7 @@ export class NoteDeleteService { }); } - async #findCascadingNotes(note: Note) { + private async findCascadingNotes(note: Note) { const cascadingNotes: Note[] = []; const recursive = async (noteId: string) => { @@ -132,7 +132,7 @@ export class NoteDeleteService { return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users } - async #getMentionedRemoteUsers(note: Note) { + private async getMentionedRemoteUsers(note: Note) { const where = [] as any[]; // mention / reply / dm @@ -157,10 +157,10 @@ export class NoteDeleteService { }) as IRemoteUser[]; } - async #deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) { + private async deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) { this.apDeliverManagerService.deliverToFollowers(user, content); this.relayService.deliverToRelays(user, content); - const remoteUsers = await this.#getMentionedRemoteUsers(note); + const remoteUsers = await this.getMentionedRemoteUsers(note); for (const remoteUser of remoteUsers) { this.apDeliverManagerService.deliverToUser(user, content, remoteUser); } diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index 44957d62d3..ca9e60889d 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -38,8 +38,8 @@ export class NotificationService { if (result.affected === 0) return; - if (!await this.userEntityService.getHasUnreadNotification(userId)) return this.#postReadAllNotifications(userId); - else return this.#postReadNotifications(userId, notificationIds); + if (!await this.userEntityService.getHasUnreadNotification(userId)) return this.postReadAllNotifications(userId); + else return this.postReadNotifications(userId, notificationIds); } public async readNotificationByQuery( @@ -55,12 +55,12 @@ export class NotificationService { return this.readNotification(userId, notificationIds); } - #postReadAllNotifications(userId: User['id']) { + private postReadAllNotifications(userId: User['id']) { this.globalEventService.publishMainStream(userId, 'readAllNotifications'); return this.pushNotificationService.pushNotification(userId, 'readAllNotifications', undefined); } - #postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) { + private postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) { this.globalEventService.publishMainStream(userId, 'readNotifications', notificationIds); return this.pushNotificationService.pushNotification(userId, 'readNotifications', { notificationIds }); } diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts index c1d85e8b48..688ea03d34 100644 --- a/packages/backend/src/core/RelayService.ts +++ b/packages/backend/src/core/RelayService.ts @@ -14,7 +14,7 @@ const ACTOR_USERNAME = 'relay.actor' as const; @Injectable() export class RelayService { - #relaysCache: Cache; + private relaysCache: Cache; constructor( @Inject(DI.usersRepository) @@ -28,10 +28,10 @@ export class RelayService { private createSystemUserService: CreateSystemUserService, private apRendererService: ApRendererService, ) { - this.#relaysCache = new Cache(1000 * 60 * 10); + this.relaysCache = new Cache(1000 * 60 * 10); } - async #getRelayActor(): Promise { + private async getRelayActor(): Promise { const user = await this.usersRepository.findOneBy({ host: IsNull(), username: ACTOR_USERNAME, @@ -50,7 +50,7 @@ export class RelayService { status: 'requesting', }).then(x => this.relaysRepository.findOneByOrFail(x.identifiers[0])); - const relayActor = await this.#getRelayActor(); + const relayActor = await this.getRelayActor(); const follow = await this.apRendererService.renderFollowRelay(relay, relayActor); const activity = this.apRendererService.renderActivity(follow); this.queueService.deliver(relayActor, activity, relay.inbox); @@ -67,7 +67,7 @@ export class RelayService { throw new Error('relay not found'); } - const relayActor = await this.#getRelayActor(); + const relayActor = await this.getRelayActor(); const follow = this.apRendererService.renderFollowRelay(relay, relayActor); const undo = this.apRendererService.renderUndo(follow, relayActor); const activity = this.apRendererService.renderActivity(undo); @@ -100,7 +100,7 @@ export class RelayService { public async deliverToRelays(user: { id: User['id']; host: null; }, activity: any): Promise { if (activity == null) return; - const relays = await this.#relaysCache.fetch(null, () => this.relaysRepository.findBy({ + const relays = await this.relaysCache.fetch(null, () => this.relaysRepository.findBy({ status: 'accepted', })); if (relays.length === 0) return; diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index a9396fcbba..9efb021f64 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -44,11 +44,11 @@ export class UserBlockingService { public async block(blocker: User, blockee: User) { await Promise.all([ - this.#cancelRequest(blocker, blockee), - this.#cancelRequest(blockee, blocker), - this.#unFollow(blocker, blockee), - this.#unFollow(blockee, blocker), - this.#removeFromList(blockee, blocker), + this.cancelRequest(blocker, blockee), + this.cancelRequest(blockee, blocker), + this.unFollow(blocker, blockee), + this.unFollow(blockee, blocker), + this.removeFromList(blockee, blocker), ]); const blocking = { @@ -68,7 +68,7 @@ export class UserBlockingService { } } - async #cancelRequest(follower: User, followee: User) { + private async cancelRequest(follower: User, followee: User) { const request = await this.followRequestsRepository.findOneBy({ followeeId: followee.id, followerId: follower.id, @@ -118,7 +118,7 @@ export class UserBlockingService { } } - async #unFollow(follower: User, followee: User) { + private async unFollow(follower: User, followee: User) { const following = await this.followingsRepository.findOneBy({ followerId: follower.id, followeeId: followee.id, @@ -159,7 +159,7 @@ export class UserBlockingService { } } - async #removeFromList(listOwner: User, user: User) { + private async removeFromList(listOwner: User, user: User) { const userLists = await this.userListsRepository.findBy({ userId: listOwner.id, }); diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 6aae5a6b99..ff86d4343f 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -131,7 +131,7 @@ export class UserFollowingService { } } - await this.#insertFollowingDoc(followee, follower); + await this.insertFollowingDoc(followee, follower); if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { const content = this.apRendererService.renderActivity(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee)); @@ -139,7 +139,7 @@ export class UserFollowingService { } } - async #insertFollowingDoc( + private async insertFollowingDoc( followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox'] }, @@ -273,7 +273,7 @@ export class UserFollowingService { await this.followingsRepository.delete(following.id); - this.#decrementFollowing(follower, followee); + this.decrementFollowing(follower, followee); // Publish unfollow event if (!silent && this.userEntityService.isLocalUser(follower)) { @@ -304,7 +304,7 @@ export class UserFollowingService { } } - async #decrementFollowing( + private async decrementFollowing( follower: {id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }, ): Promise { @@ -445,7 +445,7 @@ export class UserFollowingService { throw new IdentifiableError('8884c2dd-5795-4ac9-b27e-6a01d38190f9', 'No follow request.'); } - await this.#insertFollowingDoc(followee, follower); + await this.insertFollowingDoc(followee, follower); if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { const content = this.apRendererService.renderActivity(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, request.requestId!), followee)); @@ -477,13 +477,13 @@ export class UserFollowingService { */ public async rejectFollowRequest(user: Local, follower: Both): Promise { if (this.userEntityService.isRemoteUser(follower)) { - this.#deliverReject(user, follower); + this.deliverReject(user, follower); } - await this.#removeFollowRequest(user, follower); + await this.removeFollowRequest(user, follower); if (this.userEntityService.isLocalUser(follower)) { - this.#publishUnfollow(user, follower); + this.publishUnfollow(user, follower); } } @@ -492,13 +492,13 @@ export class UserFollowingService { */ public async rejectFollow(user: Local, follower: Both): Promise { if (this.userEntityService.isRemoteUser(follower)) { - this.#deliverReject(user, follower); + this.deliverReject(user, follower); } - await this.#removeFollow(user, follower); + await this.removeFollow(user, follower); if (this.userEntityService.isLocalUser(follower)) { - this.#publishUnfollow(user, follower); + this.publishUnfollow(user, follower); } } @@ -506,15 +506,15 @@ export class UserFollowingService { * AP Reject/Follow */ public async remoteReject(actor: Remote, follower: Local): Promise { - await this.#removeFollowRequest(actor, follower); - await this.#removeFollow(actor, follower); - this.#publishUnfollow(actor, follower); + await this.removeFollowRequest(actor, follower); + await this.removeFollow(actor, follower); + this.publishUnfollow(actor, follower); } /** * Remove follow request record */ - async #removeFollowRequest(followee: Both, follower: Both): Promise { + private async removeFollowRequest(followee: Both, follower: Both): Promise { const request = await this.followRequestsRepository.findOneBy({ followeeId: followee.id, followerId: follower.id, @@ -528,7 +528,7 @@ export class UserFollowingService { /** * Remove follow record */ - async #removeFollow(followee: Both, follower: Both): Promise { + private async removeFollow(followee: Both, follower: Both): Promise { const following = await this.followingsRepository.findOneBy({ followeeId: followee.id, followerId: follower.id, @@ -537,13 +537,13 @@ export class UserFollowingService { if (!following) return; await this.followingsRepository.delete(following.id); - this.#decrementFollowing(follower, followee); + this.decrementFollowing(follower, followee); } /** * Deliver Reject to remote */ - async #deliverReject(followee: Local, follower: Remote): Promise { + private async deliverReject(followee: Local, follower: Remote): Promise { const request = await this.followRequestsRepository.findOneBy({ followeeId: followee.id, followerId: follower.id, @@ -556,7 +556,7 @@ export class UserFollowingService { /** * Publish unfollow to local */ - async #publishUnfollow(followee: Both, follower: Local): Promise { + private async publishUnfollow(followee: Both, follower: Local): Promise { const packedFollowee = await this.userEntityService.pack(followee.id, follower, { detail: true, }); diff --git a/packages/backend/src/core/UserKeypairStoreService.ts b/packages/backend/src/core/UserKeypairStoreService.ts index f093d2ea91..e53f37b714 100644 --- a/packages/backend/src/core/UserKeypairStoreService.ts +++ b/packages/backend/src/core/UserKeypairStoreService.ts @@ -7,16 +7,16 @@ import { DI } from '@/di-symbols.js'; @Injectable() export class UserKeypairStoreService { - #cache: Cache; + private cache: Cache; constructor( @Inject(DI.userKeypairsRepository) private userKeypairsRepository: UserKeypairsRepository, ) { - this.#cache = new Cache(Infinity); + this.cache = new Cache(Infinity); } public async getUserKeypair(userId: User['id']): Promise { - return await this.#cache.fetch(userId, () => this.userKeypairsRepository.findOneByOrFail({ userId: userId })); + return await this.cache.fetch(userId, () => this.userKeypairsRepository.findOneByOrFail({ userId: userId })); } } diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index 3ed615ca0b..1d74290dd9 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -7,8 +7,8 @@ import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class WebhookService implements OnApplicationShutdown { - #webhooksFetched = false; - #webhooks: Webhook[] = []; + private webhooksFetched = false; + private webhooks: Webhook[] = []; constructor( @Inject(DI.redisSubscriber) @@ -22,14 +22,14 @@ export class WebhookService implements OnApplicationShutdown { } public async getActiveWebhooks() { - if (!this.#webhooksFetched) { - this.#webhooks = await this.webhooksRepository.findBy({ + if (!this.webhooksFetched) { + this.webhooks = await this.webhooksRepository.findBy({ active: true, }); - this.#webhooksFetched = true; + this.webhooksFetched = true; } - return this.#webhooks; + return this.webhooks; } private async onMessage(_, data) { @@ -40,23 +40,23 @@ export class WebhookService implements OnApplicationShutdown { switch (type) { case 'webhookCreated': if (body.active) { - this.#webhooks.push(body); + this.webhooks.push(body); } break; case 'webhookUpdated': if (body.active) { - const i = this.#webhooks.findIndex(a => a.id === body.id); + const i = this.webhooks.findIndex(a => a.id === body.id); if (i > -1) { - this.#webhooks[i] = body; + this.webhooks[i] = body; } else { - this.#webhooks.push(body); + this.webhooks.push(body); } } else { - this.#webhooks = this.#webhooks.filter(a => a.id !== body.id); + this.webhooks = this.webhooks.filter(a => a.id !== body.id); } break; case 'webhookDeleted': - this.#webhooks = this.#webhooks.filter(a => a.id !== body.id); + this.webhooks = this.webhooks.filter(a => a.id !== body.id); break; default: break; diff --git a/packages/backend/src/core/chart/core.ts b/packages/backend/src/core/chart/core.ts index 93a6c3ce05..cf5aa48884 100644 --- a/packages/backend/src/core/chart/core.ts +++ b/packages/backend/src/core/chart/core.ts @@ -109,7 +109,7 @@ export function getJsonSchema(schema: S): ToJsonSchema { - #logger: Logger; + private logger: Logger; public schema: T; @@ -242,7 +242,7 @@ export default abstract class Chart { this.name = name; this.schema = schema; this.lock = lock; - this.#logger = logger; + this.logger = logger; const { hour, day } = Chart.schemaToEntity(name, schema, grouped); this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour); @@ -333,7 +333,7 @@ export default abstract class Chart { // 初期ログデータを作成 data = this.getNewLog(null); - this.#logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): Initial commit created`); + this.logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): Initial commit created`); } const date = Chart.dateToTimestamp(current); @@ -363,7 +363,7 @@ export default abstract class Chart { ...columns, }).then(x => repository.findOneByOrFail(x.identifiers[0])) as RawRecord; - this.#logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`); + this.logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`); return log; } finally { @@ -382,7 +382,7 @@ export default abstract class Chart { public async save(): Promise { if (this.buffer.length === 0) { - this.#logger.info(`${this.name}: Write skipped`); + this.logger.info(`${this.name}: Write skipped`); return; } @@ -481,7 +481,7 @@ export default abstract class Chart { .execute(), ]); - this.#logger.info(`${this.name + (logHour.group ? `:${logHour.group}` : '')}: Updated`); + this.logger.info(`${this.name + (logHour.group ? `:${logHour.group}` : '')}: Updated`); // TODO: この一連の処理が始まった後に新たにbufferに入ったものは消さないようにする this.buffer = this.buffer.filter(q => q.group != null && (q.group !== logHour.group)); diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index eab233bbef..669680758d 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -68,7 +68,7 @@ export class NoteEntityService implements OnModuleInit { this.reactionService = this.moduleRef.get('ReactionService'); } - async #hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) { + private async hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) { // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) let hide = false; @@ -128,7 +128,7 @@ export class NoteEntityService implements OnModuleInit { } } - async #populatePoll(note: Note, meId: User['id'] | null) { + private async populatePoll(note: Note, meId: User['id'] | null) { const poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id }); const choices = poll.choices.map(c => ({ text: c, @@ -166,7 +166,7 @@ export class NoteEntityService implements OnModuleInit { }; } - async #populateMyReaction(note: Note, meId: User['id'], _hint_?: { + private async populateMyReaction(note: Note, meId: User['id'], _hint_?: { myReactions: Map; }) { if (_hint_?.myReactions) { @@ -319,10 +319,10 @@ export class NoteEntityService implements OnModuleInit { _hint_: options?._hint_, }) : undefined, - poll: note.hasPoll ? this.#populatePoll(note, meId) : undefined, + poll: note.hasPoll ? this.populatePoll(note, meId) : undefined, ...(meId ? { - myReaction: this.#populateMyReaction(note, meId, options?._hint_), + myReaction: this.populateMyReaction(note, meId, options?._hint_), } : {}), } : {}), }); @@ -339,7 +339,7 @@ export class NoteEntityService implements OnModuleInit { } if (!opts.skipHide) { - await this.#hideNote(packed, meId); + await this.hideNote(packed, meId); } return packed; diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 48d8af83fb..343e42df07 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -48,7 +48,7 @@ export class UserEntityService implements OnModuleInit { private pageEntityService: PageEntityService; private customEmojiService: CustomEmojiService; private antennaService: AntennaService; - #userInstanceCache: Cache; + private userInstanceCache: Cache; constructor( private moduleRef: ModuleRef, @@ -119,7 +119,7 @@ export class UserEntityService implements OnModuleInit { //private customEmojiService: CustomEmojiService, //private antennaService: AntennaService, ) { - this.#userInstanceCache = new Cache(1000 * 60 * 60 * 3); + this.userInstanceCache = new Cache(1000 * 60 * 60 * 3); } onModuleInit() { @@ -384,7 +384,7 @@ export class UserEntityService implements OnModuleInit { isModerator: user.isModerator ?? falsy, isBot: user.isBot ?? falsy, isCat: user.isCat ?? falsy, - instance: user.host ? this.#userInstanceCache.fetch(user.host, + instance: user.host ? this.userInstanceCache.fetch(user.host, () => this.instancesRepository.findOneBy({ host: user.host! }), v => v != null, ).then(instance => instance ? { diff --git a/packages/backend/src/core/remote/ResolveUserService.ts b/packages/backend/src/core/remote/ResolveUserService.ts index 4ef91bc8e6..b45168fb02 100644 --- a/packages/backend/src/core/remote/ResolveUserService.ts +++ b/packages/backend/src/core/remote/ResolveUserService.ts @@ -14,7 +14,7 @@ import { ApPersonService } from './activitypub/models/ApPersonService.js'; @Injectable() export class ResolveUserService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -28,14 +28,14 @@ export class ResolveUserService { private remoteLoggerService: RemoteLoggerService, private apPersonService: ApPersonService, ) { - this.#logger = this.remoteLoggerService.logger.createSubLogger('resolve-user'); + this.logger = this.remoteLoggerService.logger.createSubLogger('resolve-user'); } public async resolveUser(username: string, host: string | null): Promise { const usernameLower = username.toLowerCase(); if (host == null) { - this.#logger.info(`return local user: ${usernameLower}`); + this.logger.info(`return local user: ${usernameLower}`); return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => { if (u == null) { throw new Error('user not found'); @@ -48,7 +48,7 @@ export class ResolveUserService { host = this.utilityService.toPuny(host); if (this.config.host === host) { - this.#logger.info(`return local user: ${usernameLower}`); + this.logger.info(`return local user: ${usernameLower}`); return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => { if (u == null) { throw new Error('user not found'); @@ -63,9 +63,9 @@ export class ResolveUserService { const acctLower = `${usernameLower}@${host}`; if (user == null) { - const self = await this.#resolveSelf(acctLower); + const self = await this.resolveSelf(acctLower); - this.#logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`); + this.logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`); return await this.apPersonService.createPerson(self.href); } @@ -76,13 +76,13 @@ export class ResolveUserService { lastFetchedAt: new Date(), }); - this.#logger.info(`try resync: ${acctLower}`); - const self = await this.#resolveSelf(acctLower); + this.logger.info(`try resync: ${acctLower}`); + const self = await this.resolveSelf(acctLower); if (user.uri !== self.href) { // if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping. - this.#logger.info(`uri missmatch: ${acctLower}`); - this.#logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`); + this.logger.info(`uri missmatch: ${acctLower}`); + this.logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`); // validate uri const uri = new URL(self.href); @@ -97,12 +97,12 @@ export class ResolveUserService { uri: self.href, }); } else { - this.#logger.info(`uri is fine: ${acctLower}`); + this.logger.info(`uri is fine: ${acctLower}`); } await this.apPersonService.updatePerson(self.href); - this.#logger.info(`return resynced remote user: ${acctLower}`); + this.logger.info(`return resynced remote user: ${acctLower}`); return await this.usersRepository.findOneBy({ uri: self.href }).then(u => { if (u == null) { throw new Error('user not found'); @@ -112,19 +112,19 @@ export class ResolveUserService { }); } - this.#logger.info(`return existing remote user: ${acctLower}`); + this.logger.info(`return existing remote user: ${acctLower}`); return user; } - async #resolveSelf(acctLower: string) { - this.#logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); + private async resolveSelf(acctLower: string) { + this.logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); const finger = await this.webfingerService.webfinger(acctLower).catch(err => { - this.#logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`); + this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`); throw new Error(`Failed to WebFinger for ${acctLower}: ${ err.statusCode ?? err.message }`); }); const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self'); if (!self) { - this.#logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`); + this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`); throw new Error('self link not found'); } return self; diff --git a/packages/backend/src/core/remote/WebfingerService.ts b/packages/backend/src/core/remote/WebfingerService.ts index 24ccaf528b..ab46314792 100644 --- a/packages/backend/src/core/remote/WebfingerService.ts +++ b/packages/backend/src/core/remote/WebfingerService.ts @@ -26,12 +26,12 @@ export class WebfingerService { } public async webfinger(query: string): Promise { - const url = this.#genUrl(query); + const url = this.genUrl(query); return await this.httpRequestService.getJson(url, 'application/jrd+json, application/json') as IWebFinger; } - #genUrl(query: string): string { + private genUrl(query: string): string { if (query.match(/^https?:\/\//)) { const u = new URL(query); return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query }); diff --git a/packages/backend/src/core/remote/activitypub/ApAudienceService.ts b/packages/backend/src/core/remote/activitypub/ApAudienceService.ts index 178631ac55..744017aa3a 100644 --- a/packages/backend/src/core/remote/activitypub/ApAudienceService.ts +++ b/packages/backend/src/core/remote/activitypub/ApAudienceService.ts @@ -25,8 +25,8 @@ export class ApAudienceService { } public async parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { - const toGroups = this.#groupingAudience(getApIds(to), actor); - const ccGroups = this.#groupingAudience(getApIds(cc), actor); + const toGroups = this.groupingAudience(getApIds(to), actor); + const ccGroups = this.groupingAudience(getApIds(cc), actor); const others = unique(concat([toGroups.other, ccGroups.other])); @@ -66,7 +66,7 @@ export class ApAudienceService { }; } - #groupingAudience(ids: string[], actor: CacheableRemoteUser) { + private groupingAudience(ids: string[], actor: CacheableRemoteUser) { const groups = { public: [] as string[], followers: [] as string[], @@ -74,9 +74,9 @@ export class ApAudienceService { }; for (const id of ids) { - if (this.#isPublic(id)) { + if (this.isPublic(id)) { groups.public.push(id); - } else if (this.#isFollowers(id, actor)) { + } else if (this.isFollowers(id, actor)) { groups.followers.push(id); } else { groups.other.push(id); @@ -88,7 +88,7 @@ export class ApAudienceService { return groups; } - #isPublic(id: string) { + private isPublic(id: string) { return [ 'https://www.w3.org/ns/activitystreams#Public', 'as#Public', @@ -96,7 +96,7 @@ export class ApAudienceService { ].includes(id); } - #isFollowers(id: string, actor: CacheableRemoteUser) { + private isFollowers(id: string, actor: CacheableRemoteUser) { return ( id === (actor.followersUri ?? `${actor.uri}/followers`) ); diff --git a/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts b/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts index 37f58c6b0c..6f197985da 100644 --- a/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts @@ -31,8 +31,8 @@ export type UriParseResult = { @Injectable() export class ApDbResolverService { - #publicKeyCache: Cache; - #publicKeyByUserIdCache: Cache; + private publicKeyCache: Cache; + private publicKeyByUserIdCache: Cache; constructor( @Inject(DI.config) @@ -53,8 +53,8 @@ export class ApDbResolverService { private userCacheService: UserCacheService, private apPersonService: ApPersonService, ) { - this.#publicKeyCache = new Cache(Infinity); - this.#publicKeyByUserIdCache = new Cache(Infinity); + this.publicKeyCache = new Cache(Infinity); + this.publicKeyByUserIdCache = new Cache(Infinity); } public parseUri(value: string | IObject): UriParseResult { @@ -140,7 +140,7 @@ export class ApDbResolverService { user: CacheableRemoteUser; key: UserPublickey; } | null> { - const key = await this.#publicKeyCache.fetch(keyId, async () => { + const key = await this.publicKeyCache.fetch(keyId, async () => { const key = await this.userPublickeysRepository.findOneBy({ keyId, }); @@ -169,7 +169,7 @@ export class ApDbResolverService { if (user == null) return null; - const key = await this.#publicKeyByUserIdCache.fetch(user.id, () => this.userPublickeysRepository.findOneBy({ userId: user.id }), v => v != null); + const key = await this.publicKeyByUserIdCache.fetch(user.id, () => this.userPublickeysRepository.findOneBy({ userId: user.id }), v => v != null); return { user, diff --git a/packages/backend/src/core/remote/activitypub/ApInboxService.ts b/packages/backend/src/core/remote/activitypub/ApInboxService.ts index dc65f65ae1..0482e029d2 100644 --- a/packages/backend/src/core/remote/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/remote/activitypub/ApInboxService.ts @@ -34,7 +34,7 @@ import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow @Injectable() export class ApInboxService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -81,7 +81,7 @@ export class ApInboxService { private queueService: QueueService, private messagingService: MessagingService, ) { - this.#logger = this.apLoggerService.logger; + this.logger = this.apLoggerService.logger; } public async performActivity(actor: CacheableRemoteUser, activity: IObject) { @@ -93,7 +93,7 @@ export class ApInboxService { await this.performOneActivity(actor, act); } catch (err) { if (err instanceof Error || typeof err === 'string') { - this.#logger.error(err); + this.logger.error(err); } } } @@ -115,39 +115,39 @@ export class ApInboxService { if (actor.isSuspended) return; if (isCreate(activity)) { - await this.#create(actor, activity); + await this.create(actor, activity); } else if (isDelete(activity)) { - await this.#delete(actor, activity); + await this.delete(actor, activity); } else if (isUpdate(activity)) { - await this.#update(actor, activity); + await this.update(actor, activity); } else if (isRead(activity)) { - await this.#read(actor, activity); + await this.read(actor, activity); } else if (isFollow(activity)) { - await this.#follow(actor, activity); + await this.follow(actor, activity); } else if (isAccept(activity)) { - await this.#accept(actor, activity); + await this.accept(actor, activity); } else if (isReject(activity)) { - await this.#reject(actor, activity); + await this.reject(actor, activity); } else if (isAdd(activity)) { - await this.#add(actor, activity).catch(err => this.#logger.error(err)); + await this.add(actor, activity).catch(err => this.logger.error(err)); } else if (isRemove(activity)) { - await this.#remove(actor, activity).catch(err => this.#logger.error(err)); + await this.remove(actor, activity).catch(err => this.logger.error(err)); } else if (isAnnounce(activity)) { - await this.#announce(actor, activity); + await this.announce(actor, activity); } else if (isLike(activity)) { - await this.#like(actor, activity); + await this.like(actor, activity); } else if (isUndo(activity)) { - await this.#undo(actor, activity); + await this.undo(actor, activity); } else if (isBlock(activity)) { - await this.#block(actor, activity); + await this.block(actor, activity); } else if (isFlag(activity)) { - await this.#flag(actor, activity); + await this.flag(actor, activity); } else { - this.#logger.warn(`unrecognized activity type: ${(activity as any).type}`); + this.logger.warn(`unrecognized activity type: ${(activity as any).type}`); } } - async #follow(actor: CacheableRemoteUser, activity: IFollow): Promise { + private async follow(actor: CacheableRemoteUser, activity: IFollow): Promise { const followee = await this.apDbResolverService.getUserFromApId(activity.object); if (followee == null) { @@ -162,7 +162,7 @@ export class ApInboxService { return 'ok'; } - async #like(actor: CacheableRemoteUser, activity: ILike): Promise { + private async like(actor: CacheableRemoteUser, activity: ILike): Promise { const targetUri = getApId(activity.object); const note = await this.apNoteService.fetchNote(targetUri); @@ -179,7 +179,7 @@ export class ApInboxService { }).then(() => 'ok'); } - async #read(actor: CacheableRemoteUser, activity: IRead): Promise { + private async read(actor: CacheableRemoteUser, activity: IRead): Promise { const id = await getApId(activity.object); if (!this.utilityService.isSelfHost(this.utilityService.extractDbHost(id))) { @@ -201,24 +201,24 @@ export class ApInboxService { return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`; } - async #accept(actor: CacheableRemoteUser, activity: IAccept): Promise { + private async accept(actor: CacheableRemoteUser, activity: IAccept): Promise { const uri = activity.id ?? activity; - this.#logger.info(`Accept: ${uri}`); + this.logger.info(`Accept: ${uri}`); const resolver = this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(err => { - this.#logger.error(`Resolution failed: ${err}`); + this.logger.error(`Resolution failed: ${err}`); throw err; }); - if (isFollow(object)) return await this.#acceptFollow(actor, object); + if (isFollow(object)) return await this.acceptFollow(actor, object); return `skip: Unknown Accept type: ${getApType(object)}`; } - async #acceptFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { + private async acceptFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある const follower = await this.apDbResolverService.getUserFromApId(activity.actor); @@ -241,7 +241,7 @@ export class ApInboxService { return 'ok'; } - async #add(actor: CacheableRemoteUser, activity: IAdd): Promise { + private async add(actor: CacheableRemoteUser, activity: IAdd): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -260,17 +260,17 @@ export class ApInboxService { throw new Error(`unknown target: ${activity.target}`); } - async #announce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { + private async announce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { const uri = getApId(activity); - this.#logger.info(`Announce: ${uri}`); + this.logger.info(`Announce: ${uri}`); const targetUri = getApId(activity.object); - this.#announceNote(actor, activity, targetUri); + this.announceNote(actor, activity, targetUri); } - async #announceNote(actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { + private async announceNote(actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { const uri = getApId(activity); if (actor.isSuspended) { @@ -298,18 +298,18 @@ export class ApInboxService { // 対象が4xxならスキップ if (err instanceof StatusError) { if (err.isClientError) { - this.#logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`); + this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`); return; } - this.#logger.warn(`Error in announce target ${targetUri} - ${err.statusCode ?? err}`); + this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode ?? err}`); } throw err; } if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) return 'skip: invalid actor for this activity'; - this.#logger.info(`Creating the (Re)Note: ${uri}`); + this.logger.info(`Creating the (Re)Note: ${uri}`); const activityAudience = await this.apAudienceService.parseAudience(actor, activity.to, activity.cc); @@ -325,7 +325,7 @@ export class ApInboxService { } } - async #block(actor: CacheableRemoteUser, activity: IBlock): Promise { + private async block(actor: CacheableRemoteUser, activity: IBlock): Promise { // ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず const blockee = await this.apDbResolverService.getUserFromApId(activity.object); @@ -342,10 +342,10 @@ export class ApInboxService { return 'ok'; } - async #create(actor: CacheableRemoteUser, activity: ICreate): Promise { + private async create(actor: CacheableRemoteUser, activity: ICreate): Promise { const uri = getApId(activity); - this.#logger.info(`Create: ${uri}`); + this.logger.info(`Create: ${uri}`); // copy audiences between activity <=> object. if (typeof activity.object === 'object') { @@ -366,18 +366,18 @@ export class ApInboxService { const resolver = this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { - this.#logger.error(`Resolution failed: ${e}`); + this.logger.error(`Resolution failed: ${e}`); throw e; }); if (isPost(object)) { - this.#createNote(resolver, actor, object, false, activity); + this.createNote(resolver, actor, object, false, activity); } else { - this.#logger.warn(`Unknown type: ${getApType(object)}`); + this.logger.warn(`Unknown type: ${getApType(object)}`); } } - async #createNote(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { + private async createNote(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { const uri = getApId(note); if (typeof note === 'object') { @@ -411,7 +411,7 @@ export class ApInboxService { } } - async #delete(actor: CacheableRemoteUser, activity: IDelete): Promise { + private async delete(actor: CacheableRemoteUser, activity: IDelete): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -444,16 +444,16 @@ export class ApInboxService { } if (validPost.includes(formerType)) { - return await this.#deleteNote(actor, uri); + return await this.deleteNote(actor, uri); } else if (validActor.includes(formerType)) { - return await this.#deleteActor(actor, uri); + return await this.deleteActor(actor, uri); } else { return `Unknown type ${formerType}`; } } - async #deleteActor(actor: CacheableRemoteUser, uri: string): Promise { - this.#logger.info(`Deleting the Actor: ${uri}`); + private async deleteActor(actor: CacheableRemoteUser, uri: string): Promise { + this.logger.info(`Deleting the Actor: ${uri}`); if (actor.uri !== uri) { return `skip: delete actor ${actor.uri} !== ${uri}`; @@ -461,7 +461,7 @@ export class ApInboxService { const user = await this.usersRepository.findOneByOrFail({ id: actor.id }); if (user.isDeleted) { - this.#logger.info('skip: already deleted'); + this.logger.info('skip: already deleted'); } const job = await this.queueService.createDeleteAccountJob(actor); @@ -473,8 +473,8 @@ export class ApInboxService { return `ok: queued ${job.name} ${job.id}`; } - async #deleteNote(actor: CacheableRemoteUser, uri: string): Promise { - this.#logger.info(`Deleting the Note: ${uri}`); + private async deleteNote(actor: CacheableRemoteUser, uri: string): Promise { + this.logger.info(`Deleting the Note: ${uri}`); const unlock = await this.appLockService.getApLock(uri); @@ -505,7 +505,7 @@ export class ApInboxService { } } - async #flag(actor: CacheableRemoteUser, activity: IFlag): Promise { + private async flag(actor: CacheableRemoteUser, activity: IFlag): Promise { // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する const uris = getApIds(activity.object); @@ -529,24 +529,24 @@ export class ApInboxService { return 'ok'; } - async #reject(actor: CacheableRemoteUser, activity: IReject): Promise { + private async reject(actor: CacheableRemoteUser, activity: IReject): Promise { const uri = activity.id ?? activity; - this.#logger.info(`Reject: ${uri}`); + this.logger.info(`Reject: ${uri}`); const resolver = this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { - this.#logger.error(`Resolution failed: ${e}`); + this.logger.error(`Resolution failed: ${e}`); throw e; }); - if (isFollow(object)) return await this.#rejectFollow(actor, object); + if (isFollow(object)) return await this.rejectFollow(actor, object); return `skip: Unknown Reject type: ${getApType(object)}`; } - async #rejectFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { + private async rejectFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある const follower = await this.apDbResolverService.getUserFromApId(activity.actor); @@ -569,7 +569,7 @@ export class ApInboxService { return 'ok'; } - async #remove(actor: CacheableRemoteUser, activity: IRemove): Promise { + private async remove(actor: CacheableRemoteUser, activity: IRemove): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -588,32 +588,32 @@ export class ApInboxService { throw new Error(`unknown target: ${activity.target}`); } - async #undo(actor: CacheableRemoteUser, activity: IUndo): Promise { + private async undo(actor: CacheableRemoteUser, activity: IUndo): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } const uri = activity.id ?? activity; - this.#logger.info(`Undo: ${uri}`); + this.logger.info(`Undo: ${uri}`); const resolver = this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { - this.#logger.error(`Resolution failed: ${e}`); + this.logger.error(`Resolution failed: ${e}`); throw e; }); - if (isFollow(object)) return await this.#undoFollow(actor, object); - if (isBlock(object)) return await this.#undoBlock(actor, object); - if (isLike(object)) return await this.#undoLike(actor, object); - if (isAnnounce(object)) return await this.#undoAnnounce(actor, object); - if (isAccept(object)) return await this.#undoAccept(actor, object); + if (isFollow(object)) return await this.undoFollow(actor, object); + if (isBlock(object)) return await this.undoBlock(actor, object); + if (isLike(object)) return await this.undoLike(actor, object); + if (isAnnounce(object)) return await this.undoAnnounce(actor, object); + if (isAccept(object)) return await this.undoAccept(actor, object); return `skip: unknown object type ${getApType(object)}`; } - async #undoAccept(actor: CacheableRemoteUser, activity: IAccept): Promise { + private async undoAccept(actor: CacheableRemoteUser, activity: IAccept): Promise { const follower = await this.apDbResolverService.getUserFromApId(activity.object); if (follower == null) { return 'skip: follower not found'; @@ -632,7 +632,7 @@ export class ApInboxService { return 'skip: フォローされていない'; } - async #undoAnnounce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { + private async undoAnnounce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { const uri = getApId(activity); const note = await this.notesRepository.findOneBy({ @@ -646,7 +646,7 @@ export class ApInboxService { return 'ok: deleted'; } - async #undoBlock(actor: CacheableRemoteUser, activity: IBlock): Promise { + private async undoBlock(actor: CacheableRemoteUser, activity: IBlock): Promise { const blockee = await this.apDbResolverService.getUserFromApId(activity.object); if (blockee == null) { @@ -661,7 +661,7 @@ export class ApInboxService { return 'ok'; } - async #undoFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { + private async undoFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { const followee = await this.apDbResolverService.getUserFromApId(activity.object); if (followee == null) { return 'skip: followee not found'; @@ -694,7 +694,7 @@ export class ApInboxService { return 'skip: リクエストもフォローもされていない'; } - async #undoLike(actor: CacheableRemoteUser, activity: ILike): Promise { + private async undoLike(actor: CacheableRemoteUser, activity: ILike): Promise { const targetUri = getApId(activity.object); const note = await this.apNoteService.fetchNote(targetUri); @@ -708,17 +708,17 @@ export class ApInboxService { return 'ok'; } - async #update(actor: CacheableRemoteUser, activity: IUpdate): Promise { + private async update(actor: CacheableRemoteUser, activity: IUpdate): Promise { if ('actor' in activity && actor.uri !== activity.actor) { return 'skip: invalid actor'; } - this.#logger.debug('Update'); + this.logger.debug('Update'); const resolver = this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { - this.#logger.error(`Resolution failed: ${e}`); + this.logger.error(`Resolution failed: ${e}`); throw e; }); diff --git a/packages/backend/src/core/remote/activitypub/ApRendererService.ts b/packages/backend/src/core/remote/activitypub/ApRendererService.ts index b065c1acdd..5a4cef63e0 100644 --- a/packages/backend/src/core/remote/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/remote/activitypub/ApRendererService.ts @@ -365,7 +365,7 @@ export class ApRendererService { text: apText, })); - const emojis = await this.#getEmojis(note.emojis); + const emojis = await this.getEmojis(note.emojis); const apemojis = emojis.map(emoji => this.renderEmoji(emoji)); const tag = [ @@ -448,7 +448,7 @@ export class ApRendererService { } } - const emojis = await this.#getEmojis(user.emojis); + const emojis = await this.getEmojis(user.emojis); const apemojis = emojis.map(emoji => this.renderEmoji(emoji)); const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag)); @@ -687,7 +687,7 @@ export class ApRendererService { return page; } - async #getEmojis(names: string[]): Promise { + private async getEmojis(names: string[]): Promise { if (names == null || names.length === 0) return []; const emojis = await Promise.all( diff --git a/packages/backend/src/core/remote/activitypub/ApRequestService.ts b/packages/backend/src/core/remote/activitypub/ApRequestService.ts index a449a77dc8..2abaca06af 100644 --- a/packages/backend/src/core/remote/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/remote/activitypub/ApRequestService.ts @@ -36,14 +36,14 @@ export class ApRequestService { ) { } - #createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record }): Signed { + private createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record }): Signed { const u = new URL(args.url); const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`; const request: Request = { url: u.href, method: 'POST', - headers: this.#objectAssignWithLcKey({ + headers: this.objectAssignWithLcKey({ 'Date': new Date().toUTCString(), 'Host': u.hostname, 'Content-Type': 'application/activity+json', @@ -51,7 +51,7 @@ export class ApRequestService { }, args.additionalHeaders), }; - const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']); + const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']); return { request, @@ -61,20 +61,20 @@ export class ApRequestService { }; } - #createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record }): Signed { + private createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record }): Signed { const u = new URL(args.url); const request: Request = { url: u.href, method: 'GET', - headers: this.#objectAssignWithLcKey({ + headers: this.objectAssignWithLcKey({ 'Accept': 'application/activity+json, application/ld+json', 'Date': new Date().toUTCString(), 'Host': new URL(args.url).hostname, }, args.additionalHeaders), }; - const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']); + const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']); return { request, @@ -84,12 +84,12 @@ export class ApRequestService { }; } - #signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed { - const signingString = this.#genSigningString(request, includeHeaders); + private signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed { + const signingString = this.genSigningString(request, includeHeaders); const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64'); const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`; - request.headers = this.#objectAssignWithLcKey(request.headers, { + request.headers = this.objectAssignWithLcKey(request.headers, { Signature: signatureHeader, }); @@ -101,8 +101,8 @@ export class ApRequestService { }; } - #genSigningString(request: Request, includeHeaders: string[]): string { - request.headers = this.#lcObjectKey(request.headers); + private genSigningString(request: Request, includeHeaders: string[]): string { + request.headers = this.lcObjectKey(request.headers); const results: string[] = []; @@ -117,14 +117,14 @@ export class ApRequestService { return results.join('\n'); } - #lcObjectKey(src: Record): Record { + private lcObjectKey(src: Record): Record { const dst: Record = {}; for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key]; return dst; } - #objectAssignWithLcKey(a: Record, b: Record): Record { - return Object.assign(this.#lcObjectKey(a), this.#lcObjectKey(b)); + private objectAssignWithLcKey(a: Record, b: Record): Record { + return Object.assign(this.lcObjectKey(a), this.lcObjectKey(b)); } public async signedPost(user: { id: User['id'] }, url: string, object: any) { @@ -132,7 +132,7 @@ export class ApRequestService { const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); - const req = this.#createSignedPost({ + const req = this.createSignedPost({ key: { privateKeyPem: keypair.privateKey, keyId: `${this.config.url}/users/${user.id}#main-key`, @@ -160,7 +160,7 @@ export class ApRequestService { public async signedGet(url: string, user: { id: User['id'] }) { const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); - const req = this.#createSignedGet({ + const req = this.createSignedGet({ key: { privateKeyPem: keypair.privateKey, keyId: `${this.config.url}/users/${user.id}#main-key`, diff --git a/packages/backend/src/core/remote/activitypub/models/ApImageService.ts b/packages/backend/src/core/remote/activitypub/models/ApImageService.ts index d4e01923a2..da6ed61c56 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApImageService.ts @@ -14,7 +14,7 @@ import { ApLoggerService } from '../ApLoggerService.js'; @Injectable() export class ApImageService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -28,7 +28,7 @@ export class ApImageService { private driveService: DriveService, private apLoggerService: ApLoggerService, ) { - this.#logger = this.apLoggerService.logger; + this.logger = this.apLoggerService.logger; } /** @@ -46,7 +46,7 @@ export class ApImageService { throw new Error('invalid image: url not privided'); } - this.#logger.info(`Creating the Image: ${image.url}`); + this.logger.info(`Creating the Image: ${image.url}`); const instance = await this.metaService.fetch(); diff --git a/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts b/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts index c74949c595..1efe62333b 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts @@ -35,7 +35,7 @@ import type { IObject, IPost } from '../type.js'; @Injectable() export class ApNoteService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -71,7 +71,7 @@ export class ApNoteService { private apDbResolverService: ApDbResolverService, private apLoggerService: ApLoggerService, ) { - this.#logger = this.apLoggerService.logger; + this.logger = this.apLoggerService.logger; } public validateNote(object: any, uri: string) { @@ -116,7 +116,7 @@ export class ApNoteService { const entryUri = getApId(value); const err = this.validateNote(object, entryUri); if (err) { - this.#logger.error(`${err.message}`, { + this.logger.error(`${err.message}`, { resolver: { history: resolver.getHistory(), }, @@ -128,9 +128,9 @@ export class ApNoteService { const note: IPost = object; - this.#logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); + this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); - this.#logger.info(`Creating the Note: ${note.id}`); + this.logger.info(`Creating the Note: ${note.id}`); // 投稿者をフェッチ const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser; @@ -174,7 +174,7 @@ export class ApNoteService { const reply: Note | null = note.inReplyTo ? await this.resolveNote(note.inReplyTo, resolver).then(x => { if (x == null) { - this.#logger.warn('Specified inReplyTo, but nout found'); + this.logger.warn('Specified inReplyTo, but nout found'); throw new Error('inReplyTo not found'); } else { return x; @@ -191,7 +191,7 @@ export class ApNoteService { } } - this.#logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`); + this.logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`); throw err; }) : null; @@ -255,9 +255,9 @@ export class ApNoteService { const tryCreateVote = async (name: string, index: number): Promise => { if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { - this.#logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); + this.logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); } else if (index >= 0) { - this.#logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); + this.logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); await this.pollService.vote(actor, reply, index); // リモートフォロワーにUpdate配信 @@ -272,7 +272,7 @@ export class ApNoteService { } const emojis = await this.extractEmojis(note.tag ?? [], actor.host).catch(e => { - this.#logger.info(`extractEmojis: ${e}`); + this.logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); @@ -386,7 +386,7 @@ export class ApNoteService { return exists; } - this.#logger.info(`register emoji host=${host}, name=${name}`); + this.logger.info(`register emoji host=${host}, name=${name}`); return await this.emojisRepository.insert({ id: this.idService.genId(), diff --git a/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts b/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts index 1ca6463484..d088fa5554 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts @@ -91,7 +91,7 @@ export class ApPersonService implements OnModuleInit { private usersChart: UsersChart; private instanceChart: InstanceChart; private apLoggerService: ApLoggerService; - #logger: Logger; + private logger: Logger; constructor( private moduleRef: ModuleRef, @@ -153,7 +153,7 @@ export class ApPersonService implements OnModuleInit { this.usersChart = this.moduleRef.get('UsersChart'); this.instanceChart = this.moduleRef.get('InstanceChart'); this.apLoggerService = this.moduleRef.get('ApLoggerService'); - this.#logger = this.apLoggerService.logger; + this.logger = this.apLoggerService.logger; } /** @@ -161,7 +161,7 @@ export class ApPersonService implements OnModuleInit { * @param x Fetched object * @param uri Fetch target URI */ - #validateActor(x: IObject, uri: string): IActor { + private validateActor(x: IObject, uri: string): IActor { const expectHost = this.utilityService.toPuny(new URL(uri).hostname); if (x == null) { @@ -264,9 +264,9 @@ export class ApPersonService implements OnModuleInit { const object = await resolver.resolve(uri) as any; - const person = this.#validateActor(object, uri); + const person = this.validateActor(object, uri); - this.#logger.info(`Creating the Person: ${person.id}`); + this.logger.info(`Creating the Person: ${person.id}`); const host = this.utilityService.toPuny(new URL(object.id).hostname); @@ -338,7 +338,7 @@ export class ApPersonService implements OnModuleInit { throw new Error('already registered'); } } else { - this.#logger.error(e instanceof Error ? e : new Error(e as string)); + this.logger.error(e instanceof Error ? e : new Error(e as string)); throw e; } } @@ -379,7 +379,7 @@ export class ApPersonService implements OnModuleInit { //#region カスタム絵文字取得 const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host).catch(err => { - this.#logger.info(`extractEmojis: ${err}`); + this.logger.info(`extractEmojis: ${err}`); return [] as Emoji[]; }); @@ -390,7 +390,7 @@ export class ApPersonService implements OnModuleInit { }); //#endregion - await this.updateFeatured(user!.id).catch(err => this.#logger.error(err)); + await this.updateFeatured(user!.id).catch(err => this.logger.error(err)); return user!; } @@ -422,9 +422,9 @@ export class ApPersonService implements OnModuleInit { const object = hint ?? await resolver.resolve(uri); - const person = this.#validateActor(object, uri); + const person = this.validateActor(object, uri); - this.#logger.info(`Updating the Person: ${person.id}`); + this.logger.info(`Updating the Person: ${person.id}`); // アバターとヘッダー画像をフェッチ const [avatar, banner] = await Promise.all([ @@ -438,7 +438,7 @@ export class ApPersonService implements OnModuleInit { // カスタム絵文字取得 const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => { - this.#logger.info(`extractEmojis: ${e}`); + this.logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); @@ -503,7 +503,7 @@ export class ApPersonService implements OnModuleInit { followerSharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), }); - await this.updateFeatured(exist.id).catch(err => this.#logger.error(err)); + await this.updateFeatured(exist.id).catch(err => this.logger.error(err)); } /** @@ -556,7 +556,7 @@ export class ApPersonService implements OnModuleInit { if (!this.userEntityService.isRemoteUser(user)) return; if (!user.featured) return; - this.#logger.info(`Updating the featured: ${user.uri}`); + this.logger.info(`Updating the featured: ${user.uri}`); const resolver = this.apResolverService.createResolver(); diff --git a/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts index 97f26d1681..2b89cb030b 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts @@ -12,7 +12,7 @@ import type { IObject, IQuestion } from '../type.js'; @Injectable() export class ApQuestionService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -27,7 +27,7 @@ export class ApQuestionService { private apResolverService: ApResolverService, private apLoggerService: ApLoggerService, ) { - this.#logger = this.apLoggerService.logger; + this.logger = this.apLoggerService.logger; } public async extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise { @@ -82,7 +82,7 @@ export class ApQuestionService { // resolve new Question object const resolver = this.apResolverService.createResolver(); const question = await resolver.resolve(value) as IQuestion; - this.#logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); + this.logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); if (question.type !== 'Question') throw new Error('object is not a Question'); diff --git a/packages/backend/src/daemons/JanitorService.ts b/packages/backend/src/daemons/JanitorService.ts index dacbb8cca6..a51f570725 100644 --- a/packages/backend/src/daemons/JanitorService.ts +++ b/packages/backend/src/daemons/JanitorService.ts @@ -8,7 +8,7 @@ const interval = 30 * 60 * 1000; @Injectable() export class JanitorService implements OnApplicationShutdown { - #intervalId: NodeJS.Timer; + private intervalId: NodeJS.Timer; constructor( @Inject(DI.attestationChallengesRepository) @@ -28,10 +28,10 @@ export class JanitorService implements OnApplicationShutdown { tick(); - this.#intervalId = setInterval(tick, interval); + this.intervalId = setInterval(tick, interval); } public onApplicationShutdown(signal?: string | undefined) { - clearInterval(this.#intervalId); + clearInterval(this.intervalId); } } diff --git a/packages/backend/src/daemons/QueueStatsService.ts b/packages/backend/src/daemons/QueueStatsService.ts index f05d3ce33f..931de19067 100644 --- a/packages/backend/src/daemons/QueueStatsService.ts +++ b/packages/backend/src/daemons/QueueStatsService.ts @@ -10,7 +10,7 @@ const interval = 10000; @Injectable() export class QueueStatsService implements OnApplicationShutdown { - #intervalId: NodeJS.Timer; + private intervalId: NodeJS.Timer; constructor( private queueService: QueueService, @@ -68,10 +68,10 @@ export class QueueStatsService implements OnApplicationShutdown { tick(); - this.#intervalId = setInterval(tick, interval); + this.intervalId = setInterval(tick, interval); } public onApplicationShutdown(signal?: string | undefined) { - clearInterval(this.#intervalId); + clearInterval(this.intervalId); } } diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts index 3a6d408431..e40912442d 100644 --- a/packages/backend/src/daemons/ServerStatsService.ts +++ b/packages/backend/src/daemons/ServerStatsService.ts @@ -14,7 +14,7 @@ const round = (num: number) => Math.round(num * 10) / 10; @Injectable() export class ServerStatsService implements OnApplicationShutdown { - #intervalId: NodeJS.Timer; + private intervalId: NodeJS.Timer; constructor( ) { @@ -58,11 +58,11 @@ export class ServerStatsService implements OnApplicationShutdown { tick(); - this.#intervalId = setInterval(tick, interval); + this.intervalId = setInterval(tick, interval); } public onApplicationShutdown(signal?: string | undefined) { - clearInterval(this.#intervalId); + clearInterval(this.intervalId); } } diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 7be0f7b469..753df8cad6 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -16,7 +16,7 @@ import { QueueLoggerService } from './QueueLoggerService.js'; @Injectable() export class QueueProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -32,7 +32,7 @@ export class QueueProcessorService { private deliverProcessorService: DeliverProcessorService, private inboxProcessorService: InboxProcessorService, ) { - this.#logger = this.queueLoggerService.logger; + this.logger = this.queueLoggerService.logger; } public start() { @@ -44,12 +44,12 @@ export class QueueProcessorService { }; } - const systemLogger = this.#logger.createSubLogger('system'); - const deliverLogger = this.#logger.createSubLogger('deliver'); - const webhookLogger = this.#logger.createSubLogger('webhook'); - const inboxLogger = this.#logger.createSubLogger('inbox'); - const dbLogger = this.#logger.createSubLogger('db'); - const objectStorageLogger = this.#logger.createSubLogger('objectStorage'); + const systemLogger = this.logger.createSubLogger('system'); + const deliverLogger = this.logger.createSubLogger('deliver'); + const webhookLogger = this.logger.createSubLogger('webhook'); + const inboxLogger = this.logger.createSubLogger('inbox'); + const dbLogger = this.logger.createSubLogger('db'); + const objectStorageLogger = this.logger.createSubLogger('objectStorage'); this.queueService.systemQueue .on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`)) diff --git a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts index 514dc1dcf3..17337837a3 100644 --- a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts +++ b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts @@ -10,7 +10,7 @@ import type Bull from 'bull'; @Injectable() export class CheckExpiredMutingsProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -22,11 +22,11 @@ export class CheckExpiredMutingsProcessorService { private globalEventService: GlobalEventService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('check-expired-mutings'); + this.logger = this.queueLoggerService.logger.createSubLogger('check-expired-mutings'); } public async process(job: Bull.Job>, done: () => void): Promise { - this.#logger.info('Checking expired mutings...'); + this.logger.info('Checking expired mutings...'); const expired = await this.mutingsRepository.createQueryBuilder('muting') .where('muting.expiresAt IS NOT NULL') @@ -44,7 +44,7 @@ export class CheckExpiredMutingsProcessorService { } } - this.#logger.succ('All expired mutings checked.'); + this.logger.succ('All expired mutings checked.'); done(); } } diff --git a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts index 0eaad9b9ed..6f2fb8dea0 100644 --- a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts @@ -20,7 +20,7 @@ import type Bull from 'bull'; @Injectable() export class CleanChartsProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -41,11 +41,11 @@ export class CleanChartsProcessorService { private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('clean-charts'); + this.logger = this.queueLoggerService.logger.createSubLogger('clean-charts'); } public async process(job: Bull.Job>, done: () => void): Promise { - this.#logger.info('Clean charts...'); + this.logger.info('Clean charts...'); await Promise.all([ this.federationChart.clean(), @@ -62,7 +62,7 @@ export class CleanChartsProcessorService { this.apRequestChart.clean(), ]); - this.#logger.succ('All charts successfully cleaned.'); + this.logger.succ('All charts successfully cleaned.'); done(); } } diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts index 6150120806..830f0c56b6 100644 --- a/packages/backend/src/queue/processors/CleanProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanProcessorService.ts @@ -9,7 +9,7 @@ import type Bull from 'bull'; @Injectable() export class CleanProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -20,17 +20,17 @@ export class CleanProcessorService { private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('clean'); + this.logger = this.queueLoggerService.logger.createSubLogger('clean'); } public async process(job: Bull.Job>, done: () => void): Promise { - this.#logger.info('Cleaning...'); + this.logger.info('Cleaning...'); this.userIpsRepository.delete({ createdAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90))), }); - this.#logger.succ('Cleaned.'); + this.logger.succ('Cleaned.'); done(); } } diff --git a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts index 8c53632563..c3c68be1bc 100644 --- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts @@ -10,7 +10,7 @@ import type Bull from 'bull'; @Injectable() export class CleanRemoteFilesProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -22,11 +22,11 @@ export class CleanRemoteFilesProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('clean-remote-files'); + this.logger = this.queueLoggerService.logger.createSubLogger('clean-remote-files'); } public async process(job: Bull.Job>, done: () => void): Promise { - this.#logger.info('Deleting cached remote files...'); + this.logger.info('Deleting cached remote files...'); let deletedCount = 0; let cursor: any = null; @@ -63,7 +63,7 @@ export class CleanRemoteFilesProcessorService { job.progress(deletedCount / total); } - this.#logger.succ('All cahced remote files has been deleted.'); + this.logger.succ('All cahced remote files has been deleted.'); done(); } } diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index a5255c5c05..ab82f87d5e 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -14,7 +14,7 @@ import type { DbUserDeleteJobData } from '../types.js'; @Injectable() export class DeleteAccountProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -36,11 +36,11 @@ export class DeleteAccountProcessorService { private emailService: EmailService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('delete-account'); + this.logger = this.queueLoggerService.logger.createSubLogger('delete-account'); } public async process(job: Bull.Job): Promise { - this.#logger.info(`Deleting account of ${job.data.user.id} ...`); + this.logger.info(`Deleting account of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -71,7 +71,7 @@ export class DeleteAccountProcessorService { await this.notesRepository.delete(notes.map(note => note.id)); } - this.#logger.succ('All of notes deleted'); + this.logger.succ('All of notes deleted'); } { // Delete files @@ -100,7 +100,7 @@ export class DeleteAccountProcessorService { } } - this.#logger.succ('All of files deleted'); + this.logger.succ('All of files deleted'); } { // Send email notification diff --git a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts index 80814bb5a2..430fbf19e9 100644 --- a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts @@ -11,7 +11,7 @@ import type { DbUserJobData } from '../types.js'; @Injectable() export class DeleteDriveFilesProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -26,11 +26,11 @@ export class DeleteDriveFilesProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('delete-drive-files'); + this.logger = this.queueLoggerService.logger.createSubLogger('delete-drive-files'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Deleting drive files of ${job.data.user.id} ...`); + this.logger.info(`Deleting drive files of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -72,7 +72,7 @@ export class DeleteDriveFilesProcessorService { job.progress(deletedCount / total); } - this.#logger.succ(`All drive files (${deletedCount}) of ${user.id} has been deleted.`); + this.logger.succ(`All drive files (${deletedCount}) of ${user.id} has been deleted.`); done(); } } diff --git a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts index 55424f6444..72923b80a9 100644 --- a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts @@ -9,7 +9,7 @@ import type { ObjectStorageFileJobData } from '../types.js'; @Injectable() export class DeleteFileProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -18,7 +18,7 @@ export class DeleteFileProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('delete-file'); + this.logger = this.queueLoggerService.logger.createSubLogger('delete-file'); } public async process(job: Bull.Job): Promise { diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 3403ec83a9..1bf51c1bc6 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -21,9 +21,9 @@ import type { DeliverJobData } from '../types.js'; @Injectable() export class DeliverProcessorService { - #logger: Logger; - #suspendedHostsCache: Cache; - #latest: string | null; + private logger: Logger; + private suspendedHostsCache: Cache; + private latest: string | null; constructor( @Inject(DI.config) @@ -45,9 +45,9 @@ export class DeliverProcessorService { private federationChart: FederationChart, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('deliver'); - this.#suspendedHostsCache = new Cache(1000 * 60 * 60); - this.#latest = null; + this.logger = this.queueLoggerService.logger.createSubLogger('deliver'); + this.suspendedHostsCache = new Cache(1000 * 60 * 60); + this.latest = null; } public async process(job: Bull.Job): Promise { @@ -60,22 +60,22 @@ export class DeliverProcessorService { } // isSuspendedなら中断 - let suspendedHosts = this.#suspendedHostsCache.get(null); + let suspendedHosts = this.suspendedHostsCache.get(null); if (suspendedHosts == null) { suspendedHosts = await this.instancesRepository.find({ where: { isSuspended: true, }, }); - this.#suspendedHostsCache.set(null, suspendedHosts); + this.suspendedHostsCache.set(null, suspendedHosts); } if (suspendedHosts.map(x => x.host).includes(this.utilityService.toPuny(host))) { return 'skip (suspended)'; } try { - if (this.#latest !== (this.#latest = JSON.stringify(job.data.content, null, 2))) { - this.#logger.debug(`delivering ${this.#latest}`); + if (this.latest !== (this.latest = JSON.stringify(job.data.content, null, 2))) { + this.logger.debug(`delivering ${this.latest}`); } await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content); diff --git a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts index b90c7be629..3e55a351a1 100644 --- a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts +++ b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts @@ -11,7 +11,7 @@ import type { EndedPollNotificationJobData } from '../types.js'; @Injectable() export class EndedPollNotificationProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -26,7 +26,7 @@ export class EndedPollNotificationProcessorService { private createNotificationService: CreateNotificationService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('ended-poll-notification'); + this.logger = this.queueLoggerService.logger.createSubLogger('ended-poll-notification'); } public async process(job: Bull.Job, done: () => void): Promise { diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index 9b520be06e..cbc483698f 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -16,7 +16,7 @@ import type { DbUserJobData } from '../types.js'; @Injectable() export class ExportBlockingProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -32,11 +32,11 @@ export class ExportBlockingProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('export-blocking'); + this.logger = this.queueLoggerService.logger.createSubLogger('export-blocking'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Exporting blocking of ${job.data.user.id} ...`); + this.logger.info(`Exporting blocking of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -47,7 +47,7 @@ export class ExportBlockingProcessorService { // Create temp file const [path, cleanup] = await createTemp(); - this.#logger.info(`Temp file is ${path}`); + this.logger.info(`Temp file is ${path}`); try { const stream = fs.createWriteStream(path, { flags: 'a' }); @@ -84,7 +84,7 @@ export class ExportBlockingProcessorService { await new Promise((res, rej) => { stream.write(content + '\n', err => { if (err) { - this.#logger.error(err); + this.logger.error(err); rej(err); } else { res(); @@ -102,12 +102,12 @@ export class ExportBlockingProcessorService { } stream.end(); - this.#logger.succ(`Exported to: ${path}`); + this.logger.succ(`Exported to: ${path}`); const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); - this.#logger.succ(`Exported to: ${driveFile.id}`); + this.logger.succ(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index 93341c2c63..c49a47561b 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -17,7 +17,7 @@ import type Bull from 'bull'; @Injectable() export class ExportCustomEmojisProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -33,11 +33,11 @@ export class ExportCustomEmojisProcessorService { private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis'); + this.logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info('Exporting custom emojis ...'); + this.logger.info('Exporting custom emojis ...'); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -47,7 +47,7 @@ export class ExportCustomEmojisProcessorService { const [path, cleanup] = await createTempDir(); - this.#logger.info(`Temp dir is ${path}`); + this.logger.info(`Temp dir is ${path}`); const metaPath = path + '/meta.json'; @@ -59,7 +59,7 @@ export class ExportCustomEmojisProcessorService { return new Promise((res, rej) => { metaStream.write(text, err => { if (err) { - this.#logger.error(err); + this.logger.error(err); rej(err); } else { res(); @@ -90,7 +90,7 @@ export class ExportCustomEmojisProcessorService { await this.downloadService.downloadUrl(emoji.originalUrl, emojiPath); downloaded = true; } catch (e) { // TODO: 何度か再試行 - this.#logger.error(e instanceof Error ? e : new Error(e as string)); + this.logger.error(e instanceof Error ? e : new Error(e as string)); } if (!downloaded) { @@ -118,12 +118,12 @@ export class ExportCustomEmojisProcessorService { zlib: { level: 0 }, }); archiveStream.on('close', async () => { - this.#logger.succ(`Exported to: ${archivePath}`); + this.logger.succ(`Exported to: ${archivePath}`); const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.zip'; const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true }); - this.#logger.succ(`Exported to: ${driveFile.id}`); + this.logger.succ(`Exported to: ${driveFile.id}`); cleanup(); archiveCleanup(); done(); diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index 9946015ff7..4c6162432a 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -16,7 +16,7 @@ import type { DbUserJobData } from '../types.js'; @Injectable() export class ExportFollowingProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -35,11 +35,11 @@ export class ExportFollowingProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('export-following'); + this.logger = this.queueLoggerService.logger.createSubLogger('export-following'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Exporting following of ${job.data.user.id} ...`); + this.logger.info(`Exporting following of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -50,7 +50,7 @@ export class ExportFollowingProcessorService { // Create temp file const [path, cleanup] = await createTemp(); - this.#logger.info(`Temp file is ${path}`); + this.logger.info(`Temp file is ${path}`); try { const stream = fs.createWriteStream(path, { flags: 'a' }); @@ -94,7 +94,7 @@ export class ExportFollowingProcessorService { await new Promise((res, rej) => { stream.write(content + '\n', err => { if (err) { - this.#logger.error(err); + this.logger.error(err); rej(err); } else { res(); @@ -105,12 +105,12 @@ export class ExportFollowingProcessorService { } stream.end(); - this.#logger.succ(`Exported to: ${path}`); + this.logger.succ(`Exported to: ${path}`); const fileName = 'following-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); - this.#logger.succ(`Exported to: ${driveFile.id}`); + this.logger.succ(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index a34cea0f41..7781d2787f 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -15,7 +15,7 @@ import type { DbUserJobData } from '../types.js'; @Injectable() export class ExportMutingProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -34,11 +34,11 @@ export class ExportMutingProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('export-muting'); + this.logger = this.queueLoggerService.logger.createSubLogger('export-muting'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Exporting muting of ${job.data.user.id} ...`); + this.logger.info(`Exporting muting of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -49,7 +49,7 @@ export class ExportMutingProcessorService { // Create temp file const [path, cleanup] = await createTemp(); - this.#logger.info(`Temp file is ${path}`); + this.logger.info(`Temp file is ${path}`); try { const stream = fs.createWriteStream(path, { flags: 'a' }); @@ -87,7 +87,7 @@ export class ExportMutingProcessorService { await new Promise((res, rej) => { stream.write(content + '\n', err => { if (err) { - this.#logger.error(err); + this.logger.error(err); rej(err); } else { res(); @@ -105,12 +105,12 @@ export class ExportMutingProcessorService { } stream.end(); - this.#logger.succ(`Exported to: ${path}`); + this.logger.succ(`Exported to: ${path}`); const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); - this.#logger.succ(`Exported to: ${driveFile.id}`); + this.logger.succ(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index 24fcc1a8ad..62b3a53c49 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -16,7 +16,7 @@ import type { DbUserJobData } from '../types.js'; @Injectable() export class ExportNotesProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -34,11 +34,11 @@ export class ExportNotesProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('export-notes'); + this.logger = this.queueLoggerService.logger.createSubLogger('export-notes'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Exporting notes of ${job.data.user.id} ...`); + this.logger.info(`Exporting notes of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -49,7 +49,7 @@ export class ExportNotesProcessorService { // Create temp file const [path, cleanup] = await createTemp(); - this.#logger.info(`Temp file is ${path}`); + this.logger.info(`Temp file is ${path}`); try { const stream = fs.createWriteStream(path, { flags: 'a' }); @@ -58,7 +58,7 @@ export class ExportNotesProcessorService { return new Promise((res, rej) => { stream.write(text, err => { if (err) { - this.#logger.error(err); + this.logger.error(err); rej(err); } else { res(); @@ -112,12 +112,12 @@ export class ExportNotesProcessorService { await write(']'); stream.end(); - this.#logger.succ(`Exported to: ${path}`); + this.logger.succ(`Exported to: ${path}`); const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); - this.#logger.succ(`Exported to: ${driveFile.id}`); + this.logger.succ(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index a02e9bdee4..097835ac81 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -15,7 +15,7 @@ import type { DbUserJobData } from '../types.js'; @Injectable() export class ExportUserListsProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -34,11 +34,11 @@ export class ExportUserListsProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('export-user-lists'); + this.logger = this.queueLoggerService.logger.createSubLogger('export-user-lists'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Exporting user lists of ${job.data.user.id} ...`); + this.logger.info(`Exporting user lists of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -53,7 +53,7 @@ export class ExportUserListsProcessorService { // Create temp file const [path, cleanup] = await createTemp(); - this.#logger.info(`Temp file is ${path}`); + this.logger.info(`Temp file is ${path}`); try { const stream = fs.createWriteStream(path, { flags: 'a' }); @@ -70,7 +70,7 @@ export class ExportUserListsProcessorService { await new Promise((res, rej) => { stream.write(content + '\n', err => { if (err) { - this.#logger.error(err); + this.logger.error(err); rej(err); } else { res(); @@ -81,12 +81,12 @@ export class ExportUserListsProcessorService { } stream.end(); - this.#logger.succ(`Exported to: ${path}`); + this.logger.succ(`Exported to: ${path}`); const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); - this.#logger.succ(`Exported to: ${driveFile.id}`); + this.logger.succ(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index abae196299..44c8800a68 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -15,7 +15,7 @@ import type { DbUserImportJobData } from '../types.js'; @Injectable() export class ImportBlockingProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -36,11 +36,11 @@ export class ImportBlockingProcessorService { private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('import-blocking'); + this.logger = this.queueLoggerService.logger.createSubLogger('import-blocking'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Importing blocking of ${job.data.user.id} ...`); + this.logger.info(`Importing blocking of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -88,15 +88,15 @@ export class ImportBlockingProcessorService { // skip myself if (target.id === job.data.user.id) continue; - this.#logger.info(`Block[${linenum}] ${target.id} ...`); + this.logger.info(`Block[${linenum}] ${target.id} ...`); await this.userBlockingService.block(user, target); } catch (e) { - this.#logger.warn(`Error in line:${linenum} ${e}`); + this.logger.warn(`Error in line:${linenum} ${e}`); } } - this.#logger.succ('Imported'); + this.logger.succ('Imported'); done(); } } diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index 6f86589aec..4919fb2f7b 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -17,7 +17,7 @@ import type { DbUserImportJobData } from '../types.js'; // TODO: 名前衝突時の動作を選べるようにする @Injectable() export class ImportCustomEmojisProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -40,11 +40,11 @@ export class ImportCustomEmojisProcessorService { private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('import-custom-emojis'); + this.logger = this.queueLoggerService.logger.createSubLogger('import-custom-emojis'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info('Importing custom emojis ...'); + this.logger.info('Importing custom emojis ...'); const file = await this.driveFilesRepository.findOneBy({ id: job.data.fileId, @@ -56,7 +56,7 @@ export class ImportCustomEmojisProcessorService { const [path, cleanup] = await createTempDir(); - this.#logger.info(`Temp dir is ${path}`); + this.logger.info(`Temp dir is ${path}`); const destPath = path + '/emojis.zip'; @@ -65,7 +65,7 @@ export class ImportCustomEmojisProcessorService { await this.downloadService.downloadUrl(file.url, destPath); } catch (e) { // TODO: 何度か再試行 if (e instanceof Error || typeof e === 'string') { - this.#logger.error(e); + this.logger.error(e); } throw e; } @@ -101,10 +101,10 @@ export class ImportCustomEmojisProcessorService { cleanup(); - this.#logger.succ('Imported'); + this.logger.succ('Imported'); done(); }); unzipStream.pipe(extractor); - this.#logger.succ(`Unzipping to ${outputPath}`); + this.logger.succ(`Unzipping to ${outputPath}`); } } diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index 087e0baf96..5e49678d05 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -15,7 +15,7 @@ import type { DbUserImportJobData } from '../types.js'; @Injectable() export class ImportFollowingProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -33,11 +33,11 @@ export class ImportFollowingProcessorService { private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('import-following'); + this.logger = this.queueLoggerService.logger.createSubLogger('import-following'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Importing following of ${job.data.user.id} ...`); + this.logger.info(`Importing following of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -85,15 +85,15 @@ export class ImportFollowingProcessorService { // skip myself if (target.id === job.data.user.id) continue; - this.#logger.info(`Follow[${linenum}] ${target.id} ...`); + this.logger.info(`Follow[${linenum}] ${target.id} ...`); this.userFollowingService.follow(user, target); } catch (e) { - this.#logger.warn(`Error in line:${linenum} ${e}`); + this.logger.warn(`Error in line:${linenum} ${e}`); } } - this.#logger.succ('Imported'); + this.logger.succ('Imported'); done(); } } diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index 404091e8ca..c613c7e74e 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -16,7 +16,7 @@ import type { DbUserImportJobData } from '../types.js'; @Injectable() export class ImportMutingProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -34,11 +34,11 @@ export class ImportMutingProcessorService { private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('import-muting'); + this.logger = this.queueLoggerService.logger.createSubLogger('import-muting'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Importing muting of ${job.data.user.id} ...`); + this.logger.info(`Importing muting of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -86,15 +86,15 @@ export class ImportMutingProcessorService { // skip myself if (target.id === job.data.user.id) continue; - this.#logger.info(`Mute[${linenum}] ${target.id} ...`); + this.logger.info(`Mute[${linenum}] ${target.id} ...`); await this.userMutingService.mute(user, target); } catch (e) { - this.#logger.warn(`Error in line:${linenum} ${e}`); + this.logger.warn(`Error in line:${linenum} ${e}`); } } - this.#logger.succ('Imported'); + this.logger.succ('Imported'); done(); } } diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index aed1a4cde5..96c862e5c9 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -16,7 +16,7 @@ import type { DbUserImportJobData } from '../types.js'; @Injectable() export class ImportUserListsProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -41,11 +41,11 @@ export class ImportUserListsProcessorService { private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('import-user-lists'); + this.logger = this.queueLoggerService.logger.createSubLogger('import-user-lists'); } public async process(job: Bull.Job, done: () => void): Promise { - this.#logger.info(`Importing user lists of ${job.data.user.id} ...`); + this.logger.info(`Importing user lists of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -102,11 +102,11 @@ export class ImportUserListsProcessorService { this.userListService.push(target, list!); } catch (e) { - this.#logger.warn(`Error in line:${linenum} ${e}`); + this.logger.warn(`Error in line:${linenum} ${e}`); } } - this.#logger.succ('Imported'); + this.logger.succ('Imported'); done(); } } diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 5733f5d0a9..4593b4fb61 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -31,7 +31,7 @@ import type { DeliverJobData, InboxJobData } from '../types.js'; // ユーザーのinboxにアクティビティが届いた時の処理 @Injectable() export class InboxProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -57,7 +57,7 @@ export class InboxProcessorService { private federationChart: FederationChart, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('inbox'); + this.logger = this.queueLoggerService.logger.createSubLogger('inbox'); } public async process(job: Bull.Job): Promise { @@ -67,7 +67,7 @@ export class InboxProcessorService { //#region Log const info = Object.assign({}, activity) as any; delete info['@context']; - this.#logger.debug(JSON.stringify(info, null, 2)); + this.logger.debug(JSON.stringify(info, null, 2)); //#endregion const host = this.utilityService.toPuny(new URL(signature.keyId).hostname); diff --git a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts index f976232a24..75d02d527a 100644 --- a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts @@ -20,7 +20,7 @@ import type Bull from 'bull'; @Injectable() export class ResyncChartsProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -41,11 +41,11 @@ export class ResyncChartsProcessorService { private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('resync-charts'); + this.logger = this.queueLoggerService.logger.createSubLogger('resync-charts'); } public async process(job: Bull.Job>, done: () => void): Promise { - this.#logger.info('Resync charts...'); + this.logger.info('Resync charts...'); // TODO: ユーザーごとのチャートも更新する // TODO: インスタンスごとのチャートも更新する @@ -55,7 +55,7 @@ export class ResyncChartsProcessorService { this.usersChart.resync(), ]); - this.#logger.succ('All charts successfully resynced.'); + this.logger.succ('All charts successfully resynced.'); done(); } } diff --git a/packages/backend/src/queue/processors/TickChartsProcessorService.ts b/packages/backend/src/queue/processors/TickChartsProcessorService.ts index d1ca3c4576..e16956df0c 100644 --- a/packages/backend/src/queue/processors/TickChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/TickChartsProcessorService.ts @@ -20,7 +20,7 @@ import type Bull from 'bull'; @Injectable() export class TickChartsProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -41,11 +41,11 @@ export class TickChartsProcessorService { private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('tick-charts'); + this.logger = this.queueLoggerService.logger.createSubLogger('tick-charts'); } public async process(job: Bull.Job>, done: () => void): Promise { - this.#logger.info('Tick charts...'); + this.logger.info('Tick charts...'); await Promise.all([ this.federationChart.tick(false), @@ -62,7 +62,7 @@ export class TickChartsProcessorService { this.apRequestChart.tick(false), ]); - this.#logger.succ('All charts successfully ticked.'); + this.logger.succ('All charts successfully ticked.'); done(); } } diff --git a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts index 5723fe2eeb..27243be51b 100644 --- a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts @@ -12,7 +12,7 @@ import type { WebhookDeliverJobData } from '../types.js'; @Injectable() export class WebhookDeliverProcessorService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -24,12 +24,12 @@ export class WebhookDeliverProcessorService { private httpRequestService: HttpRequestService, private queueLoggerService: QueueLoggerService, ) { - this.#logger = this.queueLoggerService.logger.createSubLogger('webhook'); + this.logger = this.queueLoggerService.logger.createSubLogger('webhook'); } public async process(job: Bull.Job): Promise { try { - this.#logger.debug(`delivering ${job.data.webhookId}`); + this.logger.debug(`delivering ${job.data.webhookId}`); const res = await this.httpRequestService.getResponse({ url: job.data.to, diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 281da5175d..21ecc7177a 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -58,7 +58,7 @@ export class ActivityPubServerService { ) { } - #setResponseType(ctx: Router.RouterContext) { + private setResponseType(ctx: Router.RouterContext) { const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON); if (accept === LD_JSON) { ctx.response.type = LD_JSON; @@ -71,7 +71,7 @@ export class ActivityPubServerService { * Pack Create or Announce Activity * @param note Note */ - async #packActivity(note: Note): Promise { + private async packActivity(note: Note): Promise { if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { const renote = await Notes.findOneByOrFail({ id: note.renoteId }); return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note); @@ -80,7 +80,7 @@ export class ActivityPubServerService { return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note); } - #inbox(ctx: Router.RouterContext) { + private inbox(ctx: Router.RouterContext) { let signature; try { @@ -95,7 +95,7 @@ export class ActivityPubServerService { ctx.status = 202; } - async #followers(ctx: Router.RouterContext) { + private async followers(ctx: Router.RouterContext) { const userId = ctx.params.user; const cursor = ctx.request.query.cursor; @@ -169,17 +169,17 @@ export class ActivityPubServerService { ); ctx.body = this.apRendererService.renderActivity(rendered); - this.#setResponseType(ctx); + this.setResponseType(ctx); } else { // index page const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`); ctx.body = this.apRendererService.renderActivity(rendered); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); } } - async #following(ctx: Router.RouterContext) { + private async following(ctx: Router.RouterContext) { const userId = ctx.params.user; const cursor = ctx.request.query.cursor; @@ -253,17 +253,17 @@ export class ActivityPubServerService { ); ctx.body = this.apRendererService.renderActivity(rendered); - this.#setResponseType(ctx); + this.setResponseType(ctx); } else { // index page const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`); ctx.body = this.apRendererService.renderActivity(rendered); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); } } - async #featured(ctx: Router.RouterContext) { + private async featured(ctx: Router.RouterContext) { const userId = ctx.params.user; const user = await this.usersRepository.findOneBy({ @@ -293,10 +293,10 @@ export class ActivityPubServerService { ctx.body = this.apRendererService.renderActivity(rendered); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); } - async #outbox(ctx: Router.RouterContext) { + private async outbox(ctx: Router.RouterContext) { const userId = ctx.params.user; const sinceId = ctx.request.query.since_id; @@ -344,7 +344,7 @@ export class ActivityPubServerService { if (sinceId) notes.reverse(); - const activities = await Promise.all(notes.map(note => this.#packActivity(note))); + const activities = await Promise.all(notes.map(note => this.packActivity(note))); const rendered = this.apRendererService.renderOrderedCollectionPage( `${partOf}?${url.query({ page: 'true', @@ -363,7 +363,7 @@ export class ActivityPubServerService { ); ctx.body = this.apRendererService.renderActivity(rendered); - this.#setResponseType(ctx); + this.setResponseType(ctx); } else { // index page const rendered = this.apRendererService.renderOrderedCollection(partOf, user.notesCount, @@ -372,11 +372,11 @@ export class ActivityPubServerService { ); ctx.body = this.apRendererService.renderActivity(rendered); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); } } - async #userInfo(ctx: Router.RouterContext, user: User | null) { + private async userInfo(ctx: Router.RouterContext, user: User | null) { if (user == null) { ctx.status = 404; return; @@ -384,7 +384,7 @@ export class ActivityPubServerService { ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderPerson(user as ILocalUser)); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); } public createRouter() { @@ -399,8 +399,8 @@ export class ActivityPubServerService { } // inbox - router.post('/inbox', json(), ctx => this.#inbox(ctx)); - router.post('/users/:user/inbox', json(), ctx => this.#inbox(ctx)); + router.post('/inbox', json(), ctx => this.inbox(ctx)); + router.post('/users/:user/inbox', json(), ctx => this.inbox(ctx)); // note router.get('/notes/:note', async (ctx, next) => { @@ -429,7 +429,7 @@ export class ActivityPubServerService { ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderNote(note, false)); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); }); // note activity @@ -446,22 +446,22 @@ export class ActivityPubServerService { return; } - ctx.body = this.apRendererService.renderActivity(await this.#packActivity(note)); + ctx.body = this.apRendererService.renderActivity(await this.packActivity(note)); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); }); // outbox - router.get('/users/:user/outbox', (ctx) => this.#outbox(ctx)); + router.get('/users/:user/outbox', (ctx) => this.outbox(ctx)); // followers - router.get('/users/:user/followers', (ctx) => this.#followers(ctx)); + router.get('/users/:user/followers', (ctx) => this.followers(ctx)); // following - router.get('/users/:user/following', (ctx) => this.#following(ctx)); + router.get('/users/:user/following', (ctx) => this.following(ctx)); // featured - router.get('/users/:user/collections/featured', (ctx) => this.#featured(ctx)); + router.get('/users/:user/collections/featured', (ctx) => this.featured(ctx)); // publickey router.get('/users/:user/publickey', async ctx => { @@ -482,7 +482,7 @@ export class ActivityPubServerService { if (this.userEntityService.isLocalUser(user)) { ctx.body = this.apRendererService.renderActivity(this.apRendererService.renderKey(user, keypair)); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); } else { ctx.status = 400; } @@ -499,7 +499,7 @@ export class ActivityPubServerService { isSuspended: false, }); - await this.#userInfo(ctx, user); + await this.userInfo(ctx, user); }); router.get('/@:user', async (ctx, next) => { @@ -511,7 +511,7 @@ export class ActivityPubServerService { isSuspended: false, }); - await this.#userInfo(ctx, user); + await this.userInfo(ctx, user); }); //#endregion @@ -529,7 +529,7 @@ export class ActivityPubServerService { ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderEmoji(emoji)); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); }); // like @@ -550,7 +550,7 @@ export class ActivityPubServerService { ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderLike(reaction, note)); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); }); // follow @@ -576,7 +576,7 @@ export class ActivityPubServerService { ctx.body = this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee)); ctx.set('Cache-Control', 'public, max-age=180'); - this.#setResponseType(ctx); + this.setResponseType(ctx); }); return router; diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index e2b0ea5afe..becf0592d7 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -29,7 +29,7 @@ const assets = `${_dirname}/../../server/file/assets/`; @Injectable() export class FileServerService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -45,12 +45,12 @@ export class FileServerService { private internalStorageService: InternalStorageService, private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('server', 'gray', false); + this.logger = this.loggerService.getLogger('server', 'gray', false); } public commonReadableHandlerGenerator(ctx: Koa.Context) { return (e: Error): void => { - this.#logger.error(e); + this.logger.error(e); ctx.status = 500; ctx.set('Cache-Control', 'max-age=300'); }; @@ -74,8 +74,8 @@ export class FileServerService { ctx.set('Cache-Control', 'max-age=31536000, immutable'); }); - router.get('/:key', ctx => this.#sendDriveFile(ctx)); - router.get('/:key/(.*)', ctx => this.#sendDriveFile(ctx)); + router.get('/:key', ctx => this.sendDriveFile(ctx)); + router.get('/:key/(.*)', ctx => this.sendDriveFile(ctx)); // Register router app.use(router.routes()); @@ -83,7 +83,7 @@ export class FileServerService { return app; } - async #sendDriveFile(ctx: Koa.Context) { + private async sendDriveFile(ctx: Koa.Context) { const key = ctx.params.key; // Fetch drive file @@ -139,7 +139,7 @@ export class FileServerService { ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream'); ctx.set('Cache-Control', 'max-age=31536000, immutable'); } catch (err) { - this.#logger.error(`${err}`); + this.logger.error(`${err}`); if (err instanceof StatusError && err.isClientError) { ctx.status = err.statusCode; diff --git a/packages/backend/src/server/MediaProxyServerService.ts b/packages/backend/src/server/MediaProxyServerService.ts index 5344d3a9f4..1e0385602c 100644 --- a/packages/backend/src/server/MediaProxyServerService.ts +++ b/packages/backend/src/server/MediaProxyServerService.ts @@ -19,7 +19,7 @@ import { LoggerService } from '@/core/LoggerService.js'; @Injectable() export class MediaProxyServerService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -30,7 +30,7 @@ export class MediaProxyServerService { private imageProcessingService: ImageProcessingService, private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('server', 'gray', false); + this.logger = this.loggerService.getLogger('server', 'gray', false); } public createServer() { @@ -44,7 +44,7 @@ export class MediaProxyServerService { // Init router const router = new Router(); - router.get('/:url*', ctx => this.#handler(ctx)); + router.get('/:url*', ctx => this.handler(ctx)); // Register router app.use(router.routes()); @@ -52,7 +52,7 @@ export class MediaProxyServerService { return app; } - async #handler(ctx: Koa.Context) { + private async handler(ctx: Koa.Context) { const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url; if (typeof url !== 'string') { @@ -126,7 +126,7 @@ export class MediaProxyServerService { ctx.set('Cache-Control', 'max-age=31536000, immutable'); ctx.body = image.data; } catch (err) { - this.#logger.error(`${err}`); + this.logger.error(`${err}`); if (err instanceof StatusError && (err.statusCode === 302 || err.isClientError)) { ctx.status = err.statusCode; diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 8349e1e9f5..14d5ed45ab 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -30,7 +30,7 @@ import { ClientServerService } from './web/ClientServerService.js'; @Injectable() export class ServerService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -54,7 +54,7 @@ export class ServerService { private globalEventService: GlobalEventService, private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('server', 'gray', false); + this.logger = this.loggerService.getLogger('server', 'gray', false); } public launch() { @@ -65,7 +65,7 @@ export class ServerService { if (!['production', 'test'].includes(process.env.NODE_ENV ?? '')) { // Logger koa.use(koaLogger(str => { - this.#logger.info(str); + this.logger.info(str); })); // Delay @@ -157,13 +157,13 @@ export class ServerService { server.on('error', err => { switch ((err as any).code) { case 'EACCES': - this.#logger.error(`You do not have permission to listen on port ${this.config.port}.`); + this.logger.error(`You do not have permission to listen on port ${this.config.port}.`); break; case 'EADDRINUSE': - this.#logger.error(`Port ${this.config.port} is already in use by another process.`); + this.logger.error(`Port ${this.config.port} is already in use by another process.`); break; default: - this.#logger.error(err); + this.logger.error(err); break; } diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index f2ead3d4a1..d13b8d5ced 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -23,9 +23,9 @@ const accessDenied = { @Injectable() export class ApiCallService implements OnApplicationShutdown { - #logger: Logger; - #userIpHistories: Map>; - #userIpHistoriesClearIntervalId: NodeJS.Timer; + private logger: Logger; + private userIpHistories: Map>; + private userIpHistoriesClearIntervalId: NodeJS.Timer; constructor( @Inject(DI.userIpsRepository) @@ -36,11 +36,11 @@ export class ApiCallService implements OnApplicationShutdown { private rateLimiterService: RateLimiterService, private apiLoggerService: ApiLoggerService, ) { - this.#logger = this.apiLoggerService.logger; - this.#userIpHistories = new Map>(); + this.logger = this.apiLoggerService.logger; + this.userIpHistories = new Map>(); - this.#userIpHistoriesClearIntervalId = setInterval(() => { - this.#userIpHistories.clear(); + this.userIpHistoriesClearIntervalId = setInterval(() => { + this.userIpHistories.clear(); }, 1000 * 60 * 60); } @@ -76,7 +76,7 @@ export class ApiCallService implements OnApplicationShutdown { // Authentication this.authenticateService.authenticate(body['i']).then(([user, app]) => { // API invoking - this.#call(endpoint, exec, user, app, body, ctx).then((res: any) => { + this.call(endpoint, exec, user, app, body, ctx).then((res: any) => { if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) { ctx.set('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`); } @@ -90,10 +90,10 @@ export class ApiCallService implements OnApplicationShutdown { this.metaService.fetch().then(meta => { if (!meta.enableIpLogging) return; const ip = ctx.ip; - const ips = this.#userIpHistories.get(user.id); + const ips = this.userIpHistories.get(user.id); if (ips == null || !ips.has(ip)) { if (ips == null) { - this.#userIpHistories.set(user.id, new Set([ip])); + this.userIpHistories.set(user.id, new Set([ip])); } else { ips.add(ip); } @@ -123,7 +123,7 @@ export class ApiCallService implements OnApplicationShutdown { }); } - async #call( + private async call( ep: IEndpoint, exec: any, user: CacheableLocalUser | null | undefined, @@ -225,7 +225,7 @@ export class ApiCallService implements OnApplicationShutdown { if (err instanceof ApiError) { throw err; } else { - this.#logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, { + this.logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, { ep: ep.name, ps: data, e: { @@ -247,12 +247,12 @@ export class ApiCallService implements OnApplicationShutdown { const after = performance.now(); const time = after - before; if (time > 1000) { - this.#logger.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`); + this.logger.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`); } }); } public onApplicationShutdown(signal?: string | undefined) { - clearInterval(this.#userIpHistoriesClearIntervalId); + clearInterval(this.userIpHistoriesClearIntervalId); } } diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts index b8bd09509a..29d6ba78f0 100644 --- a/packages/backend/src/server/api/AuthenticateService.ts +++ b/packages/backend/src/server/api/AuthenticateService.ts @@ -17,7 +17,7 @@ export class AuthenticationError extends Error { @Injectable() export class AuthenticateService { - #appCache: Cache; + private appCache: Cache; constructor( @Inject(DI.usersRepository) @@ -31,7 +31,7 @@ export class AuthenticateService { private userCacheService: UserCacheService, ) { - this.#appCache = new Cache(Infinity); + this.appCache = new Cache(Infinity); } public async authenticate(token: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> { @@ -71,7 +71,7 @@ export class AuthenticateService { }) as Promise); if (accessToken.appId) { - const app = await this.#appCache.fetch(accessToken.appId, + const app = await this.appCache.fetch(accessToken.appId, () => this.appsRepository.findOneByOrFail({ id: accessToken.appId! })); return [user, { diff --git a/packages/backend/src/server/api/RateLimiterService.ts b/packages/backend/src/server/api/RateLimiterService.ts index 61b7b1ff47..35f28bfd63 100644 --- a/packages/backend/src/server/api/RateLimiterService.ts +++ b/packages/backend/src/server/api/RateLimiterService.ts @@ -8,7 +8,7 @@ import type { IEndpointMeta } from './endpoints.js'; @Injectable() export class RateLimiterService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.redis) @@ -16,7 +16,7 @@ export class RateLimiterService { private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('limiter'); + this.logger = this.loggerService.getLogger('limiter'); } public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable }, actor: string) { @@ -37,7 +37,7 @@ export class RateLimiterService { return reject('ERR'); } - this.#logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); + this.logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); if (info.remaining === 0) { reject('BRIEF_REQUEST_INTERVAL'); @@ -65,7 +65,7 @@ export class RateLimiterService { return reject('ERR'); } - this.#logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); + this.logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); if (info.remaining === 0) { reject('RATE_LIMIT_EXCEEDED'); diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index fa057dadb6..d9266aac6c 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -71,13 +71,13 @@ export default class extends Endpoint { (async () => { await this.userSuspendService.doPostSuspend(user).catch(e => {}); - await this.#unFollowAll(user).catch(e => {}); - await this.#readAllNotify(user).catch(e => {}); + await this.unFollowAll(user).catch(e => {}); + await this.readAllNotify(user).catch(e => {}); })(); }); } - async #unFollowAll(follower: User) { + private async unFollowAll(follower: User) { const followings = await this.followingsRepository.findBy({ followerId: follower.id, }); @@ -95,7 +95,7 @@ export default class extends Endpoint { } } - async #readAllNotify(notifier: User) { + private async readAllNotify(notifier: User) { await this.notificationsRepository.update({ notifierId: notifier.id, isRead: false, diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index eaf93ee977..e291b5908a 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -100,7 +100,7 @@ export default class extends Endpoint { private apNoteService: ApNoteService, ) { super(meta, paramDef, async (ps, me) => { - const object = await this.#fetchAny(ps.uri, me); + const object = await this.fetchAny(ps.uri, me); if (object) { return object; } else { @@ -112,12 +112,12 @@ export default class extends Endpoint { /*** * URIからUserかNoteを解決する */ - async #fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise | null> { + private async fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise | null> { // ブロックしてたら中断 const fetchedMeta = await this.metaService.fetch(); if (fetchedMeta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) return null; - let local = await this.#mergePack(me, ...await Promise.all([ + let local = await this.mergePack(me, ...await Promise.all([ this.apDbResolverService.getUserFromApId(uri), this.apDbResolverService.getNoteFromApId(uri), ])); @@ -130,21 +130,21 @@ export default class extends Endpoint { // /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する // これはDBに存在する可能性があるため再度DB検索 if (uri !== object.id) { - local = await this.#mergePack(me, ...await Promise.all([ + local = await this.mergePack(me, ...await Promise.all([ this.apDbResolverService.getUserFromApId(object.id), this.apDbResolverService.getNoteFromApId(object.id), ])); if (local != null) return local; } - return await this.#mergePack( + return await this.mergePack( me, isActor(object) ? await this.apPersonService.createPerson(getApId(object)) : null, isPost(object) ? await this.apNoteService.createNote(getApId(object), undefined, true) : null, ); } - async #mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise | null> { + private async mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise | null> { if (user != null) { return { type: 'User', diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index 604013c454..ea044c27d5 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -42,12 +42,12 @@ export class DiscordServerService { const router = new Router(); router.get('/disconnect/discord', async ctx => { - if (!this.#compareOrigin(ctx)) { + if (!this.compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; } - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); if (!userToken) { ctx.throw(400, 'signin required'); return; @@ -91,12 +91,12 @@ export class DiscordServerService { }; router.get('/connect/discord', async ctx => { - if (!this.#compareOrigin(ctx)) { + if (!this.compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; } - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); if (!userToken) { ctx.throw(400, 'signin required'); return; @@ -138,7 +138,7 @@ export class DiscordServerService { }); router.get('/dc/cb', async ctx => { - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); const oauth2 = await getOAuth2(); @@ -299,11 +299,11 @@ export class DiscordServerService { return router; } - #getUserToken(ctx: Koa.BaseContext): string | null { + private getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } - #compareOrigin(ctx: Koa.BaseContext): boolean { + private compareOrigin(ctx: Koa.BaseContext): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; } diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index 6864f21523..58b170d0e4 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -42,12 +42,12 @@ export class GithubServerService { const router = new Router(); router.get('/disconnect/github', async ctx => { - if (!this.#compareOrigin(ctx)) { + if (!this.compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; } - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); if (!userToken) { ctx.throw(400, 'signin required'); return; @@ -91,12 +91,12 @@ export class GithubServerService { }; router.get('/connect/github', async ctx => { - if (!this.#compareOrigin(ctx)) { + if (!this.compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; } - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); if (!userToken) { ctx.throw(400, 'signin required'); return; @@ -136,7 +136,7 @@ export class GithubServerService { }); router.get('/gh/cb', async ctx => { - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); const oauth2 = await getOath2(); @@ -271,11 +271,11 @@ export class GithubServerService { return router; } - #getUserToken(ctx: Koa.BaseContext): string | null { + private getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } - #compareOrigin(ctx: Koa.BaseContext): boolean { + private compareOrigin(ctx: Koa.BaseContext): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; } diff --git a/packages/backend/src/server/api/integration/TwitterServerService.ts b/packages/backend/src/server/api/integration/TwitterServerService.ts index 14b5f40ea5..a4a67f6c8c 100644 --- a/packages/backend/src/server/api/integration/TwitterServerService.ts +++ b/packages/backend/src/server/api/integration/TwitterServerService.ts @@ -42,12 +42,12 @@ export class TwitterServerService { const router = new Router(); router.get('/disconnect/twitter', async ctx => { - if (!this.#compareOrigin(ctx)) { + if (!this.compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; } - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); if (userToken == null) { ctx.throw(400, 'signin required'); return; @@ -90,12 +90,12 @@ export class TwitterServerService { }; router.get('/connect/twitter', async ctx => { - if (!this.#compareOrigin(ctx)) { + if (!this.compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; } - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); if (userToken == null) { ctx.throw(400, 'signin required'); return; @@ -125,7 +125,7 @@ export class TwitterServerService { }); router.get('/tw/cb', async ctx => { - const userToken = this.#getUserToken(ctx); + const userToken = this.getUserToken(ctx); const twAuth = await getTwAuth(); @@ -214,11 +214,11 @@ export class TwitterServerService { return router; } - #getUserToken(ctx: Koa.BaseContext): string | null { + private getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } - #compareOrigin(ctx: Koa.BaseContext): boolean { + private compareOrigin(ctx: Koa.BaseContext): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; } diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 67a7efaa25..85b31312b3 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -84,7 +84,7 @@ export class ClientServerService { ) { } - async #manifestHandler(ctx: Koa.Context) { + private async manifestHandler(ctx: Koa.Context) { // TODO //const res = structuredClone(manifest); const res = JSON.parse(JSON.stringify(manifest)); @@ -264,7 +264,7 @@ export class ClientServerService { }); // Manifest - router.get('/manifest.json', ctx => this.#manifestHandler(ctx)); + router.get('/manifest.json', ctx => this.manifestHandler(ctx)); router.get('/robots.txt', async ctx => { await send(ctx as any, '/robots.txt', { diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 726b158340..1cbb3f36c2 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -12,7 +12,7 @@ import type Koa from 'koa'; @Injectable() export class UrlPreviewService { - #logger: Logger; + private logger: Logger; constructor( @Inject(DI.config) @@ -25,10 +25,10 @@ export class UrlPreviewService { private httpRequestService: HttpRequestService, private loggerService: LoggerService, ) { - this.#logger = this.loggerService.getLogger('url-preview'); + this.logger = this.loggerService.getLogger('url-preview'); } - #wrap(url?: string): string | null { + private wrap(url?: string): string | null { return url != null ? url.match(/^https?:\/\//) ? `${this.config.url}/proxy/preview.webp?${query({ @@ -54,7 +54,7 @@ export class UrlPreviewService { const meta = await this.metaService.fetch(); - this.#logger.info(meta.summalyProxy + this.logger.info(meta.summalyProxy ? `(Proxy) Getting preview of ${url}@${lang} ...` : `Getting preview of ${url}@${lang} ...`); @@ -67,17 +67,17 @@ export class UrlPreviewService { lang: lang ?? 'ja-JP', }); - this.#logger.succ(`Got preview of ${url}: ${summary.title}`); + this.logger.succ(`Got preview of ${url}: ${summary.title}`); - summary.icon = this.#wrap(summary.icon); - summary.thumbnail = this.#wrap(summary.thumbnail); + summary.icon = this.wrap(summary.icon); + summary.thumbnail = this.wrap(summary.thumbnail); // Cache 7days ctx.set('Cache-Control', 'max-age=604800, immutable'); ctx.body = summary; } catch (err) { - this.#logger.warn(`Failed to get preview of ${url}: ${err}`); + this.logger.warn(`Failed to get preview of ${url}: ${err}`); ctx.status = 200; ctx.set('Cache-Control', 'max-age=86400, immutable'); ctx.body = '{}'; -- cgit v1.2.3-freya From 567c5501209d370c98a08ba7d25bfb274244a1ac Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 20 Sep 2022 05:32:18 +0900 Subject: lint --- packages/backend/src/core/DriveService.ts | 6 +++--- packages/backend/src/misc/gen-identicon.ts | 2 +- packages/backend/src/misc/is-instance-muted.ts | 10 +++++----- packages/backend/src/misc/is-quote.ts | 2 +- packages/backend/src/misc/show-machine-info.ts | 2 +- packages/backend/src/server/api/common/GetterService.ts | 2 +- packages/backend/src/server/api/common/inject-featured.ts | 13 +++++-------- packages/backend/src/server/api/common/inject-promo.ts | 5 ++--- packages/backend/src/server/api/endpoints/endpoint.ts | 2 +- .../src/server/api/endpoints/notes/thread-muting/create.ts | 6 +++--- .../src/server/api/endpoints/notes/thread-muting/delete.ts | 2 +- packages/backend/src/server/api/openapi/schemas.ts | 5 +++-- packages/client/.eslintrc.js | 2 +- 13 files changed, 28 insertions(+), 31 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 467f3c1cde..e356fa0009 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -147,7 +147,7 @@ export class DriveService { } const baseUrl = meta.objectStorageBaseUrl - || `${ meta.objectStorageUseSSL ? 'https' : 'http' }://${ meta.objectStorageEndpoint }${ meta.objectStoragePort ? `:${meta.objectStoragePort}` : '' }/${ meta.objectStorageBucket }`; + ?? `${ meta.objectStorageUseSSL ? 'https' : 'http' }://${ meta.objectStorageEndpoint }${ meta.objectStoragePort ? `:${meta.objectStoragePort}` : '' }/${ meta.objectStorageBucket }`; // for original const key = `${meta.objectStoragePrefix}/${uuid()}${ext}`; @@ -285,7 +285,7 @@ export class DriveService { satisfyWebpublic = !!( type !== 'image/svg+xml' && type !== 'image/webp' && - !(metadata.exif || metadata.iptc || metadata.xmp || metadata.tifftagPhotoshop) && + !(metadata.exif ?? metadata.iptc ?? metadata.xmp ?? metadata.tifftagPhotoshop) && metadata.width && metadata.width <= 2048 && metadata.height && metadata.height <= 2048 ); @@ -438,7 +438,7 @@ export class DriveService { //} // detect name - const detectedName = name || (info.type.ext ? `untitled.${info.type.ext}` : 'untitled'); + const detectedName = name ?? (info.type.ext ? `untitled.${info.type.ext}` : 'untitled'); if (user && !force) { // Check if there is a file with the same hash diff --git a/packages/backend/src/misc/gen-identicon.ts b/packages/backend/src/misc/gen-identicon.ts index 322ffee22e..4a70d7a4bb 100644 --- a/packages/backend/src/misc/gen-identicon.ts +++ b/packages/backend/src/misc/gen-identicon.ts @@ -3,9 +3,9 @@ * https://en.wikipedia.org/wiki/Identicon */ -import { WriteStream } from 'node:fs'; import * as p from 'pureimage'; import gen from 'random-seed'; +import type { WriteStream } from 'node:fs'; const size = 128; // px const n = 5; // resolution diff --git a/packages/backend/src/misc/is-instance-muted.ts b/packages/backend/src/misc/is-instance-muted.ts index a74ba524e3..e11a18bb70 100644 --- a/packages/backend/src/misc/is-instance-muted.ts +++ b/packages/backend/src/misc/is-instance-muted.ts @@ -1,15 +1,15 @@ -import { Packed } from './schema.js'; +import type { Packed } from './schema.js'; export function isInstanceMuted(note: Packed<'Note'>, mutedInstances: Set): boolean { - if (mutedInstances.has(note?.user?.host ?? '')) return true; - if (mutedInstances.has(note?.reply?.user?.host ?? '')) return true; - if (mutedInstances.has(note?.renote?.user?.host ?? '')) return true; + if (mutedInstances.has(note.user.host ?? '')) return true; + if (mutedInstances.has(note.reply?.user.host ?? '')) return true; + if (mutedInstances.has(note.renote?.user.host ?? '')) return true; return false; } export function isUserFromMutedInstance(notif: Packed<'Notification'>, mutedInstances: Set): boolean { - if (mutedInstances.has(notif?.user?.host ?? '')) return true; + if (mutedInstances.has(notif.user?.host ?? '')) return true; return false; } diff --git a/packages/backend/src/misc/is-quote.ts b/packages/backend/src/misc/is-quote.ts index 6ea71cd878..248b25a0bf 100644 --- a/packages/backend/src/misc/is-quote.ts +++ b/packages/backend/src/misc/is-quote.ts @@ -1,4 +1,4 @@ -import { Note } from '@/models/entities/Note.js'; +import type { Note } from '@/models/entities/Note.js'; export default function(note: Note): boolean { return note.renoteId != null && (note.text != null || note.hasPoll || (note.fileIds != null && note.fileIds.length > 0)); diff --git a/packages/backend/src/misc/show-machine-info.ts b/packages/backend/src/misc/show-machine-info.ts index bfb1b85f33..fa5a53e313 100644 --- a/packages/backend/src/misc/show-machine-info.ts +++ b/packages/backend/src/misc/show-machine-info.ts @@ -1,6 +1,6 @@ import * as os from 'node:os'; import sysUtils from 'systeminformation'; -import Logger from '@/core/logger.js'; +import type Logger from '@/logger.js'; export async function showMachineInfo(parentLogger: Logger) { const logger = parentLogger.createSubLogger('machine'); diff --git a/packages/backend/src/server/api/common/GetterService.ts b/packages/backend/src/server/api/common/GetterService.ts index a6b60d1f5a..5523539b91 100644 --- a/packages/backend/src/server/api/common/GetterService.ts +++ b/packages/backend/src/server/api/common/GetterService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { NotesRepository, UsersRepository } from '@/models/index.js'; +import { NotesRepository, UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; diff --git a/packages/backend/src/server/api/common/inject-featured.ts b/packages/backend/src/server/api/common/inject-featured.ts index 75126fa304..d59bec33ab 100644 --- a/packages/backend/src/server/api/common/inject-featured.ts +++ b/packages/backend/src/server/api/common/inject-featured.ts @@ -1,9 +1,6 @@ import rndstr from 'rndstr'; -import { Note } from '@/models/entities/Note.js'; -import { User } from '@/models/entities/User.js'; -import { Notes, UserProfiles, NoteReactions } from '@/models/index.js'; -import { generateMutedUserQuery } from './generate-muted-user-query.js'; -import { generateBlockedUserQuery } from './generate-block-query.js'; +import type { Note } from '@/models/entities/Note.js'; +import type { User } from '@/models/entities/User.js'; // TODO: リアクション、Renote、返信などをしたノートは除外する @@ -21,9 +18,9 @@ export async function injectFeatured(timeline: Note[], user?: User | null) { const query = Notes.createQueryBuilder('note') .addSelect('note.score') .where('note.userHost IS NULL') - .andWhere(`note.score > 0`) - .andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) }) - .andWhere(`note.visibility = 'public'`) + .andWhere('note.score > 0') + .andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) }) + .andWhere('note.visibility = \'public\'') .innerJoinAndSelect('note.user', 'user'); if (user) { diff --git a/packages/backend/src/server/api/common/inject-promo.ts b/packages/backend/src/server/api/common/inject-promo.ts index 454f5dbb0e..fe02897509 100644 --- a/packages/backend/src/server/api/common/inject-promo.ts +++ b/packages/backend/src/server/api/common/inject-promo.ts @@ -1,7 +1,6 @@ import rndstr from 'rndstr'; -import { Note } from '@/models/entities/Note.js'; -import { User } from '@/models/entities/User.js'; -import { PromoReads, PromoNotes, Notes, Users } from '@/models/index.js'; +import type { Note } from '@/models/entities/Note.js'; +import type { User } from '@/models/entities/User.js'; export async function injectPromo(timeline: Note[], user?: User | null) { if (timeline.length < 5) return; diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts index 2141dfbeb0..a337a05f8c 100644 --- a/packages/backend/src/server/api/endpoints/endpoint.ts +++ b/packages/backend/src/server/api/endpoints/endpoint.ts @@ -25,7 +25,7 @@ export default class extends Endpoint { const ep = endpoints.find(x => x.name === ps.endpoint); if (ep == null) return null; return { - params: Object.entries(ep.params.properties || {}).map(([k, v]) => ({ + params: Object.entries(ep.params.properties ?? {}).map(([k, v]) => ({ name: k, type: v.type.charAt(0).toUpperCase() + v.type.slice(1), })), diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index 1c83adddff..060581d74b 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -53,9 +53,9 @@ export default class extends Endpoint { const mutedNotes = await this.notesRepository.find({ where: [{ - id: note.threadId || note.id, + id: note.threadId ?? note.id, }, { - threadId: note.threadId || note.id, + threadId: note.threadId ?? note.id, }], }); @@ -64,7 +64,7 @@ export default class extends Endpoint { await this.noteThreadMutingsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), - threadId: note.threadId || note.id, + threadId: note.threadId ?? note.id, userId: me.id, }); }); diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts index 1f896734d1..aed15852d4 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -45,7 +45,7 @@ export default class extends Endpoint { }); await this.noteThreadMutingsRepository.delete({ - threadId: note.threadId || note.id, + threadId: note.threadId ?? note.id, userId: me.id, }); }); diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts index 14bef9cab1..796383f5e3 100644 --- a/packages/backend/src/server/api/openapi/schemas.ts +++ b/packages/backend/src/server/api/openapi/schemas.ts @@ -1,4 +1,5 @@ -import { refs, Schema } from '@/misc/schema.js'; +import type { Schema } from '@/misc/schema.js'; +import { refs } from '@/misc/schema.js'; export function convertSchemaToOpenApiSchema(schema: Schema) { const res: any = schema; @@ -55,6 +56,6 @@ export const schemas = { }, ...Object.fromEntries( - Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema)]) + Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema)]), ), }; diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index 01dedd1c69..c3a78bd5fd 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -38,7 +38,7 @@ module.exports = { 'vue/no-multi-spaces': ['error', { 'ignoreProperties': false, }], - 'vue/no-v-html': 'error', + 'vue/no-v-html': 'warn', 'vue/order-in-components': 'error', 'vue/html-indent': ['warn', 'tab', { 'attribute': 1, -- cgit v1.2.3-freya From dc43fc68ef2b8e0d6f2cfd94911b367fd915f445 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 21 Sep 2022 02:35:49 +0900 Subject: enhance(backend): metaのポーリング頻度を減らし、redisで更新を受け取るように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/MetaService.ts | 33 +++++++++++++++++++--- .../src/server/api/endpoints/admin/update-meta.ts | 16 +++++++++-- packages/backend/src/server/api/stream/types.ts | 2 ++ 3 files changed, 45 insertions(+), 6 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index 4099e340be..5ae9a6fc83 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import type { UsersRepository } from '@/models/index.js'; +import Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import { Meta } from '@/models/entities/Meta.js'; import type { OnApplicationShutdown } from '@nestjs/common'; @@ -11,19 +11,27 @@ export class MetaService implements OnApplicationShutdown { private intervalId: NodeJS.Timer; constructor( + @Inject(DI.redisSubscriber) + private redisSubscriber: Redis.Redis, + @Inject(DI.db) private db: DataSource, ) { + this.onMessage = this.onMessage.bind(this); + if (process.env.NODE_ENV !== 'test') { this.intervalId = setInterval(() => { this.fetch(true).then(meta => { + // fetch内でもセットしてるけど仕様変更の可能性もあるため一応 this.cache = meta; }); - }, 1000 * 10); + }, 1000 * 60 * 5); } + + this.redisSubscriber.on('message', this.onMessage); } - async fetch(noCache = false): Promise { + public async fetch(noCache = false): Promise { if (!noCache && this.cache) return this.cache; return await this.db.transaction(async transactionalEntityManager => { @@ -56,8 +64,25 @@ export class MetaService implements OnApplicationShutdown { } }); } - + + private async onMessage(_, data) { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message; + switch (type) { + case 'metaUpdated': { + this.cache = body; + break; + } + default: + break; + } + } + } + public onApplicationShutdown(signal?: string | undefined) { clearInterval(this.intervalId); + this.redisSubscriber.off('message', this.onMessage); } } diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 968ed4d26d..0ab0780e3d 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -5,6 +5,7 @@ import { ModerationLogService } from '@/core/ModerationLogService.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; export const meta = { tags: ['admin'], @@ -115,6 +116,7 @@ export default class extends Endpoint { @Inject(DI.db) private db: DataSource, + private globalEventService: GlobalEventService, private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { @@ -436,7 +438,7 @@ export default class extends Endpoint { set.enableActiveEmailValidation = ps.enableActiveEmailValidation; } - await this.db.transaction(async transactionalEntityManager => { + const updated = await this.db.transaction(async transactionalEntityManager => { const metas = await transactionalEntityManager.find(Meta, { order: { id: 'DESC', @@ -447,11 +449,21 @@ export default class extends Endpoint { if (meta) { await transactionalEntityManager.update(Meta, meta.id, set); + + const metas = await transactionalEntityManager.find(Meta, { + order: { + id: 'DESC', + }, + }); + + return metas[0]; } else { - await transactionalEntityManager.save(Meta, set); + return await transactionalEntityManager.save(Meta, set); } }); + this.globalEventService.publishInternalEvent('metaUpdated', updated); + this.moderationLogService.insertModerationLog(me, 'updateMeta'); }); } diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 000f9a25dd..ec05be56ee 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -13,6 +13,7 @@ import type { Signin } from '@/models/entities/Signin.js'; import type { Page } from '@/models/entities/Page.js'; import type { Packed } from '@/misc/schema.js'; import type { Webhook } from '@/models/entities/Webhook.js'; +import type { Meta } from '@/models/entities/Meta.js'; import type Emitter from 'strict-event-emitter-types'; import type { EventEmitter } from 'events'; @@ -29,6 +30,7 @@ export interface InternalStreamTypes { antennaCreated: Antenna; antennaDeleted: Antenna; antennaUpdated: Antenna; + metaUpdated: Meta, } export interface BroadcastTypes { -- cgit v1.2.3-freya From 3bc6205150c627699a9597158bc662ae8a2c0afe Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 21 Sep 2022 02:52:19 +0900 Subject: refactor(backend): ロジックをサービスに切り出す MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/MetaService.ts | 57 +++++++++++++++++----- .../src/server/api/endpoints/admin/update-meta.ts | 32 ++---------- 2 files changed, 49 insertions(+), 40 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index 5ae9a6fc83..1744f64217 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import { Meta } from '@/models/entities/Meta.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() @@ -16,6 +17,8 @@ export class MetaService implements OnApplicationShutdown { @Inject(DI.db) private db: DataSource, + + private globalEventService: GlobalEventService, ) { this.onMessage = this.onMessage.bind(this); @@ -31,6 +34,22 @@ export class MetaService implements OnApplicationShutdown { this.redisSubscriber.on('message', this.onMessage); } + private async onMessage(_, data): Promise { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message; + switch (type) { + case 'metaUpdated': { + this.cache = body; + break; + } + default: + break; + } + } + } + public async fetch(noCache = false): Promise { if (!noCache && this.cache) return this.cache; @@ -65,20 +84,34 @@ export class MetaService implements OnApplicationShutdown { }); } - private async onMessage(_, data) { - const obj = JSON.parse(data); + public async update(data: Partial): Promise { + const updated = await this.db.transaction(async transactionalEntityManager => { + const metas = await transactionalEntityManager.find(Meta, { + order: { + id: 'DESC', + }, + }); - if (obj.channel === 'internal') { - const { type, body } = obj.message; - switch (type) { - case 'metaUpdated': { - this.cache = body; - break; - } - default: - break; + const meta = metas[0]; + + if (meta) { + await transactionalEntityManager.update(Meta, meta.id, data); + + const metas = await transactionalEntityManager.find(Meta, { + order: { + id: 'DESC', + }, + }); + + return metas[0]; + } else { + return await transactionalEntityManager.save(Meta, data); } - } + }); + + this.globalEventService.publishInternalEvent('metaUpdated', updated); + + return updated; } public onApplicationShutdown(signal?: string | undefined) { diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 0ab0780e3d..48fae9b947 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -1,11 +1,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { Meta } from '@/models/entities/Meta.js'; +import type { Meta } from '@/models/entities/Meta.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { MetaService } from '@/core/MetaService.js'; export const meta = { tags: ['admin'], @@ -116,7 +117,7 @@ export default class extends Endpoint { @Inject(DI.db) private db: DataSource, - private globalEventService: GlobalEventService, + private metaService: MetaService, private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { @@ -438,32 +439,7 @@ export default class extends Endpoint { set.enableActiveEmailValidation = ps.enableActiveEmailValidation; } - const updated = await this.db.transaction(async transactionalEntityManager => { - const metas = await transactionalEntityManager.find(Meta, { - order: { - id: 'DESC', - }, - }); - - const meta = metas[0]; - - if (meta) { - await transactionalEntityManager.update(Meta, meta.id, set); - - const metas = await transactionalEntityManager.find(Meta, { - order: { - id: 'DESC', - }, - }); - - return metas[0]; - } else { - return await transactionalEntityManager.save(Meta, set); - } - }); - - this.globalEventService.publishInternalEvent('metaUpdated', updated); - + await this.metaService.update(set); this.moderationLogService.insertModerationLog(me, 'updateMeta'); }); } -- cgit v1.2.3-freya From 01d4d55e78fd977b9d44a68a2504e6091d346b7a Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 21 Sep 2022 05:33:11 +0900 Subject: fix import type --- packages/backend/src/core/AccountUpdateService.ts | 4 ++-- packages/backend/src/core/AiService.ts | 2 +- packages/backend/src/core/AntennaService.ts | 2 +- packages/backend/src/core/CaptchaService.ts | 2 +- packages/backend/src/core/CreateNotificationService.ts | 2 +- packages/backend/src/core/CustomEmojiService.ts | 4 ++-- packages/backend/src/core/DeleteAccountService.ts | 2 +- packages/backend/src/core/DownloadService.ts | 2 +- packages/backend/src/core/DriveService.ts | 4 ++-- packages/backend/src/core/EmailService.ts | 4 ++-- packages/backend/src/core/FederatedInstanceService.ts | 2 +- packages/backend/src/core/FetchInstanceMetadataService.ts | 2 +- packages/backend/src/core/GlobalEventService.ts | 2 +- packages/backend/src/core/HashtagService.ts | 2 +- packages/backend/src/core/HttpRequestService.ts | 2 +- packages/backend/src/core/IdService.ts | 2 +- packages/backend/src/core/ImageProcessingService.ts | 2 +- packages/backend/src/core/InstanceActorService.ts | 2 +- packages/backend/src/core/InternalStorageService.ts | 2 +- packages/backend/src/core/LoggerService.ts | 2 +- packages/backend/src/core/MessagingService.ts | 4 ++-- packages/backend/src/core/MfmService.ts | 2 +- packages/backend/src/core/ModerationLogService.ts | 2 +- packages/backend/src/core/NoteCreateService.ts | 4 ++-- packages/backend/src/core/NoteDeleteService.ts | 4 ++-- packages/backend/src/core/NotePiningService.ts | 4 ++-- packages/backend/src/core/NotificationService.ts | 2 +- packages/backend/src/core/PollService.ts | 2 +- packages/backend/src/core/ProxyAccountService.ts | 2 +- packages/backend/src/core/PushNotificationService.ts | 4 ++-- packages/backend/src/core/QueueService.ts | 4 ++-- packages/backend/src/core/ReactionService.ts | 2 +- packages/backend/src/core/RelayService.ts | 2 +- packages/backend/src/core/S3Service.ts | 2 +- packages/backend/src/core/SignupService.ts | 4 ++-- packages/backend/src/core/TwoFactorAuthenticationService.ts | 4 ++-- packages/backend/src/core/UserCacheService.ts | 2 +- packages/backend/src/core/UserKeypairStoreService.ts | 2 +- packages/backend/src/core/UserListService.ts | 2 +- packages/backend/src/core/UserMutingService.ts | 2 +- packages/backend/src/core/UserSuspendService.ts | 4 ++-- packages/backend/src/core/UtilityService.ts | 2 +- packages/backend/src/core/VideoProcessingService.ts | 2 +- packages/backend/src/core/WebhookService.ts | 2 +- packages/backend/src/core/chart/charts/federation.ts | 2 +- packages/backend/src/core/chart/charts/instance.ts | 2 +- packages/backend/src/core/chart/charts/notes.ts | 2 +- packages/backend/src/core/chart/charts/per-user-drive.ts | 2 +- packages/backend/src/core/chart/charts/per-user-following.ts | 2 +- packages/backend/src/core/chart/charts/per-user-notes.ts | 2 +- packages/backend/src/core/chart/charts/users.ts | 2 +- packages/backend/src/core/entities/AbuseUserReportEntityService.ts | 2 +- packages/backend/src/core/entities/AntennaEntityService.ts | 2 +- packages/backend/src/core/entities/AppEntityService.ts | 2 +- packages/backend/src/core/entities/AuthSessionEntityService.ts | 2 +- packages/backend/src/core/entities/BlockingEntityService.ts | 2 +- packages/backend/src/core/entities/ChannelEntityService.ts | 2 +- packages/backend/src/core/entities/ClipEntityService.ts | 2 +- packages/backend/src/core/entities/DriveFileEntityService.ts | 4 ++-- packages/backend/src/core/entities/DriveFolderEntityService.ts | 2 +- packages/backend/src/core/entities/EmojiEntityService.ts | 2 +- packages/backend/src/core/entities/FollowRequestEntityService.ts | 2 +- packages/backend/src/core/entities/FollowingEntityService.ts | 2 +- packages/backend/src/core/entities/GalleryLikeEntityService.ts | 2 +- packages/backend/src/core/entities/GalleryPostEntityService.ts | 2 +- packages/backend/src/core/entities/HashtagEntityService.ts | 2 +- packages/backend/src/core/entities/InstanceEntityService.ts | 2 +- packages/backend/src/core/entities/MessagingMessageEntityService.ts | 2 +- packages/backend/src/core/entities/ModerationLogEntityService.ts | 2 +- packages/backend/src/core/entities/MutingEntityService.ts | 2 +- packages/backend/src/core/entities/NoteEntityService.ts | 2 +- packages/backend/src/core/entities/NoteFavoriteEntityService.ts | 2 +- packages/backend/src/core/entities/NoteReactionEntityService.ts | 2 +- packages/backend/src/core/entities/NotificationEntityService.ts | 2 +- packages/backend/src/core/entities/PageEntityService.ts | 2 +- packages/backend/src/core/entities/PageLikeEntityService.ts | 2 +- packages/backend/src/core/entities/SigninEntityService.ts | 2 +- packages/backend/src/core/entities/UserEntityService.ts | 4 ++-- packages/backend/src/core/entities/UserGroupEntityService.ts | 2 +- .../backend/src/core/entities/UserGroupInvitationEntityService.ts | 2 +- packages/backend/src/core/entities/UserListEntityService.ts | 2 +- packages/backend/src/core/remote/ResolveUserService.ts | 4 ++-- packages/backend/src/core/remote/WebfingerService.ts | 2 +- packages/backend/src/core/remote/activitypub/ApDbResolverService.ts | 4 ++-- .../backend/src/core/remote/activitypub/ApDeliverManagerService.ts | 4 ++-- packages/backend/src/core/remote/activitypub/ApInboxService.ts | 2 +- packages/backend/src/core/remote/activitypub/ApMfmService.ts | 2 +- packages/backend/src/core/remote/activitypub/ApRendererService.ts | 2 +- packages/backend/src/core/remote/activitypub/ApRequestService.ts | 2 +- packages/backend/src/core/remote/activitypub/ApResolverService.ts | 4 ++-- .../backend/src/core/remote/activitypub/models/ApImageService.ts | 4 ++-- .../backend/src/core/remote/activitypub/models/ApMentionService.ts | 2 +- packages/backend/src/core/remote/activitypub/models/ApNoteService.ts | 4 ++-- .../backend/src/core/remote/activitypub/models/ApPersonService.ts | 4 ++-- .../backend/src/core/remote/activitypub/models/ApQuestionService.ts | 4 ++-- packages/backend/src/daemons/JanitorService.ts | 2 +- packages/backend/src/queue/DbQueueProcessorsService.ts | 2 +- packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts | 2 +- packages/backend/src/queue/QueueProcessorService.ts | 2 +- packages/backend/src/queue/SystemQueueProcessorsService.ts | 2 +- .../src/queue/processors/CheckExpiredMutingsProcessorService.ts | 4 ++-- packages/backend/src/queue/processors/CleanChartsProcessorService.ts | 2 +- packages/backend/src/queue/processors/CleanProcessorService.ts | 4 ++-- .../backend/src/queue/processors/CleanRemoteFilesProcessorService.ts | 4 ++-- .../backend/src/queue/processors/DeleteAccountProcessorService.ts | 4 ++-- .../backend/src/queue/processors/DeleteDriveFilesProcessorService.ts | 4 ++-- packages/backend/src/queue/processors/DeleteFileProcessorService.ts | 2 +- packages/backend/src/queue/processors/DeliverProcessorService.ts | 4 ++-- .../src/queue/processors/EndedPollNotificationProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ExportBlockingProcessorService.ts | 5 ++--- .../src/queue/processors/ExportCustomEmojisProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ExportFollowingProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ExportMutingProcessorService.ts | 4 ++-- packages/backend/src/queue/processors/ExportNotesProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ExportUserListsProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ImportBlockingProcessorService.ts | 4 ++-- .../src/queue/processors/ImportCustomEmojisProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ImportFollowingProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ImportMutingProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ImportUserListsProcessorService.ts | 4 ++-- packages/backend/src/queue/processors/InboxProcessorService.ts | 4 ++-- .../backend/src/queue/processors/ResyncChartsProcessorService.ts | 2 +- packages/backend/src/queue/processors/TickChartsProcessorService.ts | 2 +- .../backend/src/queue/processors/WebhookDeliverProcessorService.ts | 4 ++-- packages/backend/src/server/ActivityPubServerService.ts | 4 ++-- packages/backend/src/server/FileServerService.ts | 4 ++-- packages/backend/src/server/MediaProxyServerService.ts | 2 +- packages/backend/src/server/NodeinfoServerService.ts | 4 ++-- packages/backend/src/server/ServerService.ts | 4 ++-- packages/backend/src/server/WellKnownServerService.ts | 4 ++-- packages/backend/src/server/api/ApiCallService.ts | 2 +- packages/backend/src/server/api/ApiServerService.ts | 4 ++-- packages/backend/src/server/api/AuthenticateService.ts | 2 +- packages/backend/src/server/api/SigninApiService.ts | 4 ++-- packages/backend/src/server/api/SigninService.ts | 4 ++-- packages/backend/src/server/api/SignupApiService.ts | 4 ++-- packages/backend/src/server/api/StreamingApiServerService.ts | 4 ++-- packages/backend/src/server/api/common/GetterService.ts | 2 +- .../backend/src/server/api/endpoints/admin/abuse-user-reports.ts | 2 +- packages/backend/src/server/api/endpoints/admin/accounts/create.ts | 2 +- packages/backend/src/server/api/endpoints/admin/accounts/delete.ts | 2 +- packages/backend/src/server/api/endpoints/admin/ad/create.ts | 2 +- packages/backend/src/server/api/endpoints/admin/ad/delete.ts | 2 +- packages/backend/src/server/api/endpoints/admin/ad/list.ts | 2 +- packages/backend/src/server/api/endpoints/admin/ad/update.ts | 2 +- .../backend/src/server/api/endpoints/admin/announcements/create.ts | 2 +- .../backend/src/server/api/endpoints/admin/announcements/delete.ts | 2 +- .../backend/src/server/api/endpoints/admin/announcements/list.ts | 2 +- .../backend/src/server/api/endpoints/admin/announcements/update.ts | 2 +- packages/backend/src/server/api/endpoints/admin/delete-account.ts | 2 +- .../src/server/api/endpoints/admin/delete-all-files-of-a-user.ts | 2 +- .../src/server/api/endpoints/admin/drive-capacity-override.ts | 2 +- packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts | 2 +- packages/backend/src/server/api/endpoints/admin/drive/files.ts | 2 +- packages/backend/src/server/api/endpoints/admin/drive/show-file.ts | 2 +- .../backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/add.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/copy.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/delete.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/list.ts | 2 +- .../src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts | 2 +- .../backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts | 2 +- .../src/server/api/endpoints/admin/emoji/set-category-bulk.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/update.ts | 2 +- .../src/server/api/endpoints/admin/federation/delete-all-files.ts | 2 +- .../endpoints/admin/federation/refresh-remote-instance-metadata.ts | 2 +- .../server/api/endpoints/admin/federation/remove-all-following.ts | 2 +- .../src/server/api/endpoints/admin/federation/update-instance.ts | 2 +- packages/backend/src/server/api/endpoints/admin/get-user-ips.ts | 2 +- packages/backend/src/server/api/endpoints/admin/invite.ts | 2 +- packages/backend/src/server/api/endpoints/admin/meta.ts | 2 +- packages/backend/src/server/api/endpoints/admin/moderators/add.ts | 2 +- packages/backend/src/server/api/endpoints/admin/moderators/remove.ts | 2 +- packages/backend/src/server/api/endpoints/admin/promo/create.ts | 2 +- packages/backend/src/server/api/endpoints/admin/reset-password.ts | 2 +- .../src/server/api/endpoints/admin/resolve-abuse-user-report.ts | 2 +- .../backend/src/server/api/endpoints/admin/show-moderation-logs.ts | 2 +- packages/backend/src/server/api/endpoints/admin/show-user.ts | 2 +- packages/backend/src/server/api/endpoints/admin/show-users.ts | 2 +- packages/backend/src/server/api/endpoints/admin/silence-user.ts | 2 +- packages/backend/src/server/api/endpoints/admin/suspend-user.ts | 2 +- packages/backend/src/server/api/endpoints/admin/unsilence-user.ts | 2 +- packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts | 2 +- packages/backend/src/server/api/endpoints/admin/update-user-note.ts | 2 +- packages/backend/src/server/api/endpoints/announcements.ts | 2 +- packages/backend/src/server/api/endpoints/antennas/create.ts | 2 +- packages/backend/src/server/api/endpoints/antennas/delete.ts | 2 +- packages/backend/src/server/api/endpoints/antennas/list.ts | 2 +- packages/backend/src/server/api/endpoints/antennas/notes.ts | 2 +- packages/backend/src/server/api/endpoints/antennas/show.ts | 2 +- packages/backend/src/server/api/endpoints/antennas/update.ts | 2 +- packages/backend/src/server/api/endpoints/ap/show.ts | 2 +- packages/backend/src/server/api/endpoints/app/create.ts | 2 +- packages/backend/src/server/api/endpoints/app/show.ts | 2 +- packages/backend/src/server/api/endpoints/auth/accept.ts | 2 +- packages/backend/src/server/api/endpoints/auth/session/generate.ts | 4 ++-- packages/backend/src/server/api/endpoints/auth/session/show.ts | 2 +- packages/backend/src/server/api/endpoints/auth/session/userkey.ts | 2 +- packages/backend/src/server/api/endpoints/blocking/create.ts | 2 +- packages/backend/src/server/api/endpoints/blocking/delete.ts | 2 +- packages/backend/src/server/api/endpoints/blocking/list.ts | 2 +- packages/backend/src/server/api/endpoints/channels/create.ts | 2 +- packages/backend/src/server/api/endpoints/channels/featured.ts | 2 +- packages/backend/src/server/api/endpoints/channels/follow.ts | 2 +- packages/backend/src/server/api/endpoints/channels/followed.ts | 2 +- packages/backend/src/server/api/endpoints/channels/owned.ts | 2 +- packages/backend/src/server/api/endpoints/channels/show.ts | 2 +- packages/backend/src/server/api/endpoints/channels/timeline.ts | 2 +- packages/backend/src/server/api/endpoints/channels/unfollow.ts | 2 +- packages/backend/src/server/api/endpoints/channels/update.ts | 2 +- packages/backend/src/server/api/endpoints/clips/add-note.ts | 2 +- packages/backend/src/server/api/endpoints/clips/create.ts | 2 +- packages/backend/src/server/api/endpoints/clips/delete.ts | 2 +- packages/backend/src/server/api/endpoints/clips/list.ts | 2 +- packages/backend/src/server/api/endpoints/clips/notes.ts | 2 +- packages/backend/src/server/api/endpoints/clips/remove-note.ts | 2 +- packages/backend/src/server/api/endpoints/clips/show.ts | 2 +- packages/backend/src/server/api/endpoints/clips/update.ts | 2 +- packages/backend/src/server/api/endpoints/drive/files.ts | 2 +- .../backend/src/server/api/endpoints/drive/files/attached-notes.ts | 2 +- .../backend/src/server/api/endpoints/drive/files/check-existence.ts | 2 +- packages/backend/src/server/api/endpoints/drive/files/create.ts | 2 +- packages/backend/src/server/api/endpoints/drive/files/delete.ts | 2 +- .../backend/src/server/api/endpoints/drive/files/find-by-hash.ts | 2 +- packages/backend/src/server/api/endpoints/drive/files/find.ts | 2 +- packages/backend/src/server/api/endpoints/drive/files/show.ts | 2 +- packages/backend/src/server/api/endpoints/drive/files/update.ts | 2 +- .../backend/src/server/api/endpoints/drive/files/upload-from-url.ts | 2 +- packages/backend/src/server/api/endpoints/drive/folders.ts | 2 +- packages/backend/src/server/api/endpoints/drive/folders/create.ts | 2 +- packages/backend/src/server/api/endpoints/drive/folders/delete.ts | 2 +- packages/backend/src/server/api/endpoints/drive/folders/find.ts | 2 +- packages/backend/src/server/api/endpoints/drive/folders/show.ts | 2 +- packages/backend/src/server/api/endpoints/drive/folders/update.ts | 2 +- packages/backend/src/server/api/endpoints/drive/stream.ts | 2 +- packages/backend/src/server/api/endpoints/federation/followers.ts | 2 +- packages/backend/src/server/api/endpoints/federation/following.ts | 2 +- packages/backend/src/server/api/endpoints/federation/instances.ts | 2 +- .../backend/src/server/api/endpoints/federation/show-instance.ts | 2 +- packages/backend/src/server/api/endpoints/federation/stats.ts | 2 +- packages/backend/src/server/api/endpoints/federation/users.ts | 2 +- packages/backend/src/server/api/endpoints/fetch-rss.ts | 2 +- packages/backend/src/server/api/endpoints/following/create.ts | 2 +- packages/backend/src/server/api/endpoints/following/delete.ts | 2 +- packages/backend/src/server/api/endpoints/following/invalidate.ts | 2 +- .../backend/src/server/api/endpoints/following/requests/cancel.ts | 2 +- packages/backend/src/server/api/endpoints/following/requests/list.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/featured.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/popular.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/posts.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/posts/create.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/posts/delete.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/posts/like.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/posts/show.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts | 2 +- packages/backend/src/server/api/endpoints/gallery/posts/update.ts | 2 +- packages/backend/src/server/api/endpoints/get-online-users-count.ts | 2 +- packages/backend/src/server/api/endpoints/hashtags/list.ts | 2 +- packages/backend/src/server/api/endpoints/hashtags/search.ts | 2 +- packages/backend/src/server/api/endpoints/hashtags/show.ts | 2 +- packages/backend/src/server/api/endpoints/hashtags/trend.ts | 2 +- packages/backend/src/server/api/endpoints/hashtags/users.ts | 2 +- packages/backend/src/server/api/endpoints/i.ts | 2 +- packages/backend/src/server/api/endpoints/i/2fa/done.ts | 2 +- packages/backend/src/server/api/endpoints/i/2fa/key-done.ts | 4 ++-- packages/backend/src/server/api/endpoints/i/2fa/password-less.ts | 2 +- packages/backend/src/server/api/endpoints/i/2fa/register-key.ts | 2 +- packages/backend/src/server/api/endpoints/i/2fa/register.ts | 4 ++-- packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts | 2 +- packages/backend/src/server/api/endpoints/i/2fa/unregister.ts | 2 +- packages/backend/src/server/api/endpoints/i/apps.ts | 2 +- packages/backend/src/server/api/endpoints/i/authorized-apps.ts | 2 +- packages/backend/src/server/api/endpoints/i/change-password.ts | 2 +- packages/backend/src/server/api/endpoints/i/delete-account.ts | 2 +- packages/backend/src/server/api/endpoints/i/favorites.ts | 2 +- packages/backend/src/server/api/endpoints/i/gallery/likes.ts | 2 +- packages/backend/src/server/api/endpoints/i/gallery/posts.ts | 2 +- .../backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts | 2 +- packages/backend/src/server/api/endpoints/i/import-blocking.ts | 2 +- packages/backend/src/server/api/endpoints/i/import-following.ts | 2 +- packages/backend/src/server/api/endpoints/i/import-muting.ts | 2 +- packages/backend/src/server/api/endpoints/i/import-user-lists.ts | 2 +- packages/backend/src/server/api/endpoints/i/notifications.ts | 2 +- packages/backend/src/server/api/endpoints/i/page-likes.ts | 2 +- packages/backend/src/server/api/endpoints/i/pages.ts | 2 +- .../src/server/api/endpoints/i/read-all-messaging-messages.ts | 2 +- packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts | 2 +- packages/backend/src/server/api/endpoints/i/read-announcement.ts | 2 +- packages/backend/src/server/api/endpoints/i/regenerate-token.ts | 2 +- packages/backend/src/server/api/endpoints/i/registry/get-all.ts | 2 +- packages/backend/src/server/api/endpoints/i/registry/get-detail.ts | 2 +- packages/backend/src/server/api/endpoints/i/registry/get.ts | 2 +- .../backend/src/server/api/endpoints/i/registry/keys-with-type.ts | 2 +- packages/backend/src/server/api/endpoints/i/registry/keys.ts | 2 +- packages/backend/src/server/api/endpoints/i/registry/remove.ts | 2 +- packages/backend/src/server/api/endpoints/i/registry/scopes.ts | 2 +- packages/backend/src/server/api/endpoints/i/registry/set.ts | 2 +- packages/backend/src/server/api/endpoints/i/revoke-token.ts | 2 +- packages/backend/src/server/api/endpoints/i/signin-history.ts | 2 +- packages/backend/src/server/api/endpoints/i/update-email.ts | 4 ++-- packages/backend/src/server/api/endpoints/i/update.ts | 2 +- packages/backend/src/server/api/endpoints/i/user-group-invites.ts | 2 +- packages/backend/src/server/api/endpoints/i/webhooks/create.ts | 2 +- packages/backend/src/server/api/endpoints/i/webhooks/delete.ts | 2 +- packages/backend/src/server/api/endpoints/i/webhooks/list.ts | 2 +- packages/backend/src/server/api/endpoints/i/webhooks/show.ts | 2 +- packages/backend/src/server/api/endpoints/i/webhooks/update.ts | 2 +- packages/backend/src/server/api/endpoints/messaging/history.ts | 2 +- packages/backend/src/server/api/endpoints/messaging/messages.ts | 2 +- .../backend/src/server/api/endpoints/messaging/messages/create.ts | 2 +- .../backend/src/server/api/endpoints/messaging/messages/delete.ts | 2 +- packages/backend/src/server/api/endpoints/messaging/messages/read.ts | 2 +- packages/backend/src/server/api/endpoints/meta.ts | 4 ++-- packages/backend/src/server/api/endpoints/miauth/gen-token.ts | 2 +- packages/backend/src/server/api/endpoints/mute/create.ts | 2 +- packages/backend/src/server/api/endpoints/mute/delete.ts | 2 +- packages/backend/src/server/api/endpoints/mute/list.ts | 2 +- packages/backend/src/server/api/endpoints/my/apps.ts | 2 +- packages/backend/src/server/api/endpoints/notes.ts | 2 +- packages/backend/src/server/api/endpoints/notes/children.ts | 2 +- packages/backend/src/server/api/endpoints/notes/clips.ts | 2 +- packages/backend/src/server/api/endpoints/notes/conversation.ts | 2 +- packages/backend/src/server/api/endpoints/notes/create.ts | 2 +- packages/backend/src/server/api/endpoints/notes/delete.ts | 2 +- packages/backend/src/server/api/endpoints/notes/favorites/create.ts | 2 +- packages/backend/src/server/api/endpoints/notes/favorites/delete.ts | 2 +- packages/backend/src/server/api/endpoints/notes/featured.ts | 2 +- packages/backend/src/server/api/endpoints/notes/global-timeline.ts | 2 +- packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts | 2 +- packages/backend/src/server/api/endpoints/notes/local-timeline.ts | 2 +- packages/backend/src/server/api/endpoints/notes/mentions.ts | 2 +- .../backend/src/server/api/endpoints/notes/polls/recommendation.ts | 2 +- packages/backend/src/server/api/endpoints/notes/polls/vote.ts | 2 +- packages/backend/src/server/api/endpoints/notes/reactions.ts | 2 +- packages/backend/src/server/api/endpoints/notes/renotes.ts | 2 +- packages/backend/src/server/api/endpoints/notes/replies.ts | 2 +- packages/backend/src/server/api/endpoints/notes/search-by-tag.ts | 2 +- packages/backend/src/server/api/endpoints/notes/search.ts | 4 ++-- packages/backend/src/server/api/endpoints/notes/show.ts | 2 +- packages/backend/src/server/api/endpoints/notes/state.ts | 2 +- .../backend/src/server/api/endpoints/notes/thread-muting/create.ts | 2 +- .../backend/src/server/api/endpoints/notes/thread-muting/delete.ts | 2 +- packages/backend/src/server/api/endpoints/notes/timeline.ts | 2 +- packages/backend/src/server/api/endpoints/notes/translate.ts | 4 ++-- packages/backend/src/server/api/endpoints/notes/unrenote.ts | 2 +- .../backend/src/server/api/endpoints/notes/user-list-timeline.ts | 2 +- .../src/server/api/endpoints/notifications/mark-all-as-read.ts | 2 +- packages/backend/src/server/api/endpoints/page-push.ts | 2 +- packages/backend/src/server/api/endpoints/pages/create.ts | 2 +- packages/backend/src/server/api/endpoints/pages/delete.ts | 2 +- packages/backend/src/server/api/endpoints/pages/featured.ts | 2 +- packages/backend/src/server/api/endpoints/pages/like.ts | 2 +- packages/backend/src/server/api/endpoints/pages/show.ts | 2 +- packages/backend/src/server/api/endpoints/pages/unlike.ts | 2 +- packages/backend/src/server/api/endpoints/pages/update.ts | 2 +- packages/backend/src/server/api/endpoints/pinned-users.ts | 2 +- packages/backend/src/server/api/endpoints/promo/read.ts | 2 +- packages/backend/src/server/api/endpoints/request-reset-password.ts | 4 ++-- packages/backend/src/server/api/endpoints/reset-password.ts | 2 +- packages/backend/src/server/api/endpoints/stats.ts | 2 +- packages/backend/src/server/api/endpoints/sw/register.ts | 2 +- packages/backend/src/server/api/endpoints/sw/unregister.ts | 2 +- packages/backend/src/server/api/endpoints/username/available.ts | 2 +- packages/backend/src/server/api/endpoints/users.ts | 2 +- packages/backend/src/server/api/endpoints/users/clips.ts | 2 +- packages/backend/src/server/api/endpoints/users/followers.ts | 2 +- packages/backend/src/server/api/endpoints/users/following.ts | 2 +- packages/backend/src/server/api/endpoints/users/gallery/posts.ts | 2 +- .../src/server/api/endpoints/users/get-frequently-replied-users.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/create.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/delete.ts | 2 +- .../src/server/api/endpoints/users/groups/invitations/accept.ts | 2 +- .../src/server/api/endpoints/users/groups/invitations/reject.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/invite.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/joined.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/leave.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/owned.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/pull.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/show.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/transfer.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/update.ts | 2 +- packages/backend/src/server/api/endpoints/users/lists/create.ts | 2 +- packages/backend/src/server/api/endpoints/users/lists/delete.ts | 2 +- packages/backend/src/server/api/endpoints/users/lists/list.ts | 2 +- packages/backend/src/server/api/endpoints/users/lists/pull.ts | 2 +- packages/backend/src/server/api/endpoints/users/lists/push.ts | 2 +- packages/backend/src/server/api/endpoints/users/lists/show.ts | 2 +- packages/backend/src/server/api/endpoints/users/lists/update.ts | 2 +- packages/backend/src/server/api/endpoints/users/notes.ts | 2 +- packages/backend/src/server/api/endpoints/users/pages.ts | 2 +- packages/backend/src/server/api/endpoints/users/reactions.ts | 2 +- packages/backend/src/server/api/endpoints/users/recommendation.ts | 2 +- packages/backend/src/server/api/endpoints/users/relation.ts | 2 +- packages/backend/src/server/api/endpoints/users/report-abuse.ts | 2 +- .../src/server/api/endpoints/users/search-by-username-and-host.ts | 2 +- packages/backend/src/server/api/endpoints/users/search.ts | 2 +- packages/backend/src/server/api/endpoints/users/show.ts | 2 +- packages/backend/src/server/api/integration/DiscordServerService.ts | 4 ++-- packages/backend/src/server/api/integration/GithubServerService.ts | 4 ++-- packages/backend/src/server/api/integration/TwitterServerService.ts | 4 ++-- packages/backend/src/server/api/stream/channels/messaging.ts | 2 +- packages/backend/src/server/api/stream/channels/user-list.ts | 2 +- packages/backend/src/server/web/ClientServerService.ts | 2 +- packages/backend/src/server/web/FeedService.ts | 4 ++-- packages/backend/src/server/web/UrlPreviewService.ts | 4 ++-- packages/shared/.eslintrc.js | 2 +- 408 files changed, 474 insertions(+), 475 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/core/AccountUpdateService.ts b/packages/backend/src/core/AccountUpdateService.ts index 204e1d0170..6fe0e05c6d 100644 --- a/packages/backend/src/core/AccountUpdateService.ts +++ b/packages/backend/src/core/AccountUpdateService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { User } from '@/models/entities/User.js'; import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; import { RelayService } from '@/core/RelayService.js'; diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index e6102a1b91..15084b8ff1 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -4,7 +4,7 @@ import { dirname } from 'node:path'; import { Inject, Injectable } from '@nestjs/common'; import * as nsfw from 'nsfwjs'; import si from 'systeminformation'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; const _filename = fileURLToPath(import.meta.url); diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index e0af033952..0146f959e6 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -10,7 +10,7 @@ import * as Acct from '@/misc/acct.js'; import { Cache } from '@/misc/cache.js'; import type { Packed } from '@/misc/schema.js'; import { DI } from '@/di-symbols.js'; -import { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; import { UtilityService } from './UtilityService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index b1b52fd6a9..67b4b90061 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { HttpRequestService } from './HttpRequestService.js'; type CaptchaResponse = { diff --git a/packages/backend/src/core/CreateNotificationService.ts b/packages/backend/src/core/CreateNotificationService.ts index 525fac6d92..feb82dcbf9 100644 --- a/packages/backend/src/core/CreateNotificationService.ts +++ b/packages/backend/src/core/CreateNotificationService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { MutingsRepository, NotificationsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { MutingsRepository, NotificationsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import type { Notification } from '@/models/entities/Notification.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 32dad70d1c..e1355fff07 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -2,14 +2,14 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In, IsNull } from 'typeorm'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { IdService } from '@/core/IdService.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Emoji } from '@/models/entities/Emoji.js'; import { Cache } from '@/misc/cache.js'; import { query } from '@/misc/prelude/url.js'; import type { Note } from '@/models/entities/Note.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { UtilityService } from './UtilityService.js'; import { ReactionService } from './ReactionService.js'; diff --git a/packages/backend/src/core/DeleteAccountService.ts b/packages/backend/src/core/DeleteAccountService.ts index ba67bc499e..53d48c450b 100644 --- a/packages/backend/src/core/DeleteAccountService.ts +++ b/packages/backend/src/core/DeleteAccountService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { QueueService } from '@/core/QueueService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts index 81939d5f51..25965b7ac4 100644 --- a/packages/backend/src/core/DownloadService.ts +++ b/packages/backend/src/core/DownloadService.ts @@ -7,7 +7,7 @@ import PrivateIp from 'private-ip'; import got, * as Got from 'got'; import chalk from 'chalk'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { createTemp } from '@/misc/create-temp.js'; import { StatusError } from '@/misc/status-error.js'; diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index e356fa0009..643c51c37d 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -4,8 +4,8 @@ import { v4 as uuid } from 'uuid'; import sharp from 'sharp'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import Logger from '@/logger.js'; import type { IRemoteUser, User } from '@/models/entities/User.js'; import { MetaService } from '@/core/MetaService.js'; diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 521ab7fd81..4c5cf7dfc4 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -3,9 +3,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { validate as validateEmail } from 'deep-email-validator'; import { MetaService } from '@/core/MetaService.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; -import { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/index.js'; import { LoggerService } from '@/core/LoggerService.js'; @Injectable() diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index a4894a4376..b98a41f757 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/index.js'; import type { Instance } from '@/models/entities/Instance.js'; import { Cache } from '@/misc/cache.js'; import { IdService } from '@/core/IdService.js'; diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 376617914e..9a51ed0c0a 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -4,7 +4,7 @@ import { JSDOM } from 'jsdom'; import fetch from 'node-fetch'; import tinycolor from 'tinycolor2'; import type { Instance } from '@/models/entities/Instance.js'; -import { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/index.js'; import { AppLockService } from '@/core/AppLockService.js'; import type Logger from '@/logger.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index c36de63fde..df0c9b5ccb 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -24,7 +24,7 @@ import type { } from '@/server/api/stream/types.js'; import type { Packed } from '@/misc/schema.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; @Injectable() export class GlobalEventService { diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts index f6c06d48f4..83950aa890 100644 --- a/packages/backend/src/core/HashtagService.ts +++ b/packages/backend/src/core/HashtagService.ts @@ -5,7 +5,7 @@ import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { IdService } from '@/core/IdService.js'; import type { Hashtag } from '@/models/entities/Hashtag.js'; import HashtagChart from '@/core/chart/charts/hashtag.js'; -import { HashtagsRepository, UsersRepository } from '@/models/index.js'; +import type { HashtagsRepository, UsersRepository } from '@/models/index.js'; import { UserEntityService } from './entities/UserEntityService.js'; @Injectable() diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index f4c00cd259..396fefad1c 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -5,7 +5,7 @@ import fetch from 'node-fetch'; import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { StatusError } from '@/misc/status-error.js'; import type { Response } from 'node-fetch'; import type { URL } from 'node:url'; diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts index 345b72bac5..997be17937 100644 --- a/packages/backend/src/core/IdService.ts +++ b/packages/backend/src/core/IdService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { ulid } from 'ulid'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { genAid } from '@/misc/id/aid.js'; import { genMeid } from '@/misc/id/meid.js'; import { genMeidg } from '@/misc/id/meidg.js'; diff --git a/packages/backend/src/core/ImageProcessingService.ts b/packages/backend/src/core/ImageProcessingService.ts index d215be2131..3a50361a42 100644 --- a/packages/backend/src/core/ImageProcessingService.ts +++ b/packages/backend/src/core/ImageProcessingService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import sharp from 'sharp'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; export type IImage = { data: Buffer; diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts index 57d55870b1..fa906df4a2 100644 --- a/packages/backend/src/core/InstanceActorService.ts +++ b/packages/backend/src/core/InstanceActorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import type { ILocalUser } from '@/models/entities/User.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; import { DI } from '@/di-symbols.js'; import { CreateSystemUserService } from './CreateSystemUserService.js'; diff --git a/packages/backend/src/core/InternalStorageService.ts b/packages/backend/src/core/InternalStorageService.ts index 9bc3597baf..6d2a9b2db6 100644 --- a/packages/backend/src/core/InternalStorageService.ts +++ b/packages/backend/src/core/InternalStorageService.ts @@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); diff --git a/packages/backend/src/core/LoggerService.ts b/packages/backend/src/core/LoggerService.ts index 558e3016dc..a3192c0262 100644 --- a/packages/backend/src/core/LoggerService.ts +++ b/packages/backend/src/core/LoggerService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as SyslogPro from 'syslog-pro'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import Logger from '@/logger.js'; @Injectable() diff --git a/packages/backend/src/core/MessagingService.ts b/packages/backend/src/core/MessagingService.ts index 1819b32a45..0603da0651 100644 --- a/packages/backend/src/core/MessagingService.ts +++ b/packages/backend/src/core/MessagingService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; import type { Note } from '@/models/entities/Note.js'; @@ -10,7 +10,7 @@ import type { UserGroup } from '@/models/entities/UserGroup.js'; import { QueueService } from '@/core/QueueService.js'; import { toArray } from '@/misc/prelude/array.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { MessagingMessagesRepository, MutingsRepository, UserGroupJoiningsRepository, UsersRepository } from '@/models/index.js'; +import type { MessagingMessagesRepository, MutingsRepository, UserGroupJoiningsRepository, UsersRepository } from '@/models/index.js'; import { IdService } from './IdService.js'; import { GlobalEventService } from './GlobalEventService.js'; import { UserEntityService } from './entities/UserEntityService.js'; diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index 236be4bbf8..2e03bf3cc0 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -4,7 +4,7 @@ import * as parse5 from 'parse5'; import { JSDOM } from 'jsdom'; import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { intersperse } from '@/misc/prelude/array.js'; import type { IMentionedRemoteUsers } from '@/models/entities/Note.js'; import * as TreeAdapter from '../../node_modules/parse5/dist/tree-adapters/default.js'; diff --git a/packages/backend/src/core/ModerationLogService.ts b/packages/backend/src/core/ModerationLogService.ts index 191148ac25..81ae322b95 100644 --- a/packages/backend/src/core/ModerationLogService.ts +++ b/packages/backend/src/core/ModerationLogService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { ModerationLogsRepository } from '@/models/index.js'; +import type { ModerationLogsRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { IdService } from '@/core/IdService.js'; diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 5acc07fba6..6e5cce8f62 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -6,7 +6,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf import { extractHashtags } from '@/misc/extract-hashtags.js'; import type { IMentionedRemoteUsers } from '@/models/entities/Note.js'; import { Note } from '@/models/entities/Note.js'; -import { ChannelFollowingsRepository, ChannelsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { ChannelFollowingsRepository, ChannelsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { App } from '@/models/entities/App.js'; import { concat } from '@/misc/prelude/array.js'; @@ -23,7 +23,7 @@ import type { UserProfile } from '@/models/entities/UserProfile.js'; import { RelayService } from '@/core/RelayService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import NotesChart from '@/core/chart/charts/notes.js'; import PerUserNotesChart from '@/core/chart/charts/per-user-notes.js'; import InstanceChart from '@/core/chart/charts/instance.js'; diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index 48d0d31e88..ccc583c5b6 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -2,11 +2,11 @@ import { Brackets, In } from 'typeorm'; import { Injectable, Inject } from '@nestjs/common'; import type { User, ILocalUser, IRemoteUser } from '@/models/entities/User.js'; import type { Note, IMentionedRemoteUsers } from '@/models/entities/Note.js'; -import { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js'; +import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js'; import { RelayService } from '@/core/RelayService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import NotesChart from '@/core/chart/charts/notes.js'; import PerUserNotesChart from '@/core/chart/charts/per-user-notes.js'; import InstanceChart from '@/core/chart/charts/instance.js'; diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts index 576e90bd43..b70c051efd 100644 --- a/packages/backend/src/core/NotePiningService.ts +++ b/packages/backend/src/core/NotePiningService.ts @@ -1,13 +1,13 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; import { IdService } from '@/core/IdService.js'; import type { UserNotePining } from '@/models/entities/UserNotePining.js'; import { RelayService } from '@/core/RelayService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { UserEntityService } from './entities/UserEntityService.js'; import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js'; import { ApRendererService } from './remote/activitypub/ApRendererService.js'; diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index ca9e60889d..2606ca4de0 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { NotificationsRepository } from '@/models/index.js'; +import type { NotificationsRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import type { Notification } from '@/models/entities/Notification.js'; diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index 8bc94c8a82..1bb68f7804 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { NotesRepository, UsersRepository, BlockingsRepository } from '@/models/index.js'; +import type { NotesRepository, UsersRepository, BlockingsRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import { RelayService } from '@/core/RelayService.js'; import type { CacheableUser } from '@/models/entities/User.js'; diff --git a/packages/backend/src/core/ProxyAccountService.ts b/packages/backend/src/core/ProxyAccountService.ts index 40ccc8226a..07d8d0dbd5 100644 --- a/packages/backend/src/core/ProxyAccountService.ts +++ b/packages/backend/src/core/ProxyAccountService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import type { ILocalUser, User } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from './MetaService.js'; diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index 31d29bed97..5eaaed00eb 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -1,10 +1,10 @@ import { Inject, Injectable } from '@nestjs/common'; import push from 'web-push'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { Packed } from '@/misc/schema'; import { getNoteSummary } from '@/misc/get-note-summary.js'; -import { SwSubscriptionsRepository } from '@/models/index.js'; +import type { SwSubscriptionsRepository } from '@/models/index.js'; import { MetaService } from './MetaService.js'; // Defined also packages/sw/types.ts#L14-L21 diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 7e771c100f..12be57c7fb 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -3,9 +3,9 @@ import { v4 as uuid } from 'uuid'; import type { IActivity } from '@/core/remote/activitypub/type.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Webhook, webhookEventTypes } from '@/models/entities/Webhook.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; -import { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from './queue/QueueModule.js'; +import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from './queue/QueueModule.js'; import type { ThinUser } from '../queue/types.js'; import type httpSignature from '@peertube/http-signature'; diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 3006456577..d5b3c0e799 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { EmojisRepository, BlockingsRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/index.js'; +import type { EmojisRepository, BlockingsRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { IRemoteUser, User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts index 688ea03d34..5324826ec1 100644 --- a/packages/backend/src/core/RelayService.ts +++ b/packages/backend/src/core/RelayService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import type { ILocalUser, User } from '@/models/entities/User.js'; -import { RelaysRepository, UsersRepository } from '@/models/index.js'; +import type { RelaysRepository, UsersRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Cache } from '@/misc/cache.js'; import type { Relay } from '@/models/entities/Relay.js'; diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts index 9549e19990..723a79dc59 100644 --- a/packages/backend/src/core/S3Service.ts +++ b/packages/backend/src/core/S3Service.ts @@ -2,7 +2,7 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import S3 from 'aws-sdk/clients/s3.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { Meta } from '@/models/entities/Meta.js'; import { HttpRequestService } from './HttpRequestService.js'; diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index a876668b94..8b72d73af6 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import { DataSource, IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { UsedUsernamesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsedUsernamesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { User } from '@/models/entities/User.js'; import { UserProfile } from '@/models/entities/UserProfile.js'; import { IdService } from '@/core/IdService.js'; diff --git a/packages/backend/src/core/TwoFactorAuthenticationService.ts b/packages/backend/src/core/TwoFactorAuthenticationService.ts index be31534c02..0962f88a7c 100644 --- a/packages/backend/src/core/TwoFactorAuthenticationService.ts +++ b/packages/backend/src/core/TwoFactorAuthenticationService.ts @@ -2,8 +2,8 @@ import * as crypto from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import * as jsrsasign from 'jsrsasign'; import { DI } from '@/di-symbols.js'; -import { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; const ECC_PRELUDE = Buffer.from([0x04]); const NULL_BYTE = Buffer.from([0]); diff --git a/packages/backend/src/core/UserCacheService.ts b/packages/backend/src/core/UserCacheService.ts index 8212abf7bb..666bef3f49 100644 --- a/packages/backend/src/core/UserCacheService.ts +++ b/packages/backend/src/core/UserCacheService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import Redis from 'ioredis'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; import type { CacheableLocalUser, CacheableUser, ILocalUser } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/UserKeypairStoreService.ts b/packages/backend/src/core/UserKeypairStoreService.ts index e53f37b714..8eca03a10b 100644 --- a/packages/backend/src/core/UserKeypairStoreService.ts +++ b/packages/backend/src/core/UserKeypairStoreService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import type { User } from '@/models/entities/User.js'; -import { UserKeypairsRepository } from '@/models/index.js'; +import type { UserKeypairsRepository } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; import type { UserKeypair } from '@/models/entities/UserKeypair.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 03113f042a..b1d01a1565 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListJoiningsRepository, UsersRepository } from '@/models/index.js'; +import type { UserListJoiningsRepository, UsersRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import type { UserList } from '@/models/entities/UserList.js'; import type { UserListJoining } from '@/models/entities/UserListJoining.js'; diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts index 9146360df1..4c09e450c8 100644 --- a/packages/backend/src/core/UserMutingService.ts +++ b/packages/backend/src/core/UserMutingService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, MutingsRepository } from '@/models/index.js'; +import type { UsersRepository, MutingsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index 068341cb2c..82c2e98236 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -1,11 +1,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { Not, IsNull } from 'typeorm'; -import { FollowingsRepository, UsersRepository } from '@/models/index.js'; +import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { ApRendererService } from './remote/activitypub/ApRendererService.js'; import { UserEntityService } from './entities/UserEntityService.js'; diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index ba03dfc069..15dd684286 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -2,7 +2,7 @@ import { URL } from 'node:url'; import { toASCII } from 'punycode'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; @Injectable() export class UtilityService { diff --git a/packages/backend/src/core/VideoProcessingService.ts b/packages/backend/src/core/VideoProcessingService.ts index 70b9664c76..af4036a291 100644 --- a/packages/backend/src/core/VideoProcessingService.ts +++ b/packages/backend/src/core/VideoProcessingService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import FFmpeg from 'fluent-ffmpeg'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { ImageProcessingService } from '@/core/ImageProcessingService.js'; import type { IImage } from '@/core/ImageProcessingService.js'; import { createTempDir } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index 1d74290dd9..347b2b16c4 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import Redis from 'ioredis'; -import { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/index.js'; import type { Webhook } from '@/models/entities/Webhook.js'; import { DI } from '@/di-symbols.js'; import type { OnApplicationShutdown } from '@nestjs/common'; diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts index 4366d4cce1..21e4cedea3 100644 --- a/packages/backend/src/core/chart/charts/federation.ts +++ b/packages/backend/src/core/chart/charts/federation.ts @@ -1,6 +1,6 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { FollowingsRepository, InstancesRepository } from '@/models/index.js'; +import type { FollowingsRepository, InstancesRepository } from '@/models/index.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; diff --git a/packages/backend/src/core/chart/charts/instance.ts b/packages/backend/src/core/chart/charts/instance.ts index be70bc79c0..2e0f4c7126 100644 --- a/packages/backend/src/core/chart/charts/instance.ts +++ b/packages/backend/src/core/chart/charts/instance.ts @@ -1,6 +1,6 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { DriveFilesRepository, FollowingsRepository, UsersRepository, NotesRepository } from '@/models/index.js'; +import type { DriveFilesRepository, FollowingsRepository, UsersRepository, NotesRepository } from '@/models/index.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; diff --git a/packages/backend/src/core/chart/charts/notes.ts b/packages/backend/src/core/chart/charts/notes.ts index e1bfeabf99..2153cfe4b4 100644 --- a/packages/backend/src/core/chart/charts/notes.ts +++ b/packages/backend/src/core/chart/charts/notes.ts @@ -1,6 +1,6 @@ import { Injectable, Inject } from '@nestjs/common'; import { Not, IsNull, DataSource } from 'typeorm'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/chart/charts/per-user-drive.ts b/packages/backend/src/core/chart/charts/per-user-drive.ts index 752203daaf..a44460bb4e 100644 --- a/packages/backend/src/core/chart/charts/per-user-drive.ts +++ b/packages/backend/src/core/chart/charts/per-user-drive.ts @@ -1,6 +1,6 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/chart/charts/per-user-following.ts b/packages/backend/src/core/chart/charts/per-user-following.ts index 48bf3d7c62..5ea08a0872 100644 --- a/packages/backend/src/core/chart/charts/per-user-following.ts +++ b/packages/backend/src/core/chart/charts/per-user-following.ts @@ -4,7 +4,7 @@ import type { User } from '@/models/entities/User.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/index.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-following.js'; diff --git a/packages/backend/src/core/chart/charts/per-user-notes.ts b/packages/backend/src/core/chart/charts/per-user-notes.ts index ffe52dcd56..5c14309d89 100644 --- a/packages/backend/src/core/chart/charts/per-user-notes.ts +++ b/packages/backend/src/core/chart/charts/per-user-notes.ts @@ -4,7 +4,7 @@ import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-notes.js'; diff --git a/packages/backend/src/core/chart/charts/users.ts b/packages/backend/src/core/chart/charts/users.ts index b3187997cf..f0359968eb 100644 --- a/packages/backend/src/core/chart/charts/users.ts +++ b/packages/backend/src/core/chart/charts/users.ts @@ -4,7 +4,7 @@ import type { User } from '@/models/entities/User.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/users.js'; diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts index 6cc511fb48..1660894571 100644 --- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts +++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { AbuseUserReportsRepository } from '@/models/index.js'; +import type { AbuseUserReportsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { AbuseUserReport } from '@/models/entities/AbuseUserReport.js'; import { UserEntityService } from './UserEntityService.js'; diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts index 9193cb81d7..44110e7364 100644 --- a/packages/backend/src/core/entities/AntennaEntityService.ts +++ b/packages/backend/src/core/entities/AntennaEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { Antenna } from '@/models/entities/Antenna.js'; diff --git a/packages/backend/src/core/entities/AppEntityService.ts b/packages/backend/src/core/entities/AppEntityService.ts index 6491b0b2d9..1cc7ca11dc 100644 --- a/packages/backend/src/core/entities/AppEntityService.ts +++ b/packages/backend/src/core/entities/AppEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { AccessTokensRepository, AppsRepository } from '@/models/index.js'; +import type { AccessTokensRepository, AppsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { App } from '@/models/entities/App.js'; diff --git a/packages/backend/src/core/entities/AuthSessionEntityService.ts b/packages/backend/src/core/entities/AuthSessionEntityService.ts index a4dab3d9cf..bf8efa5f78 100644 --- a/packages/backend/src/core/entities/AuthSessionEntityService.ts +++ b/packages/backend/src/core/entities/AuthSessionEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { AuthSessionsRepository } from '@/models/index.js'; +import type { AuthSessionsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { AuthSession } from '@/models/entities/AuthSession.js'; diff --git a/packages/backend/src/core/entities/BlockingEntityService.ts b/packages/backend/src/core/entities/BlockingEntityService.ts index 74ce6830b6..49a96037ca 100644 --- a/packages/backend/src/core/entities/BlockingEntityService.ts +++ b/packages/backend/src/core/entities/BlockingEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { BlockingsRepository } from '@/models/index.js'; +import type { BlockingsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { Blocking } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts index fec76e4e63..860967443e 100644 --- a/packages/backend/src/core/entities/ChannelEntityService.ts +++ b/packages/backend/src/core/entities/ChannelEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NoteUnreadsRepository } from '@/models/index.js'; +import type { ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NoteUnreadsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts index 27637e42e8..7a5d2f7f0a 100644 --- a/packages/backend/src/core/entities/ClipEntityService.ts +++ b/packages/backend/src/core/entities/ClipEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index 521bf51da0..f0ac6518d0 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -2,8 +2,8 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { DataSource, In } from 'typeorm'; import * as mfm from 'mfm-js'; import { DI } from '@/di-symbols.js'; -import { NotesRepository, DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { NotesRepository, DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { Packed } from '@/misc/schema.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { User } from '@/models/entities/User.js'; diff --git a/packages/backend/src/core/entities/DriveFolderEntityService.ts b/packages/backend/src/core/entities/DriveFolderEntityService.ts index beebbc3206..5761fa37bc 100644 --- a/packages/backend/src/core/entities/DriveFolderEntityService.ts +++ b/packages/backend/src/core/entities/DriveFolderEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts index 10ed0f19ee..fc09b5a2c7 100644 --- a/packages/backend/src/core/entities/EmojiEntityService.ts +++ b/packages/backend/src/core/entities/EmojiEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/FollowRequestEntityService.ts b/packages/backend/src/core/entities/FollowRequestEntityService.ts index f7e7fd42e4..4a60c1263f 100644 --- a/packages/backend/src/core/entities/FollowRequestEntityService.ts +++ b/packages/backend/src/core/entities/FollowRequestEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { FollowRequestsRepository } from '@/models/index.js'; +import type { FollowRequestsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/FollowingEntityService.ts b/packages/backend/src/core/entities/FollowingEntityService.ts index 93fed85f72..c7e040a57b 100644 --- a/packages/backend/src/core/entities/FollowingEntityService.ts +++ b/packages/backend/src/core/entities/FollowingEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/GalleryLikeEntityService.ts b/packages/backend/src/core/entities/GalleryLikeEntityService.ts index 9473ed90b7..bf7c2b7f8d 100644 --- a/packages/backend/src/core/entities/GalleryLikeEntityService.ts +++ b/packages/backend/src/core/entities/GalleryLikeEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { GalleryPosts, GalleryLikesRepository } from '@/models/index.js'; +import type { GalleryPosts, GalleryLikesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts index 82b41697c9..ca98687d7b 100644 --- a/packages/backend/src/core/entities/GalleryPostEntityService.ts +++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { GalleryLikesRepository, GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryLikesRepository, GalleryPostsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/HashtagEntityService.ts b/packages/backend/src/core/entities/HashtagEntityService.ts index 6dcbf49030..511992c44f 100644 --- a/packages/backend/src/core/entities/HashtagEntityService.ts +++ b/packages/backend/src/core/entities/HashtagEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { HashtagsRepository } from '@/models/index.js'; +import type { HashtagsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index c58c2f8f34..c54285d9df 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/MessagingMessageEntityService.ts b/packages/backend/src/core/entities/MessagingMessageEntityService.ts index 04467b94e4..b7c42a5760 100644 --- a/packages/backend/src/core/entities/MessagingMessageEntityService.ts +++ b/packages/backend/src/core/entities/MessagingMessageEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { MessagingMessagesRepository } from '@/models/index.js'; +import type { MessagingMessagesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/ModerationLogEntityService.ts b/packages/backend/src/core/entities/ModerationLogEntityService.ts index 45d15088fc..2f508710b8 100644 --- a/packages/backend/src/core/entities/ModerationLogEntityService.ts +++ b/packages/backend/src/core/entities/ModerationLogEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { ModerationLogsRepository } from '@/models/index.js'; +import type { ModerationLogsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/MutingEntityService.ts b/packages/backend/src/core/entities/MutingEntityService.ts index c5245bf203..862be009da 100644 --- a/packages/backend/src/core/entities/MutingEntityService.ts +++ b/packages/backend/src/core/entities/MutingEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { MutingsRepository } from '@/models/index.js'; +import type { MutingsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 669680758d..a9c63566b4 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -4,7 +4,7 @@ import * as mfm from 'mfm-js'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; import type { Notes, Polls, PollVotes, DriveFiles, Channels, Followings, Users, NoteReactions } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { Packed } from '@/misc/schema.js'; import { nyaize } from '@/misc/nyaize.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; diff --git a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts index f0bbf27b6a..1a68a5c628 100644 --- a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts +++ b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { NoteFavoritesRepository } from '@/models/index.js'; +import type { NoteFavoritesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index e64f2af681..47008ee08e 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { NoteReactionsRepository } from '@/models/index.js'; +import type { NoteReactionsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { OnModuleInit } from '@nestjs/common'; diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index 6a0683d543..c415599fea 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; -import { AccessTokensRepository, NoteReactionsRepository, NotificationsRepository } from '@/models/index.js'; +import type { AccessTokensRepository, NoteReactionsRepository, NotificationsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Notification } from '@/models/entities/Notification.js'; import type { NoteReaction } from '@/models/entities/NoteReaction.js'; diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts index cbd193fe0a..004443759b 100644 --- a/packages/backend/src/core/entities/PageEntityService.ts +++ b/packages/backend/src/core/entities/PageEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository, PagesRepository, PageLikesRepository } from '@/models/index.js'; +import type { DriveFilesRepository, PagesRepository, PageLikesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/PageLikeEntityService.ts b/packages/backend/src/core/entities/PageLikeEntityService.ts index dccaf2edec..62d9c82ca6 100644 --- a/packages/backend/src/core/entities/PageLikeEntityService.ts +++ b/packages/backend/src/core/entities/PageLikeEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { PageLikesRepository } from '@/models/index.js'; +import type { PageLikesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/SigninEntityService.ts b/packages/backend/src/core/entities/SigninEntityService.ts index 521c7669e7..fd89662f7d 100644 --- a/packages/backend/src/core/entities/SigninEntityService.ts +++ b/packages/backend/src/core/entities/SigninEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { SigninsRepository } from '@/models/index.js'; +import type { SigninsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 343e42df07..a35703e80e 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -1,9 +1,9 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; -import { EntityRepository, Repository, In, Not } from 'typeorm'; +import type { EntityRepository, Repository, In, Not } from 'typeorm'; import Ajv from 'ajv'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { Packed } from '@/misc/schema.js'; import type { Promiseable } from '@/misc/prelude/await-all.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; diff --git a/packages/backend/src/core/entities/UserGroupEntityService.ts b/packages/backend/src/core/entities/UserGroupEntityService.ts index acd26ea1eb..e399197618 100644 --- a/packages/backend/src/core/entities/UserGroupEntityService.ts +++ b/packages/backend/src/core/entities/UserGroupEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { UserGroupJoiningsRepository, UserGroupsRepository } from '@/models/index.js'; +import type { UserGroupJoiningsRepository, UserGroupsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts b/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts index 50ff2231ab..f5c9be3475 100644 --- a/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts +++ b/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { UserGroupInvitationsRepository } from '@/models/index.js'; +import type { UserGroupInvitationsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts index 05434d9c8a..e2b0814914 100644 --- a/packages/backend/src/core/entities/UserListEntityService.ts +++ b/packages/backend/src/core/entities/UserListEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; +import type { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/remote/ResolveUserService.ts b/packages/backend/src/core/remote/ResolveUserService.ts index b45168fb02..2fd9e7c378 100644 --- a/packages/backend/src/core/remote/ResolveUserService.ts +++ b/packages/backend/src/core/remote/ResolveUserService.ts @@ -3,9 +3,9 @@ import { Inject, Injectable } from '@nestjs/common'; import chalk from 'chalk'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import type { IRemoteUser, User } from '@/models/entities/User.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { UtilityService } from '../UtilityService.js'; import { WebfingerService } from './WebfingerService.js'; diff --git a/packages/backend/src/core/remote/WebfingerService.ts b/packages/backend/src/core/remote/WebfingerService.ts index ab46314792..d2a88be583 100644 --- a/packages/backend/src/core/remote/WebfingerService.ts +++ b/packages/backend/src/core/remote/WebfingerService.ts @@ -1,7 +1,7 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { query as urlQuery } from '@/misc/prelude/url.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts b/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts index 6f197985da..77d200c3c8 100644 --- a/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import escapeRegexp from 'escape-regexp'; import { DI } from '@/di-symbols.js'; -import { MessagingMessagesRepository, NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { MessagingMessagesRepository, NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js'; import { Cache } from '@/misc/cache.js'; import type { UserPublickey } from '@/models/entities/UserPublickey.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts index a6ee857526..6fc75a0397 100644 --- a/packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts +++ b/packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { FollowingsRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; import { QueueService } from '@/core/QueueService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApInboxService.ts b/packages/backend/src/core/remote/activitypub/ApInboxService.ts index 0482e029d2..a3cb08063d 100644 --- a/packages/backend/src/core/remote/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/remote/activitypub/ApInboxService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { CacheableRemoteUser } from '@/models/entities/User.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { ReactionService } from '@/core/ReactionService.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApMfmService.ts b/packages/backend/src/core/remote/activitypub/ApMfmService.ts index 3c3b98b139..8804fde64a 100644 --- a/packages/backend/src/core/remote/activitypub/ApMfmService.ts +++ b/packages/backend/src/core/remote/activitypub/ApMfmService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as mfm from 'mfm-js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { MfmService } from '@/core/MfmService.js'; import type { Note } from '@/models/entities/Note.js'; import { extractApHashtagObjects } from './models/tag.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApRendererService.ts b/packages/backend/src/core/remote/activitypub/ApRendererService.ts index 5a4cef63e0..6058929d35 100644 --- a/packages/backend/src/core/remote/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/remote/activitypub/ApRendererService.ts @@ -4,7 +4,7 @@ import { In, IsNull } from 'typeorm'; import { v4 as uuid } from 'uuid'; import * as mfm from 'mfm-js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; import type { IMentionedRemoteUsers, Note } from '@/models/entities/Note.js'; import type { Blocking } from '@/models/entities/Blocking.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApRequestService.ts b/packages/backend/src/core/remote/activitypub/ApRequestService.ts index 2abaca06af..baad46d668 100644 --- a/packages/backend/src/core/remote/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/remote/activitypub/ApRequestService.ts @@ -2,7 +2,7 @@ import * as crypto from 'node:crypto'; import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { User } from '@/models/entities/User.js'; import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApResolverService.ts b/packages/backend/src/core/remote/activitypub/ApResolverService.ts index 9d8e177758..e2d6a77370 100644 --- a/packages/backend/src/core/remote/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/remote/activitypub/ApResolverService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import type { ILocalUser } from '@/models/entities/User.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; -import { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/remote/activitypub/models/ApImageService.ts b/packages/backend/src/core/remote/activitypub/models/ApImageService.ts index da6ed61c56..9bf87f19d4 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApImageService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { CacheableRemoteUser } from '@/models/entities/User.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { MetaService } from '@/core/MetaService.js'; diff --git a/packages/backend/src/core/remote/activitypub/models/ApMentionService.ts b/packages/backend/src/core/remote/activitypub/models/ApMentionService.ts index 898da07a26..710e1acfaf 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApMentionService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApMentionService.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import promiseLimit from 'promise-limit'; import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { toArray, unique } from '@/misc/prelude/array.js'; import type { CacheableUser } from '@/models/entities/User.js'; import { isMention } from '../type.js'; diff --git a/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts b/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts index 1efe62333b..a34a1d1eb8 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts @@ -1,9 +1,9 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import promiseLimit from 'promise-limit'; import { DI } from '@/di-symbols.js'; -import { MessagingMessagesRepository, PollsRepository, EmojisRepository } from '@/models/index.js'; +import type { MessagingMessagesRepository, PollsRepository, EmojisRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type { CacheableRemoteUser } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; import { toArray, toSingle, unique } from '@/misc/prelude/array.js'; diff --git a/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts b/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts index d088fa5554..5135473862 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts @@ -3,8 +3,8 @@ import promiseLimit from 'promise-limit'; import { DataSource } from 'typeorm'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; -import { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { CacheableUser, IRemoteUser } from '@/models/entities/User.js'; import { User } from '@/models/entities/User.js'; import { truncate } from '@/misc/truncate.js'; diff --git a/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts index 2b89cb030b..acd5cdae83 100644 --- a/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts +++ b/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { NotesRepository, PollsRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { NotesRepository, PollsRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { IPoll } from '@/models/entities/Poll.js'; import type Logger from '@/logger.js'; import { isQuestion } from '../type.js'; diff --git a/packages/backend/src/daemons/JanitorService.ts b/packages/backend/src/daemons/JanitorService.ts index a51f570725..dbad576abe 100644 --- a/packages/backend/src/daemons/JanitorService.ts +++ b/packages/backend/src/daemons/JanitorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { LessThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { AttestationChallengesRepository } from '@/models/index.js'; +import type { AttestationChallengesRepository } from '@/models/index.js'; import type { OnApplicationShutdown } from '@nestjs/common'; const interval = 30 * 60 * 1000; diff --git a/packages/backend/src/queue/DbQueueProcessorsService.ts b/packages/backend/src/queue/DbQueueProcessorsService.ts index fcc9873a6f..7622ab8800 100644 --- a/packages/backend/src/queue/DbQueueProcessorsService.ts +++ b/packages/backend/src/queue/DbQueueProcessorsService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { DbJobData } from '@/queue/types.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js'; import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js'; import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js'; diff --git a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts index 402c038be0..659e9b8e42 100644 --- a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts +++ b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { ObjectStorageJobData } from '@/queue/types.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js'; import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js'; import type Bull from 'bull'; diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 753df8cad6..a4879cef3d 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import { QueueService } from '@/core/QueueService.js'; diff --git a/packages/backend/src/queue/SystemQueueProcessorsService.ts b/packages/backend/src/queue/SystemQueueProcessorsService.ts index 7c227296e7..ccb040fae5 100644 --- a/packages/backend/src/queue/SystemQueueProcessorsService.ts +++ b/packages/backend/src/queue/SystemQueueProcessorsService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js'; import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js'; import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js'; diff --git a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts index 17337837a3..e91cba9d10 100644 --- a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts +++ b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { MutingsRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { MutingsRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; diff --git a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts index 6f2fb8dea0..e8e90f1422 100644 --- a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import FederationChart from '@/core/chart/charts/federation.js'; import NotesChart from '@/core/chart/charts/notes.js'; diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts index 830f0c56b6..6eb457ce9f 100644 --- a/packages/backend/src/queue/processors/CleanProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, LessThan, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { UserIpsRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UserIpsRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; diff --git a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts index c3c68be1bc..4d46cdeaf9 100644 --- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan, Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index ab82f87d5e..4d6ed2008a 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository, UserProfilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository, UserProfilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; diff --git a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts index 430fbf19e9..682382b2db 100644 --- a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { UsersRepository, DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; diff --git a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts index 72923b80a9..6740643fe2 100644 --- a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 1bf51c1bc6..9042a21d2c 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository, InstancesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository, InstancesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { MetaService } from '@/core/MetaService.js'; import { ApRequestService } from '@/core/remote/activitypub/ApRequestService.js'; diff --git a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts index 3e55a351a1..2fc7fe219e 100644 --- a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts +++ b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { PollVotesRepository, NotesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { PollVotesRepository, NotesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index cbc483698f..db149b68cf 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -3,9 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import { UsersRepository, BlockingsRepository } from '@/models/index.js'; -import type { DriveFilesRepository, UserProfilesRepository, NotesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsersRepository, BlockingsRepository, DriveFilesRepository, UserProfilesRepository, NotesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index c49a47561b..aa7a0ef929 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -6,8 +6,8 @@ import { ulid } from 'ulid'; import mime from 'mime-types'; import archiver from 'archiver'; import { DI } from '@/di-symbols.js'; -import { EmojisRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { EmojisRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp, createTempDir } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index 4c6162432a..eac9c6925e 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, MoreThan, Not } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import { FollowingsRepository, MutingsRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { FollowingsRepository, MutingsRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index 7781d2787f..e263c245fd 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import { MutingsRepository, UsersRepository, BlockingsRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { MutingsRepository, UsersRepository, BlockingsRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index 62b3a53c49..533d4bd7c6 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import { NotesRepository, PollsRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { NotesRepository, PollsRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index 097835ac81..8c3e3dbe17 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, IsNull, MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import { UserListJoiningsRepository, UserListsRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UserListJoiningsRepository, UserListsRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index 44c8800a68..12f77638bb 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { BlockingsRepository, DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { BlockingsRepository, DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index 4919fb2f7b..492f17f9ff 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan, DataSource } from 'typeorm'; import unzipper from 'unzipper'; import { DI } from '@/di-symbols.js'; -import { EmojisRepository, DriveFilesRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { EmojisRepository, DriveFilesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { createTempDir } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index 5e49678d05..f649014399 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index c613c7e74e..f004f2d64b 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index 96c862e5c9..168f850718 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository, UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository, UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 4593b4fb61..ad72263a8d 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import httpSignature from '@peertube/http-signature'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { MetaService } from '@/core/MetaService.js'; import { ApRequestService } from '@/core/remote/activitypub/ApRequestService.js'; diff --git a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts index 75d02d527a..bf2fdeb7a0 100644 --- a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import FederationChart from '@/core/chart/charts/federation.js'; import NotesChart from '@/core/chart/charts/notes.js'; diff --git a/packages/backend/src/queue/processors/TickChartsProcessorService.ts b/packages/backend/src/queue/processors/TickChartsProcessorService.ts index e16956df0c..96607e1d68 100644 --- a/packages/backend/src/queue/processors/TickChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/TickChartsProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import FederationChart from '@/core/chart/charts/federation.js'; import NotesChart from '@/core/chart/charts/notes.js'; diff --git a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts index 27243be51b..43e3f37201 100644 --- a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { WebhooksRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { WebhooksRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { StatusError } from '@/misc/status-error.js'; diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 21ecc7177a..b2fcf9f81a 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -4,9 +4,9 @@ import json from 'koa-json-body'; import httpSignature from '@peertube/http-signature'; import { Brackets, In, IsNull, LessThan, Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; +import type { EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; import * as url from '@/misc/prelude/url.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; import { QueueService } from '@/core/QueueService.js'; import type { ILocalUser, User } from '@/models/entities/User.js'; diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index becf0592d7..dc073e34ac 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -7,8 +7,8 @@ import cors from '@koa/cors'; import Router from '@koa/router'; import send from 'koa-send'; import rename from 'rename'; -import { Config } from '@/config.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { createTemp } from '@/misc/create-temp.js'; import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; diff --git a/packages/backend/src/server/MediaProxyServerService.ts b/packages/backend/src/server/MediaProxyServerService.ts index 1e0385602c..31841d39df 100644 --- a/packages/backend/src/server/MediaProxyServerService.ts +++ b/packages/backend/src/server/MediaProxyServerService.ts @@ -5,7 +5,7 @@ import cors from '@koa/cors'; import Router from '@koa/router'; import sharp from 'sharp'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { isMimeImage } from '@/misc/is-mime-image.js'; import { createTemp } from '@/misc/create-temp.js'; import { DownloadService } from '@/core/DownloadService.js'; diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index 04a5f1484b..b07dc4cf8f 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -2,8 +2,8 @@ import { Inject, Injectable } from '@nestjs/common'; import Router from '@koa/router'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { NotesRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { NotesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Cache } from '@/misc/cache.js'; diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 14d5ed45ab..d42972614f 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -9,8 +9,8 @@ import koaLogger from 'koa-logger'; import * as slow from 'koa-slow'; import { IsNull } from 'typeorm'; import { GlobalEventService } from '@/core/GlobalEventService.js'; -import { Config } from '@/config.js'; -import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import { envOption } from '@/env.js'; diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts index 7f827d439b..f2eee88e09 100644 --- a/packages/backend/src/server/WellKnownServerService.ts +++ b/packages/backend/src/server/WellKnownServerService.ts @@ -2,8 +2,8 @@ import { Inject, Injectable } from '@nestjs/common'; import Router from '@koa/router'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js'; import type { User } from '@/models/entities/User.js'; import * as Acct from '@/misc/acct.js'; diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index d13b8d5ced..c3ce12e0c3 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -5,7 +5,7 @@ import { getIpHash } from '@/misc/get-ip-hash.js'; import type { CacheableLocalUser, User } from '@/models/entities/User.js'; import type { AccessToken } from '@/models/entities/AccessToken.js'; import type Logger from '@/logger.js'; -import { UserIpsRepository } from '@/models/index.js'; +import type { UserIpsRepository } from '@/models/index.js'; import { MetaService } from '@/core/MetaService.js'; import { ApiError } from './error.js'; import { RateLimiterService } from './RateLimiterService.js'; diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index cfe39238dd..52654dbaee 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -5,8 +5,8 @@ import multer from '@koa/multer'; import bodyParser from 'koa-bodyparser'; import cors from '@koa/cors'; import { ModuleRef } from '@nestjs/core'; -import { Config } from '@/config.js'; -import { UsersRepository, InstancesRepository, AccessTokensRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { UsersRepository, InstancesRepository, AccessTokensRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import endpoints from './endpoints.js'; diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts index 29d6ba78f0..4ce9b91f42 100644 --- a/packages/backend/src/server/api/AuthenticateService.ts +++ b/packages/backend/src/server/api/AuthenticateService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/index.js'; +import type { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/index.js'; import type { CacheableLocalUser, ILocalUser } from '@/models/entities/User.js'; import type { AccessToken } from '@/models/entities/AccessToken.js'; import { Cache } from '@/misc/cache.js'; diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 5cda3c6205..a5e2b09012 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -4,8 +4,8 @@ import bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { UserSecurityKeysRepository, SigninsRepository, UserProfilesRepository, AttestationChallengesRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UserSecurityKeysRepository, SigninsRepository, UserProfilesRepository, AttestationChallengesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; import type { ILocalUser } from '@/models/entities/User.js'; import { IdService } from '@/core/IdService.js'; diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts index 19d14bad67..3b96dfee6f 100644 --- a/packages/backend/src/server/api/SigninService.ts +++ b/packages/backend/src/server/api/SigninService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { SigninsRepository } from '@/models/index.js'; +import type { SigninsRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { IdService } from '@/core/IdService.js'; import type { ILocalUser } from '@/models/entities/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index df040ddcf8..06b8b29134 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -2,8 +2,8 @@ import { Inject, Injectable } from '@nestjs/common'; import rndstr from 'rndstr'; import bcrypt from 'bcryptjs'; import { DI } from '@/di-symbols.js'; -import { RegistrationTicketsRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { RegistrationTicketsRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; import { CaptchaService } from '@/core/CaptchaService.js'; import { IdService } from '@/core/IdService.js'; diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index b08b01aef9..8a6906e5fe 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -3,8 +3,8 @@ import { Inject, Injectable } from '@nestjs/common'; import Redis from 'ioredis'; import * as websocket from 'websocket'; import { DI } from '@/di-symbols.js'; -import { UsersRepository, BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsersRepository, BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { NoteReadService } from '@/core/NoteReadService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { NotificationService } from '@/core/NotificationService.js'; diff --git a/packages/backend/src/server/api/common/GetterService.ts b/packages/backend/src/server/api/common/GetterService.ts index 5523539b91..a6b60d1f5a 100644 --- a/packages/backend/src/server/api/common/GetterService.ts +++ b/packages/backend/src/server/api/common/GetterService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { NotesRepository, UsersRepository } from '@/models/index.js'; +import type { NotesRepository, UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 480ae7166e..30183ed88b 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AbuseUserReportsRepository } from '@/models/index.js'; +import type { AbuseUserReportsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index 1b173379a0..c76ece9e05 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { SignupService } from '@/core/SignupService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { localUsernameSchema, passwordSchema } from '@/models/entities/User.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts index 2e0222f0c6..dc2d499191 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts index 6b32391e8d..8fcbde591b 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AdsRepository } from '@/models/index.js'; +import type { AdsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts index 7abefe156b..f4c9885408 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AdsRepository } from '@/models/index.js'; +import type { AdsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts index efece31bbf..29e245ab95 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AdsRepository } from '@/models/index.js'; +import type { AdsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts index 098a593379..195300666e 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AdsRepository } from '@/models/index.js'; +import type { AdsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index ee07170d62..751b6be7f4 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AnnouncementsRepository } from '@/models/index.js'; +import type { AnnouncementsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts index 9a67bdb1aa..18d50b8b2a 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AnnouncementsRepository } from '@/models/index.js'; +import type { AnnouncementsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 35c14abda2..9b20494129 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/index.js'; +import type { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/index.js'; import type { Announcement } from '@/models/entities/Announcement.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index 38358dff10..2393c2441c 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AnnouncementsRepository } from '@/models/index.js'; +import type { AnnouncementsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/delete-account.ts b/packages/backend/src/server/api/endpoints/admin/delete-account.ts index c8b67fe1c0..d0485fddd8 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-account.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DeleteAccountService } from '@/core/DeleteAccountService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts index 051e4c60fb..22b78bf19d 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DriveService } from '@/core/DriveService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts index 770bade06d..f4d39cd872 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts index 3927a89f90..4f7e02fe92 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts @@ -1,7 +1,7 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DriveService } from '@/core/DriveService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index 88529ab0aa..2459a479ab 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index 45ea9cdb50..c17f67cf2b 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts index 0b6e744ef8..7c24e8baa8 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index daa57e8eb2..c4e1987d73 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import rndstr from 'rndstr'; import { DataSource } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository, EmojisRepository } from '@/models/index.js'; +import type { DriveFilesRepository, EmojisRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index 08d40834c1..2cdd9c36bd 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts index 81b095cb57..8b2031e6dd 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts index e4278dc33a..dd7cd4cede 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts index 9d6fa53417..c03d27878c 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index 736d664cc3..e50f924044 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import type { Emoji } from '@/models/entities/Emoji.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts index d6c70eaae7..99512a26b3 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts index c438b7f9b7..697999cc7c 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts index 4a9b31fd28..00a5b162bf 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index e6eb9eb9a6..c576950ac7 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts index 789838661c..38fe99b222 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DriveService } from '@/core/DriveService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts index 476b821523..b7f2858a77 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/index.js'; import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts index 67165dc47e..b073209a5b 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { FollowingsRepository, UsersRepository } from '@/models/index.js'; +import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index b9eade5b40..0a529ecb08 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/index.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts index eddaade919..947a673def 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserIpsRepository } from '@/models/index.js'; +import type { UserIpsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/invite.ts b/packages/backend/src/server/api/endpoints/admin/invite.ts index 5fe341e5ca..bc42bf792a 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite.ts @@ -1,7 +1,7 @@ import rndstr from 'rndstr'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistrationTicketsRepository } from '@/models/index.js'; +import type { RegistrationTicketsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 615c0a0e70..5b43c180d8 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { MetaService } from '@/core/MetaService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts index fe200da6ad..2fc5a35e8e 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts index 3dc7158ba9..f0d7a3f12d 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index a179f163df..0cff6bae6a 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { PromoNotesRepository } from '@/models/index.js'; +import type { PromoNotesRepository } from '@/models/index.js'; import { GetterService } from '@/server/api/common/GetterService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index 7446746b45..f7d27be9cb 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import rndstr from 'rndstr'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index b5828ae9be..a6e59276fb 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; +import type { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; import { QueueService } from '@/core/QueueService.js'; import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts index 2424cac425..ac0a84128c 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ModerationLogsRepository } from '@/models/index.js'; +import type { ModerationLogsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index b50564210b..e4031cf960 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, SigninsRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, SigninsRepository, UserProfilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 8d11e3ea7a..d28f9c71b7 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/silence-user.ts b/packages/backend/src/server/api/endpoints/admin/silence-user.ts index bec8f7719e..b9dbd211e0 100644 --- a/packages/backend/src/server/api/endpoints/admin/silence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/silence-user.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index d9266aac6c..d30facd125 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, FollowingsRepository, NotificationsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository, NotificationsRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts index b4671a2f41..3a9d410de0 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index 96283d251f..2805c21a74 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts index 1ea0e6aac4..33808ee70f 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index aa44dfd5dc..74168481f6 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; -import { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models'; +import type { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models'; export const meta = { tags: ['meta'], diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 56bd343d55..fe24a10300 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import { UserListsRepository, UserGroupJoiningsRepository, AntennasRepository } from '@/models/index.js'; +import type { UserListsRepository, UserGroupJoiningsRepository, AntennasRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/antennas/delete.ts b/packages/backend/src/server/api/endpoints/antennas/delete.ts index 127aca0c33..5da7a2cb66 100644 --- a/packages/backend/src/server/api/endpoints/antennas/delete.ts +++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AntennasRepository } from '@/models/index.js'; +import type { AntennasRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/antennas/list.ts b/packages/backend/src/server/api/endpoints/antennas/list.ts index bdc44895cc..a0f8979574 100644 --- a/packages/backend/src/server/api/endpoints/antennas/list.ts +++ b/packages/backend/src/server/api/endpoints/antennas/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AntennasRepository } from '@/models/index.js'; +import type { AntennasRepository } from '@/models/index.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index eba42afe56..fb3c713154 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { NotesRepository, AntennaNotesRepository } from '@/models/index.js'; +import type { NotesRepository, AntennaNotesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteReadService } from '@/core/NoteReadService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/antennas/show.ts b/packages/backend/src/server/api/endpoints/antennas/show.ts index 8bd8ad124d..ef7ed5b72c 100644 --- a/packages/backend/src/server/api/endpoints/antennas/show.ts +++ b/packages/backend/src/server/api/endpoints/antennas/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AntennasRepository } from '@/models/index.js'; +import type { AntennasRepository } from '@/models/index.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 59bba04ee1..1955eac949 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AntennasRepository, UserListsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { AntennasRepository, UserListsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index e291b5908a..0a5fc31751 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, NotesRepository } from '@/models/index.js'; +import type { UsersRepository, NotesRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import type { CacheableLocalUser, User } from '@/models/entities/User.js'; import { isActor, isPost, getApId } from '@/core/remote/activitypub/type.js'; diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index f52d18f7fe..c1d0a9dd74 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AppsRepository } from '@/models/index.js'; +import type { AppsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { unique } from '@/misc/prelude/array.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; diff --git a/packages/backend/src/server/api/endpoints/app/show.ts b/packages/backend/src/server/api/endpoints/app/show.ts index f94fed5344..eaafa8dc1b 100644 --- a/packages/backend/src/server/api/endpoints/app/show.ts +++ b/packages/backend/src/server/api/endpoints/app/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AppsRepository } from '@/models/index.js'; +import type { AppsRepository } from '@/models/index.js'; import { AppEntityService } from '@/core/entities/AppEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index 6032b59bef..cb2e661bfb 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -1,7 +1,7 @@ import * as crypto from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AuthSessionsRepository, AppsRepository, AccessTokensRepository } from '@/models/index.js'; +import type { AuthSessionsRepository, AppsRepository, AccessTokensRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts index 7f8325dbbd..6108d8202d 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -1,9 +1,9 @@ import { v4 as uuid } from 'uuid'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AppsRepository, AuthSessionsRepository } from '@/models/index.js'; +import type { AppsRepository, AuthSessionsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/auth/session/show.ts b/packages/backend/src/server/api/endpoints/auth/session/show.ts index dff4c74340..db3bf7aa63 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/show.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AuthSessionsRepository } from '@/models/index.js'; +import type { AuthSessionsRepository } from '@/models/index.js'; import { AuthSessionEntityService } from '@/core/entities/AuthSessionEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts index 9c9f13f502..b1e7bbfded 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, AppsRepository, AccessTokensRepository, AuthSessionsRepository } from '@/models/index.js'; +import type { UsersRepository, AppsRepository, AccessTokensRepository, AuthSessionsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index 33614a1554..aa6d2ecf20 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, BlockingsRepository } from '@/models/index.js'; +import type { UsersRepository, BlockingsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index f2cc28e922..46a499943c 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, BlockingsRepository } from '@/models/index.js'; +import type { UsersRepository, BlockingsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/blocking/list.ts b/packages/backend/src/server/api/endpoints/blocking/list.ts index 4f5e11cd68..969aae06f9 100644 --- a/packages/backend/src/server/api/endpoints/blocking/list.ts +++ b/packages/backend/src/server/api/endpoints/blocking/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { BlockingsRepository } from '@/models/index.js'; +import type { BlockingsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { BlockingEntityService } from '@/core/entities/BlockingEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index 21979884f9..10f8b24629 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelsRepository, DriveFilesRepository } from '@/models/index.js'; +import type { ChannelsRepository, DriveFilesRepository } from '@/models/index.js'; import type { Channel } from '@/models/entities/Channel.js'; import { IdService } from '@/core/IdService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/featured.ts b/packages/backend/src/server/api/endpoints/channels/featured.ts index 0c3f9509d1..d25faae38d 100644 --- a/packages/backend/src/server/api/endpoints/channels/featured.ts +++ b/packages/backend/src/server/api/endpoints/channels/featured.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelsRepository } from '@/models/index.js'; +import type { ChannelsRepository } from '@/models/index.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index 6c6b498a94..871d3927bc 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelFollowingsRepository, ChannelsRepository } from '@/models/index.js'; +import type { ChannelFollowingsRepository, ChannelsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/followed.ts b/packages/backend/src/server/api/endpoints/channels/followed.ts index 5a8ab26af9..f49f3105d5 100644 --- a/packages/backend/src/server/api/endpoints/channels/followed.ts +++ b/packages/backend/src/server/api/endpoints/channels/followed.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelFollowingsRepository } from '@/models/index.js'; +import type { ChannelFollowingsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/owned.ts b/packages/backend/src/server/api/endpoints/channels/owned.ts index 8b8b5819e6..59df0616be 100644 --- a/packages/backend/src/server/api/endpoints/channels/owned.ts +++ b/packages/backend/src/server/api/endpoints/channels/owned.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelsRepository } from '@/models/index.js'; +import type { ChannelsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts index 54ae31790b..8718615db2 100644 --- a/packages/backend/src/server/api/endpoints/channels/show.ts +++ b/packages/backend/src/server/api/endpoints/channels/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelsRepository } from '@/models/index.js'; +import type { ChannelsRepository } from '@/models/index.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 1c7f1360b9..58f8835279 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelsRepository, NotesRepository } from '@/models/index.js'; +import type { ChannelsRepository, NotesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts index b464c55097..ac2ef825be 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ChannelFollowingsRepository, ChannelsRepository } from '@/models/index.js'; +import type { ChannelFollowingsRepository, ChannelsRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index ba62e9d371..d006e89bd2 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository, ChannelsRepository } from '@/models/index.js'; +import type { DriveFilesRepository, ChannelsRepository } from '@/models/index.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index c733d28657..77d02815e0 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; -import { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; +import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { ApiError } from '../../error.js'; import { GetterService } from '../../common/GetterService.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index 8eca3d66d1..d300203a21 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/index.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/delete.ts b/packages/backend/src/server/api/endpoints/clips/delete.ts index ea361ae9c0..077a9ec40f 100644 --- a/packages/backend/src/server/api/endpoints/clips/delete.ts +++ b/packages/backend/src/server/api/endpoints/clips/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index b57affd1c4..63ca069364 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/index.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index 4282498931..6818d31cc4 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { NotesRepository, ClipsRepository, ClipNotesRepository } from '@/models/index.js'; +import type { NotesRepository, ClipsRepository, ClipNotesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/remove-note.ts b/packages/backend/src/server/api/endpoints/clips/remove-note.ts index 3fc60e3639..93805af089 100644 --- a/packages/backend/src/server/api/endpoints/clips/remove-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/remove-note.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; +import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; import { GetterService } from '../../common/GetterService.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts index 4e93540054..e6d3f4f1f8 100644 --- a/packages/backend/src/server/api/endpoints/clips/show.ts +++ b/packages/backend/src/server/api/endpoints/clips/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/index.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts index 9880505d06..597b67c442 100644 --- a/packages/backend/src/server/api/endpoints/clips/update.ts +++ b/packages/backend/src/server/api/endpoints/clips/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/index.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index 56055d1340..f6fad50fd9 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index 9f11eb8b53..328d0e4643 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { NotesRepository, DriveFilesRepository } from '@/models/index.js'; +import type { NotesRepository, DriveFilesRepository } from '@/models/index.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index 176031d808..290cd4d2ce 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index bff83876d7..d394f5c3da 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -1,6 +1,6 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index 9d2ea6011a..be7b050907 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DriveService } from '@/core/DriveService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts index 6299ca8f6b..d6d85f4e77 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts index e4cd5213dd..858063eb4b 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index bae4d7d66f..474d599cb6 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import type { DriveFile } from '@/models/entities/DriveFile.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index 03e3663f08..703f92d8c6 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index f4f8df3c2b..19ab03a337 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -1,6 +1,6 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/folders.ts b/packages/backend/src/server/api/endpoints/drive/folders.ts index 703dc83ecd..b41eaf4463 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts index 7604eaf489..e7c11a8c13 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts index dcbaecf8af..d921bc1b17 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFoldersRepository, DriveFilesRepository } from '@/models/index.js'; +import type { DriveFoldersRepository, DriveFilesRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/folders/find.ts b/packages/backend/src/server/api/endpoints/drive/folders/find.ts index 96a87344a9..ee24db11f2 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/find.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/index.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/folders/show.ts b/packages/backend/src/server/api/endpoints/drive/folders/show.ts index 4c25bc705c..c06263b902 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/index.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts index 4fcd37bbbf..ee63d291b2 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/index.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/stream.ts b/packages/backend/src/server/api/endpoints/drive/stream.ts index aba73c2092..61bcfea0c3 100644 --- a/packages/backend/src/server/api/endpoints/drive/stream.ts +++ b/packages/backend/src/server/api/endpoints/drive/stream.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/federation/followers.ts b/packages/backend/src/server/api/endpoints/federation/followers.ts index e5222fcbfd..be1d6c8e58 100644 --- a/packages/backend/src/server/api/endpoints/federation/followers.ts +++ b/packages/backend/src/server/api/endpoints/federation/followers.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/federation/following.ts b/packages/backend/src/server/api/endpoints/federation/following.ts index a20c5a31b3..74656ce863 100644 --- a/packages/backend/src/server/api/endpoints/federation/following.ts +++ b/packages/backend/src/server/api/endpoints/federation/following.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index e7f8cefff5..81276a7ab0 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/index.js'; import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; import { MetaService } from '@/core/MetaService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts index f855b54537..66502748b3 100644 --- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts +++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/index.js'; import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/federation/stats.ts b/packages/backend/src/server/api/endpoints/federation/stats.ts index d07a08637f..19418e698c 100644 --- a/packages/backend/src/server/api/endpoints/federation/stats.ts +++ b/packages/backend/src/server/api/endpoints/federation/stats.ts @@ -1,6 +1,6 @@ import { IsNull, MoreThan, Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { FollowingsRepository, InstancesRepository } from '@/models/index.js'; +import type { FollowingsRepository, InstancesRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts index 0400cacd02..a028930f21 100644 --- a/packages/backend/src/server/api/endpoints/federation/users.ts +++ b/packages/backend/src/server/api/endpoints/federation/users.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/fetch-rss.ts b/packages/backend/src/server/api/endpoints/fetch-rss.ts index 9e6a3cc717..58fa01ac48 100644 --- a/packages/backend/src/server/api/endpoints/fetch-rss.ts +++ b/packages/backend/src/server/api/endpoints/fetch-rss.ts @@ -1,7 +1,7 @@ import Parser from 'rss-parser'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 3a06c63d52..be322e2896 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 07366bc821..afb59dd2c2 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index 8285189d66..e67e136ead 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts index 0b79a80649..213b5ce32e 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/following/requests/list.ts b/packages/backend/src/server/api/endpoints/following/requests/list.ts index c36d4a077f..5b11633e6f 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/list.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { FollowRequestsRepository } from '@/models/index.js'; +import type { FollowRequestsRepository } from '@/models/index.js'; import { FollowRequestEntityService } from '@/core/entities/FollowRequestEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/featured.ts b/packages/backend/src/server/api/endpoints/gallery/featured.ts index 3b892ef522..9994ce90d7 100644 --- a/packages/backend/src/server/api/endpoints/gallery/featured.ts +++ b/packages/backend/src/server/api/endpoints/gallery/featured.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/index.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/popular.ts b/packages/backend/src/server/api/endpoints/gallery/popular.ts index 551ea64835..55d3dabfb0 100644 --- a/packages/backend/src/server/api/endpoints/gallery/popular.ts +++ b/packages/backend/src/server/api/endpoints/gallery/popular.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/index.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts.ts b/packages/backend/src/server/api/endpoints/gallery/posts.ts index 4afcbce816..e94003eb79 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index 9e8bcac131..2842308510 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js'; +import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js'; import { GalleryPost } from '@/models/entities/GalleryPost.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { IdService } from '@/core/IdService.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts index ad5f95c853..6cdcc17b39 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index 8aca98119b..519e56ed6a 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryLikesRepository, GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryLikesRepository, GalleryPostsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts index 723906d60f..f7e828142b 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/index.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts index d878582998..cfbedcc4d9 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository, GalleryLikesRepository } from '@/models/index.js'; +import type { GalleryPostsRepository, GalleryLikesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index 1900afaeb6..d261aaa966 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js'; +import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js'; import { GalleryPost } from '@/models/entities/GalleryPost.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index 2d9bf29dd9..dea0f4799c 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -1,7 +1,7 @@ import { MoreThan } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { USER_ONLINE_THRESHOLD } from '@/const.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/hashtags/list.ts b/packages/backend/src/server/api/endpoints/hashtags/list.ts index a7e7e6ba35..226a11de0b 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/list.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { HashtagsRepository } from '@/models/index.js'; +import type { HashtagsRepository } from '@/models/index.js'; import { HashtagEntityService } from '@/core/entities/HashtagEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts index 3fb77bef9b..7f787ea38f 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/search.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { HashtagsRepository } from '@/models/index.js'; +import type { HashtagsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/hashtags/show.ts b/packages/backend/src/server/api/endpoints/hashtags/show.ts index 59170f6d0e..06b0d6e9b2 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/show.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { HashtagsRepository } from '@/models/index.js'; +import type { HashtagsRepository } from '@/models/index.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { HashtagEntityService } from '@/core/entities/HashtagEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/hashtags/trend.ts b/packages/backend/src/server/api/endpoints/hashtags/trend.ts index 7e483ea214..cf45cc6c24 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/trend.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/trend.ts @@ -1,7 +1,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import { safeForSql } from '@/misc/safe-for-sql.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index 10a88fbefa..c3f2ea9ea7 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index 815b3168b4..3bcd6ff8fb 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts index bcf3931b04..ec9ac1ef90 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -1,7 +1,7 @@ import * as speakeasy from 'speakeasy'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index f2f4c2044e..6e0849f2b2 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -4,11 +4,11 @@ import * as cbor from 'cbor'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; -import { AttestationChallengesRepository, UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; +import type { AttestationChallengesRepository, UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; const cborDecodeFirst = promisify(cbor.decodeFirst) as any; diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts index 3eb9f43c2b..0655a86350 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index df37db4c6a..19c77365c6 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -3,7 +3,7 @@ import * as crypto from 'node:crypto'; import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserProfilesRepository, AttestationChallengesRepository } from '@/models/index.js'; +import type { UserProfilesRepository, AttestationChallengesRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index e20911f35e..a539c5c221 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -2,10 +2,10 @@ import bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; import * as QRCode from 'qrcode'; import { Inject, Injectable } from '@nestjs/common'; -import { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; export const meta = { requireCredential: true, diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index 1889dd7893..f40ec9797d 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -1,7 +1,7 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; +import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index 4607e5d981..4c5b151f78 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -1,7 +1,7 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index 8d5851659b..3361e5a4d3 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AccessTokensRepository } from '@/models/index.js'; +import type { AccessTokensRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts index a5592d20de..d0bdb5695b 100644 --- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts +++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AccessTokensRepository } from '@/models/index.js'; +import type { AccessTokensRepository } from '@/models/index.js'; import { AppEntityService } from '@/core/entities/AppEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index cc5b712ecf..873835a36c 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -1,7 +1,7 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index a1804599df..77a03d9811 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -1,6 +1,6 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DeleteAccountService } from '@/core/DeleteAccountService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts index 350abd9f7b..ce8ab4962a 100644 --- a/packages/backend/src/server/api/endpoints/i/favorites.ts +++ b/packages/backend/src/server/api/endpoints/i/favorites.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { NoteFavoritesRepository } from '@/models/index.js'; +import type { NoteFavoritesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteFavoriteEntityService } from '@/core/entities/NoteFavoriteEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts index ff6bcc01ab..d1b04cb655 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryLikesRepository } from '@/models/index.js'; +import type { GalleryLikesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { GalleryLikeEntityService } from '@/core/entities/GalleryLikeEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts index 927be51f79..32d14293f7 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts index 0695abdd85..3179457817 100644 --- a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts +++ b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MutedNotesRepository } from '@/models/index.js'; +import type { MutedNotesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index bfba1fc36f..8c1c158ab1 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index c7cb2e0330..383bdc02b5 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index 060c37c13f..345ad916cb 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index a5e17283e5..875af7ec23 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; -import { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index 96927dad49..13de3382dd 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotificationsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotificationsRepository } from '@/models/index.js'; import { notificationTypes } from '@/types.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; diff --git a/packages/backend/src/server/api/endpoints/i/page-likes.ts b/packages/backend/src/server/api/endpoints/i/page-likes.ts index 9a909eedf4..70e6e0a6a8 100644 --- a/packages/backend/src/server/api/endpoints/i/page-likes.ts +++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { PageLikesRepository } from '@/models/index.js'; +import type { PageLikesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { PageLikeEntityService } from '@/core/entities/PageLikeEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts index 7c4e4a6c7d..285aa34e91 100644 --- a/packages/backend/src/server/api/endpoints/i/pages.ts +++ b/packages/backend/src/server/api/endpoints/i/pages.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { PagesRepository } from '@/models/index.js'; +import type { PagesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts index 36c3566f55..109d6d1068 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts index b4bb83c6eb..b92de4b739 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { NoteUnreadsRepository } from '@/models/index.js'; +import type { NoteUnreadsRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts index 5a7909674f..cb5b4b0a60 100644 --- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts +++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models/index.js'; +import type { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts index 7796fd97cb..f942f43cc8 100644 --- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -1,7 +1,7 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; import generateUserToken from '@/misc/generate-native-user-token.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts index 3b4db5fae3..17154c1f76 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts index d24dff95b0..233686dbe1 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts index 98d94a4c02..99cdf95bad 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts index d1a05d9d06..362a5e89f4 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys.ts b/packages/backend/src/server/api/endpoints/i/registry/keys.ts index 6df5f4ecc3..99f69d8bed 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/remove.ts b/packages/backend/src/server/api/endpoints/i/registry/remove.ts index b5870f099d..78a641f5e2 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/remove.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/remove.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts index 58085ddbc5..0a4ecb9c51 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/set.ts b/packages/backend/src/server/api/endpoints/i/registry/set.ts index 585aac2e01..c8e72203c4 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/set.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/set.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts index 86a82e6a6c..5e1dddb6b7 100644 --- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AccessTokensRepository } from '@/models/index.js'; +import type { AccessTokensRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/signin-history.ts b/packages/backend/src/server/api/endpoints/i/signin-history.ts index 410cd72065..9b30a24336 100644 --- a/packages/backend/src/server/api/endpoints/i/signin-history.ts +++ b/packages/backend/src/server/api/endpoints/i/signin-history.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { SigninsRepository } from '@/models/index.js'; +import type { SigninsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { SigninEntityService } from '@/core/entities/SigninEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 719cc14f09..b656c5c51d 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -3,10 +3,10 @@ import rndstr from 'rndstr'; import ms from 'ms'; import bcrypt from 'bcryptjs'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { EmailService } from '@/core/EmailService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 4b904d4696..9bf0616e3a 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -3,7 +3,7 @@ import * as mfm from 'mfm-js'; import { Inject, Injectable } from '@nestjs/common'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractHashtags } from '@/misc/extract-hashtags.js'; -import { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/index.js'; +import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/entities/User.js'; import type { UserProfile } from '@/models/entities/UserProfile.js'; diff --git a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts index 6dd1626bb8..1ad2f7d68f 100644 --- a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts +++ b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserGroupInvitationsRepository } from '@/models/index.js'; +import type { UserGroupInvitationsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { UserGroupInvitationEntityService } from '@/core/entities/UserGroupInvitationEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts index 016b1b5d6a..584c2ba6a4 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/index.js'; import { webhookEventTypes } from '@/models/entities/Webhook.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts index 53b553b43e..7bdad136aa 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts index 8e4aff45dd..58c84938cc 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts index 622c2ade98..d15ca0050d 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts index 3a0ef1a526..50098f96e7 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/index.js'; import { webhookEventTypes } from '@/models/entities/Webhook.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/messaging/history.ts b/packages/backend/src/server/api/endpoints/messaging/history.ts index da3ba59df9..0b6099d4ac 100644 --- a/packages/backend/src/server/api/endpoints/messaging/history.ts +++ b/packages/backend/src/server/api/endpoints/messaging/history.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; -import { MutingsRepository, UserGroupJoiningsRepository, MessagingMessagesRepository } from '@/models/index.js'; +import type { MutingsRepository, UserGroupJoiningsRepository, MessagingMessagesRepository } from '@/models/index.js'; import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts index 6579b03987..f563da3278 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository } from '@/models/index.js'; -import { UserGroupsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts index e02afcbcfd..f61662af75 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { BlockingsRepository, UserGroupJoiningsRepository, DriveFilesRepository, UserGroupsRepository } from '@/models/index.js'; +import type { BlockingsRepository, UserGroupJoiningsRepository, DriveFilesRepository, UserGroupsRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import type { UserGroup } from '@/models/entities/UserGroup.js'; import { GetterService } from '@/server/api/common/GetterService.js'; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts index 5baecb9114..cd74f5f197 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MessagingMessagesRepository } from '@/models/index.js'; +import type { MessagingMessagesRepository } from '@/models/index.js'; import { MessagingService } from '@/core/MessagingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts index 6e66cafe1e..bddb6d932d 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MessagingMessagesRepository } from '@/models/index.js'; +import type { MessagingMessagesRepository } from '@/models/index.js'; import { MessagingService } from '@/core/MessagingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 9a6258d7dd..5c09c33941 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -1,13 +1,13 @@ import { IsNull, MoreThan } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { AdsRepository, EmojisRepository, UsersRepository } from '@/models/index.js'; +import type { AdsRepository, EmojisRepository, UsersRepository } from '@/models/index.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { MetaService } from '@/core/MetaService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts index d8eb89c0e6..97def86262 100644 --- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts +++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AccessTokensRepository } from '@/models/index.js'; +import type { AccessTokensRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index cbdd001185..3b4507281f 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import { MutingsRepository } from '@/models/index.js'; +import type { MutingsRepository } from '@/models/index.js'; import type { Muting } from '@/models/entities/Muting.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts index c7098059d5..2fc5cee95e 100644 --- a/packages/backend/src/server/api/endpoints/mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/mute/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MutingsRepository } from '@/models/index.js'; +import type { MutingsRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/mute/list.ts b/packages/backend/src/server/api/endpoints/mute/list.ts index 11c05eb795..9ec6d17273 100644 --- a/packages/backend/src/server/api/endpoints/mute/list.ts +++ b/packages/backend/src/server/api/endpoints/mute/list.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MutingsRepository } from '@/models/index.js'; +import type { MutingsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { MutingEntityService } from '@/core/entities/MutingEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/my/apps.ts b/packages/backend/src/server/api/endpoints/my/apps.ts index 90cd53a133..4b7ed80123 100644 --- a/packages/backend/src/server/api/endpoints/my/apps.ts +++ b/packages/backend/src/server/api/endpoints/my/apps.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AppsRepository } from '@/models/index.js'; +import type { AppsRepository } from '@/models/index.js'; import { AppEntityService } from '@/core/entities/AppEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts index 288e195316..0a8f2292ac 100644 --- a/packages/backend/src/server/api/endpoints/notes.ts +++ b/packages/backend/src/server/api/endpoints/notes.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index 86f90e049f..ea7a825f9d 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index 7d893f32a1..579466d4fd 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -1,6 +1,6 @@ import { In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; +import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index 2f8324ed62..ddfee31525 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import type { Note } from '@/models/entities/Note.js'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 30b7a889fc..92bc8a7595 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -2,7 +2,7 @@ import ms from 'ms'; import { In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import type { User } from '@/models/entities/User.js'; -import { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesRepository, ChannelsRepository } from '@/models/index.js'; +import type { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesRepository, ChannelsRepository } from '@/models/index.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Note } from '@/models/entities/Note.js'; import type { Channel } from '@/models/entities/Channel.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 4769c8bdf1..5765dfe66f 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -1,6 +1,6 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteDeleteService } from '@/core/NoteDeleteService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index bfdd1acd22..edbd300e08 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NoteFavoritesRepository } from '@/models/index.js'; +import type { NoteFavoritesRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/common/GetterService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index 6b3a02b101..8f4f2b2b9e 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/common/GetterService.js'; import { DI } from '@/di-symbols.js'; -import { NoteFavoritesRepository } from '@/models/index.js'; +import type { NoteFavoritesRepository } from '@/models/index.js'; import { ApiError } from '../../../error.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index 9985f9d257..76834cfde9 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 73b5afa40a..b6eaccb5ac 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index c6458223eb..6573e9454a 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import type { NotesRepository, FollowingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 7b8859639d..fac14fa225 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index 9b2dabc88b..92b82eb5de 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import type { NotesRepository, FollowingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index 11bfdbba0f..6cdc9b902c 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -1,6 +1,6 @@ import { Brackets, In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository, MutingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; +import type { NotesRepository, MutingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 76f07528d7..515f03dcce 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -1,6 +1,6 @@ import { Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, BlockingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; +import type { UsersRepository, BlockingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; import type { IRemoteUser } from '@/models/entities/User.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index d57950f012..02ae212a30 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -1,6 +1,6 @@ import { DeepPartial } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NoteReactionsRepository } from '@/models/index.js'; +import type { NoteReactionsRepository } from '@/models/index.js'; import type { NoteReaction } from '@/models/entities/NoteReaction.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 57b7aeae0d..97ef1a17ec 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index 7020d0c681..4df95962c8 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index 0727c9af6c..061e371d65 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { safeForSql } from '@/misc/safe-for-sql.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index 484cfc1128..27b477e141 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -1,10 +1,10 @@ import { In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index c3f5b9dfb0..7849cfa401 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index 7756d39f7c..a02b8d2559 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository, NoteThreadMutingsRepository, NoteFavoritesRepository } from '@/models/index.js'; +import type { NotesRepository, NoteThreadMutingsRepository, NoteFavoritesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index 060581d74b..b8ddf83f3c 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository, NoteThreadMutingsRepository } from '@/models/index.js'; +import type { NotesRepository, NoteThreadMutingsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/common/GetterService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts index aed15852d4..54e9b4939f 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NoteThreadMutingsRepository } from '@/models/index.js'; +import type { NoteThreadMutingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/common/GetterService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 53a1ae1348..8542af17db 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import type { NotesRepository, FollowingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index c24f1e401e..7a3daf741e 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -1,9 +1,9 @@ import { URLSearchParams } from 'node:url'; import fetch from 'node-fetch'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { MetaService } from '@/core/MetaService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index c0048888b4..7378c4b600 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -1,6 +1,6 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, NotesRepository } from '@/models/index.js'; +import type { UsersRepository, NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteDeleteService } from '@/core/NoteDeleteService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 87a464578c..9b23103fd4 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import type { NotesRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts index 3d1eb2b39c..09134cf48f 100644 --- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { NotificationsRepository } from '@/models/index.js'; +import type { NotificationsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { PushNotificationService } from '@/core/PushNotificationService.js'; diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts index 1b0299c3c6..1841a84539 100644 --- a/packages/backend/src/server/api/endpoints/page-push.ts +++ b/packages/backend/src/server/api/endpoints/page-push.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { PagesRepository } from '@/models/index.js'; +import type { PagesRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index ac80849aa0..eae8f18403 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -1,6 +1,6 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import { DriveFilesRepository, PagesRepository } from '@/models/index.js'; +import type { DriveFilesRepository, PagesRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Page } from '@/models/entities/Page.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index 4e97755761..e64733131c 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { PagesRepository } from '@/models/index.js'; +import type { PagesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/pages/featured.ts b/packages/backend/src/server/api/endpoints/pages/featured.ts index 3e3dbb0832..31844165e2 100644 --- a/packages/backend/src/server/api/endpoints/pages/featured.ts +++ b/packages/backend/src/server/api/endpoints/pages/featured.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { PagesRepository } from '@/models/index.js'; +import type { PagesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index f3c55fed8b..41a11d1a31 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { PagesRepository, PageLikesRepository } from '@/models/index.js'; +import type { PagesRepository, PageLikesRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts index 6d73889d39..651252afbb 100644 --- a/packages/backend/src/server/api/endpoints/pages/show.ts +++ b/packages/backend/src/server/api/endpoints/pages/show.ts @@ -1,6 +1,6 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, PagesRepository } from '@/models/index.js'; +import type { UsersRepository, PagesRepository } from '@/models/index.js'; import type { Page } from '@/models/entities/Page.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts index 88386739be..e397e2a23b 100644 --- a/packages/backend/src/server/api/endpoints/pages/unlike.ts +++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { PagesRepository, PageLikesRepository } from '@/models/index.js'; +import type { PagesRepository, PageLikesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index 8980ac4906..4db0f80b26 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { PagesRepository, DriveFilesRepository } from '@/models/index.js'; +import type { PagesRepository, DriveFilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 573331e0d8..6c941314e2 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -1,6 +1,6 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import * as Acct from '@/misc/acct.js'; import type { User } from '@/models/entities/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index 7c8188ce3c..a28229add3 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { PromoReadsRepository } from '@/models/index.js'; +import type { PromoReadsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 4766239533..42b10a4fb3 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -2,10 +2,10 @@ import rndstr from 'rndstr'; import ms from 'ms'; import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { PasswordResetRequestsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { PasswordResetRequestsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { EmailService } from '@/core/EmailService.js'; import { ApiError } from '../error.js'; diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 48edde5196..cf7fcb7afd 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -1,6 +1,6 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; -import { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/index.js'; +import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index 17af75578b..3adf0a4bb8 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; -import { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js'; +import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index 73a084c2ad..ddec877dd4 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { IdService } from '@/core/IdService.js'; -import { SwSubscriptionsRepository } from '@/models/index.js'; +import type { SwSubscriptionsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { MetaService } from '@/core/MetaService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index feb6730154..5772eeee26 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { SwSubscriptionsRepository } from '@/models/index.js'; +import type { SwSubscriptionsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index 56474d6988..c80b6efdcd 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -1,6 +1,6 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsedUsernamesRepository, UsersRepository } from '@/models/index.js'; +import type { UsedUsernamesRepository, UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { localUsernameSchema } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 3d05ec2e1d..b015129a7a 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index 2d5545cbab..e3fd0920c9 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 08bcdd9f88..17ce920011 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -1,6 +1,6 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 225ab5210a..6dbda0d72f 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -1,6 +1,6 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts index 2d28d6ca07..6e57eee5fb 100644 --- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index 3eeca7562f..22c49860bd 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -1,7 +1,7 @@ import { Not, In, IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { maximum } from '@/misc/prelude/array.js'; -import { NotesRepository, UsersRepository } from '@/models/index.js'; +import type { NotesRepository, UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts index 5d7ad84ae0..c1f4f48445 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import type { UserGroup } from '@/models/entities/UserGroup.js'; import type { UserGroupJoining } from '@/models/entities/UserGroupJoining.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts index 50156b049e..d238ae9f16 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository } from '@/models/index.js'; +import type { UserGroupsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts index 0490fd41a0..f154a57f61 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupInvitationsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupInvitationsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import type { UserGroupJoining } from '@/models/entities/UserGroupJoining.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts index 26efc1ecf3..1fd3b2f4b3 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupInvitationsRepository } from '@/models/index.js'; +import type { UserGroupInvitationsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index 4ae32a6bda..127f4ca65b 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository, UserGroupJoiningsRepository, UserGroupInvitationsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, UserGroupJoiningsRepository, UserGroupInvitationsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import type { UserGroupInvitation } from '@/models/entities/UserGroupInvitation.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts index e7e69f257d..8daee3a6f5 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -1,6 +1,6 @@ import { Not, In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts index 0a63dbb7f1..846f80e64d 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts index c9ae39561f..0bc6e8b3fc 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository } from '@/models/index.js'; +import type { UserGroupsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index e6f60eef0a..3474f22c6e 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/common/GetterService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts index 1cebfcd204..2b0f403f33 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/show.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index a8b2533b73..e0b2b13c7f 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; import { GetterService } from '@/server/api/common/GetterService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts index b679625c85..5af849de14 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/update.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupsRepository } from '@/models/index.js'; +import type { UserGroupsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index aa64ca1229..99f0751ea8 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import type { UserList } from '@/models/entities/UserList.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts index 0f4125a39f..237cb075ab 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts index 919de22377..2104c4377d 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index 89d97be93e..7e54d33376 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import type { UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GetterService } from '@/server/api/common/GetterService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 77ad772b13..06ea43d654 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js'; +import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/common/GetterService.js'; import { UserListService } from '@/core/UserListService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 62e730b2f7..77f9cba808 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts index c6669d24d1..6453d7d980 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index bb8104584c..d2c9616f68 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index 96c7ef1e70..e007aa57b2 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; -import { PagesRepository } from '@/models'; +import type { PagesRepository } from '@/models'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index 6b4d882b7c..9ec911f322 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserProfilesRepository, NoteReactionsRepository } from '@/models/index.js'; +import type { UserProfilesRepository, NoteReactionsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index e50a5706d9..5498b8c854 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -1,6 +1,6 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index aea75ae799..ac9104bf92 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index 5c211a9017..bb37dd2715 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -1,6 +1,6 @@ import * as sanitizeHtml from 'sanitize-html'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; +import type { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index 1747dc93f6..f13df3ee9d 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; import { USER_ACTIVE_THRESHOLD } from '@/const.js'; import type { User } from '@/models/entities/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 9879b1b68b..ba07714972 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -1,6 +1,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 98f5f03063..1e15025bf4 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -1,6 +1,6 @@ import { In, IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index ea044c27d5..cc3f4e0633 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -4,8 +4,8 @@ import Router from '@koa/router'; import { OAuth2 } from 'oauth'; import { v4 as uuid } from 'uuid'; import { IsNull } from 'typeorm'; -import { Config } from '@/config.js'; -import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import type { ILocalUser } from '@/models/entities/User.js'; diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index 58b170d0e4..2195e82813 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -4,8 +4,8 @@ import Router from '@koa/router'; import { OAuth2 } from 'oauth'; import { v4 as uuid } from 'uuid'; import { IsNull } from 'typeorm'; -import { Config } from '@/config.js'; -import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import type { ILocalUser } from '@/models/entities/User.js'; diff --git a/packages/backend/src/server/api/integration/TwitterServerService.ts b/packages/backend/src/server/api/integration/TwitterServerService.ts index a4a67f6c8c..7f209ea285 100644 --- a/packages/backend/src/server/api/integration/TwitterServerService.ts +++ b/packages/backend/src/server/api/integration/TwitterServerService.ts @@ -4,8 +4,8 @@ import Router from '@koa/router'; import { v4 as uuid } from 'uuid'; import { IsNull } from 'typeorm'; import autwh from 'autwh'; -import { Config } from '@/config.js'; -import { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import type { ILocalUser } from '@/models/entities/User.js'; diff --git a/packages/backend/src/server/api/stream/channels/messaging.ts b/packages/backend/src/server/api/stream/channels/messaging.ts index 5bf20c4101..b6ce6c217e 100644 --- a/packages/backend/src/server/api/stream/channels/messaging.ts +++ b/packages/backend/src/server/api/stream/channels/messaging.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserGroupJoiningsRepository, UsersRepository, MessagingMessagesRepository } from '@/models/index.js'; +import type { UserGroupJoiningsRepository, UsersRepository, MessagingMessagesRepository } from '@/models/index.js'; import type { User, ILocalUser, IRemoteUser } from '@/models/entities/User.js'; import type { UserGroup } from '@/models/entities/UserGroup.js'; import { MessagingService } from '@/core/MessagingService.js'; diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index a45c7d9468..f9f0d02558 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; +import type { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; import type { NotesRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { isUserRelated } from '@/misc/is-user-related.js'; diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 85b31312b3..44acd12796 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -13,7 +13,7 @@ import { createBullBoard } from '@bull-board/api'; import { BullAdapter } from '@bull-board/api/bullAdapter.js'; import { KoaAdapter } from '@bull-board/koa'; import { In, IsNull } from 'typeorm'; -import { Config } from '@/config.js'; +import type { Config } from '@/config.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; import { DI } from '@/di-symbols.js'; import * as Acct from '@/misc/acct.js'; diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts index 8b676aebe5..1d7d49961d 100644 --- a/packages/backend/src/server/web/FeedService.ts +++ b/packages/backend/src/server/web/FeedService.ts @@ -2,8 +2,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, IsNull } from 'typeorm'; import { Feed } from 'feed'; import { DI } from '@/di-symbols.js'; -import { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import type { User } from '@/models/entities/User.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 1cbb3f36c2..f5dddd2db7 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import summaly from 'summaly'; import { DI } from '@/di-symbols.js'; -import { UsersRepository } from '@/models/index.js'; -import { Config } from '@/config.js'; +import type { UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import type Logger from '@/logger.js'; diff --git a/packages/shared/.eslintrc.js b/packages/shared/.eslintrc.js index 2952b8ba16..6d38a9fb9f 100644 --- a/packages/shared/.eslintrc.js +++ b/packages/shared/.eslintrc.js @@ -73,7 +73,7 @@ module.exports = { '@typescript-eslint/no-misused-promises': ['error', { 'checksVoidReturn': false, }], - '@typescript-eslint/consistent-type-imports': 'error', + '@typescript-eslint/consistent-type-imports': 'off', '@typescript-eslint/prefer-nullish-coalescing': [ 'error', ], -- cgit v1.2.3-freya From bcbda6940a55d7d258c7ed9c5f3ae8d32f59ea47 Mon Sep 17 00:00:00 2001 From: こけっち <50144466+sim1222@users.noreply.github.com> Date: Thu, 22 Sep 2022 08:20:31 +0900 Subject: feat: Youtube window player (#9095) * wip: feat: Youtube Player Window * fix: player fill window * fix: improve design * fix: disable at mobile and creanup code * fix: tailing comma * fix: delete debug output * fix: eslint * fix: switch to component * fix(backend): add missing dependency Fix #9101 Co-authored-by: syuilo --- .../src/server/api/endpoints/admin/show-users.ts | 3 + packages/client/src/components/MkUrlPreview.vue | 15 ++++- packages/client/src/components/MkYoutubePlayer.vue | 72 ++++++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/components/MkYoutubePlayer.vue (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index d28f9c71b7..5471ef9b4c 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; export const meta = { tags: ['admin'], @@ -45,6 +46,8 @@ export default class extends Endpoint { constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, + + private userEntityService: UserEntityService, ) { super(meta, paramDef, async (ps, me) => { const query = this.usersRepository.createQueryBuilder('user'); diff --git a/packages/client/src/components/MkUrlPreview.vue b/packages/client/src/components/MkUrlPreview.vue index 9b2a785351..af27f644ed 100644 --- a/packages/client/src/components/MkUrlPreview.vue +++ b/packages/client/src/components/MkUrlPreview.vue @@ -10,7 +10,7 @@
- +
@@ -33,9 +33,11 @@ + + -- cgit v1.2.3-freya From 174a8b1b3e705ac350ebeb00299fbca94c916ddf Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 23 Sep 2022 06:21:31 +0900 Subject: fixes --- packages/backend/src/core/NotePiningService.ts | 2 +- packages/backend/src/core/NoteReadService.ts | 1 + packages/backend/src/core/PollService.ts | 2 +- packages/backend/src/core/QueryService.ts | 1 + packages/backend/src/core/QueueService.ts | 2 +- packages/backend/src/core/UserBlockingService.ts | 2 ++ packages/backend/src/core/UserFollowingService.ts | 1 + packages/backend/src/core/entities/NoteEntityService.ts | 4 ++-- packages/backend/src/core/entities/UserEntityService.ts | 3 ++- packages/backend/src/core/remote/activitypub/ApInboxService.ts | 1 + .../backend/src/core/remote/activitypub/ApRendererService.ts | 1 + .../backend/src/core/remote/activitypub/LdSignatureService.ts | 4 ++-- .../backend/src/core/remote/activitypub/misc/get-note-html.ts | 8 -------- packages/backend/src/queue/DbQueueProcessorsService.ts | 2 +- .../backend/src/queue/ObjectStorageQueueProcessorsService.ts | 4 ++-- .../src/queue/processors/DeleteAccountProcessorService.ts | 2 +- .../src/queue/processors/ExportFollowingProcessorService.ts | 2 +- .../src/queue/processors/ImportBlockingProcessorService.ts | 2 +- .../src/queue/processors/ImportFollowingProcessorService.ts | 2 +- .../src/queue/processors/ImportMutingProcessorService.ts | 3 +-- .../src/queue/processors/ImportUserListsProcessorService.ts | 2 +- packages/backend/src/queue/processors/InboxProcessorService.ts | 2 +- packages/backend/src/server/ActivityPubServerService.ts | 4 ++-- packages/backend/src/server/api/common/GetterService.ts | 3 +++ packages/backend/src/server/api/endpoints/announcements.ts | 2 +- packages/backend/src/server/api/endpoints/users/pages.ts | 2 +- packages/backend/src/server/api/endpoints/users/stats.ts | 10 ++++++---- 27 files changed, 39 insertions(+), 35 deletions(-) delete mode 100644 packages/backend/src/core/remote/activitypub/misc/get-note-html.ts (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts index b70c051efd..8c4a761ba6 100644 --- a/packages/backend/src/core/NotePiningService.ts +++ b/packages/backend/src/core/NotePiningService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { NotesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index b1572c631a..2c84e1d4d5 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -7,6 +7,7 @@ import type { Packed } from '@/misc/schema.js'; import type { Note } from '@/models/entities/Note.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; +import type { UsersRepository, NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository, FollowingsRepository, ChannelFollowingsRepository, AntennaNotesRepository } from '@/models/index.js'; import { UserEntityService } from './entities/UserEntityService.js'; import { NotificationService } from './NotificationService.js'; import { AntennaService } from './AntennaService.js'; diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index 1bb68f7804..e3e12b5320 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { NotesRepository, UsersRepository, BlockingsRepository } from '@/models/index.js'; +import type { NotesRepository, UsersRepository, BlockingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import { RelayService } from '@/core/RelayService.js'; import type { CacheableUser } from '@/models/entities/User.js'; diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index 1613f70c80..771adeaed5 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { User } from '@/models/entities/User.js'; +import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository } from '@/models/index.js'; import type { SelectQueryBuilder } from 'typeorm'; @Injectable() diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 12be57c7fb..d9ad26747f 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -24,7 +24,7 @@ export class QueueService { @Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue, ) {} - public deliver(user: ThinUser, content: IActivity, to: string | null) { + public deliver(user: ThinUser, content: IActivity | null, to: string | null) { if (content == null) return null; if (to == null) return null; diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 9efb021f64..5ff76b48a9 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -7,6 +7,8 @@ import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; import { DI } from '@/di-symbols.js'; +import logger from '@/logger.js'; +import type { UsersRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; import { UserEntityService } from './entities/UserEntityService.js'; import { WebhookService } from './WebhookService.js'; import { ApRendererService } from './remote/activitypub/ApRendererService.js'; diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index ff86d4343f..31e08c1366 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -12,6 +12,7 @@ import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { WebhookService } from '@/core/WebhookService.js'; import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { DI } from '@/di-symbols.js'; +import type { BlockingsRepository, FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import Logger from '../logger.js'; import { UserEntityService } from './entities/UserEntityService.js'; import { ApRendererService } from './remote/activitypub/ApRendererService.js'; diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index a9c63566b4..098f072a51 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -3,7 +3,6 @@ import { DataSource, In } from 'typeorm'; import * as mfm from 'mfm-js'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; -import type { Notes, Polls, PollVotes, DriveFiles, Channels, Followings, Users, NoteReactions } from '@/models/index.js'; import type { Config } from '@/config.js'; import type { Packed } from '@/misc/schema.js'; import { nyaize } from '@/misc/nyaize.js'; @@ -11,6 +10,7 @@ import { awaitAll } from '@/misc/prelude/await-all.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; import type { NoteReaction } from '@/models/entities/NoteReaction.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository, DriveFilesRepository } from '@/models/index.js'; import type { OnModuleInit } from '@nestjs/common'; import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { ReactionService } from '../ReactionService.js'; @@ -267,7 +267,7 @@ export class NoteEntityService implements OnModuleInit { let text = note.text; if (note.name && (note.url ?? note.uri)) { - text = `【${note.name}】\n${(note.text || '').trim()}\n\n${note.url ?? note.uri}`; + text = `【${note.name}】\n${(note.text ?? '').trim()}\n\n${note.url ?? note.uri}`; } const channel = note.channelId diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 539a9cc9eb..c691eaebdf 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -1,5 +1,5 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; -import { EntityRepository, Repository, In, Not } from 'typeorm'; +import { In, Not } from 'typeorm'; import Ajv from 'ajv'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; @@ -12,6 +12,7 @@ import { Cache } from '@/misc/cache.js'; import type { Instance } from '@/models/entities/Instance.js'; import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js'; +import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository } from '@/models/index.js'; import type { OnModuleInit } from '@nestjs/common'; import type { AntennaService } from '../AntennaService.js'; import type { CustomEmojiService } from '../CustomEmojiService.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApInboxService.ts b/packages/backend/src/core/remote/activitypub/ApInboxService.ts index a3cb08063d..7525aa8d47 100644 --- a/packages/backend/src/core/remote/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/remote/activitypub/ApInboxService.ts @@ -21,6 +21,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { QueueService } from '@/core/QueueService.js'; import { MessagingService } from '@/core/MessagingService.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, MessagingMessagesRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js'; import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; import { ApNoteService } from './models/ApNoteService.js'; import { ApLoggerService } from './ApLoggerService.js'; diff --git a/packages/backend/src/core/remote/activitypub/ApRendererService.ts b/packages/backend/src/core/remote/activitypub/ApRendererService.ts index 6058929d35..cf10c83f39 100644 --- a/packages/backend/src/core/remote/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/remote/activitypub/ApRendererService.ts @@ -20,6 +20,7 @@ import { MfmService } from '@/core/MfmService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import type { UserKeypair } from '@/models/entities/UserKeypair.js'; +import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, EmojisRepository, PollsRepository } from '@/models/index.js'; import { LdSignatureService } from './LdSignatureService.js'; import { ApMfmService } from './ApMfmService.js'; import type { IActivity } from './type.js'; diff --git a/packages/backend/src/core/remote/activitypub/LdSignatureService.ts b/packages/backend/src/core/remote/activitypub/LdSignatureService.ts index ea0d2daf3d..b9c57f4ba6 100644 --- a/packages/backend/src/core/remote/activitypub/LdSignatureService.ts +++ b/packages/backend/src/core/remote/activitypub/LdSignatureService.ts @@ -81,12 +81,12 @@ class LdSignature { delete transformedOptions['id']; delete transformedOptions['signatureValue']; const canonizedOptions = await this.normalize(transformedOptions); - const optionsHash = this.sha256(canonizedOptions); + const optionsHash = this.sha256(canonizedOptions.toString()); const transformedData = { ...data }; delete transformedData['signature']; const cannonidedData = await this.normalize(transformedData); if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`); - const documentHash = this.sha256(cannonidedData); + const documentHash = this.sha256(cannonidedData.toString()); const verifyData = `${optionsHash}${documentHash}`; return verifyData; } diff --git a/packages/backend/src/core/remote/activitypub/misc/get-note-html.ts b/packages/backend/src/core/remote/activitypub/misc/get-note-html.ts deleted file mode 100644 index af23a04a71..0000000000 --- a/packages/backend/src/core/remote/activitypub/misc/get-note-html.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as mfm from 'mfm-js'; -import type { Note } from '@/models/entities/Note.js'; -import { toHtml } from '../../../../mfm/to-html.js'; - -export default function(note: Note) { - if (!note.text) return ''; - return toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); -} diff --git a/packages/backend/src/queue/DbQueueProcessorsService.ts b/packages/backend/src/queue/DbQueueProcessorsService.ts index 7622ab8800..f79100e6ce 100644 --- a/packages/backend/src/queue/DbQueueProcessorsService.ts +++ b/packages/backend/src/queue/DbQueueProcessorsService.ts @@ -53,7 +53,7 @@ export class DbQueueProcessorsService { importBlocking: (job, done) => this.importBlockingProcessorService.process(job, done), importUserLists: (job, done) => this.importUserListsProcessorService.process(job, done), importCustomEmojis: (job, done) => this.importCustomEmojisProcessorService.process(job, done), - deleteAccount: (job, done) => this.deleteAccountProcessorService.process(job, done), + deleteAccount: (job) => this.deleteAccountProcessorService.process(job), } as Record>>; for (const [k, v] of Object.entries(jobs)) { diff --git a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts index 659e9b8e42..e031581488 100644 --- a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts +++ b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts @@ -19,8 +19,8 @@ export class ObjectStorageQueueProcessorsService { public start(q: Bull.Queue) { const jobs = { - deleteFile: (job, done) => this.deleteFileProcessorService.process(job, done), - cleanRemoteFiles: (job, done) => this.cleanRemoteFilesProcessorService.process(job, done), + deleteFile: (job) => this.deleteFileProcessorService.process(job), + cleanRemoteFiles: (job) => this.cleanRemoteFilesProcessorService.process(job), } as Record>>; for (const [k, v] of Object.entries(jobs)) { diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index 4d6ed2008a..5e4c8bdd69 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, UserProfilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index eac9c6925e..1e3fba06b5 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -3,7 +3,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, MoreThan, Not } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import type { FollowingsRepository, MutingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository, MutingsRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index 12f77638bb..6c6789cff4 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { BlockingsRepository, DriveFilesRepository } from '@/models/index.js'; +import type { UsersRepository, BlockingsRepository, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index f649014399..9f2e5e801a 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index f004f2d64b..62ad3b5c88 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -1,13 +1,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; import { DownloadService } from '@/core/DownloadService.js'; -import type { UserFollowingService } from '@/core/UserFollowingService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index 168f850718..f214d59e1c 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; +import type { UsersRepository, DriveFilesRepository, UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index ad72263a8d..e8cd7dcaca 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -3,7 +3,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import httpSignature from '@peertube/http-signature'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { InstancesRepository, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { MetaService } from '@/core/MetaService.js'; diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index b2fcf9f81a..2e7bd4dcb2 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -4,7 +4,7 @@ import json from 'koa-json-body'; import httpSignature from '@peertube/http-signature'; import { Brackets, In, IsNull, LessThan, Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; +import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; import * as url from '@/misc/prelude/url.js'; import type { Config } from '@/config.js'; import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; @@ -73,7 +73,7 @@ export class ActivityPubServerService { */ private async packActivity(note: Note): Promise { if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { - const renote = await Notes.findOneByOrFail({ id: note.renoteId }); + const renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId }); return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note); } diff --git a/packages/backend/src/server/api/common/GetterService.ts b/packages/backend/src/server/api/common/GetterService.ts index a6b60d1f5a..70ab46ec35 100644 --- a/packages/backend/src/server/api/common/GetterService.ts +++ b/packages/backend/src/server/api/common/GetterService.ts @@ -4,6 +4,7 @@ import type { NotesRepository, UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; @Injectable() export class GetterService { @@ -13,6 +14,8 @@ export class GetterService { @Inject(DI.notesRepository) private notesRepository: NotesRepository, + + private userEntityService: UserEntityService, ) { } diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index 74168481f6..79788be4e2 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; -import type { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models'; +import type { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models/index.js'; export const meta = { tags: ['meta'], diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index e007aa57b2..a105103f16 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; -import type { PagesRepository } from '@/models'; +import type { PagesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index 71f4ca0cfa..7479793afe 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -3,6 +3,7 @@ import { awaitAll } from '@/misc/prelude/await-all.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, DriveFilesRepository, NoteReactionsRepository, PageLikesRepository, NoteFavoritesRepository, PollVotesRepository } from '@/models/index.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -217,10 +218,11 @@ export default class extends Endpoint { driveUsage: this.driveFileEntityService.calcDriveUsageOf(user), }); - result.followingCount = result.localFollowingCount + result.remoteFollowingCount; - result.followersCount = result.localFollowersCount + result.remoteFollowersCount; - - return result; + return { + ...result, + followingCount: result.localFollowingCount + result.remoteFollowingCount, + followersCount: result.localFollowersCount + result.remoteFollowersCount, + }; }); } } -- cgit v1.2.3-freya From 417f52359dd4e708478ed43e2e07efb09ecf1fd2 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 06:45:44 +0900 Subject: fixes --- packages/backend/src/boot/master.ts | 3 +- packages/backend/src/core/AntennaService.ts | 2 +- .../queue/ObjectStorageQueueProcessorsService.ts | 2 +- .../backend/src/server/NodeinfoServerService.ts | 2 +- .../src/server/api/StreamingApiServerService.ts | 2 +- .../api/endpoints/admin/abuse-user-reports.ts | 2 + .../server/api/endpoints/admin/accounts/delete.ts | 2 + .../api/endpoints/admin/drive-capacity-override.ts | 2 + .../src/server/api/endpoints/admin/drive/files.ts | 2 + .../server/api/endpoints/admin/drive/show-file.ts | 8 ++- .../src/server/api/endpoints/admin/emoji/list.ts | 2 + .../api/endpoints/admin/queue/deliver-delayed.ts | 2 +- .../api/endpoints/admin/queue/inbox-delayed.ts | 2 +- .../src/server/api/endpoints/admin/queue/stats.ts | 2 +- .../src/server/api/endpoints/admin/suspend-user.ts | 2 + .../src/server/api/endpoints/antennas/notes.ts | 2 +- .../src/server/api/endpoints/channels/follow.ts | 2 +- packages/backend/src/server/api/endpoints/stats.ts | 2 +- .../server/api/integration/DiscordServerService.ts | 66 +++++++++++----------- .../server/api/integration/GithubServerService.ts | 48 ++++++++-------- packages/backend/tsconfig.json | 1 + 21 files changed, 88 insertions(+), 70 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index ec09b4350b..fc07510ffb 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -119,8 +119,7 @@ function loadConfigBoot(): Config { if (typeof exception === 'string') { configLogger.error(exception); process.exit(1); - } - if (exception.code === 'ENOENT') { + } else if ((exception as any).code === 'ENOENT') { configLogger.error('Configuration file not found', null, true); process.exit(1); } diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 0146f959e6..af76767f31 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -60,7 +60,7 @@ export class AntennaService implements OnApplicationShutdown { this.redisSubscriber.off('message', this.onRedisMessage); } - private async onRedisMessage(_, data) { + private async onRedisMessage(_: string, data: string): Promise { const obj = JSON.parse(data); if (obj.channel === 'internal') { diff --git a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts index e031581488..097e4448a5 100644 --- a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts +++ b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts @@ -20,7 +20,7 @@ export class ObjectStorageQueueProcessorsService { public start(q: Bull.Queue) { const jobs = { deleteFile: (job) => this.deleteFileProcessorService.process(job), - cleanRemoteFiles: (job) => this.cleanRemoteFilesProcessorService.process(job), + cleanRemoteFiles: (job, done) => this.cleanRemoteFilesProcessorService.process(job, done), } as Record>>; for (const [k, v] of Object.entries(jobs)) { diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index b07dc4cf8f..ef4ec74a35 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -118,7 +118,7 @@ export class NodeinfoServerService { router.get(nodeinfo2_0path, async ctx => { const base = await cache.fetch(null, () => nodeinfo2()); - delete base.software.repository; + delete (base as any).software.repository; ctx.body = { version: '2.0', ...base }; ctx.set('Cache-Control', 'public, max-age=600'); diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index 8a6906e5fe..46eaf8566e 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -72,7 +72,7 @@ export class StreamingApiServerService { const ev = new EventEmitter(); - async function onRedisMessage(_: string, data: string) { + async function onRedisMessage(_: string, data: string): Promise { const parsed = JSON.parse(data); ev.emit(parsed.channel, parsed.message); } diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 30183ed88b..9bba16166f 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -3,6 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { AbuseUserReportsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; +import { AbuseUserReportEntityService } from '@/core/entities/AbuseUserReportEntityService.js'; export const meta = { tags: ['admin'], @@ -93,6 +94,7 @@ export default class extends Endpoint { @Inject(DI.abuseUserReportsRepository) private abuseUserReportsRepository: AbuseUserReportsRepository, + private abuseUserReportEntityService: AbuseUserReportEntityService, private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts index dc2d499191..b7081987ca 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -5,6 +5,7 @@ import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; export const meta = { tags: ['admin'], @@ -28,6 +29,7 @@ export default class extends Endpoint { @Inject(DI.usersRepository) private usersRepository: UsersRepository, + private userEntityService: UserEntityService, private queueService: QueueService, private globalEventService: GlobalEventService, private userSuspendService: UserSuspendService, diff --git a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts index f4d39cd872..665e2a8cce 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts @@ -3,6 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository } from '@/models/index.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { DI } from '@/di-symbols.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; export const meta = { tags: ['admin'], @@ -27,6 +28,7 @@ export default class extends Endpoint { @Inject(DI.usersRepository) private usersRepository: UsersRepository, + private userEntityService: UserEntityService, private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index 2459a479ab..53a37cb691 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -3,6 +3,7 @@ import type { DriveFilesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; export const meta = { tags: ['admin'], @@ -47,6 +48,7 @@ export default class extends Endpoint { @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + private driveFileEntityService: DriveFileEntityService, private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index c17f67cf2b..25d13d13a3 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -192,12 +192,14 @@ export default class extends Endpoint { throw new ApiError(meta.errors.noSuchFile); } + const res = file as Partial; + if (!me.isAdmin) { - delete file.requestIp; - delete file.requestHeaders; + delete res.requestIp; + delete res.requestHeaders; } - return file; + return res; }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index e50f924044..8624229db8 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -4,6 +4,7 @@ import type { EmojisRepository } from '@/models/index.js'; import type { Emoji } from '@/models/entities/Emoji.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; export const meta = { tags: ['admin'], @@ -71,6 +72,7 @@ export default class extends Endpoint { @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, + private emojiEntityService: EmojiEntityService, private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts index 4b5be70d56..526efa9f9d 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts @@ -1,7 +1,7 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DeliverQueue } from '@/core/queue/QueueModule.js'; +import type { DeliverQueue } from '@/core/queue/QueueModule.js'; export const meta = { tags: ['admin'], diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts index 715974e917..b8934428c3 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts @@ -1,7 +1,7 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { InboxQueue } from '@/core/queue/QueueModule.js'; +import type { InboxQueue } from '@/core/queue/QueueModule.js'; export const meta = { tags: ['admin'], diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts index f2ca81a8da..605ea3d042 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/queue/QueueModule.js'; +import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/queue/QueueModule.js'; export const meta = { tags: ['admin'], diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index d30facd125..53de8d9495 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -7,6 +7,7 @@ import { ModerationLogService } from '@/core/ModerationLogService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; export const meta = { tags: ['admin'], @@ -36,6 +37,7 @@ export default class extends Endpoint { @Inject(DI.notificationsRepository) private notificationsRepository: NotificationsRepository, + private userEntityService: UserEntityService, private userFollowingService: UserFollowingService, private userSuspendService: UserSuspendService, private moderationLogService: ModerationLogService, diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index fb3c713154..fbb5acf617 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { NotesRepository, AntennaNotesRepository } from '@/models/index.js'; +import type { NotesRepository, AntennaNotesRepository, AntennasRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteReadService } from '@/core/NoteReadService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index 871d3927bc..91693918f2 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -44,7 +44,7 @@ export default class extends Endpoint { private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { - const channel = await Channels.findOneBy({ + const channel = await this.channelsRepository.findOneBy({ id: ps.channelId, }); diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index 3adf0a4bb8..96b22b0261 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; -import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js'; +import type { InstancesRepository, NoteReactionsRepository, NotesRepository, UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index 79ce9c8973..8e4f595ada 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -159,6 +159,7 @@ export class DiscordServerService { const { redirect_uri, state } = await new Promise((res, rej) => { this.redisClient.get(sessid, async (_, state) => { + if (state == null) throw new Error('empty state'); res(JSON.parse(state)); }); }); @@ -169,22 +170,22 @@ export class DiscordServerService { } const { accessToken, refreshToken, expiresDate } = await new Promise((res, rej) => - oauth2!.getOAuthAccessToken(code, { - grant_type: 'authorization_code', - redirect_uri, - }, (err, accessToken, refreshToken, result) => { - if (err) { - rej(err); - } else if (result.error) { - rej(result.error); - } else { - res({ - accessToken, - refreshToken, - expiresDate: Date.now() + Number(result.expires_in) * 1000, - }); - } - })); + oauth2!.getOAuthAccessToken(code, { + grant_type: 'authorization_code', + redirect_uri, + }, (err, accessToken, refreshToken, result) => { + if (err) { + rej(err); + } else if (result.error) { + rej(result.error); + } else { + res({ + accessToken, + refreshToken, + expiresDate: Date.now() + Number(result.expires_in) * 1000, + }); + } + })); const { id, username, discriminator } = (await this.httpRequestService.getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { 'Authorization': `Bearer ${accessToken}`, @@ -230,6 +231,7 @@ export class DiscordServerService { const { redirect_uri, state } = await new Promise((res, rej) => { this.redisClient.get(userToken, async (_, state) => { + if (state == null) throw new Error('empty state'); res(JSON.parse(state)); }); }); @@ -240,22 +242,22 @@ export class DiscordServerService { } const { accessToken, refreshToken, expiresDate } = await new Promise((res, rej) => - oauth2!.getOAuthAccessToken(code, { - grant_type: 'authorization_code', - redirect_uri, - }, (err, accessToken, refreshToken, result) => { - if (err) { - rej(err); - } else if (result.error) { - rej(result.error); - } else { - res({ - accessToken, - refreshToken, - expiresDate: Date.now() + Number(result.expires_in) * 1000, - }); - } - })); + oauth2!.getOAuthAccessToken(code, { + grant_type: 'authorization_code', + redirect_uri, + }, (err, accessToken, refreshToken, result) => { + if (err) { + rej(err); + } else if (result.error) { + rej(result.error); + } else { + res({ + accessToken, + refreshToken, + expiresDate: Date.now() + Number(result.expires_in) * 1000, + }); + } + })); const { id, username, discriminator } = (await this.httpRequestService.getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { 'Authorization': `Bearer ${accessToken}`, diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index 7fb31c39a2..2ccea36dc4 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -157,6 +157,7 @@ export class GithubServerService { const { redirect_uri, state } = await new Promise((res, rej) => { this.redisClient.get(sessid, async (_, state) => { + if (state == null) throw new Error('empty state'); res(JSON.parse(state)); }); }); @@ -167,17 +168,17 @@ export class GithubServerService { } const { accessToken } = await new Promise((res, rej) => - oauth2!.getOAuthAccessToken(code, { - redirect_uri, - }, (err, accessToken, refresh, result) => { - if (err) { - rej(err); - } else if (result.error) { - rej(result.error); - } else { - res({ accessToken }); - } - })); + oauth2!.getOAuthAccessToken(code, { + redirect_uri, + }, (err, accessToken, refresh, result) => { + if (err) { + rej(err); + } else if (result.error) { + rej(result.error); + } else { + res({ accessToken }); + } + })); const { login, id } = (await this.httpRequestService.getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { 'Authorization': `bearer ${accessToken}`, @@ -208,6 +209,7 @@ export class GithubServerService { const { redirect_uri, state } = await new Promise((res, rej) => { this.redisClient.get(userToken, async (_, state) => { + if (state == null) throw new Error('empty state'); res(JSON.parse(state)); }); }); @@ -218,18 +220,18 @@ export class GithubServerService { } const { accessToken } = await new Promise((res, rej) => - oauth2!.getOAuthAccessToken( - code, - { redirect_uri }, - (err, accessToken, refresh, result) => { - if (err) { - rej(err); - } else if (result.error) { - rej(result.error); - } else { - res({ accessToken }); - } - })); + oauth2!.getOAuthAccessToken( + code, + { redirect_uri }, + (err, accessToken, refresh, result) => { + if (err) { + rej(err); + } else if (result.error) { + rej(result.error); + } else { + res({ accessToken }); + } + })); const { login, id } = (await this.httpRequestService.getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { 'Authorization': `bearer ${accessToken}`, diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index 0c95925744..2c8adf7708 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -18,6 +18,7 @@ "strict": true, "strictNullChecks": true, "strictPropertyInitialization": false, + "skipLibCheck": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "resolveJsonModule": true, -- cgit v1.2.3-freya From c8f6bc0dabcbe119533ef8f0edfb35d9405818c8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 07:12:11 +0900 Subject: fixes --- packages/backend/src/core/DriveService.ts | 2 +- .../src/core/FetchInstanceMetadataService.ts | 33 +++++++++++++--------- packages/backend/src/core/MetaService.ts | 2 +- packages/backend/src/core/NoteCreateService.ts | 5 +--- packages/backend/src/core/SignupService.ts | 2 +- .../src/core/TwoFactorAuthenticationService.ts | 2 ++ packages/backend/src/core/UserBlockingService.ts | 8 +++++- packages/backend/src/core/UserCacheService.ts | 2 +- packages/backend/src/core/WebhookService.ts | 2 +- .../src/server/api/endpoints/i/authorized-apps.ts | 4 ++- 10 files changed, 37 insertions(+), 25 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 643c51c37d..e0bdd29c0f 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -61,7 +61,7 @@ type AddFileArgs = { type UploadFromUrlArgs = { url: string; - user: { id: User['id']; host: User['host'] } | null; + user: { id: User['id']; host: User['host']; driveCapacityOverrideMb: User['driveCapacityOverrideMb'] } | null; folderId?: DriveFolder['id'] | null; uri?: string | null; sensitive?: boolean; diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 9a51ed0c0a..184404123c 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -13,20 +13,21 @@ import { HttpRequestService } from './HttpRequestService.js'; import type { DOMWindow } from 'jsdom'; type NodeInfo = { - openRegistrations?: any; + openRegistrations?: unknown; software?: { - name?: any; - version?: any; + name?: unknown; + version?: unknown; }; metadata?: { - name?: any; - nodeName?: any; - nodeDescription?: any; - description?: any; + name?: unknown; + nodeName?: unknown; + nodeDescription?: unknown; + description?: unknown; maintainer?: { - name?: any; - email?: any; + name?: unknown; + email?: unknown; }; + themeColor?: unknown; }; }; @@ -81,7 +82,7 @@ export class FetchInstanceMetadataService { } as Record; if (info) { - updates.softwareName = info.software?.name.toLowerCase(); + updates.softwareName = typeof info.software?.name === 'string' ? info.software.name.toLowerCase() : '?'; updates.softwareVersion = info.software?.version; updates.openRegistrations = info.openRegistrations; updates.maintainerName = info.metadata ? info.metadata.maintainer ? (info.metadata.maintainer.name ?? null) : null : null; @@ -238,8 +239,10 @@ export class FetchInstanceMetadataService { private async getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (info && info.metadata) { - if (info.metadata.nodeName || info.metadata.name) { - return info.metadata.nodeName ?? info.metadata.name; + if (typeof info.metadata.nodeName === 'string') { + return info.metadata.nodeName; + } else if (typeof info.metadata.name === 'string') { + return info.metadata.name; } } @@ -260,8 +263,10 @@ export class FetchInstanceMetadataService { private async getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (info && info.metadata) { - if (info.metadata.nodeDescription || info.metadata.description) { - return info.metadata.nodeDescription ?? info.metadata.description; + if (typeof info.metadata.nodeDescription === 'string') { + return info.metadata.nodeDescription; + } else if (typeof info.metadata.description === 'string') { + return info.metadata.description; } } diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index 1744f64217..c3d41bfccb 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -34,7 +34,7 @@ export class MetaService implements OnApplicationShutdown { this.redisSubscriber.on('message', this.onMessage); } - private async onMessage(_, data): Promise { + private async onMessage(_: string, data: string): Promise { const obj = JSON.parse(data); if (obj.channel === 'internal') { diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 6e5cce8f62..a23e105674 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -534,7 +534,6 @@ export class NoteCreateService { }); const nm = new NotificationManager(this.mutingsRepository, this.createNotificationService, user, note); - const nmRelatedPromises = []; await this.createMentionedEvents(mentionedUsers, note, nm); @@ -583,9 +582,7 @@ export class NoteCreateService { } } - Promise.all(nmRelatedPromises).then(() => { - nm.deliver(); - }); + nm.deliver(); //#region AP deliver if (this.userEntityService.isLocalUser(user)) { diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 8b72d73af6..2239d5fd83 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -3,7 +3,7 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import { DataSource, IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UsedUsernamesRepository } from '@/models/index.js'; +import type { UsedUsernamesRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import { User } from '@/models/entities/User.js'; import { UserProfile } from '@/models/entities/UserProfile.js'; diff --git a/packages/backend/src/core/TwoFactorAuthenticationService.ts b/packages/backend/src/core/TwoFactorAuthenticationService.ts index 0962f88a7c..54e87ec36f 100644 --- a/packages/backend/src/core/TwoFactorAuthenticationService.ts +++ b/packages/backend/src/core/TwoFactorAuthenticationService.ts @@ -67,6 +67,8 @@ function verifyCertificateChain(certificates: string[]) { const CACert = i + 1 >= certificates.length ? Cert : certificates[i + 1]; const certStruct = jsrsasign.ASN1HEX.getTLVbyList(certificate.hex!, 0, [0]); + if (certStruct == null) throw new Error('certStruct is null'); + const algorithm = certificate.getSignatureAlgorithmField(); const signatureHex = certificate.getSignatureValueHex(); diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 5ff76b48a9..b7a434684e 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -9,12 +9,16 @@ import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; import { DI } from '@/di-symbols.js'; import logger from '@/logger.js'; import type { UsersRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import Logger from '@/logger.js'; import { UserEntityService } from './entities/UserEntityService.js'; import { WebhookService } from './WebhookService.js'; import { ApRendererService } from './remote/activitypub/ApRendererService.js'; +import { LoggerService } from './LoggerService.js'; @Injectable() export class UserBlockingService { + private logger: Logger; + constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -41,7 +45,9 @@ export class UserBlockingService { private webhookService: WebhookService, private apRendererService: ApRendererService, private perUserFollowingChart: PerUserFollowingChart, + private loggerService: LoggerService, ) { + this.logger = this.loggerService.getLogger('user-block'); } public async block(blocker: User, blockee: User) { @@ -181,7 +187,7 @@ export class UserBlockingService { }); if (blocking == null) { - logger.warn('ブロック解除がリクエストされましたがブロックしていませんでした'); + this.logger.warn('ブロック解除がリクエストされましたがブロックしていませんでした'); return; } diff --git a/packages/backend/src/core/UserCacheService.ts b/packages/backend/src/core/UserCacheService.ts index 666bef3f49..b7166010ee 100644 --- a/packages/backend/src/core/UserCacheService.ts +++ b/packages/backend/src/core/UserCacheService.ts @@ -33,7 +33,7 @@ export class UserCacheService implements OnApplicationShutdown { this.redisSubscriber.on('message', this.onMessage); } - private async onMessage(_, data) { + private async onMessage(_: string, data: string): Promise { const obj = JSON.parse(data); if (obj.channel === 'internal') { diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index 347b2b16c4..1a690314f8 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -32,7 +32,7 @@ export class WebhookService implements OnApplicationShutdown { return this.webhooks; } - private async onMessage(_, data) { + private async onMessage(_: string, data: string): Promise { const obj = JSON.parse(data); if (obj.channel === 'internal') { diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts index d0bdb5695b..f5a946eb91 100644 --- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts +++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import { IsNull, Not } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { AccessTokensRepository } from '@/models/index.js'; import { AppEntityService } from '@/core/entities/AppEntityService.js'; @@ -34,6 +35,7 @@ export default class extends Endpoint { const tokens = await this.accessTokensRepository.find({ where: { userId: me.id, + appId: Not(IsNull()), }, take: ps.limit, skip: ps.offset, @@ -42,7 +44,7 @@ export default class extends Endpoint { }, }); - return await Promise.all(tokens.map(token => this.appEntityService.pack(token.appId, me, { + return await Promise.all(tokens.map(token => this.appEntityService.pack(token.appId!, me, { detail: true, }))); }); -- cgit v1.2.3-freya From 614b11951b6116a6485bee85126858189ecaa681 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 07:15:16 +0900 Subject: refactor --- packages/backend/src/server/ServerModule.ts | 2 +- packages/backend/src/server/api/EndpointsModule.ts | 2 +- packages/backend/src/server/api/GetterService.ts | 74 ++++++++++++++++++++++ .../backend/src/server/api/common/GetterService.ts | 74 ---------------------- .../src/server/api/endpoints/admin/promo/create.ts | 2 +- .../src/server/api/endpoints/blocking/create.ts | 2 +- .../src/server/api/endpoints/blocking/delete.ts | 2 +- .../src/server/api/endpoints/clips/add-note.ts | 2 +- .../src/server/api/endpoints/clips/remove-note.ts | 2 +- .../api/endpoints/federation/update-remote-user.ts | 2 +- .../src/server/api/endpoints/following/create.ts | 2 +- .../src/server/api/endpoints/following/delete.ts | 2 +- .../server/api/endpoints/following/invalidate.ts | 2 +- .../api/endpoints/following/requests/accept.ts | 2 +- .../api/endpoints/following/requests/cancel.ts | 5 +- .../api/endpoints/following/requests/reject.ts | 2 +- .../src/server/api/endpoints/messaging/messages.ts | 5 +- .../api/endpoints/messaging/messages/create.ts | 2 +- .../src/server/api/endpoints/mute/create.ts | 2 +- .../src/server/api/endpoints/mute/delete.ts | 2 +- .../src/server/api/endpoints/notes/clips.ts | 2 +- .../src/server/api/endpoints/notes/conversation.ts | 2 +- .../src/server/api/endpoints/notes/delete.ts | 2 +- .../server/api/endpoints/notes/favorites/create.ts | 2 +- .../server/api/endpoints/notes/favorites/delete.ts | 2 +- .../src/server/api/endpoints/notes/polls/vote.ts | 2 +- .../server/api/endpoints/notes/reactions/create.ts | 2 +- .../server/api/endpoints/notes/reactions/delete.ts | 2 +- .../src/server/api/endpoints/notes/renotes.ts | 2 +- .../backend/src/server/api/endpoints/notes/show.ts | 2 +- .../api/endpoints/notes/thread-muting/create.ts | 2 +- .../api/endpoints/notes/thread-muting/delete.ts | 2 +- .../src/server/api/endpoints/notes/translate.ts | 2 +- .../src/server/api/endpoints/notes/unrenote.ts | 2 +- .../backend/src/server/api/endpoints/promo/read.ts | 2 +- .../users/get-frequently-replied-users.ts | 2 +- .../server/api/endpoints/users/groups/invite.ts | 2 +- .../src/server/api/endpoints/users/groups/pull.ts | 2 +- .../server/api/endpoints/users/groups/transfer.ts | 2 +- .../src/server/api/endpoints/users/lists/pull.ts | 2 +- .../src/server/api/endpoints/users/lists/push.ts | 2 +- .../src/server/api/endpoints/users/notes.ts | 2 +- .../src/server/api/endpoints/users/report-abuse.ts | 2 +- 43 files changed, 117 insertions(+), 119 deletions(-) create mode 100644 packages/backend/src/server/api/GetterService.ts delete mode 100644 packages/backend/src/server/api/common/GetterService.ts (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts index f05eda1cb8..474edafe41 100644 --- a/packages/backend/src/server/ServerModule.ts +++ b/packages/backend/src/server/ServerModule.ts @@ -7,7 +7,7 @@ import { MediaProxyServerService } from './MediaProxyServerService.js'; import { NodeinfoServerService } from './NodeinfoServerService.js'; import { ServerService } from './ServerService.js'; import { WellKnownServerService } from './WellKnownServerService.js'; -import { GetterService } from './api/common/GetterService.js'; +import { GetterService } from './api/GetterService.js'; import { DiscordServerService } from './api/integration/DiscordServerService.js'; import { GithubServerService } from './api/integration/GithubServerService.js'; import { TwitterServerService } from './api/integration/TwitterServerService.js'; diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index d2dfd78fd4..e41ed388b4 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -313,7 +313,7 @@ import * as ep___users_show from './endpoints/users/show.js'; import * as ep___users_stats from './endpoints/users/stats.js'; import * as ep___fetchRss from './endpoints/fetch-rss.js'; import * as ep___admin_driveCapOverride from './endpoints/admin/drive-capacity-override.js'; -import { GetterService } from './common/GetterService.js'; +import { GetterService } from './GetterService.js'; import { ApiLoggerService } from './ApiLoggerService.js'; import type { Provider } from '@nestjs/common'; diff --git a/packages/backend/src/server/api/GetterService.ts b/packages/backend/src/server/api/GetterService.ts new file mode 100644 index 0000000000..70ab46ec35 --- /dev/null +++ b/packages/backend/src/server/api/GetterService.ts @@ -0,0 +1,74 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { NotesRepository, UsersRepository } from '@/models/index.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import type { User } from '@/models/entities/User.js'; +import type { Note } from '@/models/entities/Note.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; + +@Injectable() +export class GetterService { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private userEntityService: UserEntityService, + ) { + } + + /** + * Get note for API processing + */ + public async getNote(noteId: Note['id']) { + const note = await this.notesRepository.findOneBy({ id: noteId }); + + if (note == null) { + throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); + } + + return note; + } + + /** + * Get user for API processing + */ + public async getUser(userId: User['id']) { + const user = await this.usersRepository.findOneBy({ id: userId }); + + if (user == null) { + throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.'); + } + + return user; + } + + /** + * Get remote user for API processing + */ + public async getRemoteUser(userId: User['id']) { + const user = await this.getUser(userId); + + if (!this.userEntityService.isRemoteUser(user)) { + throw new Error('user is not a remote user'); + } + + return user; + } + + /** + * Get local user for API processing + */ + public async getLocalUser(userId: User['id']) { + const user = await this.getUser(userId); + + if (!this.userEntityService.isLocalUser(user)) { + throw new Error('user is not a local user'); + } + + return user; + } +} + diff --git a/packages/backend/src/server/api/common/GetterService.ts b/packages/backend/src/server/api/common/GetterService.ts deleted file mode 100644 index 70ab46ec35..0000000000 --- a/packages/backend/src/server/api/common/GetterService.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { DI } from '@/di-symbols.js'; -import type { NotesRepository, UsersRepository } from '@/models/index.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import type { User } from '@/models/entities/User.js'; -import type { Note } from '@/models/entities/Note.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; - -@Injectable() -export class GetterService { - constructor( - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - private userEntityService: UserEntityService, - ) { - } - - /** - * Get note for API processing - */ - public async getNote(noteId: Note['id']) { - const note = await this.notesRepository.findOneBy({ id: noteId }); - - if (note == null) { - throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); - } - - return note; - } - - /** - * Get user for API processing - */ - public async getUser(userId: User['id']) { - const user = await this.usersRepository.findOneBy({ id: userId }); - - if (user == null) { - throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.'); - } - - return user; - } - - /** - * Get remote user for API processing - */ - public async getRemoteUser(userId: User['id']) { - const user = await this.getUser(userId); - - if (!this.userEntityService.isRemoteUser(user)) { - throw new Error('user is not a remote user'); - } - - return user; - } - - /** - * Get local user for API processing - */ - public async getLocalUser(userId: User['id']) { - const user = await this.getUser(userId); - - if (!this.userEntityService.isLocalUser(user)) { - throw new Error('user is not a local user'); - } - - return user; - } -} - diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index 0cff6bae6a..bee1ffbaee 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { PromoNotesRepository } from '@/models/index.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index aa6d2ecf20..c468010bce 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -6,7 +6,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['account'], diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index 46a499943c..46dd26a45a 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -6,7 +6,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['account'], diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index 77d02815e0..a242124e6a 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -4,7 +4,7 @@ import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['account', 'notes', 'clips'], diff --git a/packages/backend/src/server/api/endpoints/clips/remove-note.ts b/packages/backend/src/server/api/endpoints/clips/remove-note.ts index 93805af089..55778c7ecb 100644 --- a/packages/backend/src/server/api/endpoints/clips/remove-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/remove-note.ts @@ -3,7 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['account', 'notes', 'clips'], diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts index 57497bbf62..30e77aab45 100644 --- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts +++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { ApPersonService } from '@/core/remote/activitypub/models/ApPersonService.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['federation'], diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index be322e2896..f879429372 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -7,7 +7,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['following', 'users'], diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index afb59dd2c2..4f12db1273 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -6,7 +6,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['following', 'users'], diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index e67e136ead..22304cacda 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -6,7 +6,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['following', 'users'], diff --git a/packages/backend/src/server/api/endpoints/following/requests/accept.ts b/packages/backend/src/server/api/endpoints/following/requests/accept.ts index 5f082c087a..dcb98485da 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/accept.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/accept.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts index 213b5ce32e..f39c4e3767 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts @@ -1,10 +1,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { FollowingsRepository } from '@/models/index.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/following/requests/reject.ts b/packages/backend/src/server/api/endpoints/following/requests/reject.ts index 663c659bf3..ab5706e8ef 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/reject.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/reject.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts index f563da3278..3673e252ae 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts @@ -1,15 +1,14 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository } from '@/models/index.js'; -import type { UserGroupsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; +import type { UsersRepository, UserGroupsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; import { MessagingService } from '@/core/MessagingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['messaging'], diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts index f61662af75..00e65b4875 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts @@ -3,7 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { BlockingsRepository, UserGroupJoiningsRepository, DriveFilesRepository, UserGroupsRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import type { UserGroup } from '@/models/entities/UserGroup.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { MessagingService } from '@/core/MessagingService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 3b4507281f..5ead470314 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -6,7 +6,7 @@ import type { Muting } from '@/models/entities/Muting.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['account'], diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts index 2fc5cee95e..612c4a4c04 100644 --- a/packages/backend/src/server/api/endpoints/mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/mute/delete.ts @@ -4,7 +4,7 @@ import type { MutingsRepository } from '@/models/index.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['account'], diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index 579466d4fd..d5caec6e1d 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -5,7 +5,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['clips', 'notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index ddfee31525..5ecf7cf458 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -5,7 +5,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 5765dfe66f..3c6e7bf768 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -5,7 +5,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteDeleteService } from '@/core/NoteDeleteService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index edbd300e08..e742c1bb35 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { NoteFavoritesRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index 8f4f2b2b9e..bb3a7c501a 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import type { NoteFavoritesRepository } from '@/models/index.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 515f03dcce..6b3b062c15 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -4,7 +4,7 @@ import type { UsersRepository, BlockingsRepository, PollsRepository, PollVotesRe import type { IRemoteUser } from '@/models/entities/User.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { QueueService } from '@/core/QueueService.js'; import { PollService } from '@/core/PollService.js'; import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts index 2af734307d..839f893db2 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { ReactionService } from '@/core/ReactionService.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts index 31ed962922..cf90d7b5f6 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { ReactionService } from '@/core/ReactionService.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 97ef1a17ec..026a1baa3e 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -5,7 +5,7 @@ import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index 7849cfa401..6b1b84a18e 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -4,7 +4,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index b8ddf83f3c..140614d36e 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { NotesRepository, NoteThreadMutingsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { NoteReadService } from '@/core/NoteReadService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts index 54e9b4939f..30016d48bc 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { NoteThreadMutingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index 7a3daf741e..ec16965998 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -9,7 +9,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { MetaService } from '@/core/MetaService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index 7378c4b600..74e459b426 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -5,7 +5,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteDeleteService } from '@/core/NoteDeleteService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index a28229add3..90febdbce7 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -4,7 +4,7 @@ import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index 22c49860bd..09f6acde9c 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -6,7 +6,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['users'], diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index 127f4ca65b..2e040c0601 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -3,7 +3,7 @@ import type { UserGroupsRepository, UserGroupJoiningsRepository, UserGroupInvita import { IdService } from '@/core/IdService.js'; import type { UserGroupInvitation } from '@/models/entities/UserGroupInvitation.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index 3474f22c6e..409006b0b0 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index e0b2b13c7f..3130d98ed1 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index 7e54d33376..d2dd5731ee 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 06ea43d654..c3a1308286 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { UserListService } from '@/core/UserListService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index d2c9616f68..aab32cc58c 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -5,8 +5,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; +import { GetterService } from '@/server/api/GetterService.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; export const meta = { tags: ['users', 'notes'], diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index bb37dd2715..13badab727 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -8,7 +8,7 @@ import { MetaService } from '@/core/MetaService.js'; import { EmailService } from '@/core/EmailService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; -import { GetterService } from '../../common/GetterService.js'; +import { GetterService } from '@/server/api/GetterService.js'; export const meta = { tags: ['users'], -- cgit v1.2.3-freya From f4b981cefed8ffbd18cc10efe79ebec51a9fd053 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 10:25:32 +0900 Subject: fix type --- .../server/api/endpoints/admin/drive/show-file.ts | 61 ++++++++++++---------- 1 file changed, 33 insertions(+), 28 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index 25d13d13a3..6180eeae2b 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -76,23 +76,6 @@ export const meta = { properties: { type: 'object', optional: false, nullable: false, - properties: { - width: { - type: 'number', - optional: false, nullable: false, - example: 1280, - }, - height: { - type: 'number', - optional: false, nullable: false, - example: 720, - }, - avgColor: { - type: 'string', - optional: true, nullable: false, - example: 'rgb(40,65,87)', - }, - }, }, storedInternal: { type: 'boolean', @@ -116,15 +99,15 @@ export const meta = { }, accessKey: { type: 'string', - optional: false, nullable: false, + optional: false, nullable: true, }, thumbnailAccessKey: { type: 'string', - optional: false, nullable: false, + optional: false, nullable: true, }, webpublicAccessKey: { type: 'string', - optional: false, nullable: false, + optional: false, nullable: true, }, uri: { type: 'string', @@ -192,14 +175,36 @@ export default class extends Endpoint { throw new ApiError(meta.errors.noSuchFile); } - const res = file as Partial; - - if (!me.isAdmin) { - delete res.requestIp; - delete res.requestHeaders; - } - - return res; + return { + id: file.id, + userId: file.userId, + userHost: file.userHost, + isLink: file.isLink, + maybePorn: file.maybePorn, + maybeSensitive: file.maybeSensitive, + isSensitive: file.isSensitive, + folderId: file.folderId, + src: file.src, + uri: file.uri, + webpublicAccessKey: file.webpublicAccessKey, + thumbnailAccessKey: file.thumbnailAccessKey, + accessKey: file.accessKey, + webpublicType: file.webpublicType, + webpublicUrl: file.webpublicUrl, + thumbnailUrl: file.thumbnailUrl, + url: file.url, + storedInternal: file.storedInternal, + properties: file.properties, + blurhash: file.blurhash, + comment: file.comment, + size: file.size, + type: file.type, + name: file.name, + md5: file.md5, + createdAt: file.createdAt.toISOString(), + requestIp: me.isAdmin ? file.requestIp : null, + requestHeaders: me.isAdmin ? file.requestHeaders : null, + }; }); } } -- cgit v1.2.3-freya From 0f7cbb59228d3dfc86c153d25d8fcbf8e2a244e3 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 10:39:17 +0900 Subject: fix type --- .../backend/src/server/api/endpoints/notes/state.ts | 4 ---- packages/client/src/scripts/get-note-menu.ts | 19 ++----------------- 2 files changed, 2 insertions(+), 21 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index a02b8d2559..d0036f0fb7 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -16,10 +16,6 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, - isWatching: { - type: 'boolean', - optional: false, nullable: false, - }, isMutedThread: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts index a5cf982066..692986cd55 100644 --- a/packages/client/src/scripts/get-note-menu.ts +++ b/packages/client/src/scripts/get-note-menu.ts @@ -60,12 +60,6 @@ export function getNoteMenu(props: { }); } - function toggleWatch(watch: boolean): void { - os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { - noteId: appearNote.id, - }); - } - function toggleThreadMute(mute: boolean): void { os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { noteId: appearNote.id, @@ -245,15 +239,6 @@ export function getNoteMenu(props: { text: i18n.ts.clip, action: () => clip(), }, - (appearNote.userId !== $i.id) ? statePromise.then(state => state.isWatching ? { - icon: 'fas fa-eye-slash', - text: i18n.ts.unwatch, - action: () => toggleWatch(false), - } : { - icon: 'fas fa-eye', - text: i18n.ts.watch, - action: () => toggleWatch(true), - }) : undefined, statePromise.then(state => state.isMutedThread ? { icon: 'fas fa-comment-slash', text: i18n.ts.unmuteThread, @@ -312,7 +297,7 @@ export function getNoteMenu(props: { }] : [] )] - .filter(x => x !== undefined); + .filter(x => x !== undefined); } else { menu = [{ icon: 'fas fa-copy', @@ -329,7 +314,7 @@ export function getNoteMenu(props: { window.open(appearNote.url || appearNote.uri, '_blank'); }, } : undefined] - .filter(x => x !== undefined); + .filter(x => x !== undefined); } if (noteActions.length > 0) { -- cgit v1.2.3-freya From 349f37bf57a7b0e332f762eb9c4f0e9de7e94372 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 10:43:23 +0900 Subject: fix --- packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts index ac0a84128c..24335a21cc 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts @@ -3,6 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { ModerationLogsRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; +import { ModerationLogEntityService } from '@/core/entities/ModerationLogEntityService.js'; export const meta = { tags: ['admin'], @@ -67,6 +68,7 @@ export default class extends Endpoint { @Inject(DI.moderationLogsRepository) private moderationLogsRepository: ModerationLogsRepository, + private moderationLogEntityService: ModerationLogEntityService, private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { -- cgit v1.2.3-freya From 89ef21e3b0ea4c27d5cb5ef5b28fb6d376878925 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 16:53:53 +0900 Subject: fix type --- packages/backend/package.json | 1 + packages/backend/src/core/EmailService.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/list.ts | 4 ++-- packages/backend/yarn.lock | 7 +++++++ 4 files changed, 11 insertions(+), 3 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/package.json b/packages/backend/package.json index 79a57fa353..eccba10ed1 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -32,6 +32,7 @@ "@sinonjs/fake-timers": "9.1.2", "@syuilo/aiscript": "0.11.1", "@types/pg": "8.6.5", + "@types/unzipper": "0.10.5", "ajv": "8.11.0", "archiver": "5.3.1", "autobind-decorator": "2.4.0", diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 4c5cf7dfc4..019b9087cd 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -159,7 +159,7 @@ export class EmailService { validateTypo: false, // TLDを見ているみたいだけどclubとか弾かれるので validateDisposable: true, // 捨てアドかどうかチェック validateSMTP: false, // 日本だと25ポートが殆どのプロバイダーで塞がれていてタイムアウトになるので - }) : { valid: true }; + }) : { valid: true, reason: null }; const available = exist === 0 && validated.valid; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index 8624229db8..271b142126 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -41,8 +41,8 @@ export const meta = { optional: false, nullable: true, }, host: { - type: 'null', - optional: false, + type: 'string', + optional: false, nullable: true, description: 'The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files.', }, url: { diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock index 34127e1ad8..f2ea0b0a58 100644 --- a/packages/backend/yarn.lock +++ b/packages/backend/yarn.lock @@ -1762,6 +1762,13 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A== +"@types/unzipper@0.10.5": + version "0.10.5" + resolved "https://registry.yarnpkg.com/@types/unzipper/-/unzipper-0.10.5.tgz#36a963cf025162b4ac31642590cb4192971d633b" + integrity sha512-NrLJb29AdnBARpg9S/4ktfPEisbJ0AvaaAr3j7Q1tg8AgcEUsq2HqbNzvgLRoWyRtjzeLEv7vuL39u1mrNIyNA== + dependencies: + "@types/node" "*" + "@types/uuid@8.3.4": version "8.3.4" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" -- cgit v1.2.3-freya From 3c4b7d3bd044ba270e5af5e272bbde76dfad9f71 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Sep 2022 17:02:19 +0900 Subject: fix type --- packages/backend/src/server/api/endpoints/i/update.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 9bf0616e3a..c2231f5f75 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -78,7 +78,7 @@ export const paramDef = { description: { ...descriptionSchema, nullable: true }, location: { ...locationSchema, nullable: true }, birthday: { ...birthdaySchema, nullable: true }, - lang: { type: 'string', enum: [null, ...Object.keys(langmap)], nullable: true }, + lang: { type: 'string', enum: [null, ...Object.keys(langmap)] as string[], nullable: true }, avatarId: { type: 'string', format: 'misskey:id', nullable: true }, bannerId: { type: 'string', format: 'misskey:id', nullable: true }, fields: { @@ -109,9 +109,7 @@ export const paramDef = { alwaysMarkNsfw: { type: 'boolean' }, autoSensitive: { type: 'boolean' }, ffVisibility: { type: 'string', enum: ['public', 'followers', 'private'] }, - pinnedPageId: { type: 'array', items: { - type: 'string', format: 'misskey:id', - } }, + pinnedPageId: { type: 'string', format: 'misskey:id' }, mutedWords: { type: 'array' }, mutedInstances: { type: 'array', items: { type: 'string', @@ -266,7 +264,7 @@ export default class extends Endpoint { // Publish meUpdated event this.globalEventService.publishMainStream(user.id, 'meUpdated', iObj); - this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', await this.userProfilesRepository.findOneBy({ userId: user.id })); + this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', await this.userProfilesRepository.findOneByOrFail({ userId: user.id })); // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 if (user.isLocked && ps.isLocked === false) { -- cgit v1.2.3-freya From 1309367884197f4f4d94686fddfbd99fa20262bc Mon Sep 17 00:00:00 2001 From: CyberRex Date: Thu, 13 Oct 2022 09:19:57 +0900 Subject: Add Cloudflare Turnstile CAPTCHA support (#9111) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Cloudflare Turnstile CAPTCHA support * Update packages/client/src/components/MkCaptcha.vue Co-authored-by: Acid Chicken (硫酸鶏) Co-authored-by: Acid Chicken (硫酸鶏) --- locales/ja-JP.yml | 4 ++++ .../backend/migration/1664694635394-turnstile.js | 15 ++++++++++++++ packages/backend/src/core/CaptchaService.ts | 11 ++++++++++ packages/backend/src/models/entities/Meta.ts | 17 +++++++++++++++ .../backend/src/server/api/SignupApiService.ts | 6 ++++++ .../backend/src/server/api/endpoints/admin/meta.ts | 15 ++++++++++++++ .../src/server/api/endpoints/admin/update-meta.ts | 15 ++++++++++++++ packages/backend/src/server/api/endpoints/meta.ts | 11 ++++++++++ packages/client/src/components/MkCaptcha.vue | 4 +++- packages/client/src/components/MkSignup.vue | 6 ++++++ packages/client/src/pages/admin/bot-protection.vue | 24 +++++++++++++++++++++- packages/client/src/pages/admin/index.vue | 2 +- packages/client/src/pages/admin/security.vue | 3 +++ 13 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 packages/backend/migration/1664694635394-turnstile.js (limited to 'packages/backend/src/server/api/endpoints') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index cedfe7b1b2..2fd44588c0 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -349,6 +349,10 @@ recaptcha: "reCAPTCHA" enableRecaptcha: "reCAPTCHAを有効にする" recaptchaSiteKey: "サイトキー" recaptchaSecretKey: "シークレットキー" +turnstile: "Turnstile" +enableTurnstile: "Turnstileを有効にする" +turnstileSiteKey: "サイトキー" +turnstileSecretKey: "シークレットキー" avoidMultiCaptchaConfirm: "複数のCaptchaを使用すると干渉を起こす可能性があります。他のCaptchaを無効にしますか?キャンセルして複数のCaptchaを有効化したままにすることも可能です。" antennas: "アンテナ" manageAntennas: "アンテナの管理" diff --git a/packages/backend/migration/1664694635394-turnstile.js b/packages/backend/migration/1664694635394-turnstile.js new file mode 100644 index 0000000000..4a33443950 --- /dev/null +++ b/packages/backend/migration/1664694635394-turnstile.js @@ -0,0 +1,15 @@ +export class turnstile1664694635394 { + name = 'turnstile1664694635394' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "enableTurnstile" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "meta" ADD "turnstileSiteKey" character varying(64)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "turnstileSecretKey" character varying(64)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "turnstileSecretKey"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "turnstileSiteKey"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableTurnstile"`); + } +} diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index 67b4b90061..acfa7d5910 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -66,5 +66,16 @@ export class CaptchaService { throw `hcaptcha-failed: ${errorCodes}`; } } + + public async verifyTurnstile(secret: string, response: string): Promise { + const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(e => { + throw `turnstile-request-failed: ${e}`; + }); + + if (result.success !== true) { + const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : ''; + throw `turnstile-failed: ${errorCodes}`; + } + } } diff --git a/packages/backend/src/models/entities/Meta.ts b/packages/backend/src/models/entities/Meta.ts index f528b7ac08..fb25e370d2 100644 --- a/packages/backend/src/models/entities/Meta.ts +++ b/packages/backend/src/models/entities/Meta.ts @@ -188,6 +188,23 @@ export class Meta { }) public recaptchaSecretKey: string | null; + @Column('boolean', { + default: false, + }) + public enableTurnstile: boolean; + + @Column('varchar', { + length: 64, + nullable: true, + }) + public turnstileSiteKey: string | null; + + @Column('varchar', { + length: 64, + nullable: true, + }) + public turnstileSecretKey: string | null; + @Column('enum', { enum: ['none', 'all', 'local', 'remote'], default: 'none', diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 6552dac4bf..edb8e4e8e6 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -61,6 +61,12 @@ export class SignupApiService { ctx.throw(400, e); }); } + + if (instance.enableTurnstile && instance.turnstileSecretKey) { + await this.captchaService.verifyTurnstile(instance.turnstileSecretKey, body['turnstile-response']).catch(e => { + ctx.throw(400, e); + }); + } } const username = body['username']; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 5b43c180d8..e5b8b6f8fe 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -47,6 +47,14 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + enableTurnstile: { + type: 'boolean', + optional: false, nullable: false, + }, + turnstileSiteKey: { + type: 'string', + optional: false, nullable: true, + }, swPublickey: { type: 'string', optional: false, nullable: true, @@ -197,6 +205,10 @@ export const meta = { type: 'string', optional: true, nullable: true, }, + turnstileSecretKey: { + type: 'string', + optional: true, nullable: true, + } sensitiveMediaDetection: { type: 'string', optional: true, nullable: false, @@ -374,6 +386,8 @@ export default class extends Endpoint { hcaptchaSiteKey: instance.hcaptchaSiteKey, enableRecaptcha: instance.enableRecaptcha, recaptchaSiteKey: instance.recaptchaSiteKey, + enableTurnstile: instance.enableTurnstile, + turnstileSiteKey: instance.turnstileSiteKey, swPublickey: instance.swPublicKey, themeColor: instance.themeColor, mascotImageUrl: instance.mascotImageUrl, @@ -400,6 +414,7 @@ export default class extends Endpoint { blockedHosts: instance.blockedHosts, hcaptchaSecretKey: instance.hcaptchaSecretKey, recaptchaSecretKey: instance.recaptchaSecretKey, + turnstileSecretKey: instance.turnstileSecretKey, sensitiveMediaDetection: instance.sensitiveMediaDetection, sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity, setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically, diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 48fae9b947..2a19b1df5f 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -52,6 +52,9 @@ export const paramDef = { enableRecaptcha: { type: 'boolean' }, recaptchaSiteKey: { type: 'string', nullable: true }, recaptchaSecretKey: { type: 'string', nullable: true }, + enableTurnstile: { type: 'boolean' }, + turnstileSiteKey: { type: 'string', nullable: true }, + turnstileSecretKey: { type: 'string', nullable: true }, sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] }, sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] }, setSensitiveFlagAutomatically: { type: 'boolean' }, @@ -231,6 +234,18 @@ export default class extends Endpoint { set.recaptchaSecretKey = ps.recaptchaSecretKey; } + if (ps.enableTurnstile !== undefined) { + set.enableTurnstile = ps.enableTurnstile; + } + + if (ps.turnstileSiteKey !== undefined) { + set.turnstileSiteKey = ps.turnstileSiteKey; + } + + if (ps.turnstileSecretKey !== undefined) { + set.turnstileSecretKey = ps.turnstileSecretKey; + } + if (ps.sensitiveMediaDetection !== undefined) { set.sensitiveMediaDetection = ps.sensitiveMediaDetection; } diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 5c09c33941..f2e6e6aea8 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -119,6 +119,14 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + enableTurnstile: { + type: 'boolean', + optional: false, nullable: false, + }, + turnstileSiteKey: { + type: 'string', + optional: false, nullable: true, + }, swPublickey: { type: 'string', optional: false, nullable: true, @@ -372,6 +380,8 @@ export default class extends Endpoint { hcaptchaSiteKey: instance.hcaptchaSiteKey, enableRecaptcha: instance.enableRecaptcha, recaptchaSiteKey: instance.recaptchaSiteKey, + enableTurnstile: instance.enableTurnstile, + turnstileSiteKey: instance.turnstileSiteKey, swPublickey: instance.swPublicKey, themeColor: instance.themeColor, mascotImageUrl: instance.mascotImageUrl, @@ -423,6 +433,7 @@ export default class extends Endpoint { elasticsearch: this.config.elasticsearch ? true : false, hcaptcha: instance.enableHcaptcha, recaptcha: instance.enableRecaptcha, + turnstile: instance.enableTurnstile, objectStorage: instance.useObjectStorage, twitter: instance.enableTwitterIntegration, github: instance.enableGithubIntegration, diff --git a/packages/client/src/components/MkCaptcha.vue b/packages/client/src/components/MkCaptcha.vue index 7360734914..b399bb8921 100644 --- a/packages/client/src/components/MkCaptcha.vue +++ b/packages/client/src/components/MkCaptcha.vue @@ -20,7 +20,7 @@ type Captcha = { getResponse(id: string): string; }; -type CaptchaProvider = 'hcaptcha' | 'recaptcha'; +type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile'; type CaptchaContainer = { readonly [_ in CaptchaProvider]?: Captcha; @@ -48,6 +48,7 @@ const variable = computed(() => { switch (props.provider) { case 'hcaptcha': return 'hcaptcha'; case 'recaptcha': return 'grecaptcha'; + case 'turnstile': return 'turnstile'; } }); @@ -57,6 +58,7 @@ const src = computed(() => { switch (props.provider) { case 'hcaptcha': return 'https://js.hcaptcha.com/1/api.js?render=explicit&recaptchacompat=off'; case 'recaptcha': return 'https://www.recaptcha.net/recaptcha/api.js?render=explicit'; + case 'turnstile': return 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'; } }); diff --git a/packages/client/src/components/MkSignup.vue b/packages/client/src/components/MkSignup.vue index a324bb6f5a..c1f91b18c2 100644 --- a/packages/client/src/components/MkSignup.vue +++ b/packages/client/src/components/MkSignup.vue @@ -59,6 +59,7 @@ + {{ i18n.ts.start }} @@ -92,6 +93,7 @@ const host = toUnicode(config.host); let hcaptcha = $ref(); let recaptcha = $ref(); +let turnstile = $ref(); let username: string = $ref(''); let password: string = $ref(''); @@ -106,12 +108,14 @@ let submitting: boolean = $ref(false); let ToSAgreement: boolean = $ref(false); let hCaptchaResponse = $ref(null); let reCaptchaResponse = $ref(null); +let turnstileResponse = $ref(null); const shouldDisableSubmitting = $computed((): boolean => { return submitting || instance.tosUrl && !ToSAgreement || instance.enableHcaptcha && !hCaptchaResponse || instance.enableRecaptcha && !reCaptchaResponse || + instance.enableTurnstile && !turnstileResponse || passwordRetypeState === 'not-match'; }); @@ -198,6 +202,7 @@ function onSubmit(): void { invitationCode, 'hcaptcha-response': hCaptchaResponse, 'g-recaptcha-response': reCaptchaResponse, + 'turnstile-response': turnstileResponse, }).then(() => { if (instance.emailRequiredForSignup) { os.alert({ @@ -222,6 +227,7 @@ function onSubmit(): void { submitting = false; hcaptcha.reset?.(); recaptcha.reset?.(); + turnstile.reset?.(); os.alert({ type: 'error', diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue index 72d5e379de..484a9d1a1a 100644 --- a/packages/client/src/pages/admin/bot-protection.vue +++ b/packages/client/src/pages/admin/bot-protection.vue @@ -6,6 +6,7 @@ + + {{ i18n.ts.save }} @@ -61,6 +76,8 @@ let hcaptchaSiteKey: string | null = $ref(null); let hcaptchaSecretKey: string | null = $ref(null); let recaptchaSiteKey: string | null = $ref(null); let recaptchaSecretKey: string | null = $ref(null); +let turnstileSiteKey: string | null = $ref(null); +let turnstileSecretKey: string | null = $ref(null); async function init() { const meta = await os.api('admin/meta'); @@ -68,8 +85,10 @@ async function init() { hcaptchaSecretKey = meta.hcaptchaSecretKey; recaptchaSiteKey = meta.recaptchaSiteKey; recaptchaSecretKey = meta.recaptchaSecretKey; + turnstileSiteKey = meta.turnstileSiteKey; + turnstileSecretKey = meta.turnstileSecretKey; - provider = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : null; + provider = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : meta.enableTurnstile ? 'turnstile' : null; } function save() { @@ -80,6 +99,9 @@ function save() { enableRecaptcha: provider === 'recaptcha', recaptchaSiteKey, recaptchaSecretKey, + enableTurnstile: provider === 'turnstile', + turnstileSiteKey, + turnstileSecretKey, }).then(() => { fetchInstance(); }); diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue index 9200b5d547..20f82bba28 100644 --- a/packages/client/src/pages/admin/index.vue +++ b/packages/client/src/pages/admin/index.vue @@ -53,7 +53,7 @@ let view = $ref(null); let el = $ref(null); let pageProps = $ref({}); let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail); -let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha; +let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableTurnstile; let noEmailServer = !instance.enableEmail; let thereIsUnresolvedAbuseReport = $ref(false); let currentPage = $computed(() => router.currentRef.value.child); diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue index c36cedb312..65d079c2cf 100644 --- a/packages/client/src/pages/admin/security.vue +++ b/packages/client/src/pages/admin/security.vue @@ -9,6 +9,7 @@ + @@ -120,6 +121,7 @@ import { definePageMetadata } from '@/scripts/page-metadata'; let summalyProxy: string = $ref(''); let enableHcaptcha: boolean = $ref(false); let enableRecaptcha: boolean = $ref(false); +let enableTurnstile: boolean = $ref(false); let sensitiveMediaDetection: string = $ref('none'); let sensitiveMediaDetectionSensitivity: number = $ref(0); let setSensitiveFlagAutomatically: boolean = $ref(false); @@ -132,6 +134,7 @@ async function init() { summalyProxy = meta.summalyProxy; enableHcaptcha = meta.enableHcaptcha; enableRecaptcha = meta.enableRecaptcha; + enableTurnstile = meta.enableTurnstile; sensitiveMediaDetection = meta.sensitiveMediaDetection; sensitiveMediaDetectionSensitivity = meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0 : -- cgit v1.2.3-freya From 2492f4e81e9403d6c6efca28f0f65f4eed57ea75 Mon Sep 17 00:00:00 2001 From: squidicuz Date: Thu, 10 Nov 2022 21:22:31 -0500 Subject: fix for pinned users. update changelog (#9159) --- CHANGELOG.md | 1 + packages/backend/src/server/api/endpoints/pinned-users.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/CHANGELOG.md b/CHANGELOG.md index 66959cd716..d7513acdb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ You should also include the user name that made the change. ### Improvements ### Bugfixes +- Server: Bug fix for Pinned Users lookup on instance @squidicuzz ## 12.119.0 (2022/09/10) diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 6c941314e2..f2c6e798ef 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -43,12 +43,12 @@ export default class extends Endpoint { super(meta, paramDef, async (ps, me) => { const meta = await this.metaService.fetch(); - const users = await Promise.all(meta.pinnedthis.usersRepository.map(acct => Acct.parse(acct)).map(acct => this.usersRepository.findOneBy({ + const users = await Promise.all(meta.pinnedUsers.map(acct => Acct.parse(acct)).map(acct => this.usersRepository.findOneBy({ usernameLower: acct.username.toLowerCase(), host: acct.host ?? IsNull(), }))); - return await this.userEntityService.packMany(users.filter(x => x !== undefined) as User[], me, { detail: true }); + return await this.userEntityService.packMany(users.filter(x => x !== null) as User[], me, { detail: true }); }); } } -- cgit v1.2.3-freya From 5decad9cf15366b7fba6c865bb15a15272ee9d41 Mon Sep 17 00:00:00 2001 From: tamaina Date: Fri, 2 Dec 2022 14:12:09 +0000 Subject: fix: add missing , --- packages/backend/src/server/api/endpoints/admin/meta.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index e5b8b6f8fe..794ea3d5c9 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -208,7 +208,7 @@ export const meta = { turnstileSecretKey: { type: 'string', optional: true, nullable: true, - } + }, sensitiveMediaDetection: { type: 'string', optional: true, nullable: false, -- cgit v1.2.3-freya From 3a7182bfb5734599321fc03ea77c48b4dbc326d5 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 3 Dec 2022 19:42:05 +0900 Subject: Fastify (#9106) * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * fix * Update SignupApiService.ts * wip * wip * Update ClientServerService.ts * wip * wip * wip * Update WellKnownServerService.ts * wip * wip * update des * wip * Update ApiServerService.ts * wip * update deps * Update WellKnownServerService.ts * wip * update deps * Update ApiCallService.ts * Update ApiCallService.ts * Update ApiServerService.ts --- packages/backend/package.json | 38 +- packages/backend/src/@types/koa-json-body.d.ts | 15 - packages/backend/src/@types/koa-slow.d.ts | 14 - packages/backend/src/boot/index.ts | 1 + packages/backend/src/core/CaptchaService.ts | 30 +- .../core/remote/activitypub/ApRendererService.ts | 2 +- packages/backend/src/env.ts | 1 - packages/backend/src/misc/create-temp.ts | 2 +- packages/backend/src/misc/fastify-reply-error.ts | 11 + .../backend/src/server/ActivityPubServerService.ts | 300 ++-- packages/backend/src/server/FileServerService.ts | 99 +- .../backend/src/server/MediaProxyServerService.ts | 51 +- .../backend/src/server/NodeinfoServerService.ts | 21 +- packages/backend/src/server/ServerService.ts | 95 +- .../backend/src/server/WellKnownServerService.ts | 81 +- packages/backend/src/server/api/ApiCallService.ts | 229 +-- .../backend/src/server/api/ApiServerService.ts | 161 ++- .../backend/src/server/api/AuthenticateService.ts | 2 +- .../backend/src/server/api/SigninApiService.ts | 99 +- packages/backend/src/server/api/SigninService.ts | 45 +- .../backend/src/server/api/SignupApiService.ts | 72 +- packages/backend/src/server/api/endpoint-base.ts | 13 +- .../src/server/api/endpoints/drive/files/create.ts | 8 +- .../server/api/integration/DiscordServerService.ts | 99 +- .../server/api/integration/GithubServerService.ts | 99 +- .../server/api/integration/TwitterServerService.ts | 83 +- .../backend/src/server/web/ClientServerService.ts | 345 ++--- .../backend/src/server/web/UrlPreviewService.ts | 25 +- packages/backend/src/server/web/bios.js | 2 +- packages/backend/src/server/web/boot.js | 10 +- packages/client/src/account.ts | 46 +- packages/client/src/components/MkCropperDialog.vue | 2 +- packages/client/src/components/MkUrlPreview.vue | 4 +- packages/client/src/components/MkYoutubePlayer.vue | 2 +- packages/client/src/components/page/page.post.vue | 24 +- packages/client/src/os.ts | 7 +- packages/client/src/ui/_common_/statusbar-rss.vue | 2 +- packages/client/src/widgets/rss-ticker.vue | 2 +- packages/client/src/widgets/rss.vue | 2 +- yarn.lock | 1520 ++++++++------------ 40 files changed, 1669 insertions(+), 1995 deletions(-) delete mode 100644 packages/backend/src/@types/koa-json-body.d.ts delete mode 100644 packages/backend/src/@types/koa-slow.d.ts create mode 100644 packages/backend/src/misc/fastify-reply-error.ts (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/package.json b/packages/backend/package.json index 9e50d4d9fa..81e7888b84 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -21,20 +21,19 @@ "@tensorflow/tfjs-node": "4.1.0" }, "dependencies": { - "@bull-board/api": "4.3.1", - "@bull-board/koa": "4.3.1", - "@bull-board/ui": "4.3.1", "@discordapp/twemoji": "14.0.2", - "@elastic/elasticsearch": "7.17.0", - "@koa/cors": "3.3.0", - "@koa/multer": "3.0.0", - "@koa/router": "9.0.1", + "@fastify/accepts": "4.0.1", + "@fastify/cors": "8.2.0", + "@fastify/multipart": "7.3.0", + "@fastify/static": "6.5.0", + "@fastify/view": "7.1.2", "@nestjs/common": "9.2.0", "@nestjs/core": "9.2.0", "@nestjs/testing": "9.2.0", "@peertube/http-signature": "1.7.0", "@sinonjs/fake-timers": "10.0.0", "@syuilo/aiscript": "0.11.1", + "accepts": "^1.3.8", "ajv": "8.11.2", "archiver": "5.3.1", "autobind-decorator": "2.4.0", @@ -54,6 +53,7 @@ "date-fns": "2.29.3", "deep-email-validator": "0.1.21", "escape-regexp": "0.0.1", + "fastify": "4.10.0", "feed": "4.2.2", "file-type": "18.0.0", "fluent-ffmpeg": "2.1.2", @@ -69,20 +69,10 @@ "json5-loader": "4.0.1", "jsonld": "8.1.0", "jsrsasign": "10.6.1", - "koa": "2.13.4", - "koa-bodyparser": "4.3.0", - "koa-favicon": "2.1.0", - "koa-json-body": "5.3.0", - "koa-logger": "3.2.1", - "koa-mount": "4.0.0", - "koa-send": "5.0.1", - "koa-slow": "2.1.0", - "koa-views": "7.0.2", "mfm-js": "0.23.0", "mime-types": "2.1.35", "misskey-js": "0.0.14", "ms": "3.0.0-canary.1", - "multer": "1.4.4", "nested-property": "4.0.0", "node-fetch": "3.3.0", "nodemailer": "6.8.0", @@ -129,6 +119,7 @@ "ulid": "2.3.0", "unzipper": "0.10.11", "uuid": "9.0.0", + "vary": "1.1.2", "web-push": "3.5.0", "websocket": "1.0.34", "ws": "8.11.0", @@ -138,6 +129,7 @@ "@redocly/openapi-core": "1.0.0-beta.114", "@swc/core": "1.3.20", "@swc/jest": "0.2.23", + "@types/accepts": "1.3.5", "@types/archiver": "5.3.1", "@types/bcryptjs": "2.4.2", "@types/bull": "4.10.0", @@ -149,17 +141,6 @@ "@types/jsdom": "20.0.1", "@types/jsonld": "1.5.8", "@types/jsrsasign": "10.5.4", - "@types/koa": "2.13.5", - "@types/koa-bodyparser": "4.3.8", - "@types/koa-cors": "0.0.2", - "@types/koa-favicon": "2.0.21", - "@types/koa-logger": "3.1.2", - "@types/koa-mount": "4.0.1", - "@types/koa-send": "4.1.3", - "@types/koa-views": "7.0.0", - "@types/koa__cors": "3.3.0", - "@types/koa__multer": "2.0.4", - "@types/koa__router": "8.0.11", "@types/mime-types": "2.1.1", "@types/node": "18.11.9", "@types/node-fetch": "3.0.3", @@ -182,6 +163,7 @@ "@types/tmp": "0.2.3", "@types/unzipper": "0.10.5", "@types/uuid": "8.3.4", + "@types/vary": "1.1.0", "@types/web-push": "3.3.2", "@types/websocket": "1.0.5", "@types/ws": "8.5.3", diff --git a/packages/backend/src/@types/koa-json-body.d.ts b/packages/backend/src/@types/koa-json-body.d.ts deleted file mode 100644 index 2971807d15..0000000000 --- a/packages/backend/src/@types/koa-json-body.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -declare module 'koa-json-body' { - import type { Middleware } from 'koa'; - - interface IKoaJsonBodyOptions { - strict: boolean; - limit: string; - fallback: boolean; - } - - function koaJsonBody(opt?: IKoaJsonBodyOptions): Middleware; - - namespace koaJsonBody {} // Hack - - export = koaJsonBody; -} diff --git a/packages/backend/src/@types/koa-slow.d.ts b/packages/backend/src/@types/koa-slow.d.ts deleted file mode 100644 index d048822efe..0000000000 --- a/packages/backend/src/@types/koa-slow.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -declare module 'koa-slow' { - import type { Middleware } from 'koa'; - - interface ISlowOptions { - url?: RegExp; - delay?: number; - } - - function slow(options?: ISlowOptions): Middleware; - - namespace slow {} // Hack - - export = slow; -} diff --git a/packages/backend/src/boot/index.ts b/packages/backend/src/boot/index.ts index fbf9e73e09..f4daf30690 100644 --- a/packages/backend/src/boot/index.ts +++ b/packages/backend/src/boot/index.ts @@ -52,6 +52,7 @@ if (!envOption.quiet) { process.on('uncaughtException', err => { try { logger.error(err); + console.trace(err); } catch { } }); diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index acfa7d5910..b2bc24ac2c 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -45,9 +45,13 @@ export class CaptchaService { return await res.json() as CaptchaResponse; } - public async verifyRecaptcha(secret: string, response: string): Promise { - const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(e => { - throw `recaptcha-request-failed: ${e}`; + public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise { + if (response == null) { + throw 'recaptcha-failed: no response provided'; + } + + const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(err => { + throw `recaptcha-request-failed: ${err}`; }); if (result.success !== true) { @@ -56,9 +60,13 @@ export class CaptchaService { } } - public async verifyHcaptcha(secret: string, response: string): Promise { - const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(e => { - throw `hcaptcha-request-failed: ${e}`; + public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise { + if (response == null) { + throw 'hcaptcha-failed: no response provided'; + } + + const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(err => { + throw `hcaptcha-request-failed: ${err}`; }); if (result.success !== true) { @@ -67,9 +75,13 @@ export class CaptchaService { } } - public async verifyTurnstile(secret: string, response: string): Promise { - const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(e => { - throw `turnstile-request-failed: ${e}`; + public async verifyTurnstile(secret: string, response: string | null | undefined): Promise { + if (response == null) { + throw 'turnstile-failed: no response provided'; + } + + const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(err => { + throw `turnstile-request-failed: ${err}`; }); if (result.success !== true) { diff --git a/packages/backend/src/core/remote/activitypub/ApRendererService.ts b/packages/backend/src/core/remote/activitypub/ApRendererService.ts index 38850fd127..38a92567c3 100644 --- a/packages/backend/src/core/remote/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/remote/activitypub/ApRendererService.ts @@ -674,7 +674,7 @@ export class ApRendererService { * @param last URL of last page (optional) * @param orderedItems attached objects (optional) */ - public renderOrderedCollection(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: Record[]) { + public renderOrderedCollection(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: IObject[]) { const page: any = { id, type: 'OrderedCollection', diff --git a/packages/backend/src/env.ts b/packages/backend/src/env.ts index 1b678edc44..d7c8304b47 100644 --- a/packages/backend/src/env.ts +++ b/packages/backend/src/env.ts @@ -6,7 +6,6 @@ const envOption = { verbose: false, withLogTime: false, quiet: false, - slow: false, }; for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) { diff --git a/packages/backend/src/misc/create-temp.ts b/packages/backend/src/misc/create-temp.ts index fa88769de0..429977669e 100644 --- a/packages/backend/src/misc/create-temp.ts +++ b/packages/backend/src/misc/create-temp.ts @@ -18,7 +18,7 @@ export function createTempDir(): Promise<[string, () => void]> { (e, path, cleanup) => { if (e) return rej(e); res([path, cleanup]); - } + }, ); }); } diff --git a/packages/backend/src/misc/fastify-reply-error.ts b/packages/backend/src/misc/fastify-reply-error.ts new file mode 100644 index 0000000000..4e987175e2 --- /dev/null +++ b/packages/backend/src/misc/fastify-reply-error.ts @@ -0,0 +1,11 @@ +// https://www.fastify.io/docs/latest/Reference/Reply/#async-await-and-promises +export class FastifyReplyError extends Error { + public message: string; + public statusCode: number; + + constructor(statusCode: number, message: string) { + super(message); + this.message = message; + this.statusCode = statusCode; + } +} diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 2e7bd4dcb2..9689a623fd 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -1,8 +1,9 @@ import { Inject, Injectable } from '@nestjs/common'; -import Router from '@koa/router'; -import json from 'koa-json-body'; +import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; +import fastifyAccepts from '@fastify/accepts'; import httpSignature from '@peertube/http-signature'; import { Brackets, In, IsNull, LessThan, Not } from 'typeorm'; +import accepts from 'accepts'; import { DI } from '@/di-symbols.js'; import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; import * as url from '@/misc/prelude/url.js'; @@ -56,14 +57,15 @@ export class ActivityPubServerService { private userKeypairStoreService: UserKeypairStoreService, private queryService: QueryService, ) { + this.createServer = this.createServer.bind(this); } - private setResponseType(ctx: Router.RouterContext) { - const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON); + private setResponseType(request: FastifyRequest, reply: FastifyReply): void { + const accept = request.accepts().type([ACTIVITY_JSON, LD_JSON]); if (accept === LD_JSON) { - ctx.response.type = LD_JSON; + reply.type(LD_JSON); } else { - ctx.response.type = ACTIVITY_JSON; + reply.type(ACTIVITY_JSON); } } @@ -80,31 +82,34 @@ export class ActivityPubServerService { return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note); } - private inbox(ctx: Router.RouterContext) { + private inbox(request: FastifyRequest, reply: FastifyReply) { let signature; try { - signature = httpSignature.parseRequest(ctx.req, { 'headers': [] }); + signature = httpSignature.parseRequest(request.raw, { 'headers': [] }); } catch (e) { - ctx.status = 401; + reply.code(401); return; } - this.queueService.inbox(ctx.request.body, signature); + this.queueService.inbox(request.body, signature); - ctx.status = 202; + reply.code(202); } - private async followers(ctx: Router.RouterContext) { - const userId = ctx.params.user; + private async followers( + request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>, + reply: FastifyReply, + ) { + const userId = request.params.user; - const cursor = ctx.request.query.cursor; + const cursor = request.query.cursor; if (cursor != null && typeof cursor !== 'string') { - ctx.status = 400; + reply.code(400); return; } - const page = ctx.request.query.page === 'true'; + const page = request.query.page === 'true'; const user = await this.usersRepository.findOneBy({ id: userId, @@ -112,7 +117,7 @@ export class ActivityPubServerService { }); if (user == null) { - ctx.status = 404; + reply.code(404); return; } @@ -120,12 +125,12 @@ export class ActivityPubServerService { const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); if (profile.ffVisibility === 'private') { - ctx.status = 403; - ctx.set('Cache-Control', 'public, max-age=30'); + reply.code(403); + reply.header('Cache-Control', 'public, max-age=30'); return; } else if (profile.ffVisibility === 'followers') { - ctx.status = 403; - ctx.set('Cache-Control', 'public, max-age=30'); + reply.code(403); + reply.header('Cache-Control', 'public, max-age=30'); return; } //#endregion @@ -168,27 +173,30 @@ export class ActivityPubServerService { })}` : undefined, ); - ctx.body = this.apRendererService.renderActivity(rendered); - this.setResponseType(ctx); + this.setResponseType(request, reply); + return (this.apRendererService.renderActivity(rendered)); } else { // index page const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`); - ctx.body = this.apRendererService.renderActivity(rendered); - ctx.set('Cache-Control', 'public, max-age=180'); - this.setResponseType(ctx); + reply.header('Cache-Control', 'public, max-age=180'); + this.setResponseType(request, reply); + return (this.apRendererService.renderActivity(rendered)); } } - private async following(ctx: Router.RouterContext) { - const userId = ctx.params.user; + private async following( + request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>, + reply: FastifyReply, + ) { + const userId = request.params.user; - const cursor = ctx.request.query.cursor; + const cursor = request.query.cursor; if (cursor != null && typeof cursor !== 'string') { - ctx.status = 400; + reply.code(400); return; } - const page = ctx.request.query.page === 'true'; + const page = request.query.page === 'true'; const user = await this.usersRepository.findOneBy({ id: userId, @@ -196,7 +204,7 @@ export class ActivityPubServerService { }); if (user == null) { - ctx.status = 404; + reply.code(404); return; } @@ -204,12 +212,12 @@ export class ActivityPubServerService { const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); if (profile.ffVisibility === 'private') { - ctx.status = 403; - ctx.set('Cache-Control', 'public, max-age=30'); + reply.code(403); + reply.header('Cache-Control', 'public, max-age=30'); return; } else if (profile.ffVisibility === 'followers') { - ctx.status = 403; - ctx.set('Cache-Control', 'public, max-age=30'); + reply.code(403); + reply.header('Cache-Control', 'public, max-age=30'); return; } //#endregion @@ -252,19 +260,19 @@ export class ActivityPubServerService { })}` : undefined, ); - ctx.body = this.apRendererService.renderActivity(rendered); - this.setResponseType(ctx); + this.setResponseType(request, reply); + return (this.apRendererService.renderActivity(rendered)); } else { // index page const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`); - ctx.body = this.apRendererService.renderActivity(rendered); - ctx.set('Cache-Control', 'public, max-age=180'); - this.setResponseType(ctx); + reply.header('Cache-Control', 'public, max-age=180'); + this.setResponseType(request, reply); + return (this.apRendererService.renderActivity(rendered)); } } - private async featured(ctx: Router.RouterContext) { - const userId = ctx.params.user; + private async featured(request: FastifyRequest<{ Params: { user: string; }; }>, reply: FastifyReply) { + const userId = request.params.user; const user = await this.usersRepository.findOneBy({ id: userId, @@ -272,7 +280,7 @@ export class ActivityPubServerService { }); if (user == null) { - ctx.status = 404; + reply.code(404); return; } @@ -291,30 +299,36 @@ export class ActivityPubServerService { renderedNotes.length, undefined, undefined, renderedNotes, ); - ctx.body = this.apRendererService.renderActivity(rendered); - ctx.set('Cache-Control', 'public, max-age=180'); - this.setResponseType(ctx); + reply.header('Cache-Control', 'public, max-age=180'); + this.setResponseType(request, reply); + return (this.apRendererService.renderActivity(rendered)); } - private async outbox(ctx: Router.RouterContext) { - const userId = ctx.params.user; + private async outbox( + request: FastifyRequest<{ + Params: { user: string; }; + Querystring: { since_id?: string; until_id?: string; page?: string; }; + }>, + reply: FastifyReply, + ) { + const userId = request.params.user; - const sinceId = ctx.request.query.since_id; + const sinceId = request.query.since_id; if (sinceId != null && typeof sinceId !== 'string') { - ctx.status = 400; + reply.code(400); return; } - const untilId = ctx.request.query.until_id; + const untilId = request.query.until_id; if (untilId != null && typeof untilId !== 'string') { - ctx.status = 400; + reply.code(400); return; } - const page = ctx.request.query.page === 'true'; + const page = request.query.page === 'true'; if (countIf(x => x != null, [sinceId, untilId]) > 1) { - ctx.status = 400; + reply.code(400); return; } @@ -324,7 +338,7 @@ export class ActivityPubServerService { }); if (user == null) { - ctx.status = 404; + reply.code(404); return; } @@ -362,110 +376,130 @@ export class ActivityPubServerService { })}` : undefined, ); - ctx.body = this.apRendererService.renderActivity(rendered); - this.setResponseType(ctx); + this.setResponseType(request, reply); + return (this.apRendererService.renderActivity(rendered)); } else { // index page const rendered = this.apRendererService.renderOrderedCollection(partOf, user.notesCount, `${partOf}?page=true`, `${partOf}?page=true&since_id=000000000000000000000000`, ); - ctx.body = this.apRendererService.renderActivity(rendered); - ctx.set('Cache-Control', 'public, max-age=180'); - this.setResponseType(ctx); + reply.header('Cache-Control', 'public, max-age=180'); + this.setResponseType(request, reply); + return (this.apRendererService.renderActivity(rendered)); } } - private async userInfo(ctx: Router.RouterContext, user: User | null) { + private async userInfo(request: FastifyRequest, reply: FastifyReply, user: User | null) { if (user == null) { - ctx.status = 404; + reply.code(404); return; } - ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderPerson(user as ILocalUser)); - ctx.set('Cache-Control', 'public, max-age=180'); - this.setResponseType(ctx); + reply.header('Cache-Control', 'public, max-age=180'); + this.setResponseType(request, reply); + return (this.apRendererService.renderActivity(await this.apRendererService.renderPerson(user as ILocalUser))); } - public createRouter() { - // Init router - const router = new Router(); + public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + fastify.addConstraintStrategy({ + name: 'apOrHtml', + storage() { + const store = {}; + return { + get(key) { + return store[key] ?? null; + }, + set(key, value) { + store[key] = value; + }, + }; + }, + deriveConstraint(request, ctx) { + const accepted = accepts(request).type(['html', ACTIVITY_JSON, LD_JSON]); + const isAp = typeof accepted === 'string' && !accepted.match(/html/); + return isAp ? 'ap' : 'html'; + }, + }); - //#region Routing - function isActivityPubReq(ctx: Router.RouterContext) { - ctx.response.vary('Accept'); - const accepted = ctx.accepts('html', ACTIVITY_JSON, LD_JSON); - return typeof accepted === 'string' && !accepted.match(/html/); - } + fastify.register(fastifyAccepts); + //#region Routing // inbox - router.post('/inbox', json(), ctx => this.inbox(ctx)); - router.post('/users/:user/inbox', json(), ctx => this.inbox(ctx)); + fastify.post('/inbox', async (request, reply) => await this.inbox(request, reply)); + fastify.post('/users/:user/inbox', async (request, reply) => await this.inbox(request, reply)); // note - router.get('/notes/:note', async (ctx, next) => { - if (!isActivityPubReq(ctx)) return await next(); - + fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { const note = await this.notesRepository.findOneBy({ - id: ctx.params.note, + id: request.params.note, visibility: In(['public' as const, 'home' as const]), localOnly: false, }); if (note == null) { - ctx.status = 404; + reply.code(404); return; } // リモートだったらリダイレクト if (note.userHost != null) { if (note.uri == null || this.utilityService.isSelfHost(note.userHost)) { - ctx.status = 500; + reply.code(500); return; } - ctx.redirect(note.uri); + reply.redirect(note.uri); return; } - ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderNote(note, false)); - ctx.set('Cache-Control', 'public, max-age=180'); - this.setResponseType(ctx); + reply.header('Cache-Control', 'public, max-age=180'); + this.setResponseType(request, reply); + return (this.apRendererService.renderActivity(await this.apRendererService.renderNote(note, false))); }); // note activity - router.get('/notes/:note/activity', async ctx => { + fastify.get<{ Params: { note: string; } }>('/notes/:note/activity', async (request, reply) => { const note = await this.notesRepository.findOneBy({ - id: ctx.params.note, + id: request.params.note, userHost: IsNull(), visibility: In(['public' as const, 'home' as const]), localOnly: false, }); if (note == null) { - ctx.status = 404; + reply.code(404); return; } - ctx.body = this.apRendererService.renderActivity(await this.packActivity(note)); - ctx.set('Cache-Control', 'public, max-age=180'); - this.setResponseType(ctx); + reply.header('Cache-Control', 'public, max-age=180'); + this.setResponseType(request, reply); + return (this.apRendererService.renderActivity(await this.packActivity(note))); }); // outbox - router.get('/users/:user/outbox', (ctx) => this.outbox(ctx)); + fastify.get<{ + Params: { user: string; }; + Querystring: { since_id?: string; until_id?: string; page?: string; }; + }>('/users/:user/outbox', async (request, reply) => await this.outbox(request, reply)); // followers - router.get('/users/:user/followers', (ctx) => this.followers(ctx)); + fastify.get<{ + Params: { user: string; }; + Querystring: { cursor?: string; page?: string; }; + }>('/users/:user/followers', async (request, reply) => await this.followers(request, reply)); // following - router.get('/users/:user/following', (ctx) => this.following(ctx)); + fastify.get<{ + Params: { user: string; }; + Querystring: { cursor?: string; page?: string; }; + }>('/users/:user/following', async (request, reply) => await this.following(request, reply)); // featured - router.get('/users/:user/collections/featured', (ctx) => this.featured(ctx)); + fastify.get<{ Params: { user: string; }; }>('/users/:user/collections/featured', async (request, reply) => await this.featured(request, reply)); // publickey - router.get('/users/:user/publickey', async ctx => { - const userId = ctx.params.user; + fastify.get<{ Params: { user: string; } }>('/users/:user/publickey', async (request, reply) => { + const userId = request.params.user; const user = await this.usersRepository.findOneBy({ id: userId, @@ -473,25 +507,23 @@ export class ActivityPubServerService { }); if (user == null) { - ctx.status = 404; + reply.code(404); return; } const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); if (this.userEntityService.isLocalUser(user)) { - ctx.body = this.apRendererService.renderActivity(this.apRendererService.renderKey(user, keypair)); - ctx.set('Cache-Control', 'public, max-age=180'); - this.setResponseType(ctx); + reply.header('Cache-Control', 'public, max-age=180'); + this.setResponseType(request, reply); + return (this.apRendererService.renderActivity(this.apRendererService.renderKey(user, keypair))); } else { - ctx.status = 400; + reply.code(400); } }); - router.get('/users/:user', async (ctx, next) => { - if (!isActivityPubReq(ctx)) return await next(); - - const userId = ctx.params.user; + fastify.get<{ Params: { user: string; } }>('/users/:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { + const userId = request.params.user; const user = await this.usersRepository.findOneBy({ id: userId, @@ -499,86 +531,84 @@ export class ActivityPubServerService { isSuspended: false, }); - await this.userInfo(ctx, user); + return await this.userInfo(request, reply, user); }); - router.get('/@:user', async (ctx, next) => { - if (!isActivityPubReq(ctx)) return await next(); - + fastify.get<{ Params: { user: string; } }>('/@:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { const user = await this.usersRepository.findOneBy({ - usernameLower: ctx.params.user.toLowerCase(), + usernameLower: request.params.user.toLowerCase(), host: IsNull(), isSuspended: false, }); - await this.userInfo(ctx, user); + return await this.userInfo(request, reply, user); }); //#endregion // emoji - router.get('/emojis/:emoji', async ctx => { + fastify.get<{ Params: { emoji: string; } }>('/emojis/:emoji', async (request, reply) => { const emoji = await this.emojisRepository.findOneBy({ host: IsNull(), - name: ctx.params.emoji, + name: request.params.emoji, }); if (emoji == null) { - ctx.status = 404; + reply.code(404); return; } - ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderEmoji(emoji)); - ctx.set('Cache-Control', 'public, max-age=180'); - this.setResponseType(ctx); + reply.header('Cache-Control', 'public, max-age=180'); + this.setResponseType(request, reply); + return (this.apRendererService.renderActivity(await this.apRendererService.renderEmoji(emoji))); }); // like - router.get('/likes/:like', async ctx => { - const reaction = await this.noteReactionsRepository.findOneBy({ id: ctx.params.like }); + fastify.get<{ Params: { like: string; } }>('/likes/:like', async (request, reply) => { + const reaction = await this.noteReactionsRepository.findOneBy({ id: request.params.like }); if (reaction == null) { - ctx.status = 404; + reply.code(404); return; } const note = await this.notesRepository.findOneBy({ id: reaction.noteId }); if (note == null) { - ctx.status = 404; + reply.code(404); return; } - ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderLike(reaction, note)); - ctx.set('Cache-Control', 'public, max-age=180'); - this.setResponseType(ctx); + reply.header('Cache-Control', 'public, max-age=180'); + this.setResponseType(request, reply); + return (this.apRendererService.renderActivity(await this.apRendererService.renderLike(reaction, note))); }); // follow - router.get('/follows/:follower/:followee', async ctx => { + fastify.get<{ Params: { follower: string; followee: string; } }>('/follows/:follower/:followee', async (request, reply) => { // This may be used before the follow is completed, so we do not // check if the following exists. const [follower, followee] = await Promise.all([ this.usersRepository.findOneBy({ - id: ctx.params.follower, + id: request.params.follower, host: IsNull(), }), this.usersRepository.findOneBy({ - id: ctx.params.followee, + id: request.params.followee, host: Not(IsNull()), }), ]); if (follower == null || followee == null) { - ctx.status = 404; + reply.code(404); return; } - ctx.body = this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee)); - ctx.set('Cache-Control', 'public, max-age=180'); - this.setResponseType(ctx); + reply.header('Cache-Control', 'public, max-age=180'); + this.setResponseType(request, reply); + return (this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee))); }); - return router; + done(); } } diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index dc073e34ac..088e780d69 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -2,10 +2,8 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; import { Inject, Injectable } from '@nestjs/common'; -import Koa from 'koa'; -import cors from '@koa/cors'; -import Router from '@koa/router'; -import send from 'koa-send'; +import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; +import fastifyStatic from '@fastify/static'; import rename from 'rename'; import type { Config } from '@/config.js'; import type { DriveFilesRepository } from '@/models/index.js'; @@ -46,45 +44,44 @@ export class FileServerService { private loggerService: LoggerService, ) { this.logger = this.loggerService.getLogger('server', 'gray', false); + + this.createServer = this.createServer.bind(this); } - public commonReadableHandlerGenerator(ctx: Koa.Context) { - return (e: Error): void => { - this.logger.error(e); - ctx.status = 500; - ctx.set('Cache-Control', 'max-age=300'); + public commonReadableHandlerGenerator(reply: FastifyReply) { + return (err: Error): void => { + this.logger.error(err); + reply.code(500); + reply.header('Cache-Control', 'max-age=300'); }; } - public createServer() { - const app = new Koa(); - app.use(cors()); - app.use(async (ctx, next) => { - ctx.set('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); - await next(); + public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + fastify.addHook('onRequest', (request, reply, done) => { + reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); + done(); }); - // Init router - const router = new Router(); + fastify.register(fastifyStatic, { + root: _dirname, + serve: false, + }); - router.get('/app-default.jpg', ctx => { + fastify.get('/app-default.jpg', (request, reply) => { const file = fs.createReadStream(`${_dirname}/assets/dummy.png`); - ctx.body = file; - ctx.set('Content-Type', 'image/jpeg'); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); + reply.header('Content-Type', 'image/jpeg'); + reply.header('Cache-Control', 'max-age=31536000, immutable'); + return reply.send(file); }); - router.get('/:key', ctx => this.sendDriveFile(ctx)); - router.get('/:key/(.*)', ctx => this.sendDriveFile(ctx)); - - // Register router - app.use(router.routes()); + fastify.get<{ Params: { key: string; } }>('/:key', async (request, reply) => await this.sendDriveFile(request, reply)); + fastify.get<{ Params: { key: string; } }>('/:key/*', async (request, reply) => await this.sendDriveFile(request, reply)); - return app; + done(); } - private async sendDriveFile(ctx: Koa.Context) { - const key = ctx.params.key; + private async sendDriveFile(request: FastifyRequest<{ Params: { key: string; } }>, reply: FastifyReply) { + const key = request.params.key; // Fetch drive file const file = await this.driveFilesRepository.createQueryBuilder('file') @@ -94,10 +91,9 @@ export class FileServerService { .getOne(); if (file == null) { - ctx.status = 404; - ctx.set('Cache-Control', 'max-age=86400'); - await send(ctx as any, '/dummy.png', { root: assets }); - return; + reply.code(404); + reply.header('Cache-Control', 'max-age=86400'); + return reply.sendFile('/dummy.png', assets); } const isThumbnail = file.thumbnailAccessKey === key; @@ -135,18 +131,18 @@ export class FileServerService { }; const image = await convertFile(); - ctx.body = image.data; - ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream'); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); + reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream'); + reply.header('Cache-Control', 'max-age=31536000, immutable'); + return image.data; } catch (err) { this.logger.error(`${err}`); if (err instanceof StatusError && err.isClientError) { - ctx.status = err.statusCode; - ctx.set('Cache-Control', 'max-age=86400'); + reply.code(err.statusCode); + reply.header('Cache-Control', 'max-age=86400'); } else { - ctx.status = 500; - ctx.set('Cache-Control', 'max-age=300'); + reply.code(500); + reply.header('Cache-Control', 'max-age=300'); } } finally { cleanup(); @@ -154,8 +150,8 @@ export class FileServerService { return; } - ctx.status = 204; - ctx.set('Cache-Control', 'max-age=86400'); + reply.code(204); + reply.header('Cache-Control', 'max-age=86400'); return; } @@ -166,18 +162,17 @@ export class FileServerService { extname: ext ? `.${ext}` : undefined, }).toString(); - ctx.body = this.internalStorageService.read(key); - ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : 'application/octet-stream'); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); - ctx.set('Content-Disposition', contentDisposition('inline', filename)); + reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : 'application/octet-stream'); + reply.header('Cache-Control', 'max-age=31536000, immutable'); + reply.header('Content-Disposition', contentDisposition('inline', filename)); + return this.internalStorageService.read(key); } else { const readable = this.internalStorageService.read(file.accessKey!); - readable.on('error', this.commonReadableHandlerGenerator(ctx)); - ctx.body = readable; - ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.type) ? file.type : 'application/octet-stream'); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); - ctx.set('Content-Disposition', contentDisposition('inline', file.name)); + readable.on('error', this.commonReadableHandlerGenerator(reply)); + reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.type) ? file.type : 'application/octet-stream'); + reply.header('Cache-Control', 'max-age=31536000, immutable'); + reply.header('Content-Disposition', contentDisposition('inline', file.name)); + return readable; } } } - diff --git a/packages/backend/src/server/MediaProxyServerService.ts b/packages/backend/src/server/MediaProxyServerService.ts index 31841d39df..4d7bbdf599 100644 --- a/packages/backend/src/server/MediaProxyServerService.ts +++ b/packages/backend/src/server/MediaProxyServerService.ts @@ -1,8 +1,6 @@ import * as fs from 'node:fs'; import { Inject, Injectable } from '@nestjs/common'; -import Koa from 'koa'; -import cors from '@koa/cors'; -import Router from '@koa/router'; +import { FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest } from 'fastify'; import sharp from 'sharp'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; @@ -31,32 +29,29 @@ export class MediaProxyServerService { private loggerService: LoggerService, ) { this.logger = this.loggerService.getLogger('server', 'gray', false); + + this.createServer = this.createServer.bind(this); } - public createServer() { - const app = new Koa(); - app.use(cors()); - app.use(async (ctx, next) => { - ctx.set('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); - await next(); + public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + fastify.addHook('onRequest', (request, reply, done) => { + reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); + done(); }); - // Init router - const router = new Router(); - - router.get('/:url*', ctx => this.handler(ctx)); - - // Register router - app.use(router.routes()); + fastify.get<{ + Params: { url: string; }; + Querystring: { url?: string; }; + }>('/:url*', async (request, reply) => await this.handler(request, reply)); - return app; + done(); } - private async handler(ctx: Koa.Context) { - const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url; + private async handler(request: FastifyRequest<{ Params: { url: string; }; Querystring: { url?: string; }; }>, reply: FastifyReply) { + const url = 'url' in request.query ? request.query.url : 'https://' + request.params.url; if (typeof url !== 'string') { - ctx.status = 400; + reply.code(400); return; } @@ -71,11 +66,11 @@ export class MediaProxyServerService { let image: IImage; - if ('static' in ctx.query && isConvertibleImage) { + if ('static' in request.query && isConvertibleImage) { image = await this.imageProcessingService.convertToWebp(path, 498, 280); - } else if ('preview' in ctx.query && isConvertibleImage) { + } else if ('preview' in request.query && isConvertibleImage) { image = await this.imageProcessingService.convertToWebp(path, 200, 200); - } else if ('badge' in ctx.query) { + } else if ('badge' in request.query) { if (!isConvertibleImage) { // 画像でないなら404でお茶を濁す throw new StatusError('Unexpected mime', 404); @@ -122,16 +117,16 @@ export class MediaProxyServerService { }; } - ctx.set('Content-Type', image.type); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); - ctx.body = image.data; + reply.header('Content-Type', image.type); + reply.header('Cache-Control', 'max-age=31536000, immutable'); + return image.data; } catch (err) { this.logger.error(`${err}`); if (err instanceof StatusError && (err.statusCode === 302 || err.isClientError)) { - ctx.status = err.statusCode; + reply.code(err.statusCode); } else { - ctx.status = 500; + reply.code(500); } } finally { cleanup(); diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index ef4ec74a35..b85925f53e 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import Router from '@koa/router'; +import { FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest } from 'fastify'; import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { NotesRepository, UsersRepository } from '@/models/index.js'; @@ -27,6 +27,7 @@ export class NodeinfoServerService { private userEntityService: UserEntityService, private metaService: MetaService, ) { + this.createServer = this.createServer.bind(this); } public getLinks() { @@ -39,9 +40,7 @@ export class NodeinfoServerService { }]; } - public createRouter() { - const router = new Router(); - + public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { const nodeinfo2 = async () => { const now = Date.now(); const [ @@ -108,22 +107,22 @@ export class NodeinfoServerService { const cache = new Cache>>(1000 * 60 * 10); - router.get(nodeinfo2_1path, async ctx => { + fastify.get(nodeinfo2_1path, async (request, reply) => { const base = await cache.fetch(null, () => nodeinfo2()); - ctx.body = { version: '2.1', ...base }; - ctx.set('Cache-Control', 'public, max-age=600'); + reply.header('Cache-Control', 'public, max-age=600'); + return { version: '2.1', ...base }; }); - router.get(nodeinfo2_0path, async ctx => { + fastify.get(nodeinfo2_0path, async (request, reply) => { const base = await cache.fetch(null, () => nodeinfo2()); delete (base as any).software.repository; - ctx.body = { version: '2.0', ...base }; - ctx.set('Cache-Control', 'public, max-age=600'); + reply.header('Cache-Control', 'public, max-age=600'); + return { version: '2.0', ...base }; }); - return router; + done(); } } diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index d42972614f..96159cfc53 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -2,11 +2,7 @@ import cluster from 'node:cluster'; import * as fs from 'node:fs'; import * as http from 'node:http'; import { Inject, Injectable } from '@nestjs/common'; -import Koa from 'koa'; -import Router from '@koa/router'; -import mount from 'koa-mount'; -import koaLogger from 'koa-logger'; -import * as slow from 'koa-slow'; +import Fastify from 'fastify'; import { IsNull } from 'typeorm'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { Config } from '@/config.js'; @@ -58,47 +54,29 @@ export class ServerService { } public launch() { - // Init app - const koa = new Koa(); - koa.proxy = true; - - if (!['production', 'test'].includes(process.env.NODE_ENV ?? '')) { - // Logger - koa.use(koaLogger(str => { - this.logger.info(str); - })); - - // Delay - if (envOption.slow) { - koa.use(slow({ - delay: 3000, - })); - } - } + const fastify = Fastify({ + trustProxy: true, + logger: !['production', 'test'].includes(process.env.NODE_ENV ?? ''), + }); // HSTS // 6months (15552000sec) if (this.config.url.startsWith('https') && !this.config.disableHsts) { - koa.use(async (ctx, next) => { - ctx.set('strict-transport-security', 'max-age=15552000; preload'); - await next(); + fastify.addHook('onRequest', (request, reply, done) => { + reply.header('strict-transport-security', 'max-age=15552000; preload'); + done(); }); } - koa.use(mount('/api', this.apiServerService.createApiServer(koa))); - koa.use(mount('/files', this.fileServerService.createServer())); - koa.use(mount('/proxy', this.mediaProxyServerService.createServer())); - - // Init router - const router = new Router(); + fastify.register(this.apiServerService.createServer, { prefix: '/api' }); + fastify.register(this.fileServerService.createServer, { prefix: '/files' }); + fastify.register(this.mediaProxyServerService.createServer, { prefix: '/proxy' }); + fastify.register(this.activityPubServerService.createServer); + fastify.register(this.nodeinfoServerService.createServer); + fastify.register(this.wellKnownServerService.createServer); - // Routing - router.use(this.activityPubServerService.createRouter().routes()); - router.use(this.nodeinfoServerService.createRouter().routes()); - router.use(this.wellKnownServerService.createRouter().routes()); - - router.get('/avatar/@:acct', async ctx => { - const { username, host } = Acct.parse(ctx.params.acct); + fastify.get<{ Params: { acct: string } }>('/avatar/@:acct', async (request, reply) => { + const { username, host } = Acct.parse(request.params.acct); const user = await this.usersRepository.findOne({ where: { usernameLower: username.toLowerCase(), @@ -109,28 +87,25 @@ export class ServerService { }); if (user) { - ctx.redirect(this.userEntityService.getAvatarUrlSync(user)); + reply.redirect(this.userEntityService.getAvatarUrlSync(user)); } else { - ctx.redirect('/static-assets/user-unknown.png'); + reply.redirect('/static-assets/user-unknown.png'); } }); - router.get('/identicon/:x', async ctx => { + fastify.get<{ Params: { x: string } }>('/identicon/:x', async (request, reply) => { const [temp, cleanup] = await createTemp(); - await genIdenticon(ctx.params.x, fs.createWriteStream(temp)); - ctx.set('Content-Type', 'image/png'); - ctx.body = fs.createReadStream(temp).on('close', () => cleanup()); + await genIdenticon(request.params.x, fs.createWriteStream(temp)); + reply.header('Content-Type', 'image/png'); + return fs.createReadStream(temp).on('close', () => cleanup()); }); - router.get('/verify-email/:code', async ctx => { + fastify.get<{ Params: { code: string } }>('/verify-email/:code', async (request, reply) => { const profile = await this.userProfilesRepository.findOneBy({ - emailVerifyCode: ctx.params.code, + emailVerifyCode: request.params.code, }); if (profile != null) { - ctx.body = 'Verify succeeded!'; - ctx.status = 200; - await this.userProfilesRepository.update({ userId: profile.userId }, { emailVerified: true, emailVerifyCode: null, @@ -140,21 +115,19 @@ export class ServerService { detail: true, includeSecrets: true, })); + + reply.code(200); + return 'Verify succeeded!'; } else { - ctx.status = 404; + reply.code(404); } }); - // Register router - koa.use(router.routes()); - - koa.use(mount(this.clientServerService.createApp())); - - const server = http.createServer(koa.callback()); + fastify.register(this.clientServerService.createServer); - this.streamingApiServerService.attachStreamingApi(server); + this.streamingApiServerService.attachStreamingApi(fastify.server); - server.on('error', err => { + fastify.server.on('error', err => { switch ((err as any).code) { case 'EACCES': this.logger.error(`You do not have permission to listen on port ${this.config.port}.`); @@ -168,13 +141,13 @@ export class ServerService { } if (cluster.isWorker) { - process.send!('listenFailed'); + process.send!('listenFailed'); } else { - // disableClustering + // disableClustering process.exit(1); } }); - server.listen(this.config.port); + fastify.listen({ port: this.config.port }); } } diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts index f2eee88e09..412c608313 100644 --- a/packages/backend/src/server/WellKnownServerService.ts +++ b/packages/backend/src/server/WellKnownServerService.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; -import Router from '@koa/router'; +import { FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest } from 'fastify'; import { IsNull, MoreThan } from 'typeorm'; +import vary from 'vary'; import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; @@ -21,11 +22,10 @@ export class WellKnownServerService { private nodeinfoServerService: NodeinfoServerService, ) { + this.createServer = this.createServer.bind(this); } - public createRouter() { - const router = new Router(); - + public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { const XRD = (...x: { element: string, value?: string, attributes?: Record }[]) => `${x.map(({ element, value, attributes }) => `<${ @@ -34,37 +34,35 @@ export class WellKnownServerService { typeof value === 'string' ? `>${escapeValue(value)}`).reduce((a, c) => a + c, '')}`; - const allPath = '/.well-known/(.*)'; + const allPath = '/.well-known/*'; const webFingerPath = '/.well-known/webfinger'; const jrd = 'application/jrd+json'; const xrd = 'application/xrd+xml'; - router.use(allPath, async (ctx, next) => { - ctx.set({ - 'Access-Control-Allow-Headers': 'Accept', - 'Access-Control-Allow-Methods': 'GET, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Expose-Headers': 'Vary', - }); - await next(); + fastify.addHook('onRequest', (request, reply, done) => { + reply.header('Access-Control-Allow-Headers', 'Accept'); + reply.header('Access-Control-Allow-Methods', 'GET, OPTIONS'); + reply.header('Access-Control-Allow-Origin', '*'); + reply.header('Access-Control-Expose-Headers', 'Vary'); + done(); }); - router.options(allPath, async ctx => { - ctx.status = 204; + fastify.options(allPath, async (request, reply) => { + reply.code(204); }); - router.get('/.well-known/host-meta', async ctx => { - ctx.set('Content-Type', xrd); - ctx.body = XRD({ element: 'Link', attributes: { + fastify.get('/.well-known/host-meta', async (request, reply) => { + reply.header('Content-Type', xrd); + return XRD({ element: 'Link', attributes: { rel: 'lrdd', type: xrd, template: `${this.config.url}${webFingerPath}?resource={uri}`, } }); }); - router.get('/.well-known/host-meta.json', async ctx => { - ctx.set('Content-Type', jrd); - ctx.body = { + fastify.get('/.well-known/host-meta.json', async (request, reply) => { + reply.header('Content-Type', jrd); + return { links: [{ rel: 'lrdd', type: jrd, @@ -73,16 +71,16 @@ export class WellKnownServerService { }; }); - router.get('/.well-known/nodeinfo', async ctx => { - ctx.body = { links: this.nodeinfoServerService.getLinks() }; + fastify.get('/.well-known/nodeinfo', async (request, reply) => { + return { links: this.nodeinfoServerService.getLinks() }; }); /* TODO -router.get('/.well-known/change-password', async ctx => { +fastify.get('/.well-known/change-password', async (request, reply) => { }); */ - router.get(webFingerPath, async ctx => { + fastify.get<{ Querystring: { resource: string } }>(webFingerPath, async (request, reply) => { const fromId = (id: User['id']): FindOptionsWhere => ({ id, host: IsNull(), @@ -104,22 +102,22 @@ router.get('/.well-known/change-password', async ctx => { isSuspended: false, } : 422; - if (typeof ctx.query.resource !== 'string') { - ctx.status = 400; + if (typeof request.query.resource !== 'string') { + reply.code(400); return; } - const query = generateQuery(ctx.query.resource.toLowerCase()); + const query = generateQuery(request.query.resource.toLowerCase()); if (typeof query === 'number') { - ctx.status = query; + reply.code(query); return; } const user = await this.usersRepository.findOneBy(query); if (user == null) { - ctx.status = 404; + reply.code(404); return; } @@ -139,30 +137,25 @@ router.get('/.well-known/change-password', async ctx => { template: `${this.config.url}/authorize-follow?acct={uri}`, }; - if (ctx.accepts(jrd, xrd) === xrd) { - ctx.body = XRD( + vary(reply.raw, 'Accept'); + reply.header('Cache-Control', 'public, max-age=180'); + + if (request.accepts().type([jrd, xrd]) === xrd) { + reply.type(xrd); + return XRD( { element: 'Subject', value: subject }, { element: 'Link', attributes: self }, { element: 'Link', attributes: profilePage }, { element: 'Link', attributes: subscribe }); - ctx.type = xrd; } else { - ctx.body = { + reply.type(jrd); + return { subject, links: [self, profilePage, subscribe], }; - ctx.type = jrd; } - - ctx.vary('Accept'); - ctx.set('Cache-Control', 'public, max-age=180'); - }); - - // Return 404 for other .well-known - router.all(allPath, async ctx => { - ctx.status = 404; }); - return router; + done(); } } diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index c3ce12e0c3..2e72cdf9f8 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -1,19 +1,25 @@ import { performance } from 'perf_hooks'; +import { pipeline } from 'node:stream'; +import * as fs from 'node:fs'; +import { promisify } from 'node:util'; import { Inject, Injectable } from '@nestjs/common'; +import { FastifyRequest, FastifyReply } from 'fastify'; import { DI } from '@/di-symbols.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; -import type { CacheableLocalUser, User } from '@/models/entities/User.js'; +import type { CacheableLocalUser, ILocalUser, User } from '@/models/entities/User.js'; import type { AccessToken } from '@/models/entities/AccessToken.js'; import type Logger from '@/logger.js'; import type { UserIpsRepository } from '@/models/index.js'; import { MetaService } from '@/core/MetaService.js'; +import { createTemp } from '@/misc/create-temp.js'; import { ApiError } from './error.js'; import { RateLimiterService } from './RateLimiterService.js'; import { ApiLoggerService } from './ApiLoggerService.js'; import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; import type { IEndpointMeta, IEndpoint } from './endpoints.js'; -import type Koa from 'koa'; + +const pump = promisify(pipeline); const accessDenied = { message: 'Access denied.', @@ -44,92 +50,149 @@ export class ApiCallService implements OnApplicationShutdown { }, 1000 * 60 * 60); } - public handleRequest(endpoint: IEndpoint, exec: any, ctx: Koa.Context) { - return new Promise((res) => { - const body = ctx.is('multipart/form-data') - ? (ctx.request as any).body - : ctx.method === 'GET' - ? ctx.query - : ctx.request.body; - - const reply = (x?: any, y?: ApiError) => { - if (x == null) { - ctx.status = 204; - } else if (typeof x === 'number' && y) { - ctx.status = x; - ctx.body = { - error: { - message: y!.message, - code: y!.code, - id: y!.id, - kind: y!.kind, - ...(y!.info ? { info: y!.info } : {}), - }, - }; - } else { - // 文字列を返す場合は、JSON.stringify通さないとJSONと認識されない - ctx.body = typeof x === 'string' ? JSON.stringify(x) : x; - } - res(); - }; - - // Authentication - this.authenticateService.authenticate(body['i']).then(([user, app]) => { - // API invoking - this.call(endpoint, exec, user, app, body, ctx).then((res: any) => { - if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) { - ctx.set('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`); - } - reply(res); - }).catch((e: ApiError) => { - reply(e.httpStatusCode ? e.httpStatusCode : e.kind === 'client' ? 400 : 500, e); - }); - - // Log IP - if (user) { - this.metaService.fetch().then(meta => { - if (!meta.enableIpLogging) return; - const ip = ctx.ip; - const ips = this.userIpHistories.get(user.id); - if (ips == null || !ips.has(ip)) { - if (ips == null) { - this.userIpHistories.set(user.id, new Set([ip])); - } else { - ips.add(ip); - } - - try { - this.userIpsRepository.createQueryBuilder().insert().values({ - createdAt: new Date(), - userId: user.id, - ip: ip, - }).orIgnore(true).execute(); - } catch { - } - } - }); - } - }).catch(e => { - if (e instanceof AuthenticationError) { - reply(403, new ApiError({ - message: 'Authentication failed. Please ensure your token is correct.', - code: 'AUTHENTICATION_FAILED', - id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', - })); - } else { - reply(500, new ApiError()); + public handleRequest( + endpoint: IEndpoint & { exec: any }, + request: FastifyRequest<{ Body: Record, Querystring: Record }>, + reply: FastifyReply, + ) { + const body = request.method === 'GET' + ? request.query + : request.body; + + const token = body['i']; + if (token != null && typeof token !== 'string') { + reply.code(400); + return; + } + this.authenticateService.authenticate(token).then(([user, app]) => { + this.call(endpoint, user, app, body, null, request).then((res) => { + if (request.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) { + reply.header('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`); } + this.send(reply, res); + }).catch((err: ApiError) => { + this.send(reply, err.httpStatusCode ? err.httpStatusCode : err.kind === 'client' ? 400 : 500, err); }); + + if (user) { + this.logIp(request, user); + } + }).catch(err => { + if (err instanceof AuthenticationError) { + this.send(reply, 403, new ApiError({ + message: 'Authentication failed. Please ensure your token is correct.', + code: 'AUTHENTICATION_FAILED', + id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', + })); + } else { + this.send(reply, 500, new ApiError()); + } }); } + public async handleMultipartRequest( + endpoint: IEndpoint & { exec: any }, + request: FastifyRequest<{ Body: Record, Querystring: Record }>, + reply: FastifyReply, + ) { + const multipartData = await request.file(); + if (multipartData == null) { + reply.code(400); + return; + } + + const [path] = await createTemp(); + await pump(multipartData.file, fs.createWriteStream(path)); + + const fields = {} as Record; + for (const [k, v] of Object.entries(multipartData.fields)) { + fields[k] = v.value; + } + + const token = fields['i']; + if (token != null && typeof token !== 'string') { + reply.code(400); + return; + } + this.authenticateService.authenticate(token).then(([user, app]) => { + this.call(endpoint, user, app, fields, { + name: multipartData.filename, + path: path, + }, request).then((res) => { + this.send(reply, res); + }).catch((err: ApiError) => { + this.send(reply, err.httpStatusCode ? err.httpStatusCode : err.kind === 'client' ? 400 : 500, err); + }); + + if (user) { + this.logIp(request, user); + } + }).catch(err => { + if (err instanceof AuthenticationError) { + this.send(reply, 403, new ApiError({ + message: 'Authentication failed. Please ensure your token is correct.', + code: 'AUTHENTICATION_FAILED', + id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', + })); + } else { + this.send(reply, 500, new ApiError()); + } + }); + } + + private send(reply: FastifyReply, x?: any, y?: ApiError) { + if (x == null) { + reply.code(204); + } else if (typeof x === 'number' && y) { + reply.code(x); + reply.send({ + error: { + message: y!.message, + code: y!.code, + id: y!.id, + kind: y!.kind, + ...(y!.info ? { info: y!.info } : {}), + }, + }); + } else { + // 文字列を返す場合は、JSON.stringify通さないとJSONと認識されない + reply.send(typeof x === 'string' ? JSON.stringify(x) : x); + } + } + + private async logIp(request: FastifyRequest, user: ILocalUser) { + const meta = await this.metaService.fetch(); + if (!meta.enableIpLogging) return; + const ip = request.ip; + const ips = this.userIpHistories.get(user.id); + if (ips == null || !ips.has(ip)) { + if (ips == null) { + this.userIpHistories.set(user.id, new Set([ip])); + } else { + ips.add(ip); + } + + try { + this.userIpsRepository.createQueryBuilder().insert().values({ + createdAt: new Date(), + userId: user.id, + ip: ip, + }).orIgnore(true).execute(); + } catch { + } + } + } + private async call( - ep: IEndpoint, - exec: any, + ep: IEndpoint & { exec: any }, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, - ctx?: Koa.Context, + file: { + name: string; + path: string; + } | null, + request: FastifyRequest<{ Body: Record, Querystring: Record }>, ) { const isSecure = user != null && token == null; const isModerator = user != null && (user.isModerator || user.isAdmin); @@ -144,7 +207,7 @@ export class ApiCallService implements OnApplicationShutdown { if (user) { limitActor = user.id; } else { - limitActor = getIpHash(ctx!.ip); + limitActor = getIpHash(request.ip); } const limit = Object.assign({}, ep.meta.limit); @@ -154,7 +217,7 @@ export class ApiCallService implements OnApplicationShutdown { } // Rate limit - await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable }, limitActor).catch(e => { + await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable }, limitActor).catch(err => { throw new ApiError({ message: 'Rate limit exceeded. Please try again later.', code: 'RATE_LIMIT_EXCEEDED', @@ -199,7 +262,7 @@ export class ApiCallService implements OnApplicationShutdown { } // Cast non JSON input - if ((ep.meta.requireFile || ctx?.method === 'GET') && ep.params.properties) { + if ((ep.meta.requireFile || request.method === 'GET') && ep.params.properties) { for (const k of Object.keys(ep.params.properties)) { const param = ep.params.properties![k]; if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') { @@ -221,7 +284,7 @@ export class ApiCallService implements OnApplicationShutdown { // API invoking const before = performance.now(); - return await exec(data, user, token, ctx?.file, ctx?.ip, ctx?.headers).catch((err: Error) => { + return await ep.exec(data, user, token, file, request.ip, request.headers).catch((err: Error) => { if (err instanceof ApiError) { throw err; } else { diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index 52654dbaee..cf3f2deebf 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -1,15 +1,13 @@ import { Inject, Injectable } from '@nestjs/common'; -import Koa from 'koa'; -import Router from '@koa/router'; -import multer from '@koa/multer'; -import bodyParser from 'koa-bodyparser'; -import cors from '@koa/cors'; -import { ModuleRef } from '@nestjs/core'; +import { FastifyInstance, FastifyPluginOptions } from 'fastify'; +import cors from '@fastify/cors'; +import multipart from '@fastify/multipart'; +import { ModuleRef, repl } from '@nestjs/core'; import type { Config } from '@/config.js'; import type { UsersRepository, InstancesRepository, AccessTokensRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import endpoints from './endpoints.js'; +import endpoints, { IEndpoint } from './endpoints.js'; import { ApiCallService } from './ApiCallService.js'; import { SignupApiService } from './SignupApiService.js'; import { SigninApiService } from './SigninApiService.js'; @@ -42,92 +40,107 @@ export class ApiServerService { private discordServerService: DiscordServerService, private twitterServerService: TwitterServerService, ) { + this.createServer = this.createServer.bind(this); } - public createApiServer() { - const handlers: Record = {}; - - for (const endpoint of endpoints) { - handlers[endpoint.name] = this.moduleRef.get('ep:' + endpoint.name, { strict: false }).exec; - } - - // Init app - const apiServer = new Koa(); - - apiServer.use(cors({ + public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + fastify.register(cors, { origin: '*', - })); - - // No caching - apiServer.use(async (ctx, next) => { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); - await next(); }); - apiServer.use(bodyParser({ - // リクエストが multipart/form-data でない限りはJSONだと見なす - detectJSON: ctx => !ctx.is('multipart/form-data'), - })); - - // Init multer instance - const upload = multer({ - storage: multer.diskStorage({}), + fastify.register(multipart, { limits: { fileSize: this.config.maxFileSize ?? 262144000, files: 1, }, }); - // Init router - const router = new Router(); + // Prevent cache + fastify.addHook('onRequest', (request, reply, done) => { + reply.header('Cache-Control', 'private, max-age=0, must-revalidate'); + done(); + }); - /** - * Register endpoint handlers - */ for (const endpoint of endpoints) { + const ep = { + name: endpoint.name, + meta: endpoint.meta, + params: endpoint.params, + exec: this.moduleRef.get('ep:' + endpoint.name, { strict: false }).exec, + }; + if (endpoint.meta.requireFile) { - router.post(`/${endpoint.name}`, upload.single('file'), this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name])); + fastify.all<{ + Params: { endpoint: string; }, + Body: Record, + Querystring: Record, + }>('/' + endpoint.name, (request, reply) => { + if (request.method === 'GET' && !endpoint.meta.allowGet) { + reply.code(405); + return; + } + + this.apiCallService.handleMultipartRequest(ep, request, reply); + }); } else { - // 後方互換性のため - if (endpoint.name.includes('-')) { - router.post(`/${endpoint.name.replace(/-/g, '_')}`, this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name])); - - if (endpoint.meta.allowGet) { - router.get(`/${endpoint.name.replace(/-/g, '_')}`, this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name])); - } else { - router.get(`/${endpoint.name.replace(/-/g, '_')}`, async ctx => { ctx.status = 405; }); + fastify.all<{ + Params: { endpoint: string; }, + Body: Record, + Querystring: Record, + }>('/' + endpoint.name, (request, reply) => { + if (request.method === 'GET' && !endpoint.meta.allowGet) { + reply.code(405); + return; } - } - - router.post(`/${endpoint.name}`, this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name])); - - if (endpoint.meta.allowGet) { - router.get(`/${endpoint.name}`, this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name])); - } else { - router.get(`/${endpoint.name}`, async ctx => { ctx.status = 405; }); - } + + this.apiCallService.handleRequest(ep, request, reply); + }); } } - router.post('/signup', ctx => this.signupApiServiceService.signup(ctx)); - router.post('/signin', ctx => this.signinApiServiceService.signin(ctx)); - router.post('/signup-pending', ctx => this.signupApiServiceService.signupPending(ctx)); - - router.use(this.discordServerService.create().routes()); - router.use(this.githubServerService.create().routes()); - router.use(this.twitterServerService.create().routes()); - - router.get('/v1/instance/peers', async ctx => { + fastify.post<{ + Body: { + username: string; + password: string; + host?: string; + invitationCode?: string; + emailAddress?: string; + 'hcaptcha-response'?: string; + 'g-recaptcha-response'?: string; + 'turnstile-response'?: string; + } + }>('/signup', (request, reply) => this.signupApiServiceService.signup(request, reply)); + + fastify.post<{ + Body: { + username: string; + password: string; + token?: string; + signature?: string; + authenticatorData?: string; + clientDataJSON?: string; + credentialId?: string; + challengeId?: string; + }; + }>('/signin', (request, reply) => this.signinApiServiceService.signin(request, reply)); + + fastify.post<{ Body: { code: string; } }>('/signup-pending', (request, reply) => this.signupApiServiceService.signupPending(request, reply)); + + fastify.register(this.discordServerService.create); + fastify.register(this.githubServerService.create); + fastify.register(this.twitterServerService.create); + + fastify.get('/v1/instance/peers', async (request, reply) => { const instances = await this.instancesRepository.find({ select: ['host'], }); - ctx.body = instances.map(instance => instance.host); + return instances.map(instance => instance.host); }); - router.post('/miauth/:session/check', async ctx => { + fastify.post<{ Params: { session: string; } }>('/miauth/:session/check', async (request, reply) => { const token = await this.accessTokensRepository.findOneBy({ - session: ctx.params.session, + session: request.params.session, }); if (token && token.session != null && !token.fetched) { @@ -135,26 +148,18 @@ export class ApiServerService { fetched: true, }); - ctx.body = { + return { ok: true, token: token.token, user: await this.userEntityService.pack(token.userId, null, { detail: true }), }; } else { - ctx.body = { + return { ok: false, }; } }); - // Return 404 for unknown API - router.all('(.*)', async ctx => { - ctx.status = 404; - }); - - // Register router - apiServer.use(router.routes()); - - return apiServer; + done(); } } diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts index 4ce9b91f42..ad387c4732 100644 --- a/packages/backend/src/server/api/AuthenticateService.ts +++ b/packages/backend/src/server/api/AuthenticateService.ts @@ -34,7 +34,7 @@ export class AuthenticateService { this.appCache = new Cache(Infinity); } - public async authenticate(token: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> { + public async authenticate(token: string | null | undefined): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> { if (token == null) { return [null, null]; } diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index a5e2b09012..8b3d86e5a6 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -3,6 +3,7 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; import { IsNull } from 'typeorm'; +import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import { DI } from '@/di-symbols.js'; import type { UserSecurityKeysRepository, SigninsRepository, UserProfilesRepository, AttestationChallengesRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; @@ -12,7 +13,6 @@ import { IdService } from '@/core/IdService.js'; import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; import { RateLimiterService } from './RateLimiterService.js'; import { SigninService } from './SigninService.js'; -import type Koa from 'koa'; @Injectable() export class SigninApiService { @@ -42,47 +42,60 @@ export class SigninApiService { ) { } - public async signin(ctx: Koa.Context) { - ctx.set('Access-Control-Allow-Origin', this.config.url); - ctx.set('Access-Control-Allow-Credentials', 'true'); + public async signin( + request: FastifyRequest<{ + Body: { + username: string; + password: string; + token?: string; + signature?: string; + authenticatorData?: string; + clientDataJSON?: string; + credentialId?: string; + challengeId?: string; + }; + }>, + reply: FastifyReply, + ) { + reply.header('Access-Control-Allow-Origin', this.config.url); + reply.header('Access-Control-Allow-Credentials', 'true'); - const body = ctx.request.body as any; + const body = request.body; const username = body['username']; const password = body['password']; const token = body['token']; function error(status: number, error: { id: string }) { - ctx.status = status; - ctx.body = { error }; + reply.code(status); + return { error }; } try { // not more than 1 attempt per second and not more than 10 attempts per hour - await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(ctx.ip)); + await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip)); } catch (err) { - ctx.status = 429; - ctx.body = { + reply.code(429); + return { error: { message: 'Too many failed attempts to sign in. Try again later.', code: 'TOO_MANY_AUTHENTICATION_FAILURES', id: '22d05606-fbcf-421a-a2db-b32610dcfd1b', }, }; - return; } if (typeof username !== 'string') { - ctx.status = 400; + reply.code(400); return; } if (typeof password !== 'string') { - ctx.status = 400; + reply.code(400); return; } if (token != null && typeof token !== 'string') { - ctx.status = 400; + reply.code(400); return; } @@ -93,17 +106,15 @@ export class SigninApiService { }) as ILocalUser; if (user == null) { - error(404, { + return error(404, { id: '6cc579cc-885d-43d8-95c2-b8c7fc963280', }); - return; } if (user.isSuspended) { - error(403, { + return error(403, { id: 'e03a5f46-d309-4865-9b69-56282d94e1eb', }); - return; } const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); @@ -117,32 +128,29 @@ export class SigninApiService { id: this.idService.genId(), createdAt: new Date(), userId: user.id, - ip: ctx.ip, - headers: ctx.headers, + ip: request.ip, + headers: request.headers, success: false, }); - error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' }); + return error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' }); }; if (!profile.twoFactorEnabled) { if (same) { - this.signinService.signin(ctx, user); - return; + return this.signinService.signin(request, reply, user); } else { - await fail(403, { + return await fail(403, { id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', }); - return; } } if (token) { if (!same) { - await fail(403, { + return await fail(403, { id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', }); - return; } const verified = (speakeasy as any).totp.verify({ @@ -153,20 +161,17 @@ export class SigninApiService { }); if (verified) { - this.signinService.signin(ctx, user); - return; + return this.signinService.signin(request, reply, user); } else { - await fail(403, { + return await fail(403, { id: 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f', }); - return; } - } else if (body.credentialId) { + } else if (body.credentialId && body.clientDataJSON && body.authenticatorData && body.signature) { if (!same && !profile.usePasswordLessLogin) { - await fail(403, { + return await fail(403, { id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', }); - return; } const clientDataJSON = Buffer.from(body.clientDataJSON, 'hex'); @@ -179,10 +184,9 @@ export class SigninApiService { }); if (!challenge) { - await fail(403, { + return await fail(403, { id: '2715a88a-2125-4013-932f-aa6fe72792da', }); - return; } await this.attestationChallengesRepository.delete({ @@ -191,10 +195,9 @@ export class SigninApiService { }); if (new Date().getTime() - challenge.createdAt.getTime() >= 5 * 60 * 1000) { - await fail(403, { + return await fail(403, { id: '2715a88a-2125-4013-932f-aa6fe72792da', }); - return; } const securityKey = await this.userSecurityKeysRepository.findOneBy({ @@ -207,10 +210,9 @@ export class SigninApiService { }); if (!securityKey) { - await fail(403, { + return await fail(403, { id: '66269679-aeaf-4474-862b-eb761197e046', }); - return; } const isValid = this.twoFactorAuthenticationService.verifySignin({ @@ -223,20 +225,17 @@ export class SigninApiService { }); if (isValid) { - this.signinService.signin(ctx, user); - return; + return this.signinService.signin(request, reply, user); } else { - await fail(403, { + return await fail(403, { id: '93b86c4b-72f9-40eb-9815-798928603d1e', }); - return; } } else { if (!same && !profile.usePasswordLessLogin) { - await fail(403, { + return await fail(403, { id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', }); - return; } const keys = await this.userSecurityKeysRepository.findBy({ @@ -244,10 +243,9 @@ export class SigninApiService { }); if (keys.length === 0) { - await fail(403, { + return await fail(403, { id: 'f27fd449-9af4-4841-9249-1f989b9fa4a4', }); - return; } // 32 byte challenge @@ -266,15 +264,14 @@ export class SigninApiService { registrationChallenge: false, }); - ctx.body = { + reply.code(200); + return { challenge, challengeId, securityKeys: keys.map(key => ({ id: key.id, })), }; - ctx.status = 200; - return; } // never get here } diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts index 3b96dfee6f..18a1d6c088 100644 --- a/packages/backend/src/server/api/SigninService.ts +++ b/packages/backend/src/server/api/SigninService.ts @@ -1,13 +1,12 @@ import { Inject, Injectable } from '@nestjs/common'; +import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import { DI } from '@/di-symbols.js'; -import type { SigninsRepository } from '@/models/index.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { SigninsRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import { IdService } from '@/core/IdService.js'; import type { ILocalUser } from '@/models/entities/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { SigninEntityService } from '@/core/entities/SigninEntityService.js'; -import type Koa from 'koa'; @Injectable() export class SigninService { @@ -24,10 +23,25 @@ export class SigninService { ) { } - public signin(ctx: Koa.Context, user: ILocalUser, redirect = false) { + public signin(request: FastifyRequest, reply: FastifyReply, user: ILocalUser, redirect = false) { + setImmediate(async () => { + // Append signin history + const record = await this.signinsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: user.id, + ip: request.ip, + headers: request.headers, + success: true, + }).then(x => this.signinsRepository.findOneByOrFail(x.identifiers[0])); + + // Publish signin event + this.globalEventService.publishMainStream(user.id, 'signin', await this.signinEntityService.pack(record)); + }); + if (redirect) { //#region Cookie - ctx.cookies.set('igi', user.token!, { + reply.cookies.set('igi', user.token!, { path: '/', // SEE: https://github.com/koajs/koa/issues/974 // When using a SSL proxy it should be configured to add the "X-Forwarded-Proto: https" header @@ -36,29 +50,14 @@ export class SigninService { }); //#endregion - ctx.redirect(this.config.url); + reply.redirect(this.config.url); } else { - ctx.body = { + reply.code(200); + return { id: user.id, i: user.token, }; - ctx.status = 200; } - - (async () => { - // Append signin history - const record = await this.signinsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), - userId: user.id, - ip: ctx.ip, - headers: ctx.headers, - success: true, - }).then(x => this.signinsRepository.findOneByOrFail(x.identifiers[0])); - - // Publish signin event - this.globalEventService.publishMainStream(user.id, 'signin', await this.signinEntityService.pack(record)); - })(); } } diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index edb8e4e8e6..771858d091 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import rndstr from 'rndstr'; import bcrypt from 'bcryptjs'; +import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import { DI } from '@/di-symbols.js'; import type { RegistrationTicketsRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; @@ -11,8 +12,8 @@ import { SignupService } from '@/core/SignupService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { EmailService } from '@/core/EmailService.js'; import { ILocalUser } from '@/models/entities/User.js'; +import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { SigninService } from './SigninService.js'; -import type Koa from 'koa'; @Injectable() export class SignupApiService { @@ -42,8 +43,22 @@ export class SignupApiService { ) { } - public async signup(ctx: Koa.Context) { - const body = ctx.request.body; + public async signup( + request: FastifyRequest<{ + Body: { + username: string; + password: string; + host?: string; + invitationCode?: string; + emailAddress?: string; + 'hcaptcha-response'?: string; + 'g-recaptcha-response'?: string; + 'turnstile-response'?: string; + } + }>, + reply: FastifyReply, + ) { + const body = request.body; const instance = await this.metaService.fetch(true); @@ -51,20 +66,20 @@ export class SignupApiService { // ただしテスト時はこの機構は障害となるため無効にする if (process.env.NODE_ENV !== 'test') { if (instance.enableHcaptcha && instance.hcaptchaSecretKey) { - await this.captchaService.verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(e => { - ctx.throw(400, e); + await this.captchaService.verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => { + throw new FastifyReplyError(400, err); }); } if (instance.enableRecaptcha && instance.recaptchaSecretKey) { - await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(e => { - ctx.throw(400, e); + await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => { + throw new FastifyReplyError(400, err); }); } if (instance.enableTurnstile && instance.turnstileSecretKey) { - await this.captchaService.verifyTurnstile(instance.turnstileSecretKey, body['turnstile-response']).catch(e => { - ctx.throw(400, e); + await this.captchaService.verifyTurnstile(instance.turnstileSecretKey, body['turnstile-response']).catch(err => { + throw new FastifyReplyError(400, err); }); } } @@ -77,20 +92,20 @@ export class SignupApiService { if (instance.emailRequiredForSignup) { if (emailAddress == null || typeof emailAddress !== 'string') { - ctx.status = 400; + reply.code(400); return; } - const available = await this.emailService.validateEmailForAccount(emailAddress); - if (!available) { - ctx.status = 400; + const res = await this.emailService.validateEmailForAccount(emailAddress); + if (!res.available) { + reply.code(400); return; } } if (instance.disableRegistration) { if (invitationCode == null || typeof invitationCode !== 'string') { - ctx.status = 400; + reply.code(400); return; } @@ -99,7 +114,7 @@ export class SignupApiService { }); if (ticket == null) { - ctx.status = 400; + reply.code(400); return; } @@ -117,18 +132,18 @@ export class SignupApiService { id: this.idService.genId(), createdAt: new Date(), code, - email: emailAddress, + email: emailAddress!, username: username, password: hash, }); const link = `${this.config.url}/signup-complete/${code}`; - this.emailService.sendEmail(emailAddress, 'Signup', + this.emailService.sendEmail(emailAddress!, 'Signup', `To complete signup, please click this link:
${link}`, `To complete signup, please click this link: ${link}`); - ctx.status = 204; + reply.code(204); } else { try { const { account, secret } = await this.signupService.signup({ @@ -140,17 +155,18 @@ export class SignupApiService { includeSecrets: true, }); - (res as any).token = secret; - - ctx.body = res; - } catch (e) { - ctx.throw(400, e); + return { + ...res, + token: secret, + }; + } catch (err) { + throw new FastifyReplyError(400, err); } } } - public async signupPending(ctx: Koa.Context) { - const body = ctx.request.body; + public async signupPending(request: FastifyRequest<{ Body: { code: string; } }>, reply: FastifyReply) { + const body = request.body; const code = body['code']; @@ -174,9 +190,9 @@ export class SignupApiService { emailVerifyCode: null, }); - this.signinService.signin(ctx, account as ILocalUser); - } catch (e) { - ctx.throw(400, e); + this.signinService.signin(request, reply, account as ILocalUser); + } catch (err) { + throw new FastifyReplyError(400, err); } } } diff --git a/packages/backend/src/server/api/endpoint-base.ts b/packages/backend/src/server/api/endpoint-base.ts index 0a7f9b3008..b27329b9a9 100644 --- a/packages/backend/src/server/api/endpoint-base.ts +++ b/packages/backend/src/server/api/endpoint-base.ts @@ -14,23 +14,28 @@ ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/); export type Response = Record | void; +type File = { + name: string | null; + path: string; +}; + // TODO: paramsの型をT['params']のスキーマ定義から推論する type executor = - (params: SchemaType, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any, ip?: string | null, headers?: Record | null) => + (params: SchemaType, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: File, cleanup?: () => any, ip?: string | null, headers?: Record | null) => Promise>>; export abstract class Endpoint { - public exec: (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, ip?: string | null, headers?: Record | null) => Promise; + public exec: (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: File, ip?: string | null, headers?: Record | null) => Promise; constructor(meta: T, paramDef: Ps, cb: executor) { const validate = ajv.compile(paramDef); - this.exec = (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, ip?: string | null, headers?: Record | null) => { + this.exec = (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: File, ip?: string | null, headers?: Record | null) => { let cleanup: undefined | (() => void) = undefined; if (meta.requireFile) { cleanup = () => { - fs.unlink(file.path, () => {}); + if (file) fs.unlink(file.path, () => {}); }; if (file == null) return Promise.reject(new ApiError({ 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 d394f5c3da..3f4485fc8b 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -78,8 +78,8 @@ export default class extends Endpoint { ) { super(meta, paramDef, async (ps, me, _, file, cleanup, ip, headers) => { // Get 'name' parameter - let name = ps.name ?? file.originalname; - if (name !== undefined && name !== null) { + let name = ps.name ?? file!.name ?? null; + if (name != null) { name = name.trim(); if (name.length === 0) { name = null; @@ -88,8 +88,6 @@ export default class extends Endpoint { } else if (!this.driveFileEntityService.validateFileName(name)) { throw new ApiError(meta.errors.invalidFileName); } - } else { - name = null; } const meta = await this.metaService.fetch(); @@ -98,7 +96,7 @@ export default class extends Endpoint { // Create file const driveFile = await this.driveService.addFile({ user: me, - path: file.path, + path: file!.path, name, comment: ps.comment, folderId: ps.folderId, diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index 1fd103797c..93c22a6c0b 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -1,9 +1,9 @@ import { Inject, Injectable } from '@nestjs/common'; import Redis from 'ioredis'; -import Router from '@koa/router'; import { OAuth2 } from 'oauth'; import { v4 as uuid } from 'uuid'; import { IsNull } from 'typeorm'; +import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import type { Config } from '@/config.js'; import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; @@ -12,8 +12,8 @@ import type { ILocalUser } from '@/models/entities/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { SigninService } from '../SigninService.js'; -import type Koa from 'koa'; @Injectable() export class DiscordServerService { @@ -36,21 +36,18 @@ export class DiscordServerService { private metaService: MetaService, private signinService: SigninService, ) { + this.create = this.create.bind(this); } - public create() { - const router = new Router(); - - router.get('/disconnect/discord', async ctx => { - if (!this.compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); - return; + public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + fastify.get('/disconnect/discord', async (request, reply) => { + if (!this.compareOrigin(request)) { + throw new FastifyReplyError(400, 'invalid origin'); } - const userToken = this.getUserToken(ctx); + const userToken = this.getUserToken(request); if (!userToken) { - ctx.throw(400, 'signin required'); - return; + throw new FastifyReplyError(400, 'signin required'); } const user = await this.usersRepository.findOneByOrFail({ @@ -66,13 +63,13 @@ export class DiscordServerService { integrations: profile.integrations, }); - ctx.body = 'Discordの連携を解除しました :v:'; - // Publish i updated event this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, { detail: true, includeSecrets: true, })); + + return 'Discordの連携を解除しました :v:'; }); const getOAuth2 = async () => { @@ -90,16 +87,14 @@ export class DiscordServerService { } }; - router.get('/connect/discord', async ctx => { - if (!this.compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); - return; + fastify.get('/connect/discord', async (request, reply) => { + if (!this.compareOrigin(request)) { + throw new FastifyReplyError(400, 'invalid origin'); } - const userToken = this.getUserToken(ctx); + const userToken = this.getUserToken(request); if (!userToken) { - ctx.throw(400, 'signin required'); - return; + throw new FastifyReplyError(400, 'signin required'); } const params = { @@ -112,10 +107,10 @@ export class DiscordServerService { this.redisClient.set(userToken, JSON.stringify(params)); const oauth2 = await getOAuth2(); - ctx.redirect(oauth2!.getAuthorizeUrl(params)); + reply.redirect(oauth2!.getAuthorizeUrl(params)); }); - router.get('/signin/discord', async ctx => { + fastify.get('/signin/discord', async (request, reply) => { const sessid = uuid(); const params = { @@ -125,7 +120,7 @@ export class DiscordServerService { response_type: 'code', }; - ctx.cookies.set('signin_with_discord_sid', sessid, { + reply.cookies.set('signin_with_discord_sid', sessid, { path: '/', secure: this.config.url.startsWith('https'), httpOnly: true, @@ -134,27 +129,25 @@ export class DiscordServerService { this.redisClient.set(sessid, JSON.stringify(params)); const oauth2 = await getOAuth2(); - ctx.redirect(oauth2!.getAuthorizeUrl(params)); + reply.redirect(oauth2!.getAuthorizeUrl(params)); }); - router.get('/dc/cb', async ctx => { - const userToken = this.getUserToken(ctx); + fastify.get('/dc/cb', async (request, reply) => { + const userToken = this.getUserToken(request); const oauth2 = await getOAuth2(); if (!userToken) { - const sessid = ctx.cookies.get('signin_with_discord_sid'); + const sessid = request.cookies.get('signin_with_discord_sid'); if (!sessid) { - ctx.throw(400, 'invalid session'); - return; + throw new FastifyReplyError(400, 'invalid session'); } - const code = ctx.query.code; + const code = request.query.code; if (!code || typeof code !== 'string') { - ctx.throw(400, 'invalid session'); - return; + throw new FastifyReplyError(400, 'invalid session'); } const { redirect_uri, state } = await new Promise((res, rej) => { @@ -164,9 +157,8 @@ export class DiscordServerService { }); }); - if (ctx.query.state !== state) { - ctx.throw(400, 'invalid session'); - return; + if (request.query.state !== state) { + throw new FastifyReplyError(400, 'invalid session'); } const { accessToken, refreshToken, expiresDate } = await new Promise((res, rej) => @@ -192,8 +184,7 @@ export class DiscordServerService { })) as Record; if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') { - ctx.throw(400, 'invalid session'); - return; + throw new FastifyReplyError(400, 'invalid session'); } const profile = await this.userProfilesRepository.createQueryBuilder() @@ -202,8 +193,7 @@ export class DiscordServerService { .getOne(); if (profile == null) { - ctx.throw(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`); - return; + throw new FastifyReplyError(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`); } await this.userProfilesRepository.update(profile.userId, { @@ -220,13 +210,12 @@ export class DiscordServerService { }, }); - this.signinService.signin(ctx, await this.usersRepository.findOneBy({ id: profile.userId }) as ILocalUser, true); + return this.signinService.signin(request, reply, await this.usersRepository.findOneBy({ id: profile.userId }) as ILocalUser, true); } else { - const code = ctx.query.code; + const code = request.query.code; if (!code || typeof code !== 'string') { - ctx.throw(400, 'invalid session'); - return; + throw new FastifyReplyError(400, 'invalid session'); } const { redirect_uri, state } = await new Promise((res, rej) => { @@ -236,9 +225,8 @@ export class DiscordServerService { }); }); - if (ctx.query.state !== state) { - ctx.throw(400, 'invalid session'); - return; + if (request.query.state !== state) { + throw new FastifyReplyError(400, 'invalid session'); } const { accessToken, refreshToken, expiresDate } = await new Promise((res, rej) => @@ -263,8 +251,7 @@ export class DiscordServerService { 'Authorization': `Bearer ${accessToken}`, })) as Record; if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') { - ctx.throw(400, 'invalid session'); - return; + throw new FastifyReplyError(400, 'invalid session'); } const user = await this.usersRepository.findOneByOrFail({ @@ -288,29 +275,29 @@ export class DiscordServerService { }, }); - ctx.body = `Discord: @${username}#${discriminator} を、Misskey: @${user.username} に接続しました!`; - // Publish i updated event this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, { detail: true, includeSecrets: true, })); + + return `Discord: @${username}#${discriminator} を、Misskey: @${user.username} に接続しました!`; } }); - return router; + done(); } - private getUserToken(ctx: Koa.BaseContext): string | null { - return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; + private getUserToken(request: FastifyRequest): string | null { + return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } - private compareOrigin(ctx: Koa.BaseContext): boolean { + private compareOrigin(request: FastifyRequest): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; } - const referer = ctx.headers['referer']; + const referer = request.headers['referer']; return (normalizeUrl(referer) === normalizeUrl(this.config.url)); } diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index 98d6230749..2fd20bf831 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -1,9 +1,9 @@ import { Inject, Injectable } from '@nestjs/common'; import Redis from 'ioredis'; -import Router from '@koa/router'; import { OAuth2 } from 'oauth'; import { v4 as uuid } from 'uuid'; import { IsNull } from 'typeorm'; +import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import type { Config } from '@/config.js'; import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; @@ -12,8 +12,8 @@ import type { ILocalUser } from '@/models/entities/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { SigninService } from '../SigninService.js'; -import type Koa from 'koa'; @Injectable() export class GithubServerService { @@ -36,21 +36,18 @@ export class GithubServerService { private metaService: MetaService, private signinService: SigninService, ) { + this.create = this.create.bind(this); } - public create() { - const router = new Router(); - - router.get('/disconnect/github', async ctx => { - if (!this.compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); - return; + public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + fastify.get('/disconnect/github', async (request, reply) => { + if (!this.compareOrigin(request)) { + throw new FastifyReplyError(400, 'invalid origin'); } - const userToken = this.getUserToken(ctx); + const userToken = this.getUserToken(request); if (!userToken) { - ctx.throw(400, 'signin required'); - return; + throw new FastifyReplyError(400, 'signin required'); } const user = await this.usersRepository.findOneByOrFail({ @@ -66,13 +63,13 @@ export class GithubServerService { integrations: profile.integrations, }); - ctx.body = 'GitHubの連携を解除しました :v:'; - // Publish i updated event this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, { detail: true, includeSecrets: true, })); + + return 'GitHubの連携を解除しました :v:'; }); const getOath2 = async () => { @@ -90,16 +87,14 @@ export class GithubServerService { } }; - router.get('/connect/github', async ctx => { - if (!this.compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); - return; + fastify.get('/connect/github', async (request, reply) => { + if (!this.compareOrigin(request)) { + throw new FastifyReplyError(400, 'invalid origin'); } - const userToken = this.getUserToken(ctx); + const userToken = this.getUserToken(request); if (!userToken) { - ctx.throw(400, 'signin required'); - return; + throw new FastifyReplyError(400, 'signin required'); } const params = { @@ -111,10 +106,10 @@ export class GithubServerService { this.redisClient.set(userToken, JSON.stringify(params)); const oauth2 = await getOath2(); - ctx.redirect(oauth2!.getAuthorizeUrl(params)); + reply.redirect(oauth2!.getAuthorizeUrl(params)); }); - router.get('/signin/github', async ctx => { + fastify.get('/signin/github', async (request, reply) => { const sessid = uuid(); const params = { @@ -123,7 +118,7 @@ export class GithubServerService { state: uuid(), }; - ctx.cookies.set('signin_with_github_sid', sessid, { + reply.cookies.set('signin_with_github_sid', sessid, { path: '/', secure: this.config.url.startsWith('https'), httpOnly: true, @@ -132,27 +127,25 @@ export class GithubServerService { this.redisClient.set(sessid, JSON.stringify(params)); const oauth2 = await getOath2(); - ctx.redirect(oauth2!.getAuthorizeUrl(params)); + reply.redirect(oauth2!.getAuthorizeUrl(params)); }); - router.get('/gh/cb', async ctx => { - const userToken = this.getUserToken(ctx); + fastify.get('/gh/cb', async (request, reply) => { + const userToken = this.getUserToken(request); const oauth2 = await getOath2(); if (!userToken) { - const sessid = ctx.cookies.get('signin_with_github_sid'); + const sessid = request.cookies.get('signin_with_github_sid'); if (!sessid) { - ctx.throw(400, 'invalid session'); - return; + throw new FastifyReplyError(400, 'invalid session'); } - const code = ctx.query.code; + const code = request.query.code; if (!code || typeof code !== 'string') { - ctx.throw(400, 'invalid session'); - return; + throw new FastifyReplyError(400, 'invalid session'); } const { redirect_uri, state } = await new Promise((res, rej) => { @@ -162,9 +155,8 @@ export class GithubServerService { }); }); - if (ctx.query.state !== state) { - ctx.throw(400, 'invalid session'); - return; + if (request.query.state !== state) { + throw new FastifyReplyError(400, 'invalid session'); } const { accessToken } = await new Promise<{ accessToken: string }>((res, rej) => @@ -184,8 +176,7 @@ export class GithubServerService { 'Authorization': `bearer ${accessToken}`, })) as Record; if (typeof login !== 'string' || typeof id !== 'string') { - ctx.throw(400, 'invalid session'); - return; + throw new FastifyReplyError(400, 'invalid session'); } const link = await this.userProfilesRepository.createQueryBuilder() @@ -194,17 +185,15 @@ export class GithubServerService { .getOne(); if (link == null) { - ctx.throw(404, `@${login}と連携しているMisskeyアカウントはありませんでした...`); - return; + throw new FastifyReplyError(404, `@${login}と連携しているMisskeyアカウントはありませんでした...`); } - this.signinService.signin(ctx, await this.usersRepository.findOneBy({ id: link.userId }) as ILocalUser, true); + return this.signinService.signin(request, reply, await this.usersRepository.findOneBy({ id: link.userId }) as ILocalUser, true); } else { - const code = ctx.query.code; + const code = request.query.code; if (!code || typeof code !== 'string') { - ctx.throw(400, 'invalid session'); - return; + throw new FastifyReplyError(400, 'invalid session'); } const { redirect_uri, state } = await new Promise((res, rej) => { @@ -214,9 +203,8 @@ export class GithubServerService { }); }); - if (ctx.query.state !== state) { - ctx.throw(400, 'invalid session'); - return; + if (request.query.state !== state) { + throw new FastifyReplyError(400, 'invalid session'); } const { accessToken } = await new Promise<{ accessToken: string }>((res, rej) => @@ -238,8 +226,7 @@ export class GithubServerService { })) as Record; if (typeof login !== 'string' || typeof id !== 'string') { - ctx.throw(400, 'invalid session'); - return; + throw new FastifyReplyError(400, 'invalid session'); } const user = await this.usersRepository.findOneByOrFail({ @@ -260,29 +247,29 @@ export class GithubServerService { }, }); - ctx.body = `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`; - // Publish i updated event this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, { detail: true, includeSecrets: true, })); + + return `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`; } }); - return router; + done(); } - private getUserToken(ctx: Koa.BaseContext): string | null { - return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; + private getUserToken(request: FastifyRequest): string | null { + return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } - private compareOrigin(ctx: Koa.BaseContext): boolean { + private compareOrigin(request: FastifyRequest): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; } - const referer = ctx.headers['referer']; + const referer = request.headers['referer']; return (normalizeUrl(referer) === normalizeUrl(this.config.url)); } diff --git a/packages/backend/src/server/api/integration/TwitterServerService.ts b/packages/backend/src/server/api/integration/TwitterServerService.ts index 57c977bc54..a8447f9d49 100644 --- a/packages/backend/src/server/api/integration/TwitterServerService.ts +++ b/packages/backend/src/server/api/integration/TwitterServerService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import Redis from 'ioredis'; -import Router from '@koa/router'; +import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import { v4 as uuid } from 'uuid'; import { IsNull } from 'typeorm'; import autwh from 'autwh'; @@ -12,8 +12,8 @@ import type { ILocalUser } from '@/models/entities/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { SigninService } from '../SigninService.js'; -import type Koa from 'koa'; @Injectable() export class TwitterServerService { @@ -36,21 +36,18 @@ export class TwitterServerService { private metaService: MetaService, private signinService: SigninService, ) { + this.create = this.create.bind(this); } - public create() { - const router = new Router(); - - router.get('/disconnect/twitter', async ctx => { - if (!this.compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); - return; + public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + fastify.get('/disconnect/twitter', async (request, reply) => { + if (!this.compareOrigin(request)) { + throw new FastifyReplyError(400, 'invalid origin'); } - const userToken = this.getUserToken(ctx); + const userToken = this.getUserToken(request); if (userToken == null) { - ctx.throw(400, 'signin required'); - return; + throw new FastifyReplyError(400, 'signin required'); } const user = await this.usersRepository.findOneByOrFail({ @@ -66,13 +63,13 @@ export class TwitterServerService { integrations: profile.integrations, }); - ctx.body = 'Twitterの連携を解除しました :v:'; - // Publish i updated event this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, { detail: true, includeSecrets: true, })); + + return 'Twitterの連携を解除しました :v:'; }); const getTwAuth = async () => { @@ -89,25 +86,23 @@ export class TwitterServerService { } }; - router.get('/connect/twitter', async ctx => { - if (!this.compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); - return; + fastify.get('/connect/twitter', async (request, reply) => { + if (!this.compareOrigin(request)) { + throw new FastifyReplyError(400, 'invalid origin'); } - const userToken = this.getUserToken(ctx); + const userToken = this.getUserToken(request); if (userToken == null) { - ctx.throw(400, 'signin required'); - return; + throw new FastifyReplyError(400, 'signin required'); } const twAuth = await getTwAuth(); const twCtx = await twAuth!.begin(); this.redisClient.set(userToken, JSON.stringify(twCtx)); - ctx.redirect(twCtx.url); + reply.redirect(twCtx.url); }); - router.get('/signin/twitter', async ctx => { + fastify.get('/signin/twitter', async (request, reply) => { const twAuth = await getTwAuth(); const twCtx = await twAuth!.begin(); @@ -115,26 +110,25 @@ export class TwitterServerService { this.redisClient.set(sessid, JSON.stringify(twCtx)); - ctx.cookies.set('signin_with_twitter_sid', sessid, { + reply.cookies.set('signin_with_twitter_sid', sessid, { path: '/', secure: this.config.url.startsWith('https'), httpOnly: true, }); - ctx.redirect(twCtx.url); + reply.redirect(twCtx.url); }); - router.get('/tw/cb', async ctx => { - const userToken = this.getUserToken(ctx); + fastify.get('/tw/cb', async (request, reply) => { + const userToken = this.getUserToken(request); const twAuth = await getTwAuth(); if (userToken == null) { - const sessid = ctx.cookies.get('signin_with_twitter_sid'); + const sessid = request.cookies.get('signin_with_twitter_sid'); if (sessid == null) { - ctx.throw(400, 'invalid session'); - return; + throw new FastifyReplyError(400, 'invalid session'); } const get = new Promise((res, rej) => { @@ -145,10 +139,9 @@ export class TwitterServerService { const twCtx = await get; - const verifier = ctx.query.oauth_verifier; + const verifier = request.query.oauth_verifier; if (!verifier || typeof verifier !== 'string') { - ctx.throw(400, 'invalid session'); - return; + throw new FastifyReplyError(400, 'invalid session'); } const result = await twAuth!.done(JSON.parse(twCtx), verifier); @@ -159,17 +152,15 @@ export class TwitterServerService { .getOne(); if (link == null) { - ctx.throw(404, `@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`); - return; + throw new FastifyReplyError(404, `@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`); } - this.signinService.signin(ctx, await this.usersRepository.findOneBy({ id: link.userId }) as ILocalUser, true); + return this.signinService.signin(request, reply, await this.usersRepository.findOneBy({ id: link.userId }) as ILocalUser, true); } else { - const verifier = ctx.query.oauth_verifier; + const verifier = request.query.oauth_verifier; if (!verifier || typeof verifier !== 'string') { - ctx.throw(400, 'invalid session'); - return; + throw new FastifyReplyError(400, 'invalid session'); } const get = new Promise((res, rej) => { @@ -201,29 +192,29 @@ export class TwitterServerService { }, }); - ctx.body = `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`; - // Publish i updated event this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, { detail: true, includeSecrets: true, })); + + return `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`; } }); - return router; + done(); } - private getUserToken(ctx: Koa.BaseContext): string | null { - return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; + private getUserToken(request: FastifyRequest): string | null { + return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } - private compareOrigin(ctx: Koa.BaseContext): boolean { + private compareOrigin(request: FastifyRequest): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; } - const referer = ctx.headers['referer']; + const referer = request.headers['referer']; return (normalizeUrl(referer) === normalizeUrl(this.config.url)); } diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 8957a91309..4c3f2bfd36 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -3,16 +3,12 @@ import { fileURLToPath } from 'node:url'; import { PathOrFileDescriptor, readFileSync } from 'node:fs'; import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; -import Koa from 'koa'; -import Router from '@koa/router'; -import send from 'koa-send'; -import favicon from 'koa-favicon'; -import views from 'koa-views'; import sharp from 'sharp'; -import { createBullBoard } from '@bull-board/api'; -import { BullAdapter } from '@bull-board/api/bullAdapter.js'; -import { KoaAdapter } from '@bull-board/koa'; +import pug from 'pug'; import { In, IsNull } from 'typeorm'; +import { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify'; +import fastifyStatic from '@fastify/static'; +import fastifyView from '@fastify/view'; import type { Config } from '@/config.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; import { DI } from '@/di-symbols.js'; @@ -84,9 +80,10 @@ export class ClientServerService { @Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue, @Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue, ) { + this.createServer = this.createServer.bind(this); } - private async manifestHandler(ctx: Koa.Context) { + private async manifestHandler(reply: FastifyReply) { const res = deepClone(manifest); const instance = await this.metaService.fetch(true); @@ -95,27 +92,26 @@ export class ClientServerService { res.name = instance.name ?? 'Misskey'; if (instance.themeColor) res.theme_color = instance.themeColor; - ctx.set('Cache-Control', 'max-age=300'); - ctx.body = res; + reply.header('Cache-Control', 'max-age=300'); + return (res); } - public createApp() { - const app = new Koa(); - + public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + /* TODO //#region Bull Dashboard const bullBoardPath = '/queue'; // Authenticate - app.use(async (ctx, next) => { + app.use(async (request, reply) => { if (ctx.path === bullBoardPath || ctx.path.startsWith(bullBoardPath + '/')) { const token = ctx.cookies.get('token'); if (token == null) { - ctx.status = 401; + reply.code(401); return; } const user = await this.usersRepository.findOneBy({ token }); if (user == null || !(user.isAdmin || user.isModerator)) { - ctx.status = 403; + reply.code(403); return; } } @@ -140,83 +136,84 @@ export class ClientServerService { serverAdapter.setBasePath(bullBoardPath); app.use(serverAdapter.registerPlugin()); //#endregion + */ - // Init renderer - app.use(views(_dirname + '/views', { - extension: 'pug', - options: { + fastify.register(fastifyView, { + root: _dirname + '/views', + engine: { + pug: pug, + }, + defaultContext: { version: this.config.version, getClientEntry: () => process.env.NODE_ENV === 'production' ? this.config.clientEntry : JSON.parse(readFileSync(`${_dirname}/../../../../../built/_client_dist_/manifest.json`, 'utf-8'))['src/init.ts'], config: this.config, }, - })); - - // Serve favicon - app.use(favicon(`${_dirname}/../../../assets/favicon.ico`)); - - // Common request handler - app.use(async (ctx, next) => { - // IFrameの中に入れられないようにする - ctx.set('X-Frame-Options', 'DENY'); - await next(); }); - // Init router - const router = new Router(); + fastify.addHook('onRequest', (request, reply, done) => { + // クリックジャッキング防止のためiFrameの中に入れられないようにする + reply.header('X-Frame-Options', 'DENY'); + done(); + }); //#region static assets - router.get('/static-assets/(.*)', async ctx => { - await send(ctx as any, ctx.path.replace('/static-assets/', ''), { - root: staticAssets, - maxage: ms('7 days'), - }); + fastify.register(fastifyStatic, { + root: _dirname, + serve: false, }); - router.get('/client-assets/(.*)', async ctx => { - await send(ctx as any, ctx.path.replace('/client-assets/', ''), { - root: clientAssets, - maxage: ms('7 days'), - }); + fastify.register(fastifyStatic, { + root: staticAssets, + prefix: '/static-assets/', + maxAge: ms('7 days'), + decorateReply: false, }); - router.get('/assets/(.*)', async ctx => { - await send(ctx as any, ctx.path.replace('/assets/', ''), { - root: assets, - maxage: ms('7 days'), - }); + fastify.register(fastifyStatic, { + root: clientAssets, + prefix: '/client-assets/', + maxAge: ms('7 days'), + decorateReply: false, }); - // Apple touch icon - router.get('/apple-touch-icon.png', async ctx => { - await send(ctx as any, '/apple-touch-icon.png', { - root: staticAssets, - }); + fastify.register(fastifyStatic, { + root: assets, + prefix: '/assets/', + maxAge: ms('7 days'), + decorateReply: false, + }); + + fastify.get('/favicon.ico', async (request, reply) => { + return reply.sendFile('/favicon.ico', staticAssets); + }); + + fastify.get('/apple-touch-icon.png', async (request, reply) => { + return reply.sendFile('/apple-touch-icon.png', staticAssets); }); - router.get('/twemoji/(.*)', async ctx => { - const path = ctx.path.replace('/twemoji/', ''); + fastify.get<{ Params: { path: string } }>('/twemoji/:path(.*)', async (request, reply) => { + const path = request.params.path; if (!path.match(/^[0-9a-f-]+\.svg$/)) { - ctx.status = 404; + reply.code(404); return; } - ctx.set('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); + reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); - await send(ctx as any, path, { - root: `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/`, - maxage: ms('30 days'), + return await reply.sendFile(path, `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/`, { + maxAge: ms('30 days'), }); }); - router.get('/twemoji-badge/(.*)', async ctx => { - const path = ctx.path.replace('/twemoji-badge/', ''); + fastify.get<{ Params: { path: string } }>('/twemoji-badge/:path(.*)', async (request, reply) => { + const path = request.params.path; if (!path.match(/^[0-9a-f-]+\.png$/)) { - ctx.status = 404; + reply.code(404); return; } @@ -249,44 +246,43 @@ export class ClientServerService { .png() .toBuffer(); - ctx.set('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); - ctx.set('Cache-Control', 'max-age=2592000'); - ctx.set('Content-Type', 'image/png'); - ctx.body = buffer; + reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); + reply.header('Cache-Control', 'max-age=2592000'); + reply.header('Content-Type', 'image/png'); + return buffer; }); // ServiceWorker - router.get('/sw.js', async ctx => { - await send(ctx as any, '/sw.js', { - root: swAssets, - maxage: ms('10 minutes'), + fastify.get('/sw.js', async (request, reply) => { + return await reply.sendFile('/sw.js', swAssets, { + maxAge: ms('10 minutes'), }); }); // Manifest - router.get('/manifest.json', ctx => this.manifestHandler(ctx)); + fastify.get('/manifest.json', async (request, reply) => await this.manifestHandler(reply)); - router.get('/robots.txt', async ctx => { - await send(ctx as any, '/robots.txt', { - root: staticAssets, - }); + fastify.get('/robots.txt', async (request, reply) => { + return await reply.sendFile('/robots.txt', staticAssets); }); //#endregion - // Docs - router.get('/api-doc', async ctx => { - await send(ctx as any, '/redoc.html', { - root: staticAssets, + const renderBase = async (reply: FastifyReply) => { + const meta = await this.metaService.fetch(); + reply.header('Cache-Control', 'public, max-age=15'); + return await reply.view('base', { + img: meta.bannerUrl, + title: meta.name ?? 'Misskey', + instanceName: meta.name ?? 'Misskey', + desc: meta.description, + icon: meta.iconUrl, + themeColor: meta.themeColor, }); - }); + }; // URL preview endpoint - router.get('/url', ctx => this.urlPreviewService.handle(ctx)); - - router.get('/api.json', async ctx => { - ctx.body = genOpenapiSpec(); - }); + fastify.get<{ Querystring: { url: string; lang: string; } }>('/url', (request, reply) => this.urlPreviewService.handle(request, reply)); const getFeed = async (acct: string) => { const { username, host } = Acct.parse(acct); @@ -300,45 +296,45 @@ export class ClientServerService { }; // Atom - router.get('/@:user.atom', async ctx => { - const feed = await getFeed(ctx.params.user); + fastify.get<{ Params: { user: string; } }>('/@:user.atom', async (request, reply) => { + const feed = await getFeed(request.params.user); if (feed) { - ctx.set('Content-Type', 'application/atom+xml; charset=utf-8'); - ctx.body = feed.atom1(); + reply.header('Content-Type', 'application/atom+xml; charset=utf-8'); + return feed.atom1(); } else { - ctx.status = 404; + reply.code(404); } }); // RSS - router.get('/@:user.rss', async ctx => { - const feed = await getFeed(ctx.params.user); + fastify.get<{ Params: { user: string; } }>('/@:user.rss', async (request, reply) => { + const feed = await getFeed(request.params.user); if (feed) { - ctx.set('Content-Type', 'application/rss+xml; charset=utf-8'); - ctx.body = feed.rss2(); + reply.header('Content-Type', 'application/rss+xml; charset=utf-8'); + return feed.rss2(); } else { - ctx.status = 404; + reply.code(404); } }); // JSON - router.get('/@:user.json', async ctx => { - const feed = await getFeed(ctx.params.user); + fastify.get<{ Params: { user: string; } }>('/@:user.json', async (request, reply) => { + const feed = await getFeed(request.params.user); if (feed) { - ctx.set('Content-Type', 'application/json; charset=utf-8'); - ctx.body = feed.json1(); + reply.header('Content-Type', 'application/json; charset=utf-8'); + return feed.json1(); } else { - ctx.status = 404; + reply.code(404); } }); //#region SSR (for crawlers) // User - router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => { - const { username, host } = Acct.parse(ctx.params.user); + fastify.get<{ Params: { user: string; sub?: string; } }>('/@:user/:sub?', async (request, reply) => { + const { username, host } = Acct.parse(request.params.user); const user = await this.usersRepository.findOneBy({ usernameLower: username.toLowerCase(), host: host ?? IsNull(), @@ -354,41 +350,41 @@ export class ClientServerService { .map(field => field.value) : []; - await ctx.render('user', { + reply.header('Cache-Control', 'public, max-age=15'); + return await reply.view('user', { user, profile, me, avatarUrl: await this.userEntityService.getAvatarUrl(user), - sub: ctx.params.sub, + sub: request.params.sub, instanceName: meta.name ?? 'Misskey', icon: meta.iconUrl, themeColor: meta.themeColor, }); - ctx.set('Cache-Control', 'public, max-age=15'); } else { // リモートユーザーなので // モデレータがAPI経由で参照可能にするために404にはしない - await next(); + return await renderBase(reply); } }); - router.get('/users/:user', async ctx => { + fastify.get<{ Params: { user: string; } }>('/users/:user', async (request, reply) => { const user = await this.usersRepository.findOneBy({ - id: ctx.params.user, + id: request.params.user, host: IsNull(), isSuspended: false, }); if (user == null) { - ctx.status = 404; + reply.code(404); return; } - ctx.redirect(`/@${user.username}${ user.host == null ? '' : '@' + user.host}`); + reply.redirect(`/@${user.username}${ user.host == null ? '' : '@' + user.host}`); }); // Note - router.get('/notes/:note', async (ctx, next) => { + fastify.get<{ Params: { note: string; } }>('/notes/:note', async (request, reply) => { const note = await this.notesRepository.findOneBy({ - id: ctx.params.note, + id: request.params.note, visibility: In(['public', 'home']), }); @@ -396,7 +392,8 @@ export class ClientServerService { const _note = await this.noteEntityService.pack(note); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId }); const meta = await this.metaService.fetch(); - await ctx.render('note', { + reply.header('Cache-Control', 'public, max-age=15'); + return await reply.view('note', { note: _note, profile, avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: note.userId })), @@ -406,18 +403,14 @@ export class ClientServerService { icon: meta.iconUrl, themeColor: meta.themeColor, }); - - ctx.set('Cache-Control', 'public, max-age=15'); - - return; + } else { + return await renderBase(reply); } - - await next(); }); // Page - router.get('/@:user/pages/:page', async (ctx, next) => { - const { username, host } = Acct.parse(ctx.params.user); + fastify.get<{ Params: { user: string; page: string; } }>('/@:user/pages/:page', async (request, reply) => { + const { username, host } = Acct.parse(request.params.user); const user = await this.usersRepository.findOneBy({ usernameLower: username.toLowerCase(), host: host ?? IsNull(), @@ -426,7 +419,7 @@ export class ClientServerService { if (user == null) return; const page = await this.pagesRepository.findOneBy({ - name: ctx.params.page, + name: request.params.page, userId: user.id, }); @@ -434,7 +427,12 @@ export class ClientServerService { const _page = await this.pageEntityService.pack(page); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: page.userId }); const meta = await this.metaService.fetch(); - await ctx.render('page', { + if (['public'].includes(page.visibility)) { + reply.header('Cache-Control', 'public, max-age=15'); + } else { + reply.header('Cache-Control', 'private, max-age=0, must-revalidate'); + } + return await reply.view('page', { page: _page, profile, avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: page.userId })), @@ -442,31 +440,24 @@ export class ClientServerService { icon: meta.iconUrl, themeColor: meta.themeColor, }); - - if (['public'].includes(page.visibility)) { - ctx.set('Cache-Control', 'public, max-age=15'); - } else { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); - } - - return; + } else { + return await renderBase(reply); } - - await next(); }); // Clip // TODO: 非publicなclipのハンドリング - router.get('/clips/:clip', async (ctx, next) => { + fastify.get<{ Params: { clip: string; } }>('/clips/:clip', async (request, reply) => { const clip = await this.clipsRepository.findOneBy({ - id: ctx.params.clip, + id: request.params.clip, }); if (clip) { const _clip = await this.clipEntityService.pack(clip); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId }); const meta = await this.metaService.fetch(); - await ctx.render('clip', { + reply.header('Cache-Control', 'public, max-age=15'); + return await reply.view('clip', { clip: _clip, profile, avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: clip.userId })), @@ -474,24 +465,21 @@ export class ClientServerService { icon: meta.iconUrl, themeColor: meta.themeColor, }); - - ctx.set('Cache-Control', 'public, max-age=15'); - - return; + } else { + return await renderBase(reply); } - - await next(); }); // Gallery post - router.get('/gallery/:post', async (ctx, next) => { - const post = await this.galleryPostsRepository.findOneBy({ id: ctx.params.post }); + fastify.get<{ Params: { post: string; } }>('/gallery/:post', async (request, reply) => { + const post = await this.galleryPostsRepository.findOneBy({ id: request.params.post }); if (post) { const _post = await this.galleryPostEntityService.pack(post); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId }); const meta = await this.metaService.fetch(); - await ctx.render('gallery-post', { + reply.header('Cache-Control', 'public, max-age=15'); + return await reply.view('gallery-post', { post: _post, profile, avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: post.userId })), @@ -499,46 +487,39 @@ export class ClientServerService { icon: meta.iconUrl, themeColor: meta.themeColor, }); - - ctx.set('Cache-Control', 'public, max-age=15'); - - return; + } else { + return await renderBase(reply); } - - await next(); }); // Channel - router.get('/channels/:channel', async (ctx, next) => { + fastify.get<{ Params: { channel: string; } }>('/channels/:channel', async (request, reply) => { const channel = await this.channelsRepository.findOneBy({ - id: ctx.params.channel, + id: request.params.channel, }); if (channel) { const _channel = await this.channelEntityService.pack(channel); const meta = await this.metaService.fetch(); - await ctx.render('channel', { + reply.header('Cache-Control', 'public, max-age=15'); + return await reply.view('channel', { channel: _channel, instanceName: meta.name ?? 'Misskey', icon: meta.iconUrl, themeColor: meta.themeColor, }); - - ctx.set('Cache-Control', 'public, max-age=15'); - - return; + } else { + return await renderBase(reply); } - - await next(); }); //#endregion - router.get('/_info_card_', async ctx => { + fastify.get('/_info_card_', async (request, reply) => { const meta = await this.metaService.fetch(true); - ctx.remove('X-Frame-Options'); + reply.removeHeader('X-Frame-Options'); - await ctx.render('info-card', { + return await reply.view('info-card', { version: this.config.version, host: this.config.host, meta: meta, @@ -547,14 +528,14 @@ export class ClientServerService { }); }); - router.get('/bios', async ctx => { - await ctx.render('bios', { + fastify.get('/bios', async (request, reply) => { + return await reply.view('bios', { version: this.config.version, }); }); - router.get('/cli', async ctx => { - await ctx.render('cli', { + fastify.get('/cli', async (request, reply) => { + return await reply.view('cli', { version: this.config.version, }); }); @@ -562,33 +543,21 @@ export class ClientServerService { const override = (source: string, target: string, depth = 0) => [, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); - router.get('/flush', async ctx => { - await ctx.render('flush'); + fastify.get('/flush', async (request, reply) => { + return await reply.view('flush'); }); // streamingに非WebSocketリクエストが来た場合にbase htmlをキャシュ付きで返すと、Proxy等でそのパスがキャッシュされておかしくなる - router.get('/streaming', async ctx => { - ctx.status = 503; - ctx.set('Cache-Control', 'private, max-age=0'); + fastify.get('/streaming', async (request, reply) => { + reply.code(503); + reply.header('Cache-Control', 'private, max-age=0'); }); // Render base html for all requests - router.get('(.*)', async ctx => { - const meta = await this.metaService.fetch(); - await ctx.render('base', { - img: meta.bannerUrl, - title: meta.name ?? 'Misskey', - instanceName: meta.name ?? 'Misskey', - desc: meta.description, - icon: meta.iconUrl, - themeColor: meta.themeColor, - }); - ctx.set('Cache-Control', 'public, max-age=15'); + fastify.get('*', async (request, reply) => { + return await renderBase(reply); }); - // Register router - app.use(router.routes()); - - return app; + done(); } } diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index f5dddd2db7..69f52cc2f2 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -1,5 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import summaly from 'summaly'; +import { FastifyRequest, FastifyReply } from 'fastify'; import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; @@ -8,7 +9,6 @@ import { HttpRequestService } from '@/core/HttpRequestService.js'; import type Logger from '@/logger.js'; import { query } from '@/misc/prelude/url.js'; import { LoggerService } from '@/core/LoggerService.js'; -import type Koa from 'koa'; @Injectable() export class UrlPreviewService { @@ -39,16 +39,19 @@ export class UrlPreviewService { : null; } - public async handle(ctx: Koa.Context) { - const url = ctx.query.url; + public async handle( + request: FastifyRequest<{ Querystring: { url: string; lang: string; } }>, + reply: FastifyReply, + ) { + const url = request.query.url; if (typeof url !== 'string') { - ctx.status = 400; + reply.code(400); return; } - const lang = ctx.query.lang; + const lang = request.query.lang; if (Array.isArray(lang)) { - ctx.status = 400; + reply.code(400); return; } @@ -73,14 +76,14 @@ export class UrlPreviewService { summary.thumbnail = this.wrap(summary.thumbnail); // Cache 7days - ctx.set('Cache-Control', 'max-age=604800, immutable'); + reply.header('Cache-Control', 'max-age=604800, immutable'); - ctx.body = summary; + return summary; } catch (err) { this.logger.warn(`Failed to get preview of ${url}: ${err}`); - ctx.status = 200; - ctx.set('Cache-Control', 'max-age=86400, immutable'); - ctx.body = '{}'; + reply.code(200); + reply.header('Cache-Control', 'max-age=86400, immutable'); + return {}; } } } diff --git a/packages/backend/src/server/web/bios.js b/packages/backend/src/server/web/bios.js index d06dee801a..c2ce5c3814 100644 --- a/packages/backend/src/server/web/bios.js +++ b/packages/backend/src/server/web/bios.js @@ -10,7 +10,7 @@ window.onload = async () => { if (i) data.i = i; // Send request - fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, { + window.fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, { method: 'POST', body: JSON.stringify(data), credentials: 'omit', diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 2aef689d3f..ffd8b8941c 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -42,7 +42,7 @@ } } - const res = await fetch(`/assets/locales/${lang}.${v}.json`); + const res = await window.fetch(`/assets/locales/${lang}.${v}.json`); if (res.status === 200) { localStorage.setItem('lang', lang); localStorage.setItem('locale', await res.text()); @@ -290,9 +290,13 @@ // eslint-disable-next-line no-inner-declarations async function checkUpdate() { try { - const res = await fetch('/api/meta', { + const res = await window.fetch('/api/meta', { method: 'POST', - cache: 'no-cache' + cache: 'no-cache', + body: '{}', + headers: { + 'Content-Type': 'application/json', + }, }); const meta = await res.json(); diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index 10257b841f..e9c29d6b0d 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -33,12 +33,15 @@ export async function signout() { const registration = await navigator.serviceWorker.ready; const push = await registration.pushManager.getSubscription(); if (push) { - await fetch(`${apiUrl}/sw/unregister`, { + await window.fetch(`${apiUrl}/sw/unregister`, { method: 'POST', body: JSON.stringify({ i: $i.token, endpoint: push.endpoint, }), + headers: { + 'Content-Type': 'application/json', + }, }); } } @@ -80,32 +83,35 @@ export async function removeAccount(id: Account['id']) { function fetchAccount(token: string): Promise { return new Promise((done, fail) => { // Fetch user - fetch(`${apiUrl}/i`, { + window.fetch(`${apiUrl}/i`, { method: 'POST', body: JSON.stringify({ i: token, }), + headers: { + 'Content-Type': 'application/json', + }, }) - .then(res => res.json()) - .then(res => { - if (res.error) { - if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') { - showSuspendedDialog().then(() => { - signout(); - }); + .then(res => res.json()) + .then(res => { + if (res.error) { + if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') { + showSuspendedDialog().then(() => { + signout(); + }); + } else { + alert({ + type: 'error', + title: i18n.ts.failedToFetchAccountInformation, + text: JSON.stringify(res.error), + }); + } } else { - alert({ - type: 'error', - title: i18n.ts.failedToFetchAccountInformation, - text: JSON.stringify(res.error), - }); + res.token = token; + done(res); } - } else { - res.token = token; - done(res); - } - }) - .catch(fail); + }) + .catch(fail); }); } diff --git a/packages/client/src/components/MkCropperDialog.vue b/packages/client/src/components/MkCropperDialog.vue index 4b05a51252..ae18160dea 100644 --- a/packages/client/src/components/MkCropperDialog.vue +++ b/packages/client/src/components/MkCropperDialog.vue @@ -66,7 +66,7 @@ const ok = async () => { formData.append('folderId', defaultStore.state.uploadFolder); } - fetch(apiUrl + '/drive/files/create', { + window.fetch(apiUrl + '/drive/files/create', { method: 'POST', body: formData, }) diff --git a/packages/client/src/components/MkUrlPreview.vue b/packages/client/src/components/MkUrlPreview.vue index af27f644ed..8fd1ce133d 100644 --- a/packages/client/src/components/MkUrlPreview.vue +++ b/packages/client/src/components/MkUrlPreview.vue @@ -68,7 +68,7 @@ let player = $ref({ let playerEnabled = $ref(false); let tweetId = $ref(null); let tweetExpanded = $ref(props.detail); -const embedId = `embed${Math.random().toString().replace(/\D/,'')}`; +const embedId = `embed${Math.random().toString().replace(/\D/, '')}`; let tweetHeight = $ref(150); const requestUrl = new URL(props.url); @@ -86,7 +86,7 @@ const requestLang = (lang || 'ja-JP').replace('ja-KS', 'ja-JP'); requestUrl.hash = ''; -fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => { +window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => { res.json().then(info => { if (info.url == null) return; title = info.title; diff --git a/packages/client/src/components/MkYoutubePlayer.vue b/packages/client/src/components/MkYoutubePlayer.vue index a6840ce647..815dc82a27 100644 --- a/packages/client/src/components/MkYoutubePlayer.vue +++ b/packages/client/src/components/MkYoutubePlayer.vue @@ -39,7 +39,7 @@ const requestLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP'); const ytFetch = (): void => { fetching = true; - fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => { + window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => { res.json().then(info => { if (info.url == null) return; title = info.title; diff --git a/packages/client/src/components/page/page.post.vue b/packages/client/src/components/page/page.post.vue index f655196359..954c7675bd 100644 --- a/packages/client/src/components/page/page.post.vue +++ b/packages/client/src/components/page/page.post.vue @@ -25,12 +25,12 @@ export default defineComponent({ props: { block: { type: Object as PropType, - required: true + required: true, }, hpml: { type: Object as PropType, - required: true - } + required: true, + }, }, data() { return { @@ -44,8 +44,8 @@ export default defineComponent({ handler() { this.text = this.hpml.interpolate(this.block.text); }, - deep: true - } + deep: true, + }, }, methods: { upload() { @@ -59,14 +59,14 @@ export default defineComponent({ formData.append('folderId', this.$store.state.uploadFolder); } - fetch(apiUrl + '/drive/files/create', { + window.fetch(apiUrl + '/drive/files/create', { method: 'POST', body: formData, }) - .then(response => response.json()) - .then(f => { - ok(f); - }); + .then(response => response.json()) + .then(f => { + ok(f); + }); }); }); os.promiseDialog(promise); @@ -81,8 +81,8 @@ export default defineComponent({ }).then(() => { this.posted = true; }); - } - } + }, + }, }); diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index 515fc47819..7e57dcb4af 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -29,11 +29,14 @@ export const api = ((endpoint: string, data: Record = {}, token?: s if (token !== undefined) (data as any).i = token; // Send request - fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, { + window.fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, { method: 'POST', body: JSON.stringify(data), credentials: 'omit', cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, }).then(async (res) => { const body = res.status === 204 ? null : await res.json(); @@ -63,7 +66,7 @@ export const apiGet = ((endpoint: string, data: Record = {}) => { const promise = new Promise((resolve, reject) => { // Send request - fetch(`${apiUrl}/${endpoint}?${query}`, { + window.fetch(`${apiUrl}/${endpoint}?${query}`, { method: 'GET', credentials: 'omit', cache: 'default', diff --git a/packages/client/src/ui/_common_/statusbar-rss.vue b/packages/client/src/ui/_common_/statusbar-rss.vue index e75e13bb48..e7f88e4984 100644 --- a/packages/client/src/ui/_common_/statusbar-rss.vue +++ b/packages/client/src/ui/_common_/statusbar-rss.vue @@ -37,7 +37,7 @@ const fetching = ref(true); let key = $ref(0); const tick = () => { - fetch(`/api/fetch-rss?url=${props.url}`, {}).then(res => { + window.fetch(`/api/fetch-rss?url=${props.url}`, {}).then(res => { res.json().then(feed => { if (props.shuffle) { shuffle(feed.items); diff --git a/packages/client/src/widgets/rss-ticker.vue b/packages/client/src/widgets/rss-ticker.vue index 58c16983c8..82a2f59ae9 100644 --- a/packages/client/src/widgets/rss-ticker.vue +++ b/packages/client/src/widgets/rss-ticker.vue @@ -83,7 +83,7 @@ const fetching = ref(true); let key = $ref(0); const tick = () => { - fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => { + window.fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => { res.json().then(feed => { if (widgetProps.shuffle) { shuffle(feed.items); diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue index 3258b6c028..f392a8249a 100644 --- a/packages/client/src/widgets/rss.vue +++ b/packages/client/src/widgets/rss.vue @@ -51,7 +51,7 @@ const items = ref([]); const fetching = ref(true); const tick = () => { - fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => { + window.fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => { res.json().then(feed => { items.value = feed.items; fetching.value = false; diff --git a/yarn.lock b/yarn.lock index 61d81c0017..06bf9d8fd1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -417,40 +417,6 @@ __metadata: languageName: node linkType: hard -"@bull-board/api@npm:4.3.1": - version: 4.3.1 - resolution: "@bull-board/api@npm:4.3.1" - dependencies: - redis-info: ^3.0.8 - checksum: 05113b1e888e79f8efecdffdc1043455fa6f8714c55a1e973d8a0a7f60cf574b00487b5b86324523ff91641784a55ff14c469edc8dd985295dcfc27cf55b4c4a - languageName: node - linkType: hard - -"@bull-board/koa@npm:4.3.1": - version: 4.3.1 - resolution: "@bull-board/koa@npm:4.3.1" - dependencies: - "@bull-board/api": 4.3.1 - "@bull-board/ui": 4.3.1 - ejs: ^3.1.7 - koa: ^2.13.1 - koa-mount: ^4.0.0 - koa-router: ^10.0.0 - koa-static: ^5.0.0 - koa-views: ^7.0.1 - checksum: 08f198cdaaa28fe8e254288a0d4c13e9cd481a97e40e5e9152fb9094cbac54459e86901da5d90c46fe2dccf310f78a50ef8763bf5980b98d33180299c64fbc3f - languageName: node - linkType: hard - -"@bull-board/ui@npm:4.3.1": - version: 4.3.1 - resolution: "@bull-board/ui@npm:4.3.1" - dependencies: - "@bull-board/api": 4.3.1 - checksum: 7bc4787ba8f9e3dda5cb580b4374872bc7b0870a08a504cfc2f380a39dda164ae71518e7b1921e53ff8abc4224c0861504b26b63510b6c9c9d23d647bdab54b2 - languageName: node - linkType: hard - "@chainsafe/is-ip@npm:^2.0.1": version: 2.0.1 resolution: "@chainsafe/is-ip@npm:2.0.1" @@ -654,18 +620,6 @@ __metadata: languageName: node linkType: hard -"@elastic/elasticsearch@npm:7.17.0": - version: 7.17.0 - resolution: "@elastic/elasticsearch@npm:7.17.0" - dependencies: - debug: ^4.3.1 - hpagent: ^0.1.1 - ms: ^2.1.3 - secure-json-parse: ^2.4.0 - checksum: 08113bcb14203c5700e6575cb720aa32f5573a1776e13b78bf101ffeae46308c3664b94f16f7e2c5ec26e14c459d8d1491e234940e635cddfe3ee61a52fb51f9 - languageName: node - linkType: hard - "@esbuild/android-arm@npm:0.15.16": version: 0.15.16 resolution: "@esbuild/android-arm@npm:0.15.16" @@ -704,6 +658,117 @@ __metadata: languageName: node linkType: hard +"@fastify/accept-negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "@fastify/accept-negotiator@npm:1.0.0" + checksum: 9b6be6bfd0f1475e4d06ffbd6359d7cf70841cc8e37abe6fe32f2e0e8185da6077da0d5c6610ade356ed3d4af4d3a642094b4861b123607987491bef2a384957 + languageName: node + linkType: hard + +"@fastify/accepts@npm:4.0.1": + version: 4.0.1 + resolution: "@fastify/accepts@npm:4.0.1" + dependencies: + accepts: ^1.3.5 + fastify-plugin: ^4.0.0 + checksum: bd14a998dececc66cbfc331aac6e9063c19baa8915e44288a621ef13737df5ca11e83f4be5767951ed6b8ddded2b2a40c19ec630c68a3ac9c299afe50d5e4153 + languageName: node + linkType: hard + +"@fastify/ajv-compiler@npm:^3.3.1": + version: 3.4.0 + resolution: "@fastify/ajv-compiler@npm:3.4.0" + dependencies: + ajv: ^8.11.0 + ajv-formats: ^2.1.1 + fast-uri: ^2.0.0 + checksum: 3e03f9673f0f13ce343bfb4a84f4e908d12bd775a2b82ff4bdf09ac062d09c6b89b62df7f96fab970dd61f77a9e43be2908eb28cd59e27654b25931444bde825 + languageName: node + linkType: hard + +"@fastify/busboy@npm:^1.0.0": + version: 1.1.0 + resolution: "@fastify/busboy@npm:1.1.0" + dependencies: + text-decoding: ^1.0.0 + checksum: 8ef01870c5e2ddae787fb8775c844b26e54c366732565287d5f7fb7d8c3c746bd4f0ad8f8695f4006e4b63af2fdd9a206ca74b2bc6c5d3c96d88abc07daa16f5 + languageName: node + linkType: hard + +"@fastify/cors@npm:8.2.0": + version: 8.2.0 + resolution: "@fastify/cors@npm:8.2.0" + dependencies: + fastify-plugin: ^4.0.0 + mnemonist: 0.39.5 + checksum: b2e30602d3aad7b2170a153b60e2b0dba8ad7df67ac3b7918374d202097f60b8a252baeafbf37f4323190fa87170960a3162aa6120540f825ae7750414c3feea + languageName: node + linkType: hard + +"@fastify/deepmerge@npm:^1.0.0": + version: 1.2.0 + resolution: "@fastify/deepmerge@npm:1.2.0" + checksum: 40f39aa859dbf90cf5cd09b0a06e86783e68f8046baad51f79e42c77db4c6ffe436e130103ade6731d81df3916818d1437ad88d1a6c53c56e809aa1a910f4c9a + languageName: node + linkType: hard + +"@fastify/error@npm:^3.0.0": + version: 3.0.0 + resolution: "@fastify/error@npm:3.0.0" + checksum: d9ea16db2d17e4d54f34ad2daf7bbd223fd3fd5682e55406f61dae66616a2fd79fa7585736e6e3b46e9dc60da6e96018f92ebb2f87fd100b4e8ad27308aa9c74 + languageName: node + linkType: hard + +"@fastify/fast-json-stringify-compiler@npm:^4.1.0": + version: 4.1.0 + resolution: "@fastify/fast-json-stringify-compiler@npm:4.1.0" + dependencies: + fast-json-stringify: ^5.0.0 + checksum: 5f848f606e23b04904189bf98c44ccae70c4ceaa793d619d3804ba4a9969d4b9846ceef4ac8a53d536a1cf8f1d3c30a4602850a44fc62bdc1893e341442b6e4f + languageName: node + linkType: hard + +"@fastify/multipart@npm:7.3.0": + version: 7.3.0 + resolution: "@fastify/multipart@npm:7.3.0" + dependencies: + "@fastify/busboy": ^1.0.0 + "@fastify/deepmerge": ^1.0.0 + "@fastify/error": ^3.0.0 + end-of-stream: ^1.4.4 + fastify-plugin: ^4.0.0 + hexoid: ^1.0.0 + secure-json-parse: ^2.4.0 + stream-wormhole: ^1.1.0 + checksum: 192fc4f0892c34d342a3673c6522e13c0987747c4972b52ea48ca7978ea54b5a892d4594778b643dc35f37f429496b13a4e244d8c7eef60a852fadb52144fcbe + languageName: node + linkType: hard + +"@fastify/static@npm:6.5.0": + version: 6.5.0 + resolution: "@fastify/static@npm:6.5.0" + dependencies: + "@fastify/accept-negotiator": ^1.0.0 + content-disposition: ^0.5.3 + fastify-plugin: ^4.0.0 + glob: ^8.0.1 + p-limit: ^3.1.0 + readable-stream: ^4.0.0 + send: ^0.18.0 + checksum: 31ef10916847c51fb4c360860f56acee1cbd7a896b66a05440d4eff70473c5b4cdf908b4719a5a22a876617fdc4a4e0fe3673bd5b40c9d61260a90e08abc1d3d + languageName: node + linkType: hard + +"@fastify/view@npm:7.1.2": + version: 7.1.2 + resolution: "@fastify/view@npm:7.1.2" + dependencies: + fastify-plugin: ^4.0.0 + hashlru: ^2.3.0 + checksum: 0d5c960dc4241ca09faf4b990aabf6ccdac27348da2e75682a738d5665973a133fbf6907309bbf002f5cb36d55240b698283eac372f521cf2a482a9c80ff788d + languageName: node + linkType: hard + "@fortawesome/fontawesome-free@npm:6.1.2": version: 6.1.2 resolution: "@fortawesome/fontawesome-free@npm:6.1.2" @@ -1110,37 +1175,6 @@ __metadata: languageName: node linkType: hard -"@koa/cors@npm:3.3.0": - version: 3.3.0 - resolution: "@koa/cors@npm:3.3.0" - dependencies: - vary: ^1.1.2 - checksum: bb49c680e0d151aec1b19c24c14d61b65f430eb379e63d83789602cc7d8e52706ebcd74867cdb60b1d50ddb6f3d59be04e1c46328fae5721aeaf50e0d4fc2d28 - languageName: node - linkType: hard - -"@koa/multer@npm:3.0.0": - version: 3.0.0 - resolution: "@koa/multer@npm:3.0.0" - peerDependencies: - multer: "*" - checksum: 7671ffed2ab23224b30b4378b44b2db1749d36f82216b0b67ae349fd6fe86b40c4a72c98412499e059f69916b454fba152451f140adf288866462ba765eb6c32 - languageName: node - linkType: hard - -"@koa/router@npm:9.0.1": - version: 9.0.1 - resolution: "@koa/router@npm:9.0.1" - dependencies: - debug: ^4.1.1 - http-errors: ^1.7.3 - koa-compose: ^4.1.0 - methods: ^1.1.2 - path-to-regexp: ^6.1.0 - checksum: 0013bfd26c1acd44c772a08adae5edac8168c662fd2a06c7e174e2a899806908e73e077234c26888fad3ddc54478b27e5b8c7f5db41cd2e27f3b837c54b2f9b8 - languageName: node - linkType: hard - "@mapbox/node-pre-gyp@npm:1.0.9": version: 1.0.9 resolution: "@mapbox/node-pre-gyp@npm:1.0.9" @@ -1894,7 +1928,7 @@ __metadata: languageName: node linkType: hard -"@types/accepts@npm:*": +"@types/accepts@npm:1.3.5": version: 1.3.5 resolution: "@types/accepts@npm:1.3.5" dependencies: @@ -1960,16 +1994,6 @@ __metadata: languageName: node linkType: hard -"@types/body-parser@npm:*": - version: 1.19.2 - resolution: "@types/body-parser@npm:1.19.2" - dependencies: - "@types/connect": "*" - "@types/node": "*" - checksum: e17840c7d747a549f00aebe72c89313d09fbc4b632b949b2470c5cb3b1cb73863901ae84d9335b567a79ec5efcfb8a28ff8e3f36bc8748a9686756b6d5681f40 - languageName: node - linkType: hard - "@types/bull@npm:4.10.0": version: 4.10.0 resolution: "@types/bull@npm:4.10.0" @@ -2000,34 +2024,6 @@ __metadata: languageName: node linkType: hard -"@types/connect@npm:*": - version: 3.4.35 - resolution: "@types/connect@npm:3.4.35" - dependencies: - "@types/node": "*" - checksum: fe81351470f2d3165e8b12ce33542eef89ea893e36dd62e8f7d72566dfb7e448376ae962f9f3ea888547ce8b55a40020ca0e01d637fab5d99567673084542641 - languageName: node - linkType: hard - -"@types/content-disposition@npm:*": - version: 0.5.5 - resolution: "@types/content-disposition@npm:0.5.5" - checksum: fdf7379db1d509990bcf9a21d85f05aad878596f28b1418f9179f6436cb22513262c670ce88c6055054a7f5804a9303eeacb70aa59a5e11ffdc1434559db9692 - languageName: node - linkType: hard - -"@types/cookies@npm:*": - version: 0.7.7 - resolution: "@types/cookies@npm:0.7.7" - dependencies: - "@types/connect": "*" - "@types/express": "*" - "@types/keygrip": "*" - "@types/node": "*" - checksum: d3759efc1182cb0651808570ae13638677b67b0ea724eef7b174e58ffe6ea044b62c7c2715e532f76f88fce4dd8101ed32ac6fbb73226db654017924e8a2a1e6 - languageName: node - linkType: hard - "@types/disposable-email-domains@npm:^1.0.1": version: 1.0.2 resolution: "@types/disposable-email-domains@npm:1.0.2" @@ -2056,29 +2052,6 @@ __metadata: languageName: node linkType: hard -"@types/express-serve-static-core@npm:^4.17.18": - version: 4.17.31 - resolution: "@types/express-serve-static-core@npm:4.17.31" - dependencies: - "@types/node": "*" - "@types/qs": "*" - "@types/range-parser": "*" - checksum: 009bfbe1070837454a1056aa710d0390ee5fb8c05dfe5a1691cc3e2ca88dc256f80e1ca27cb51a978681631d2f6431bfc9ec352ea46dd0c6eb183d0170bde5df - languageName: node - linkType: hard - -"@types/express@npm:*": - version: 4.17.14 - resolution: "@types/express@npm:4.17.14" - dependencies: - "@types/body-parser": "*" - "@types/express-serve-static-core": ^4.17.18 - "@types/qs": "*" - "@types/serve-static": "*" - checksum: 15c1af46d02de834e4a225eccaa9d85c0370fdbb3ed4e1bc2d323d24872309961542b993ae236335aeb3e278630224a6ea002078d39e651d78a3b0356b1eaa79 - languageName: node - linkType: hard - "@types/fluent-ffmpeg@npm:2.1.20": version: 2.1.20 resolution: "@types/fluent-ffmpeg@npm:2.1.20" @@ -2138,13 +2111,6 @@ __metadata: languageName: node linkType: hard -"@types/http-assert@npm:*": - version: 1.5.3 - resolution: "@types/http-assert@npm:1.5.3" - checksum: 9553e5a0b8bcfdac4b51d3fa3b89a91b5450171861a667a5b4c47204e0f4a1ca865d97396e6ceaf220e87b64d06b7a8bad7bfba15ef97acb41a87507c9940dbc - languageName: node - linkType: hard - "@types/http-cache-semantics@npm:*, @types/http-cache-semantics@npm:^4.0.1": version: 4.0.1 resolution: "@types/http-cache-semantics@npm:4.0.1" @@ -2152,13 +2118,6 @@ __metadata: languageName: node linkType: hard -"@types/http-errors@npm:*": - version: 2.0.1 - resolution: "@types/http-errors@npm:2.0.1" - checksum: 3bb0c50b0a652e679a84c30cd0340d696c32ef6558518268c238840346c077f899315daaf1c26c09c57ddd5dc80510f2a7f46acd52bf949e339e35ed3ee9654f - languageName: node - linkType: hard - "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.4 resolution: "@types/istanbul-lib-coverage@npm:2.0.4" @@ -2247,13 +2206,6 @@ __metadata: languageName: node linkType: hard -"@types/keygrip@npm:*": - version: 1.0.2 - resolution: "@types/keygrip@npm:1.0.2" - checksum: 60bc2738a4f107070ee3d96f44709cb38f3a96c7ccabab09f56c1b2b4d85f869fd8fb9f1f2937e863d0e9e781f005c2223b823bf32b859185b4f52370c352669 - languageName: node - linkType: hard - "@types/keyv@npm:^3.1.4": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4" @@ -2263,121 +2215,6 @@ __metadata: languageName: node linkType: hard -"@types/koa-bodyparser@npm:4.3.8": - version: 4.3.8 - resolution: "@types/koa-bodyparser@npm:4.3.8" - dependencies: - "@types/koa": "*" - checksum: df4501f4c29e24e6ffe4149accaaa4ac3aee9e0cac8fe0239f53f36bff4acfde4ffb4294212453efaa8a4231c66d139a9577f0d25cc491832c1b2904a5943f89 - languageName: node - linkType: hard - -"@types/koa-compose@npm:*": - version: 3.2.5 - resolution: "@types/koa-compose@npm:3.2.5" - dependencies: - "@types/koa": "*" - checksum: 5d1147c4b057eb158195f442f0384f06503f3e69dba99fb517b30a05261a9f92928945c12bb1cfc17a5b7d60db003f38b455a3a9b125f12e4fc81fffa396b3cf - languageName: node - linkType: hard - -"@types/koa-cors@npm:0.0.2": - version: 0.0.2 - resolution: "@types/koa-cors@npm:0.0.2" - dependencies: - "@types/koa": "*" - checksum: 7218bd8f4600fede227626e01fabe2022c691ee8721945792eb3dba3b348b10ddc438c3a679734de783172be512eb6b780d0600ed7052c3f881ed234a601656e - languageName: node - linkType: hard - -"@types/koa-favicon@npm:2.0.21": - version: 2.0.21 - resolution: "@types/koa-favicon@npm:2.0.21" - dependencies: - "@types/koa": "*" - checksum: 7e3da0dd430a96a007845be4d0d918281d5177ee36511c182f23048179168aac9b8b1fcd2a3de0b6e0f89b70fcb2a23c7b278de735e6daf71ef5cdea50761593 - languageName: node - linkType: hard - -"@types/koa-logger@npm:3.1.2": - version: 3.1.2 - resolution: "@types/koa-logger@npm:3.1.2" - dependencies: - "@types/koa": "*" - checksum: 8e4cfcdb2491052bbff35c8f0a1f0bf839e966c3903afcf39656bf21bd3089b1a50945ce6a92bea430a83c9341d714c968360953d3a52a5cc10cdb3fb0af4218 - languageName: node - linkType: hard - -"@types/koa-mount@npm:4.0.1": - version: 4.0.1 - resolution: "@types/koa-mount@npm:4.0.1" - dependencies: - "@types/koa": "*" - checksum: c010bfe6b2d81e6b1ca163b7699a5a0c90414079fcbc1d44c4005c896486db0d22b8220bd5f68a1cca7be481dba6a3b4f1c05b6affd80954c724d81170532f33 - languageName: node - linkType: hard - -"@types/koa-send@npm:4.1.3": - version: 4.1.3 - resolution: "@types/koa-send@npm:4.1.3" - dependencies: - "@types/koa": "*" - checksum: f20f6a0dcccd0d090348c7cce3635220cc82420b9579fa521dc6deae23c242aa8adb760a5a3fc84d7590a7f393b41b71b18312f9519c1c4a0b16ee24aae2e104 - languageName: node - linkType: hard - -"@types/koa-views@npm:7.0.0": - version: 7.0.0 - resolution: "@types/koa-views@npm:7.0.0" - dependencies: - koa-views: "*" - checksum: 03253380413e82806ef14c8f3ecadd659e0b00e4e8483fa9d8fe52f369edbfff02a779a5e09fca11eeaabf555dcc139378c8b123d1e4fc49125cb14ed27b3cab - languageName: node - linkType: hard - -"@types/koa@npm:*, @types/koa@npm:2.13.5": - version: 2.13.5 - resolution: "@types/koa@npm:2.13.5" - dependencies: - "@types/accepts": "*" - "@types/content-disposition": "*" - "@types/cookies": "*" - "@types/http-assert": "*" - "@types/http-errors": "*" - "@types/keygrip": "*" - "@types/koa-compose": "*" - "@types/node": "*" - checksum: e3b634d934b79ce8f394bf4130511596081f9c073dbfb4309aa32e4c421c47049a002b65111f8d9687eabec55d5a27b1b9ae0699afa83894cb7032c3536bfa17 - languageName: node - linkType: hard - -"@types/koa__cors@npm:3.3.0": - version: 3.3.0 - resolution: "@types/koa__cors@npm:3.3.0" - dependencies: - "@types/koa": "*" - checksum: c1aeb10b070e72b6c01a2f6abb4b0a936017794ef4eab3469697a4e24ef2054bc371519afa90c8e6c5ea9dbeda58395a64400bd499c3fda207cb593b751b44ca - languageName: node - linkType: hard - -"@types/koa__multer@npm:2.0.4": - version: 2.0.4 - resolution: "@types/koa__multer@npm:2.0.4" - dependencies: - "@types/koa": "*" - checksum: 4a945061a6a44ef981081132e85ce9d0c171c4a895439b511313d628f0567d2624635166ac3e2455bdc216b15da83819ab52537f9609bfa1540ffc0d1b09519b - languageName: node - linkType: hard - -"@types/koa__router@npm:8.0.11": - version: 8.0.11 - resolution: "@types/koa__router@npm:8.0.11" - dependencies: - "@types/koa": "*" - checksum: 81f55ed77273871728c81a20fa546ee906bebfe72fd72e3723d983a19504eb7d9578908a0fb8ef230764c5495031412df4245eee93479161bd7bd5135ca1ea04 - languageName: node - linkType: hard - "@types/long@npm:^4.0.1": version: 4.0.2 resolution: "@types/long@npm:4.0.2" @@ -2399,13 +2236,6 @@ __metadata: languageName: node linkType: hard -"@types/mime@npm:*": - version: 3.0.1 - resolution: "@types/mime@npm:3.0.1" - checksum: 4040fac73fd0cea2460e29b348c1a6173da747f3a87da0dbce80dd7a9355a3d0e51d6d9a401654f3e5550620e3718b5a899b2ec1debf18424e298a2c605346e7 - languageName: node - linkType: hard - "@types/minimatch@npm:*": version: 5.1.2 resolution: "@types/minimatch@npm:5.1.2" @@ -2526,13 +2356,6 @@ __metadata: languageName: node linkType: hard -"@types/qs@npm:*": - version: 6.9.7 - resolution: "@types/qs@npm:6.9.7" - checksum: 7fd6f9c25053e9b5bb6bc9f9f76c1d89e6c04f7707a7ba0e44cc01f17ef5284adb82f230f542c2d5557d69407c9a40f0f3515e8319afd14e1e16b5543ac6cdba - languageName: node - linkType: hard - "@types/random-seed@npm:0.3.3": version: 0.3.3 resolution: "@types/random-seed@npm:0.3.3" @@ -2540,13 +2363,6 @@ __metadata: languageName: node linkType: hard -"@types/range-parser@npm:*": - version: 1.2.4 - resolution: "@types/range-parser@npm:1.2.4" - checksum: b7c0dfd5080a989d6c8bb0b6750fc0933d9acabeb476da6fe71d8bdf1ab65e37c136169d84148034802f48378ab94e3c37bb4ef7656b2bec2cb9c0f8d4146a95 - languageName: node - linkType: hard - "@types/ratelimiter@npm:3.4.4": version: 3.4.4 resolution: "@types/ratelimiter@npm:3.4.4" @@ -2609,16 +2425,6 @@ __metadata: languageName: node linkType: hard -"@types/serve-static@npm:*": - version: 1.15.0 - resolution: "@types/serve-static@npm:1.15.0" - dependencies: - "@types/mime": "*" - "@types/node": "*" - checksum: b6ac93d471fb0f53ddcac1f9b67572a09cd62806f7db5855244b28f6f421139626f24799392566e97d1ffc61b12f9de7f30380c39fcae3c8a161fe161d44edf2 - languageName: node - linkType: hard - "@types/sharp@npm:0.31.0": version: 0.31.0 resolution: "@types/sharp@npm:0.31.0" @@ -2727,6 +2533,15 @@ __metadata: languageName: node linkType: hard +"@types/vary@npm:1.1.0": + version: 1.1.0 + resolution: "@types/vary@npm:1.1.0" + dependencies: + "@types/node": "*" + checksum: 6f434a96966ebd4b091592a085cf326f9bfe417aca8714092cc8d3a6d9da775d03c9c799b191f9bbb91ee558831f8b47b2a62f7d7ee0097e8aeee2466d01d515 + languageName: node + linkType: hard + "@types/vinyl-fs@npm:*": version: 2.4.12 resolution: "@types/vinyl-fs@npm:2.4.12" @@ -3088,7 +2903,14 @@ __metadata: languageName: node linkType: hard -"accepts@npm:^1.3.5": +"abstract-logging@npm:^2.0.1": + version: 2.0.1 + resolution: "abstract-logging@npm:2.0.1" + checksum: 6967d15e5abbafd17f56eaf30ba8278c99333586fa4f7935fd80e93cfdc006c37fcc819c5d63ee373a12e6cb2d0417f7c3c6b9e42b957a25af9937d26749415e + languageName: node + linkType: hard + +"accepts@npm:^1.3.5, accepts@npm:^1.3.8": version: 1.3.8 resolution: "accepts@npm:1.3.8" dependencies: @@ -3188,6 +3010,20 @@ __metadata: languageName: node linkType: hard +"ajv-formats@npm:^2.1.1": + version: 2.1.1 + resolution: "ajv-formats@npm:2.1.1" + dependencies: + ajv: ^8.0.0 + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: 4a287d937f1ebaad4683249a4c40c0fa3beed30d9ddc0adba04859026a622da0d317851316ea64b3680dc60f5c3c708105ddd5d5db8fe595d9d0207fd19f90b7 + languageName: node + linkType: hard + "ajv-keywords@npm:^3.5.2": version: 3.5.2 resolution: "ajv-keywords@npm:3.5.2" @@ -3197,7 +3033,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:8.11.2": +"ajv@npm:8.11.2, ajv@npm:^8.0.0, ajv@npm:^8.10.0, ajv@npm:^8.11.0": version: 8.11.2 resolution: "ajv@npm:8.11.2" dependencies: @@ -3358,13 +3194,6 @@ __metadata: languageName: node linkType: hard -"append-field@npm:^1.0.0": - version: 1.0.0 - resolution: "append-field@npm:1.0.0" - checksum: 482ba08acc0ecef00fe7da6bf2f8e48359a9905ee1af525f3120c9260c02e91eedf0579b59d898e8d8455b6c199e340bc0a2fd4b9e02adaa29a8a86c722b37f9 - languageName: node - linkType: hard - "aproba@npm:^1.0.3 || ^2.0.0": version: 2.0.0 resolution: "aproba@npm:2.0.0" @@ -3691,6 +3520,13 @@ __metadata: languageName: node linkType: hard +"atomic-sleep@npm:^1.0.0": + version: 1.0.0 + resolution: "atomic-sleep@npm:1.0.0" + checksum: b95275afb2f80732f22f43a60178430c468906a415a7ff18bcd0feeebc8eec3930b51250aeda91a476062a90e07132b43a1794e8d8ffcf9b650e8139be75fa36 + languageName: node + linkType: hard + "autobind-decorator@npm:2.4.0, autobind-decorator@npm:^2.4.0": version: 2.4.0 resolution: "autobind-decorator@npm:2.4.0" @@ -3735,6 +3571,17 @@ __metadata: languageName: node linkType: hard +"avvio@npm:^8.2.0": + version: 8.2.0 + resolution: "avvio@npm:8.2.0" + dependencies: + archy: ^1.0.0 + debug: ^4.0.0 + fastq: ^1.6.1 + checksum: bbd06eeb1f9ef428dbc32a32e06c350a7b320f60348698fd234145a4100f3688ce5d0999b966eb6ca70f9511d0c35fed5ef4651d276715e7e3e94a2d465cb56d + languageName: node + linkType: hard + "aws-sdk@npm:2.1262.0": version: 2.1262.0 resolution: "aws-sdk@npm:2.1262.0" @@ -3891,14 +3738,12 @@ __metadata: version: 0.0.0-use.local resolution: "backend@workspace:packages/backend" dependencies: - "@bull-board/api": 4.3.1 - "@bull-board/koa": 4.3.1 - "@bull-board/ui": 4.3.1 "@discordapp/twemoji": 14.0.2 - "@elastic/elasticsearch": 7.17.0 - "@koa/cors": 3.3.0 - "@koa/multer": 3.0.0 - "@koa/router": 9.0.1 + "@fastify/accepts": 4.0.1 + "@fastify/cors": 8.2.0 + "@fastify/multipart": 7.3.0 + "@fastify/static": 6.5.0 + "@fastify/view": 7.1.2 "@nestjs/common": 9.2.0 "@nestjs/core": 9.2.0 "@nestjs/testing": 9.2.0 @@ -3910,6 +3755,7 @@ __metadata: "@syuilo/aiscript": 0.11.1 "@tensorflow/tfjs": ^4.1.0 "@tensorflow/tfjs-node": 4.1.0 + "@types/accepts": 1.3.5 "@types/archiver": 5.3.1 "@types/bcryptjs": 2.4.2 "@types/bull": 4.10.0 @@ -3921,17 +3767,6 @@ __metadata: "@types/jsdom": 20.0.1 "@types/jsonld": 1.5.8 "@types/jsrsasign": 10.5.4 - "@types/koa": 2.13.5 - "@types/koa-bodyparser": 4.3.8 - "@types/koa-cors": 0.0.2 - "@types/koa-favicon": 2.0.21 - "@types/koa-logger": 3.1.2 - "@types/koa-mount": 4.0.1 - "@types/koa-send": 4.1.3 - "@types/koa-views": 7.0.0 - "@types/koa__cors": 3.3.0 - "@types/koa__multer": 2.0.4 - "@types/koa__router": 8.0.11 "@types/mime-types": 2.1.1 "@types/node": 18.11.9 "@types/node-fetch": 3.0.3 @@ -3954,11 +3789,13 @@ __metadata: "@types/tmp": 0.2.3 "@types/unzipper": 0.10.5 "@types/uuid": 8.3.4 + "@types/vary": 1.1.0 "@types/web-push": 3.3.2 "@types/websocket": 1.0.5 "@types/ws": 8.5.3 "@typescript-eslint/eslint-plugin": 5.45.0 "@typescript-eslint/parser": 5.45.0 + accepts: ^1.3.8 ajv: 8.11.2 archiver: 5.3.1 autobind-decorator: 2.4.0 @@ -3982,6 +3819,7 @@ __metadata: eslint: 8.28.0 eslint-plugin-import: 2.26.0 execa: 6.1.0 + fastify: 4.10.0 feed: 4.2.2 file-type: 18.0.0 fluent-ffmpeg: 2.1.2 @@ -3999,20 +3837,10 @@ __metadata: json5-loader: 4.0.1 jsonld: 8.1.0 jsrsasign: 10.6.1 - koa: 2.13.4 - koa-bodyparser: 4.3.0 - koa-favicon: 2.1.0 - koa-json-body: 5.3.0 - koa-logger: 3.2.1 - koa-mount: 4.0.0 - koa-send: 5.0.1 - koa-slow: 2.1.0 - koa-views: 7.0.2 mfm-js: 0.23.0 mime-types: 2.1.35 misskey-js: 0.0.14 ms: 3.0.0-canary.1 - multer: 1.4.4 nested-property: 4.0.0 node-fetch: 3.3.0 nodemailer: 6.8.0 @@ -4060,6 +3888,7 @@ __metadata: ulid: 2.3.0 unzipper: 0.10.11 uuid: 9.0.0 + vary: 1.1.2 web-push: 3.5.0 websocket: 1.0.34 ws: 8.11.0 @@ -4430,16 +4259,6 @@ __metadata: languageName: node linkType: hard -"busboy@npm:^0.2.11": - version: 0.2.14 - resolution: "busboy@npm:0.2.14" - dependencies: - dicer: 0.2.5 - readable-stream: 1.1.x - checksum: 9df9fca6d96dab9edd03f568bde31f215794e6fabd73c75d2b39a4be2e8b73a45121d987dea5db881f3fb499737c261b372106fe72d08b8db92afaed8d751165 - languageName: node - linkType: hard - "busboy@npm:^1.6.0": version: 1.6.0 resolution: "busboy@npm:1.6.0" @@ -4449,13 +4268,6 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2, bytes@npm:^3.1.0": - version: 3.1.2 - resolution: "bytes@npm:3.1.2" - checksum: e4bcd3948d289c5127591fbedf10c0b639ccbf00243504e4e127374a15c3bc8eed0d28d4aaab08ff6f1cf2abc0cce6ba3085ed32f4f90e82a5683ce0014e1b6e - languageName: node - linkType: hard - "cacache@npm:^16.1.0": version: 16.1.3 resolution: "cacache@npm:16.1.3" @@ -4702,7 +4514,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^2.0.0, chalk@npm:^2.4.2": +"chalk@npm:^2.0.0": version: 2.4.2 resolution: "chalk@npm:2.4.2" dependencies: @@ -4713,7 +4525,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.2": +"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -5118,30 +4930,6 @@ __metadata: languageName: node linkType: hard -"co-body@npm:^5.0.0": - version: 5.2.0 - resolution: "co-body@npm:5.2.0" - dependencies: - inflation: ^2.0.0 - qs: ^6.4.0 - raw-body: ^2.2.0 - type-is: ^1.6.14 - checksum: 48e1ffe00b8717b68154a939fa19f36d75aa66bba627f2977f28d11b732da56bdda445acda7053f7a85dfbac8a09a8aa257bceedaff7b6467cb25ab08ada9c8d - languageName: node - linkType: hard - -"co-body@npm:^6.0.0": - version: 6.1.0 - resolution: "co-body@npm:6.1.0" - dependencies: - inflation: ^2.0.0 - qs: ^6.5.2 - raw-body: ^2.3.3 - type-is: ^1.6.16 - checksum: d0a78831a6651f2085fce16b0ecdc49f45fb5baf4f94148c2f499e7ec89d188205362548b9c500eae15a819360cfda208079e68a72c204cf66ca3ffa2fc0f57e - languageName: node - linkType: hard - "co@npm:^4.6.0": version: 4.6.0 resolution: "co@npm:4.6.0" @@ -5315,7 +5103,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^2.19.0, commander@npm:^2.20.0": +"commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" checksum: ab8c07884e42c3a8dbc5dd9592c606176c7eb5c1ca5ff274bcf907039b2c41de3626f684ea75ccf4d361ba004bbaff1f577d5384c155f3871e456bdf27becf9e @@ -5383,7 +5171,7 @@ __metadata: languageName: node linkType: hard -"concat-stream@npm:^1.5.2, concat-stream@npm:^1.6.0": +"concat-stream@npm:^1.6.0": version: 1.6.2 resolution: "concat-stream@npm:1.6.2" dependencies: @@ -5395,27 +5183,6 @@ __metadata: languageName: node linkType: hard -"condense-newlines@npm:^0.2.1": - version: 0.2.1 - resolution: "condense-newlines@npm:0.2.1" - dependencies: - extend-shallow: ^2.0.1 - is-whitespace: ^0.3.0 - kind-of: ^3.0.2 - checksum: 3c20ff6ee88b5d2e81c122f33b5ba5d6976cdf86d83527fadea308b3020ed70af7ed98c2e2d94d36f27fcd723a7a477941c19575e0d2c8db6afc4aac6926a54e - languageName: node - linkType: hard - -"config-chain@npm:^1.1.13": - version: 1.1.13 - resolution: "config-chain@npm:1.1.13" - dependencies: - ini: ^1.3.4 - proto-list: ~1.2.1 - checksum: 828137a28e7c2fc4b7fb229bd0cd6c1397bcf83434de54347e608154008f411749041ee392cbe42fab6307e02de4c12480260bf769b7d44b778fdea3839eafab - languageName: node - linkType: hard - "consola@npm:^2.15.0": version: 2.15.3 resolution: "consola@npm:2.15.3" @@ -5430,15 +5197,6 @@ __metadata: languageName: node linkType: hard -"consolidate@npm:^0.16.0": - version: 0.16.0 - resolution: "consolidate@npm:0.16.0" - dependencies: - bluebird: ^3.7.2 - checksum: f17164ffb2c4f79b4cbf685f1c76a49f59d329a40954b436425498861dc137b46fe821b2aadafa2dcfeb7eebd46846f35bd2c36b4a704d38521b4210a22a7515 - languageName: node - linkType: hard - "constantinople@npm:^4.0.1": version: 4.0.1 resolution: "constantinople@npm:4.0.1" @@ -5449,7 +5207,7 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:0.5.4, content-disposition@npm:~0.5.2": +"content-disposition@npm:0.5.4, content-disposition@npm:^0.5.3, content-disposition@npm:~0.5.2": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" dependencies: @@ -5479,6 +5237,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^0.5.0": + version: 0.5.0 + resolution: "cookie@npm:0.5.0" + checksum: 1f4bd2ca5765f8c9689a7e8954183f5332139eb72b6ff783d8947032ec1fdf43109852c178e21a953a30c0dd42257828185be01b49d1eb1a67fd054ca588a180 + languageName: node + linkType: hard + "cookies@npm:~0.8.0": version: 0.8.0 resolution: "cookies@npm:0.8.0" @@ -5506,13 +5271,6 @@ __metadata: languageName: node linkType: hard -"copy-to@npm:^2.0.1": - version: 2.0.1 - resolution: "copy-to@npm:2.0.1" - checksum: 05ea12875bdc96ae053a3b30148e9d992026035ff2bfcc0b615e8d49d1cf8fc3d1f40843f9a4b7b1b6d9118eeebcba31e621076d7de525828aa9c07d22a81dab - languageName: node - linkType: hard - "core-js@npm:3": version: 3.26.1 resolution: "core-js@npm:3.26.1" @@ -5838,7 +5596,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:2, debug@npm:^2.2.0, debug@npm:^2.3.3, debug@npm:^2.5.2, debug@npm:^2.6.9": +"debug@npm:2, debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3, debug@npm:^2.5.2, debug@npm:^2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" dependencies: @@ -5847,7 +5605,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -6086,7 +5844,7 @@ __metadata: languageName: node linkType: hard -"destroy@npm:^1.0.4": +"destroy@npm:1.2.0, destroy@npm:^1.0.4": version: 1.2.0 resolution: "destroy@npm:1.2.0" checksum: 0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 @@ -6130,16 +5888,6 @@ __metadata: languageName: node linkType: hard -"dicer@npm:0.2.5": - version: 0.2.5 - resolution: "dicer@npm:0.2.5" - dependencies: - readable-stream: 1.1.x - streamsearch: 0.1.2 - checksum: a6f0ce9ac5099c7ffeaec7398d711eea1dd803eb99036d0f05342e9ed46a4235a5ed0ea01ad5d6c785fdb0aae6d61d2722e6e64f9fabdfe39885f7f52eb635ee - languageName: node - linkType: hard - "diff-sequences@npm:^29.3.1": version: 29.3.1 resolution: "diff-sequences@npm:29.3.1" @@ -6369,20 +6117,6 @@ __metadata: languageName: node linkType: hard -"editorconfig@npm:^0.15.3": - version: 0.15.3 - resolution: "editorconfig@npm:0.15.3" - dependencies: - commander: ^2.19.0 - lru-cache: ^4.1.5 - semver: ^5.6.0 - sigmund: ^1.0.1 - bin: - editorconfig: bin/editorconfig - checksum: a94afeda19f12a4bcc4a573f0858df13dd3a2d1a3268cc0f17a6326ebe7ddd6cb0c026f8e4e73c17d34f3892bf6f8b561512d9841e70063f61da71b4c57dc5f0 - languageName: node - linkType: hard - "ee-first@npm:1.1.1": version: 1.1.1 resolution: "ee-first@npm:1.1.1" @@ -6390,17 +6124,6 @@ __metadata: languageName: node linkType: hard -"ejs@npm:*, ejs@npm:^3.1.7": - version: 3.1.8 - resolution: "ejs@npm:3.1.8" - dependencies: - jake: ^10.8.5 - bin: - ejs: bin/cli.js - checksum: 1d40d198ad52e315ccf37e577bdec06e24eefdc4e3c27aafa47751a03a0c7f0ec4310254c9277a5f14763c3cd4bbacce27497332b2d87c74232b9b1defef8efc - languageName: node - linkType: hard - "electron-to-chromium@npm:^1.2.7, electron-to-chromium@npm:^1.4.251": version: 1.4.284 resolution: "electron-to-chromium@npm:1.4.284" @@ -6436,7 +6159,7 @@ __metadata: languageName: node linkType: hard -"encodeurl@npm:^1.0.2": +"encodeurl@npm:^1.0.2, encodeurl@npm:~1.0.2": version: 1.0.2 resolution: "encodeurl@npm:1.0.2" checksum: e50e3d508cdd9c4565ba72d2012e65038e5d71bdc9198cb125beb6237b5b1ade6c0d343998da9e170fb2eae52c1bed37d4d6d98a46ea423a0cddbed5ac3f780c @@ -6452,7 +6175,7 @@ __metadata: languageName: node linkType: hard -"end-of-stream@npm:^1.0.0, end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": +"end-of-stream@npm:^1.0.0, end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1, end-of-stream@npm:^1.4.4": version: 1.4.4 resolution: "end-of-stream@npm:1.4.4" dependencies: @@ -7074,7 +6797,7 @@ __metadata: languageName: node linkType: hard -"escape-html@npm:^1.0.3": +"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" checksum: 6213ca9ae00d0ab8bccb6d8d4e0a98e76237b2410302cf7df70aaa6591d509a2a37ce8998008cbecae8fc8ffaadf3fb0229535e6a145f3ce0b211d060decbb24 @@ -7361,6 +7084,13 @@ __metadata: languageName: node linkType: hard +"etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 571aeb3dbe0f2bbd4e4fadbdb44f325fc75335cd5f6f6b6a091e6a06a9f25ed5392f0863c5442acb0646787446e816f13cbfc6edce5b07658541dff573cab1ff + languageName: node + linkType: hard + "event-stream@npm:=3.3.4": version: 3.3.4 resolution: "event-stream@npm:3.3.4" @@ -7411,6 +7141,13 @@ __metadata: languageName: node linkType: hard +"events@npm:^3.3.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: f6f487ad2198aa41d878fa31452f1a3c00958f46e9019286ff4787c84aac329332ab45c9cdc8c445928fc6d7ded294b9e005a7fce9426488518017831b272780 + languageName: node + linkType: hard + "execa@npm:4.1.0": version: 4.1.0 resolution: "execa@npm:4.1.0" @@ -7616,6 +7353,13 @@ __metadata: languageName: node linkType: hard +"fast-decode-uri-component@npm:^1.0.1": + version: 1.0.1 + resolution: "fast-decode-uri-component@npm:1.0.1" + checksum: 427a48fe0907e76f0e9a2c228e253b4d8a8ab21d130ee9e4bb8339c5ba4086235cf9576831f7b20955a752eae4b525a177ff9d5825dd8d416e7726939194fbee + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -7643,6 +7387,20 @@ __metadata: languageName: node linkType: hard +"fast-json-stringify@npm:^5.0.0": + version: 5.4.1 + resolution: "fast-json-stringify@npm:5.4.1" + dependencies: + "@fastify/deepmerge": ^1.0.0 + ajv: ^8.10.0 + ajv-formats: ^2.1.1 + fast-deep-equal: ^3.1.3 + fast-uri: ^2.1.0 + rfdc: ^1.2.0 + checksum: 62efefaf135ff03d810fb362adca1d3471787e4e17ef10e34c8e1d61d361c09736091b1948df2cb408e6b05f18c10985e89bcdd9c08f8f5ba21e148e52a9c5fc + languageName: node + linkType: hard + "fast-levenshtein@npm:^1.0.0": version: 1.1.4 resolution: "fast-levenshtein@npm:1.1.4" @@ -7657,6 +7415,22 @@ __metadata: languageName: node linkType: hard +"fast-querystring@npm:^1.0.0": + version: 1.0.0 + resolution: "fast-querystring@npm:1.0.0" + dependencies: + fast-decode-uri-component: ^1.0.1 + checksum: 5f70df27d02fcf86ea2baa16ea59e0da8bbd891e3a97aa1e95b1c0c64d5445aeab3bde5ce3e603b21d48c87db70a458febf05150a9dbe7c099aced5f123b3ffd + languageName: node + linkType: hard + +"fast-redact@npm:^3.1.1": + version: 3.1.2 + resolution: "fast-redact@npm:3.1.2" + checksum: a30eb6b6830333ab213e0def55f46453ca777544dbd3a883016cb590a0eeb95e6fdf546553c1a13d509896bfba889b789991160a6d0996ceb19fce0a02e8b753 + languageName: node + linkType: hard + "fast-safe-stringify@npm:2.1.1": version: 2.1.1 resolution: "fast-safe-stringify@npm:2.1.1" @@ -7664,6 +7438,13 @@ __metadata: languageName: node linkType: hard +"fast-uri@npm:^2.0.0, fast-uri@npm:^2.1.0": + version: 2.1.0 + resolution: "fast-uri@npm:2.1.0" + checksum: 60ecece5ab05515729ec04d1732ee68bd4429cab8c06ebf8db512a094a0077ddc5af6a27c75922875bc9e13b58e947832242cdcb2cb23c51dc753412222dca83 + languageName: node + linkType: hard + "fast-xml-parser@npm:^3.19.0": version: 3.21.1 resolution: "fast-xml-parser@npm:3.21.1" @@ -7675,7 +7456,36 @@ __metadata: languageName: node linkType: hard -"fastq@npm:^1.6.0": +"fastify-plugin@npm:^4.0.0": + version: 4.3.0 + resolution: "fastify-plugin@npm:4.3.0" + checksum: f4831ca6de3db276f6e5c9ae172c175631be07b24b91e1de17d0cd11c1bd29fe7f671deec591a4dfd26169d3de4f9d9ca385d0e7bddccf38c6d21b5764f8b77d + languageName: node + linkType: hard + +"fastify@npm:4.10.0": + version: 4.10.0 + resolution: "fastify@npm:4.10.0" + dependencies: + "@fastify/ajv-compiler": ^3.3.1 + "@fastify/error": ^3.0.0 + "@fastify/fast-json-stringify-compiler": ^4.1.0 + abstract-logging: ^2.0.1 + avvio: ^8.2.0 + find-my-way: ^7.3.0 + light-my-request: ^5.6.1 + pino: ^8.5.0 + process-warning: ^2.0.0 + proxy-addr: ^2.0.7 + rfdc: ^1.3.0 + secure-json-parse: ^2.5.0 + semver: ^7.3.7 + tiny-lru: ^10.0.0 + checksum: 68c905a930d1e6d31d70a1e048c39c2d5d3749e00b9653fa7f20156da791d68f7edfa2856e8b3ea898b6e38b0a70b09782555e3f503a77cda49e8fd983a1a0ba + languageName: node + linkType: hard + +"fastq@npm:^1.6.0, fastq@npm:^1.6.1": version: 1.13.0 resolution: "fastq@npm:1.13.0" dependencies: @@ -7750,15 +7560,6 @@ __metadata: languageName: node linkType: hard -"filelist@npm:^1.0.1": - version: 1.0.4 - resolution: "filelist@npm:1.0.4" - dependencies: - minimatch: ^5.0.1 - checksum: a303573b0821e17f2d5e9783688ab6fbfce5d52aaac842790ae85e704a6f5e4e3538660a63183d6453834dedf1e0f19a9dadcebfa3e926c72397694ea11f5160 - languageName: node - linkType: hard - "fill-range@npm:^4.0.0": version: 4.0.0 resolution: "fill-range@npm:4.0.0" @@ -7780,6 +7581,17 @@ __metadata: languageName: node linkType: hard +"find-my-way@npm:^7.3.0": + version: 7.3.1 + resolution: "find-my-way@npm:7.3.1" + dependencies: + fast-deep-equal: ^3.1.3 + fast-querystring: ^1.0.0 + safe-regex2: ^2.0.0 + checksum: eec65665c34fbfeb323a52989de51b106485ec0d6182996fc70d42570a73f88b9637572bb8ae89332532da9ca856615e195768116aeede75d73b929b9534bf7a + languageName: node + linkType: hard + "find-up@npm:^1.0.0": version: 1.1.2 resolution: "find-up@npm:1.1.2" @@ -7989,6 +7801,13 @@ __metadata: languageName: node linkType: hard +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: fd27e2394d8887ebd16a66ffc889dc983fbbd797d5d3f01087c020283c0f019a7d05ee85669383d8e0d216b116d720fc0cef2f6e9b7eb9f4c90c6e0bc7fd28e6 + languageName: node + linkType: hard + "fragment-cache@npm:^0.2.1": version: 0.2.1 resolution: "fragment-cache@npm:0.2.1" @@ -7998,7 +7817,7 @@ __metadata: languageName: node linkType: hard -"fresh@npm:~0.5.2": +"fresh@npm:0.5.2, fresh@npm:~0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" checksum: 13ea8b08f91e669a64e3ba3a20eb79d7ca5379a81f1ff7f4310d54e2320645503cc0c78daedc93dfb6191287295f6479544a649c64d8e41a1c0fb0c221552346 @@ -8213,15 +8032,6 @@ __metadata: languageName: node linkType: hard -"get-paths@npm:0.0.7": - version: 0.0.7 - resolution: "get-paths@npm:0.0.7" - dependencies: - pify: ^4.0.1 - checksum: a17edf61fb9934b8e58a7d8ce0d9702040b7020dda86e67ce088db865c21cb230f490f25f38064cebeb2c367abc2bf39a75db6acdfddf01da63a699a47f8aba4 - languageName: node - linkType: hard - "get-pixels-frame-info-update@npm:3.3.2": version: 3.3.2 resolution: "get-pixels-frame-info-update@npm:3.3.2" @@ -8390,7 +8200,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.1, glob@npm:^8.0.3": +"glob@npm:^8.0.1": version: 8.0.3 resolution: "glob@npm:8.0.3" dependencies: @@ -8780,6 +8590,20 @@ __metadata: languageName: node linkType: hard +"hashlru@npm:^2.3.0": + version: 2.3.0 + resolution: "hashlru@npm:2.3.0" + checksum: 38b3559e6fb9d19fa731edc52d8d7e72cd378f708dcb01cecd4a6ba0c52f06d7d06d6277249f5c43d9915d8dda9be31adad768a379eef188db213c3f2b09278d + languageName: node + linkType: hard + +"hexoid@npm:^1.0.0": + version: 1.0.0 + resolution: "hexoid@npm:1.0.0" + checksum: 27a148ca76a2358287f40445870116baaff4a0ed0acc99900bf167f0f708ffd82e044ff55e9949c71963852b580fc024146d3ac6d5d76b508b78d927fa48ae2d + languageName: node + linkType: hard + "highlight.js@npm:^10.7.1": version: 10.7.3 resolution: "highlight.js@npm:10.7.3" @@ -8810,13 +8634,6 @@ __metadata: languageName: node linkType: hard -"hpagent@npm:^0.1.1": - version: 0.1.2 - resolution: "hpagent@npm:0.1.2" - checksum: 1918518ab937d9fa615a47b94489e23662547bc1edf27069ee9bf40bfefb94da65eb142b6f42336b4b0752fce87f66c284d92b97340fd2a90b24aa3616b5450d - languageName: node - linkType: hard - "html-comment-regex@npm:^1.1.0": version: 1.1.2 resolution: "html-comment-regex@npm:1.1.2" @@ -8903,7 +8720,7 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:^1.6.3, http-errors@npm:^1.7.3, http-errors@npm:~1.8.0": +"http-errors@npm:^1.6.3, http-errors@npm:~1.8.0": version: 1.8.1 resolution: "http-errors@npm:1.8.1" dependencies: @@ -8916,18 +8733,6 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:~1.6.2": - version: 1.6.3 - resolution: "http-errors@npm:1.6.3" - dependencies: - depd: ~1.1.2 - inherits: 2.0.3 - setprototypeof: 1.1.0 - statuses: ">= 1.4.0 < 2" - checksum: a9654ee027e3d5de305a56db1d1461f25709ac23267c6dc28cdab8323e3f96caa58a9a6a5e93ac15d7285cee0c2f019378c3ada9026e7fe19c872d695f27de7c - languageName: node - linkType: hard - "http-proxy-agent@npm:^5.0.0": version: 5.0.0 resolution: "http-proxy-agent@npm:5.0.0" @@ -9040,14 +8845,16 @@ __metadata: languageName: node linkType: hard -"humanize-number@npm:0.0.2": - version: 0.0.2 - resolution: "humanize-number@npm:0.0.2" - checksum: 9c98c9d06b0f3d801960be3957199232a5df52377e2502acae92e4f71de633fa62c315a83f24bf96bef76f47b2e3e0e1e4f4157c891e27074fd3272cad6724bb +"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: ">= 2.1.2 < 3.0.0" + checksum: 3f60d47a5c8fc3313317edfd29a00a692cc87a19cac0159e2ce711d0ebc9019064108323b5e493625e25594f11c6236647d8e256fbe7a58f4a3b33b89e6d30bf languageName: node linkType: hard -"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.4": +"iconv-lite@npm:^0.4.4": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" dependencies: @@ -9056,15 +8863,6 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": - version: 0.6.3 - resolution: "iconv-lite@npm:0.6.3" - dependencies: - safer-buffer: ">= 2.1.2 < 3.0.0" - checksum: 3f60d47a5c8fc3313317edfd29a00a692cc87a19cac0159e2ce711d0ebc9019064108323b5e493625e25594f11c6236647d8e256fbe7a58f4a3b33b89e6d30bf - languageName: node - linkType: hard - "idb-keyval@npm:6.2.0, idb-keyval@npm:^6.1.0": version: 6.2.0 resolution: "idb-keyval@npm:6.2.0" @@ -9152,13 +8950,6 @@ __metadata: languageName: node linkType: hard -"inflation@npm:^2.0.0": - version: 2.0.0 - resolution: "inflation@npm:2.0.0" - checksum: a0494871b12275afdef9e2710ee1af1e0fc642b04613a9be69c05ef8b5e9627f3bd7d358a937fa47aa20235ee7313a4f30255048533add0ad4918beb918a586e - languageName: node - linkType: hard - "inflight@npm:^1.0.4": version: 1.0.6 resolution: "inflight@npm:1.0.6" @@ -9176,13 +8967,6 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2.0.3": - version: 2.0.3 - resolution: "inherits@npm:2.0.3" - checksum: 78cb8d7d850d20a5e9a7f3620db31483aa00ad5f722ce03a55b110e5a723539b3716a3b463e2b96ce3fe286f33afc7c131fa2f91407528ba80cea98a7545d4c0 - languageName: node - linkType: hard - "ini@npm:2.0.0": version: 2.0.0 resolution: "ini@npm:2.0.0" @@ -9323,6 +9107,13 @@ __metadata: languageName: node linkType: hard +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: f88d3825981486f5a1942414c8d77dd6674dd71c065adcfa46f578d677edcb99fda25af42675cb59db492fdf427b34a5abfcde3982da11a8fd83a500b41cfe77 + languageName: node + linkType: hard + "ipaddr.js@npm:^2.0.1": version: 2.0.1 resolution: "ipaddr.js@npm:2.0.1" @@ -9836,13 +9627,6 @@ __metadata: languageName: node linkType: hard -"is-whitespace@npm:^0.3.0": - version: 0.3.0 - resolution: "is-whitespace@npm:0.3.0" - checksum: dac8fc9a9b797afeef703f625269601715552883790d1385d6bb27dd04ffdafd5fddca8f2d85ee96913850211595da2ba483dac1f166829c4078fb58ce815140 - languageName: node - linkType: hard - "is-windows@npm:^1.0.1, is-windows@npm:^1.0.2": version: 1.0.2 resolution: "is-windows@npm:1.0.2" @@ -9963,20 +9747,6 @@ __metadata: languageName: node linkType: hard -"jake@npm:^10.8.5": - version: 10.8.5 - resolution: "jake@npm:10.8.5" - dependencies: - async: ^3.2.3 - chalk: ^4.0.2 - filelist: ^1.0.1 - minimatch: ^3.0.4 - bin: - jake: ./bin/cli.js - checksum: 56c913ecf5a8d74325d0af9bc17a233bad50977438d44864d925bb6c45c946e0fee8c4c1f5fe2225471ef40df5222e943047982717ebff0d624770564d3c46ba - languageName: node - linkType: hard - "jest-changed-files@npm:^29.2.0": version: 29.2.0 resolution: "jest-changed-files@npm:29.2.0" @@ -10460,22 +10230,6 @@ __metadata: languageName: node linkType: hard -"js-beautify@npm:^1.6.12": - version: 1.14.7 - resolution: "js-beautify@npm:1.14.7" - dependencies: - config-chain: ^1.1.13 - editorconfig: ^0.15.3 - glob: ^8.0.3 - nopt: ^6.0.0 - bin: - css-beautify: js/bin/css-beautify.js - html-beautify: js/bin/html-beautify.js - js-beautify: js/bin/js-beautify.js - checksum: 1950d0d3f05f8ad06b73eb77b9aac602d00b24eab7d8a6d8ea0b1841ab9c730acecd5a6f3926e360dce7a2583481bc77caf6d024490a58fa9897cbbbdfc35984 - languageName: node - linkType: hard - "js-levenshtein@npm:^1.1.6": version: 1.1.6 resolution: "js-levenshtein@npm:1.1.6" @@ -10869,173 +10623,39 @@ __metadata: checksum: f2a0102ae0cf19c4a953397e552571bad2b588b53282874f25fca7236396e650e2db50d41f9f516bd402536e4df968dbb51b8e69e4d5d4a7173def78448f7bab languageName: node linkType: hard - -"kind-of@npm:^6.0.0, kind-of@npm:^6.0.2": - version: 6.0.3 - resolution: "kind-of@npm:6.0.3" - checksum: 3ab01e7b1d440b22fe4c31f23d8d38b4d9b91d9f291df683476576493d5dfd2e03848a8b05813dd0c3f0e835bc63f433007ddeceb71f05cb25c45ae1b19c6d3b - languageName: node - linkType: hard - -"kleur@npm:^3.0.3": - version: 3.0.3 - resolution: "kleur@npm:3.0.3" - checksum: df82cd1e172f957bae9c536286265a5cdbd5eeca487cb0a3b2a7b41ef959fc61f8e7c0e9aeea9c114ccf2c166b6a8dd45a46fd619c1c569d210ecd2765ad5169 - languageName: node - linkType: hard - -"koa-bodyparser@npm:4.3.0": - version: 4.3.0 - resolution: "koa-bodyparser@npm:4.3.0" - dependencies: - co-body: ^6.0.0 - copy-to: ^2.0.1 - checksum: c227fe0fb5a55b98fc91d865e80229b60178d216d53b732b07833eb38f48a7ed6aa768a083bc06e359db33298547e9a65842fbe9d3f0fdaf5149fe0becafc88f - languageName: node - linkType: hard - -"koa-compose@npm:^4.1.0": - version: 4.1.0 - resolution: "koa-compose@npm:4.1.0" - checksum: 46cb16792d96425e977c2ae4e5cb04930280740e907242ec9c25e3fb8b4a1d7b54451d7432bc24f40ec62255edea71894d2ceeb8238501842b4e48014f2e83db - languageName: node - linkType: hard - -"koa-convert@npm:^2.0.0": - version: 2.0.0 - resolution: "koa-convert@npm:2.0.0" - dependencies: - co: ^4.6.0 - koa-compose: ^4.1.0 - checksum: 7385b3391995f59c1312142e110d5dff677f9850dbfbcf387cd36a7b0af03b5d26e82b811eb9bb008b4f3e661cdab1f8817596e46b1929da2cf6e97a2f7456ed - languageName: node - linkType: hard - -"koa-favicon@npm:2.1.0": - version: 2.1.0 - resolution: "koa-favicon@npm:2.1.0" - dependencies: - mz: ^2.7.0 - checksum: 024051a0be3560a77e65651ad87690d432a28bed3c4dd24cbcbe3249a38d6cc0c80613d37b45107c8b709cb01fecf8723e77d50cbb2ce67c8451fa29891d228f - languageName: node - linkType: hard - -"koa-json-body@npm:5.3.0": - version: 5.3.0 - resolution: "koa-json-body@npm:5.3.0" - dependencies: - co-body: ^5.0.0 - checksum: b3ae5f304cbb6f8f6a4a2ba36047d1ea6fe32005e7665d795802eb4c3dd1f341a0b8e61087fffc6bd2da6f554d71b06b047cbb3669b1eea814285f75d9bdd736 - languageName: node - linkType: hard - -"koa-logger@npm:3.2.1": - version: 3.2.1 - resolution: "koa-logger@npm:3.2.1" - dependencies: - bytes: ^3.1.0 - chalk: ^2.4.2 - humanize-number: 0.0.2 - passthrough-counter: ^1.0.0 - checksum: b29ba25eb433452bfda48e51acd5d206128411966acc09bb13ce3a0cec9192f78bb27e23efd615d0e7f46eeb2588ee8d2541d72665a4aa18d27a177e78dca909 - languageName: node - linkType: hard - -"koa-mount@npm:4.0.0, koa-mount@npm:^4.0.0": - version: 4.0.0 - resolution: "koa-mount@npm:4.0.0" - dependencies: - debug: ^4.0.1 - koa-compose: ^4.1.0 - checksum: c7e8c5cca4d2ccc4742e63c81b86b44f0290075148897b5d633acdd137e90f554c60c232fbc62e843eaedb913b67c5a49367c1142e290b8cfd9c28eb4a0480ec - languageName: node - linkType: hard - -"koa-router@npm:^10.0.0": - version: 10.1.1 - resolution: "koa-router@npm:10.1.1" - dependencies: - debug: ^4.1.1 - http-errors: ^1.7.3 - koa-compose: ^4.1.0 - methods: ^1.1.2 - path-to-regexp: ^6.1.0 - checksum: 65e6cd4a7f8a4d98c665b00ee4c2c05340cb38ca035590ce71c23a25c0a01f6d2434d9a68366d7c218af9c94e5d8e20c7fe9e7f7dfbb98d69b11b5ae3246aaf8 - languageName: node - linkType: hard - -"koa-send@npm:5.0.1, koa-send@npm:^5.0.0": - version: 5.0.1 - resolution: "koa-send@npm:5.0.1" - dependencies: - debug: ^4.1.1 - http-errors: ^1.7.3 - resolve-path: ^1.4.0 - checksum: a9fbaadbe0f50efd157a733df4a1cc2b3b79b0cdf12e67c718641e6038d1792c0bebe40913e6d4ceb707d970301155be3859b98d1ef08b0fd1766f7326b82853 - languageName: node - linkType: hard - -"koa-slow@npm:2.1.0": - version: 2.1.0 - resolution: "koa-slow@npm:2.1.0" - dependencies: - lodash.isregexp: 3.0.5 - q: 1.4.1 - checksum: 1b2fa6c709cd4016f5c5c4f45a8bd569910fdfef482c85120f2bbddd5cf274d714b0d231659ac3335d15b03f0debdb71b14f3cc54624921be7d808df7f8ac513 + +"kind-of@npm:^6.0.0, kind-of@npm:^6.0.2": + version: 6.0.3 + resolution: "kind-of@npm:6.0.3" + checksum: 3ab01e7b1d440b22fe4c31f23d8d38b4d9b91d9f291df683476576493d5dfd2e03848a8b05813dd0c3f0e835bc63f433007ddeceb71f05cb25c45ae1b19c6d3b languageName: node linkType: hard -"koa-static@npm:^5.0.0": - version: 5.0.0 - resolution: "koa-static@npm:5.0.0" - dependencies: - debug: ^3.1.0 - koa-send: ^5.0.0 - checksum: 8d9b9c4d2b3b13e8818e804245d784099c4b353b55ddd7dbeeb90f27a2e9f5b6f86bd16a4909e337cb89db4d332d9002e6c0f5056caf75749cab62f93c1f0cc5 +"kleur@npm:^3.0.3": + version: 3.0.3 + resolution: "kleur@npm:3.0.3" + checksum: df82cd1e172f957bae9c536286265a5cdbd5eeca487cb0a3b2a7b41ef959fc61f8e7c0e9aeea9c114ccf2c166b6a8dd45a46fd619c1c569d210ecd2765ad5169 languageName: node linkType: hard -"koa-views@npm:*": - version: 8.0.0 - resolution: "koa-views@npm:8.0.0" - dependencies: - consolidate: ^0.16.0 - debug: ^4.1.0 - get-paths: 0.0.7 - koa-send: ^5.0.0 - mz: ^2.4.0 - pretty: ^2.0.0 - resolve-path: ^1.4.0 - peerDependencies: - "@types/koa": ^2.13.1 - peerDependenciesMeta: - "@types/koa": - optional: true - checksum: 6e7e4df04fcab8e5c50a82b38518b7e87491fef1a80f8105e34aaf82a93c0abf051a011d02a8bed8542255724bdb2b83cda7b63e094983c01a5c3927ea08be2c +"koa-compose@npm:^4.1.0": + version: 4.1.0 + resolution: "koa-compose@npm:4.1.0" + checksum: 46cb16792d96425e977c2ae4e5cb04930280740e907242ec9c25e3fb8b4a1d7b54451d7432bc24f40ec62255edea71894d2ceeb8238501842b4e48014f2e83db languageName: node linkType: hard -"koa-views@npm:7.0.2, koa-views@npm:^7.0.1": - version: 7.0.2 - resolution: "koa-views@npm:7.0.2" +"koa-convert@npm:^2.0.0": + version: 2.0.0 + resolution: "koa-convert@npm:2.0.0" dependencies: - consolidate: ^0.16.0 - debug: ^4.1.0 - get-paths: 0.0.7 - koa-send: ^5.0.0 - mz: ^2.4.0 - pretty: ^2.0.0 - resolve-path: ^1.4.0 - peerDependencies: - "@types/koa": ^2.13.1 - peerDependenciesMeta: - "@types/koa": - optional: true - checksum: e591a131de09cf2676ae0492dabf420015404cd1198092a2aa217118c5e7df5da848f49c658f46af172028a508cecbdb0a81e45c5acf5cf40c2baf7c9d08675e + co: ^4.6.0 + koa-compose: ^4.1.0 + checksum: 7385b3391995f59c1312142e110d5dff677f9850dbfbcf387cd36a7b0af03b5d26e82b811eb9bb008b4f3e661cdab1f8817596e46b1929da2cf6e97a2f7456ed languageName: node linkType: hard -"koa@npm:2.13.4, koa@npm:^2.13.1": +"koa@npm:2.13.4": version: 2.13.4 resolution: "koa@npm:2.13.4" dependencies: @@ -11176,6 +10796,17 @@ __metadata: languageName: node linkType: hard +"light-my-request@npm:^5.6.1": + version: 5.7.0 + resolution: "light-my-request@npm:5.7.0" + dependencies: + cookie: ^0.5.0 + process-warning: ^2.0.0 + set-cookie-parser: ^2.4.1 + checksum: 3da727736c03b89d1eac4b3cebc9de3d8b725a1485fb86f63b450715fa1cb7f049d7df391b831eb9ec352287fd58f1948835c1e1e78444f193eb0a9c90216aed + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -11323,13 +10954,6 @@ __metadata: languageName: node linkType: hard -"lodash.isregexp@npm:3.0.5": - version: 3.0.5 - resolution: "lodash.isregexp@npm:3.0.5" - checksum: 973f4887f003af746bf838267d9d1ea39d912f579cf402cca67049b1e4487daf2a25b10c70e4fc1c7ad97ee3be6d43d38c9839bc9c55c40e94b62dfc60f601c7 - languageName: node - linkType: hard - "lodash.map@npm:^4.4.0": version: 4.6.0 resolution: "lodash.map@npm:4.6.0" @@ -11457,16 +11081,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^4.1.5": - version: 4.1.5 - resolution: "lru-cache@npm:4.1.5" - dependencies: - pseudomap: ^1.0.2 - yallist: ^2.1.2 - checksum: 4bb4b58a36cd7dc4dcec74cbe6a8f766a38b7426f1ff59d4cf7d82a2aa9b9565cd1cb98f6ff60ce5cd174524868d7bc9b7b1c294371851356066ca9ac4cf135a - languageName: node - linkType: hard - "lru-cache@npm:^6.0.0": version: 6.0.0 resolution: "lru-cache@npm:6.0.0" @@ -11634,13 +11248,6 @@ __metadata: languageName: node linkType: hard -"methods@npm:^1.1.2": - version: 1.1.2 - resolution: "methods@npm:1.1.2" - checksum: 0917ff4041fa8e2f2fda5425a955fe16ca411591fbd123c0d722fcf02b73971ed6f764d85f0a6f547ce49ee0221ce2c19a5fa692157931cecb422984f1dcd13a - languageName: node - linkType: hard - "mfm-js@npm:0.23.0": version: 0.23.0 resolution: "mfm-js@npm:0.23.0" @@ -11697,6 +11304,15 @@ __metadata: languageName: node linkType: hard +"mime@npm:1.6.0": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: fef25e39263e6d207580bdc629f8872a3f9772c923c7f8c7e793175cee22777bbe8bba95e5d509a40aaa292d8974514ce634ae35769faa45f22d17edda5e8557 + languageName: node + linkType: hard + "mimic-fn@npm:^2.1.0": version: 2.1.0 resolution: "mimic-fn@npm:2.1.0" @@ -11904,7 +11520,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:>=0.5 0, mkdirp@npm:^0.5.4, mkdirp@npm:^0.5.5, mkdirp@npm:~0.5.1": +"mkdirp@npm:>=0.5 0, mkdirp@npm:^0.5.5, mkdirp@npm:~0.5.1": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" dependencies: @@ -11924,6 +11540,15 @@ __metadata: languageName: node linkType: hard +"mnemonist@npm:0.39.5": + version: 0.39.5 + resolution: "mnemonist@npm:0.39.5" + dependencies: + obliterator: ^2.0.1 + checksum: 6669d687a434226924b2c84ee6eb7ce7d0f83dfc5caad8bcc164c73c0c11fb6d43cbe32636e710f068046f4b40a56c3032532554e93e02640aafc6ca3dd222e6 + languageName: node + linkType: hard + "moment@npm:^2.22.2": version: 2.29.4 resolution: "moment@npm:2.29.4" @@ -11945,6 +11570,13 @@ __metadata: languageName: node linkType: hard +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d + languageName: node + linkType: hard + "ms@npm:3.0.0-canary.1": version: 3.0.0-canary.1 resolution: "ms@npm:3.0.0-canary.1" @@ -11952,13 +11584,6 @@ __metadata: languageName: node linkType: hard -"ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": - version: 2.1.3 - resolution: "ms@npm:2.1.3" - checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d - languageName: node - linkType: hard - "msgpackr-extract@npm:^2.2.0": version: 2.2.0 resolution: "msgpackr-extract@npm:2.2.0" @@ -12002,22 +11627,6 @@ __metadata: languageName: node linkType: hard -"multer@npm:1.4.4": - version: 1.4.4 - resolution: "multer@npm:1.4.4" - dependencies: - append-field: ^1.0.0 - busboy: ^0.2.11 - concat-stream: ^1.5.2 - mkdirp: ^0.5.4 - object-assign: ^4.1.1 - on-finished: ^2.3.0 - type-is: ^1.6.4 - xtend: ^4.0.0 - checksum: b5550d250aeee9c4d630eaecd133af0899239f6b10cec4b448ddd0a808025b383520b8227198a8612f60c2cd2094bcb60de93d973084f889d4e40efe6dbd641e - languageName: node - linkType: hard - "multi-integer-range@npm:3.0.0": version: 3.0.0 resolution: "multi-integer-range@npm:3.0.0" @@ -12039,7 +11648,7 @@ __metadata: languageName: node linkType: hard -"mz@npm:^2.4.0, mz@npm:^2.7.0": +"mz@npm:^2.4.0": version: 2.7.0 resolution: "mz@npm:2.7.0" dependencies: @@ -12632,6 +12241,13 @@ __metadata: languageName: node linkType: hard +"obliterator@npm:^2.0.1": + version: 2.0.4 + resolution: "obliterator@npm:2.0.4" + checksum: f28ad35b6d812089315f375dc3e6e5f9bebf958ebe4b10ccd471c7115cbcf595e74bdac4783ae758e5b1f47e3096427fdb37cfa7bed566b132df92ff317b9a7c + languageName: node + linkType: hard + "oblivious-set@npm:1.1.1": version: 1.1.1 resolution: "oblivious-set@npm:1.1.1" @@ -12646,7 +12262,14 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:^2.3.0": +"on-exit-leak-free@npm:^2.1.0": + version: 2.1.0 + resolution: "on-exit-leak-free@npm:2.1.0" + checksum: 7334d98b87b0c89c9b69c747760b21196ff35afdedc4eaf1a0a3a02964463d7f6802481b120e4c8298967c74773ca7b914ab2eb3d9b279010eb7f67ac4960eed + languageName: node + linkType: hard + +"on-finished@npm:2.4.1, on-finished@npm:^2.3.0": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -12981,13 +12604,6 @@ __metadata: languageName: node linkType: hard -"passthrough-counter@npm:^1.0.0": - version: 1.0.0 - resolution: "passthrough-counter@npm:1.0.0" - checksum: 942a0addeb677e24ddb154b04cc29ce1c5720032efc268689446420f9350d47e94f2f1f76d469686bc87c1543c2f2165f2d004d265fe1b81465c76e02d272c63 - languageName: node - linkType: hard - "path-dirname@npm:^1.0.0": version: 1.0.2 resolution: "path-dirname@npm:1.0.2" @@ -13011,7 +12627,7 @@ __metadata: languageName: node linkType: hard -"path-is-absolute@npm:1.0.1, path-is-absolute@npm:^1.0.0": +"path-is-absolute@npm:^1.0.0": version: 1.0.1 resolution: "path-is-absolute@npm:1.0.1" checksum: 060840f92cf8effa293bcc1bea81281bd7d363731d214cbe5c227df207c34cd727430f70c6037b5159c8a870b9157cba65e775446b0ab06fd5ecc7e54615a3b8 @@ -13062,13 +12678,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:^6.1.0": - version: 6.2.1 - resolution: "path-to-regexp@npm:6.2.1" - checksum: f0227af8284ea13300f4293ba111e3635142f976d4197f14d5ad1f124aebd9118783dd2e5f1fe16f7273743cc3dbeddfb7493f237bb27c10fdae07020cc9b698 - languageName: node - linkType: hard - "path-type@npm:^1.0.0": version: 1.1.0 resolution: "path-type@npm:1.1.0" @@ -13217,13 +12826,6 @@ __metadata: languageName: node linkType: hard -"pify@npm:^4.0.1": - version: 4.0.1 - resolution: "pify@npm:4.0.1" - checksum: 9c4e34278cb09987685fa5ef81499c82546c033713518f6441778fbec623fc708777fe8ac633097c72d88470d5963094076c7305cafc7ad340aae27cfacd856b - languageName: node - linkType: hard - "pinkie-promise@npm:^2.0.0": version: 2.0.1 resolution: "pinkie-promise@npm:2.0.1" @@ -13240,6 +12842,44 @@ __metadata: languageName: node linkType: hard +"pino-abstract-transport@npm:v1.0.0": + version: 1.0.0 + resolution: "pino-abstract-transport@npm:1.0.0" + dependencies: + readable-stream: ^4.0.0 + split2: ^4.0.0 + checksum: 05dd0eda52dd99fd204b39fe7b62656744b63e863bc052cdd5105d25f226a236966d0a46e39a1ace4838f6e988c608837ff946d2d0bc92835ca7baa0a3bff8d8 + languageName: node + linkType: hard + +"pino-std-serializers@npm:^6.0.0": + version: 6.0.0 + resolution: "pino-std-serializers@npm:6.0.0" + checksum: d9dc1779b3870cdbe00dc2dff15e3931eb126bb144bc9f746d83a2c1174a28e366ed0abe63379dee2fee474e6018a088bfbb2c4b57c1e206601918f5a61e276f + languageName: node + linkType: hard + +"pino@npm:^8.5.0": + version: 8.7.0 + resolution: "pino@npm:8.7.0" + dependencies: + atomic-sleep: ^1.0.0 + fast-redact: ^3.1.1 + on-exit-leak-free: ^2.1.0 + pino-abstract-transport: v1.0.0 + pino-std-serializers: ^6.0.0 + process-warning: ^2.0.0 + quick-format-unescaped: ^4.0.3 + real-require: ^0.2.0 + safe-stable-stringify: ^2.3.1 + sonic-boom: ^3.1.0 + thread-stream: ^2.0.0 + bin: + pino: bin.js + checksum: 4aa2e320aa88f4a90fd25884ee4e3b9ef7963b3c59c514f3693b5a5c987b112cf3ab4e39a8c51efe32c861f5c058d7cfa7fcda59d964ed878f842fdbc6ab2876 + languageName: node + linkType: hard + "pirates@npm:^4.0.4": version: 4.0.5 resolution: "pirates@npm:4.0.5" @@ -13728,17 +13368,6 @@ __metadata: languageName: node linkType: hard -"pretty@npm:^2.0.0": - version: 2.0.0 - resolution: "pretty@npm:2.0.0" - dependencies: - condense-newlines: ^0.2.1 - extend-shallow: ^2.0.1 - js-beautify: ^1.6.12 - checksum: 9c41ae0559195af2fb2496d84c6f442843e045d269d4008a6dd336f8372d7481395ed5ab23e5711b6172682c27cb0542e1ab3ca11b38da48f1109c0b701d0ef9 - languageName: node - linkType: hard - "prismjs@npm:1.29.0": version: 1.29.0 resolution: "prismjs@npm:1.29.0" @@ -13788,6 +13417,20 @@ __metadata: languageName: node linkType: hard +"process-warning@npm:^2.0.0": + version: 2.0.0 + resolution: "process-warning@npm:2.0.0" + checksum: a2bb299835bced58e63cbe06a8fd6e048a648d3649e81b62c442b63112a3f0a86912e7b1a9c557daca30652232d3b0a7f1972fb87c36334e2a5a6f3d5c4a76c9 + languageName: node + linkType: hard + +"process@npm:^0.11.10": + version: 0.11.10 + resolution: "process@npm:0.11.10" + checksum: bfcce49814f7d172a6e6a14d5fa3ac92cc3d0c3b9feb1279774708a719e19acd673995226351a082a9ae99978254e320ccda4240ddc474ba31a76c79491ca7c3 + languageName: node + linkType: hard + "progress@npm:^2.0.0": version: 2.0.3 resolution: "progress@npm:2.0.3" @@ -13838,10 +13481,13 @@ __metadata: languageName: node linkType: hard -"proto-list@npm:~1.2.1": - version: 1.2.4 - resolution: "proto-list@npm:1.2.4" - checksum: 4d4826e1713cbfa0f15124ab0ae494c91b597a3c458670c9714c36e8baddf5a6aad22842776f2f5b137f259c8533e741771445eb8df82e861eea37a6eaba03f7 +"proxy-addr@npm:^2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + checksum: 29c6990ce9364648255454842f06f8c46fcd124d3e6d7c5066df44662de63cdc0bad032e9bf5a3d653ff72141cc7b6019873d685708ac8210c30458ad99f2b74 languageName: node linkType: hard @@ -13863,13 +13509,6 @@ __metadata: languageName: node linkType: hard -"pseudomap@npm:^1.0.2": - version: 1.0.2 - resolution: "pseudomap@npm:1.0.2" - checksum: 856c0aae0ff2ad60881168334448e898ad7a0e45fe7386d114b150084254c01e200c957cf378378025df4e052c7890c5bd933939b0e0d2ecfcc1dc2f0b2991f5 - languageName: node - linkType: hard - "psl@npm:^1.1.28, psl@npm:^1.1.33": version: 1.9.0 resolution: "psl@npm:1.9.0" @@ -13988,7 +13627,7 @@ __metadata: languageName: node linkType: hard -"pug@npm:*, pug@npm:3.0.2": +"pug@npm:3.0.2": version: 3.0.2 resolution: "pug@npm:3.0.2" dependencies: @@ -14060,13 +13699,6 @@ __metadata: languageName: node linkType: hard -"q@npm:1.4.1": - version: 1.4.1 - resolution: "q@npm:1.4.1" - checksum: 22c8e1f24f416d0977e6da63f24712189c5dd789489999fc040467480e4e0ef4bd0e3126cce1b8ef72c709bbe1fcce10eba0f4991a03fc64ecb5a17e05ed8d35 - languageName: node - linkType: hard - "q@npm:^1.1.2": version: 1.5.1 resolution: "q@npm:1.5.1" @@ -14088,15 +13720,6 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.4.0, qs@npm:^6.5.2": - version: 6.11.0 - resolution: "qs@npm:6.11.0" - dependencies: - side-channel: ^1.0.4 - checksum: 6e1f29dd5385f7488ec74ac7b6c92f4d09a90408882d0c208414a34dd33badc1a621019d4c799a3df15ab9b1d0292f97c1dd71dc7c045e69f81a8064e5af7297 - languageName: node - linkType: hard - "qs@npm:~6.5.2": version: 6.5.3 resolution: "qs@npm:6.5.3" @@ -14149,6 +13772,13 @@ __metadata: languageName: node linkType: hard +"quick-format-unescaped@npm:^4.0.3": + version: 4.0.4 + resolution: "quick-format-unescaped@npm:4.0.4" + checksum: 7bc32b99354a1aa46c089d2a82b63489961002bb1d654cee3e6d2d8778197b68c2d854fd23d8422436ee1fdfd0abaddc4d4da120afe700ade68bd357815b26fd + languageName: node + linkType: hard + "quick-lru@npm:^5.1.1": version: 5.1.1 resolution: "quick-lru@npm:5.1.1" @@ -14165,6 +13795,13 @@ __metadata: languageName: node linkType: hard +"range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 0a268d4fea508661cf5743dfe3d5f47ce214fd6b7dec1de0da4d669dd4ef3d2144468ebe4179049eff253d9d27e719c88dae55be64f954e80135a0cada804ec9 + languageName: node + linkType: hard + "rangestr@npm:0.0.1": version: 0.0.1 resolution: "rangestr@npm:0.0.1" @@ -14179,18 +13816,6 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:^2.2.0, raw-body@npm:^2.3.3": - version: 2.5.1 - resolution: "raw-body@npm:2.5.1" - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - checksum: 5362adff1575d691bb3f75998803a0ffed8c64eabeaa06e54b4ada25a0cd1b2ae7f4f5ec46565d1bec337e08b5ac90c76eaa0758de6f72a633f025d754dec29e - languageName: node - linkType: hard - "rc@npm:^1.2.7": version: 1.2.8 resolution: "rc@npm:1.2.8" @@ -14253,18 +13878,6 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:1.1.x, readable-stream@npm:~1.1.9": - version: 1.1.14 - resolution: "readable-stream@npm:1.1.14" - dependencies: - core-util-is: ~1.0.0 - inherits: ~2.0.1 - isarray: 0.0.1 - string_decoder: ~0.10.x - checksum: 17dfeae3e909945a4a1abc5613ea92d03269ef54c49288599507fc98ff4615988a1c39a999dcf9aacba70233d9b7040bc11a5f2bfc947e262dedcc0a8b32b5a0 - languageName: node - linkType: hard - "readable-stream@npm:3, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": version: 3.6.0 resolution: "readable-stream@npm:3.6.0" @@ -14291,6 +13904,30 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^4.0.0": + version: 4.2.0 + resolution: "readable-stream@npm:4.2.0" + dependencies: + abort-controller: ^3.0.0 + buffer: ^6.0.3 + events: ^3.3.0 + process: ^0.11.10 + checksum: aa8447f781e6df90af78f6b0b9b9a77da2816dcf6c8220e7021c4de36e04f8129fed7ead81eac0baad2f42098209f9e7d7cd43169e1c156efcd2613828a37439 + languageName: node + linkType: hard + +"readable-stream@npm:~1.1.9": + version: 1.1.14 + resolution: "readable-stream@npm:1.1.14" + dependencies: + core-util-is: ~1.0.0 + inherits: ~2.0.1 + isarray: 0.0.1 + string_decoder: ~0.10.x + checksum: 17dfeae3e909945a4a1abc5613ea92d03269ef54c49288599507fc98ff4615988a1c39a999dcf9aacba70233d9b7040bc11a5f2bfc947e262dedcc0a8b32b5a0 + languageName: node + linkType: hard + "readable-web-to-node-stream@npm:^3.0.2": version: 3.0.2 resolution: "readable-web-to-node-stream@npm:3.0.2" @@ -14318,6 +13955,13 @@ __metadata: languageName: node linkType: hard +"real-require@npm:^0.2.0": + version: 0.2.0 + resolution: "real-require@npm:0.2.0" + checksum: fa060f19f2f447adf678d1376928c76379dce5f72bd334da301685ca6cdcb7b11356813332cc243c88470796bc2e2b1e2917fc10df9143dd93c2ea608694971d + languageName: node + linkType: hard + "rechoir@npm:^0.6.2": version: 0.6.2 resolution: "rechoir@npm:0.6.2" @@ -14348,15 +13992,6 @@ __metadata: languageName: node linkType: hard -"redis-info@npm:^3.0.8": - version: 3.1.0 - resolution: "redis-info@npm:3.1.0" - dependencies: - lodash: ^4.17.11 - checksum: d72ff0584ebb4a2149cfcfcf9142d9a7f9d0b96ae53fbf431f2738f33f1f42add6505ff73b2d640cab345923a34b217d7c728fa706cc81ad8bd8ad4c48987445 - languageName: node - linkType: hard - "redis-lock@npm:0.1.4": version: 0.1.4 resolution: "redis-lock@npm:0.1.4" @@ -14657,16 +14292,6 @@ __metadata: languageName: node linkType: hard -"resolve-path@npm:^1.4.0": - version: 1.4.0 - resolution: "resolve-path@npm:1.4.0" - dependencies: - http-errors: ~1.6.2 - path-is-absolute: 1.0.1 - checksum: 1a39f569ee54dd5f8ee8576ef8671c9724bea65d9f9982fbb5352af9fb4e500e1e459c1bfb1ae3ebfd8d43a709c3a01dfa4f46cf5b831e45e2caed4f1a208300 - languageName: node - linkType: hard - "resolve-url@npm:^0.2.1": version: 0.2.1 resolution: "resolve-url@npm:0.2.1" @@ -14742,6 +14367,13 @@ __metadata: languageName: node linkType: hard +"ret@npm:~0.2.0": + version: 0.2.2 + resolution: "ret@npm:0.2.2" + checksum: 774964bb413a3525e687bca92d81c1cd75555ec33147c32ecca22f3d06409e35df87952cfe3d57afff7650a0f7e42139cf60cb44e94c29dde390243bc1941f16 + languageName: node + linkType: hard + "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -14756,7 +14388,7 @@ __metadata: languageName: node linkType: hard -"rfdc@npm:^1.3.0": +"rfdc@npm:^1.2.0, rfdc@npm:^1.3.0": version: 1.3.0 resolution: "rfdc@npm:1.3.0" checksum: fb2ba8512e43519983b4c61bd3fa77c0f410eff6bae68b08614437bc3f35f91362215f7b4a73cbda6f67330b5746ce07db5dd9850ad3edc91271ad6deea0df32 @@ -14890,6 +14522,15 @@ __metadata: languageName: node linkType: hard +"safe-regex2@npm:^2.0.0": + version: 2.0.0 + resolution: "safe-regex2@npm:2.0.0" + dependencies: + ret: ~0.2.0 + checksum: f5e182fca040dedd50ae052ea0eb035d9903b2db71243d5d8b43299735857288ef2ab52546a368d9c6fd1333b2a0d039297925e78ffc14845354f3f6158af7c2 + languageName: node + linkType: hard + "safe-regex@npm:^1.1.0": version: 1.1.0 resolution: "safe-regex@npm:1.1.0" @@ -14899,6 +14540,13 @@ __metadata: languageName: node linkType: hard +"safe-stable-stringify@npm:^2.3.1": + version: 2.4.1 + resolution: "safe-stable-stringify@npm:2.4.1" + checksum: d8e505c462031301040605a4836ca25b52a1744eff01b0939b4d43136638fb8e88e0cec3d3ab6ab8e26f501086e6ba6bf34b228f57bf2ac56cb8d4061355d723 + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:^2.1.0, safer-buffer@npm:~2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -14967,7 +14615,7 @@ __metadata: languageName: node linkType: hard -"secure-json-parse@npm:^2.4.0": +"secure-json-parse@npm:^2.4.0, secure-json-parse@npm:^2.5.0": version: 2.5.0 resolution: "secure-json-parse@npm:2.5.0" checksum: 84147a32615ce0d93d2fbba60cde85ae362f45cc948ea134e4d6d1e678bb4b7f3a5ce9b9692ed052baefeb2e1c8ba183b34920390e6a089925b97b0d8f7ab064 @@ -14997,7 +14645,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.6.0": +"semver@npm:2 || 3 || 4 || 5": version: 5.7.1 resolution: "semver@npm:5.7.1" bin: @@ -15026,6 +14674,27 @@ __metadata: languageName: node linkType: hard +"send@npm:^0.18.0": + version: 0.18.0 + resolution: "send@npm:0.18.0" + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: ~1.0.2 + escape-html: ~1.0.3 + etag: ~1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: ~1.2.1 + statuses: 2.0.1 + checksum: 74fc07ebb58566b87b078ec63e5a3e41ecd987e4272ba67b7467e86c6ad51bc6b0b0154133b6d8b08a2ddda360464f71382f7ef864700f34844a76c8027817a8 + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -15033,6 +14702,13 @@ __metadata: languageName: node linkType: hard +"set-cookie-parser@npm:^2.4.1": + version: 2.5.1 + resolution: "set-cookie-parser@npm:2.5.1" + checksum: b99c37f976e68ae6eb7c758bf2bbce1e60bb54e3eccedaa25f2da45b77b9cab58d90674cf9edd7aead6fbeac6308f2eb48713320a47ca120d0e838d0194513b6 + languageName: node + linkType: hard + "set-value@npm:^2.0.0, set-value@npm:^2.0.1": version: 2.0.1 resolution: "set-value@npm:2.0.1" @@ -15052,13 +14728,6 @@ __metadata: languageName: node linkType: hard -"setprototypeof@npm:1.1.0": - version: 1.1.0 - resolution: "setprototypeof@npm:1.1.0" - checksum: 27cb44304d6c9e1a23bc6c706af4acaae1a7aa1054d4ec13c05f01a99fd4887109a83a8042b67ad90dbfcd100d43efc171ee036eb080667172079213242ca36e - languageName: node - linkType: hard - "setprototypeof@npm:1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" @@ -15122,13 +14791,6 @@ __metadata: languageName: node linkType: hard -"sigmund@npm:^1.0.1": - version: 1.0.1 - resolution: "sigmund@npm:1.0.1" - checksum: 793f81f8083ad75ff3903ffd93cf35be8d797e872822cf880aea27ce6db522b508d93ea52ae292bccf357ce34dd5c7faa544cc51c2216e70bbf5fcf09b62707c - languageName: node - linkType: hard - "signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -15270,6 +14932,15 @@ __metadata: languageName: node linkType: hard +"sonic-boom@npm:^3.1.0": + version: 3.2.0 + resolution: "sonic-boom@npm:3.2.0" + dependencies: + atomic-sleep: ^1.0.0 + checksum: 526669b78e0ac3bcbe2a53e5ac8960d3b25e61d8e6a46eaed5a0c46d7212c5f638bb136236870babedfcb626063711ba8f81e538f88b79e6a90a5b2ff71943b4 + languageName: node + linkType: hard + "sort-keys@npm:^1.0.0": version: 1.1.2 resolution: "sort-keys@npm:1.1.2" @@ -15413,7 +15084,7 @@ __metadata: languageName: node linkType: hard -"split2@npm:^4.1.0": +"split2@npm:^4.0.0, split2@npm:^4.1.0": version: 4.1.0 resolution: "split2@npm:4.1.0" checksum: ec581597cb74c13cdfb5e2047543dd40cb1e8e9803c7b1e0c29ede05f2b4f049b2d6e7f2788a225d544549375719658b8f38e9366364dec35dc7a12edfda5ee5 @@ -15532,7 +15203,7 @@ __metadata: languageName: node linkType: hard -"statuses@npm:>= 1.4.0 < 2, statuses@npm:>= 1.5.0 < 2, statuses@npm:^1.5.0": +"statuses@npm:>= 1.5.0 < 2, statuses@npm:^1.5.0": version: 1.5.0 resolution: "statuses@npm:1.5.0" checksum: c469b9519de16a4bb19600205cffb39ee471a5f17b82589757ca7bd40a8d92ebb6ed9f98b5a540c5d302ccbc78f15dc03cc0280dd6e00df1335568a5d5758a5c @@ -15571,10 +15242,10 @@ __metadata: languageName: node linkType: hard -"streamsearch@npm:0.1.2": - version: 0.1.2 - resolution: "streamsearch@npm:0.1.2" - checksum: d2db57cbfbf7947ab9c75a7b4c80a8ef8d24850cf0a1a24258bb6956c97317ce1eab7dbcbf9c5aba3e6198611af1053b02411057bbedb99bf9c64b8275248997 +"stream-wormhole@npm:^1.1.0": + version: 1.1.0 + resolution: "stream-wormhole@npm:1.1.0" + checksum: cc19e0235c5d031bd530fa83913c807d9525fa4ba33d51691dd822c0726b8b7ef138b34f289d063a3018cddba67d3ba7fd0ecedaa97242a0f1ed2eed3c6a2ab1 languageName: node linkType: hard @@ -15999,6 +15670,13 @@ __metadata: languageName: node linkType: hard +"text-decoding@npm:^1.0.0": + version: 1.0.0 + resolution: "text-decoding@npm:1.0.0" + checksum: 4b2359d8efdabea72ac470304e991913e9b82a55b1c33ab5204f115d11305ac5900add80aee5f7d22b2bcf0faebaf35b193d28a10b74adf175d9ac9d63604445 + languageName: node + linkType: hard + "text-table@npm:^0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -16038,6 +15716,15 @@ __metadata: languageName: node linkType: hard +"thread-stream@npm:^2.0.0": + version: 2.2.0 + resolution: "thread-stream@npm:2.2.0" + dependencies: + real-require: ^0.2.0 + checksum: b7f0ee166ed17ac54700a0b6fc291967c97785b458ff54efe5431a7281bb52d1163e6ec550a614f2a47f0f02de5b35a342bd5acd215af23030938c64859152b2 + languageName: node + linkType: hard + "three@npm:0.146.0": version: 0.146.0 resolution: "three@npm:0.146.0" @@ -16109,6 +15796,13 @@ __metadata: languageName: node linkType: hard +"tiny-lru@npm:^10.0.0": + version: 10.0.1 + resolution: "tiny-lru@npm:10.0.1" + checksum: 58b5f17a357625335aa3b90ee8c9b3e9abede5c1f46066c73deb129574a205efb112807d6d473909e73f1d874ea99bf14eb5c88223d540eb32ebb5e1ff146689 + languageName: node + linkType: hard + "tinycolor2@npm:1.4.2": version: 1.4.2 resolution: "tinycolor2@npm:1.4.2" @@ -16460,7 +16154,7 @@ __metadata: languageName: node linkType: hard -"type-is@npm:^1.6.14, type-is@npm:^1.6.16, type-is@npm:^1.6.4": +"type-is@npm:^1.6.16": version: 1.6.18 resolution: "type-is@npm:1.6.18" dependencies: @@ -16749,13 +16443,6 @@ __metadata: languageName: node linkType: hard -"unpipe@npm:1.0.0": - version: 1.0.0 - resolution: "unpipe@npm:1.0.0" - checksum: 4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2 - languageName: node - linkType: hard - "unset-value@npm:^1.0.0": version: 1.0.0 resolution: "unset-value@npm:1.0.0" @@ -16981,7 +16668,7 @@ __metadata: languageName: node linkType: hard -"vary@npm:^1.1.2": +"vary@npm:1.1.2, vary@npm:^1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" checksum: ae0123222c6df65b437669d63dfa8c36cee20a504101b2fcd97b8bf76f91259c17f9f2b4d70a1e3c6bbcee7f51b28392833adb6b2770b23b01abec84e369660b @@ -17554,13 +17241,6 @@ __metadata: languageName: node linkType: hard -"yallist@npm:^2.1.2": - version: 2.1.2 - resolution: "yallist@npm:2.1.2" - checksum: 9ba99409209f485b6fcb970330908a6d41fa1c933f75e08250316cce19383179a6b70a7e0721b89672ebb6199cc377bf3e432f55100da6a7d6e11902b0a642cb - languageName: node - linkType: hard - "yallist@npm:^3.0.0, yallist@npm:^3.1.1": version: 3.1.1 resolution: "yallist@npm:3.1.1" -- cgit v1.2.3-freya From 22ccb0fa716a84560c8599781647baaaeb8e80bd Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 4 Dec 2022 10:16:03 +0900 Subject: refactor --- packages/backend/src/core/AccountUpdateService.ts | 6 +- packages/backend/src/core/AntennaService.ts | 2 +- packages/backend/src/core/CaptchaService.ts | 2 +- packages/backend/src/core/CoreModule.ts | 48 +- .../backend/src/core/CreateNotificationService.ts | 4 +- packages/backend/src/core/CustomEmojiService.ts | 4 +- packages/backend/src/core/DriveService.ts | 6 +- .../backend/src/core/FederatedInstanceService.ts | 2 +- .../src/core/FetchInstanceMetadataService.ts | 2 +- packages/backend/src/core/HashtagService.ts | 2 +- packages/backend/src/core/InstanceActorService.ts | 2 +- packages/backend/src/core/MessagingService.ts | 12 +- packages/backend/src/core/NoteCreateService.ts | 16 +- packages/backend/src/core/NoteDeleteService.ts | 8 +- packages/backend/src/core/NotePiningService.ts | 6 +- packages/backend/src/core/NoteReadService.ts | 2 +- packages/backend/src/core/NotificationService.ts | 2 +- packages/backend/src/core/PollService.ts | 6 +- packages/backend/src/core/ProxyAccountService.ts | 2 +- .../backend/src/core/PushNotificationService.ts | 2 +- packages/backend/src/core/QueueModule.ts | 112 ++++ packages/backend/src/core/QueueService.ts | 4 +- packages/backend/src/core/ReactionService.ts | 10 +- packages/backend/src/core/RelayService.ts | 2 +- packages/backend/src/core/RemoteLoggerService.ts | 14 + .../backend/src/core/RemoteUserResolveService.ts | 132 ++++ packages/backend/src/core/S3Service.ts | 2 +- packages/backend/src/core/SignupService.ts | 2 +- packages/backend/src/core/UserBlockingService.ts | 8 +- packages/backend/src/core/UserCacheService.ts | 2 +- packages/backend/src/core/UserFollowingService.ts | 4 +- packages/backend/src/core/UserListService.ts | 4 +- packages/backend/src/core/UserSuspendService.ts | 4 +- packages/backend/src/core/WebfingerService.ts | 48 ++ .../src/core/activitypub/ApAudienceService.ts | 104 +++ .../src/core/activitypub/ApDbResolverService.ts | 179 +++++ .../core/activitypub/ApDeliverManagerService.ts | 199 ++++++ .../backend/src/core/activitypub/ApInboxService.ts | 740 +++++++++++++++++++++ .../src/core/activitypub/ApLoggerService.ts | 14 + .../backend/src/core/activitypub/ApMfmService.ts | 30 + .../src/core/activitypub/ApRendererService.ts | 703 ++++++++++++++++++++ .../src/core/activitypub/ApRequestService.ts | 182 +++++ .../src/core/activitypub/ApResolverService.ts | 195 ++++++ .../src/core/activitypub/LdSignatureService.ts | 147 ++++ .../backend/src/core/activitypub/misc/contexts.ts | 526 +++++++++++++++ .../src/core/activitypub/models/ApImageService.ts | 90 +++ .../core/activitypub/models/ApMentionService.ts | 39 ++ .../src/core/activitypub/models/ApNoteService.ts | 403 +++++++++++ .../src/core/activitypub/models/ApPersonService.ts | 594 +++++++++++++++++ .../core/activitypub/models/ApQuestionService.ts | 109 +++ .../backend/src/core/activitypub/models/icon.ts | 5 + .../src/core/activitypub/models/identifier.ts | 5 + .../backend/src/core/activitypub/models/tag.ts | 19 + packages/backend/src/core/activitypub/type.ts | 296 +++++++++ .../backend/src/core/chart/charts/active-users.ts | 2 +- .../backend/src/core/chart/charts/ap-request.ts | 2 +- packages/backend/src/core/chart/charts/drive.ts | 2 +- .../backend/src/core/chart/charts/federation.ts | 2 +- packages/backend/src/core/chart/charts/hashtag.ts | 2 +- packages/backend/src/core/chart/charts/instance.ts | 2 +- packages/backend/src/core/chart/charts/notes.ts | 2 +- .../src/core/chart/charts/per-user-drive.ts | 2 +- .../src/core/chart/charts/per-user-following.ts | 2 +- .../src/core/chart/charts/per-user-notes.ts | 2 +- .../src/core/chart/charts/per-user-reactions.ts | 2 +- .../backend/src/core/chart/charts/test-grouped.ts | 2 +- .../src/core/chart/charts/test-intersection.ts | 2 +- .../backend/src/core/chart/charts/test-unique.ts | 2 +- packages/backend/src/core/chart/charts/test.ts | 2 +- packages/backend/src/core/chart/charts/users.ts | 2 +- .../src/core/entities/InstanceEntityService.ts | 2 +- packages/backend/src/core/queue/QueueModule.ts | 112 ---- .../backend/src/core/remote/RemoteLoggerService.ts | 14 - .../backend/src/core/remote/ResolveUserService.ts | 132 ---- .../backend/src/core/remote/WebfingerService.ts | 48 -- .../core/remote/activitypub/ApAudienceService.ts | 104 --- .../core/remote/activitypub/ApDbResolverService.ts | 179 ----- .../remote/activitypub/ApDeliverManagerService.ts | 199 ------ .../src/core/remote/activitypub/ApInboxService.ts | 740 --------------------- .../src/core/remote/activitypub/ApLoggerService.ts | 14 - .../src/core/remote/activitypub/ApMfmService.ts | 30 - .../core/remote/activitypub/ApRendererService.ts | 703 -------------------- .../core/remote/activitypub/ApRequestService.ts | 182 ----- .../core/remote/activitypub/ApResolverService.ts | 195 ------ .../core/remote/activitypub/LdSignatureService.ts | 147 ---- .../src/core/remote/activitypub/misc/contexts.ts | 526 --------------- .../remote/activitypub/models/ApImageService.ts | 90 --- .../remote/activitypub/models/ApMentionService.ts | 39 -- .../remote/activitypub/models/ApNoteService.ts | 403 ----------- .../remote/activitypub/models/ApPersonService.ts | 594 ----------------- .../remote/activitypub/models/ApQuestionService.ts | 109 --- .../src/core/remote/activitypub/models/icon.ts | 5 - .../core/remote/activitypub/models/identifier.ts | 5 - .../src/core/remote/activitypub/models/tag.ts | 19 - .../backend/src/core/remote/activitypub/type.ts | 296 --------- .../queue/processors/DeliverProcessorService.ts | 2 +- .../processors/ImportBlockingProcessorService.ts | 6 +- .../processors/ImportFollowingProcessorService.ts | 6 +- .../processors/ImportMutingProcessorService.ts | 6 +- .../processors/ImportUserListsProcessorService.ts | 6 +- .../src/queue/processors/InboxProcessorService.ts | 12 +- packages/backend/src/queue/types.ts | 2 +- .../backend/src/server/ActivityPubServerService.ts | 2 +- .../api/endpoints/admin/queue/deliver-delayed.ts | 2 +- .../api/endpoints/admin/queue/inbox-delayed.ts | 2 +- .../src/server/api/endpoints/admin/queue/stats.ts | 2 +- .../endpoints/admin/resolve-abuse-user-report.ts | 2 +- .../backend/src/server/api/endpoints/ap/get.ts | 2 +- .../backend/src/server/api/endpoints/ap/show.ts | 10 +- .../api/endpoints/federation/update-remote-user.ts | 2 +- .../src/server/api/endpoints/notes/polls/vote.ts | 2 +- .../backend/src/server/api/endpoints/users/show.ts | 6 +- .../backend/src/server/web/ClientServerService.ts | 2 +- packages/backend/test/misc/mock-resolver.ts | 4 +- packages/backend/test/tests/activitypub.ts | 6 +- packages/backend/test/tests/ap-request.ts | 2 +- packages/backend/test/unit/RelayService.ts | 2 +- 117 files changed, 5035 insertions(+), 5035 deletions(-) create mode 100644 packages/backend/src/core/QueueModule.ts create mode 100644 packages/backend/src/core/RemoteLoggerService.ts create mode 100644 packages/backend/src/core/RemoteUserResolveService.ts create mode 100644 packages/backend/src/core/WebfingerService.ts create mode 100644 packages/backend/src/core/activitypub/ApAudienceService.ts create mode 100644 packages/backend/src/core/activitypub/ApDbResolverService.ts create mode 100644 packages/backend/src/core/activitypub/ApDeliverManagerService.ts create mode 100644 packages/backend/src/core/activitypub/ApInboxService.ts create mode 100644 packages/backend/src/core/activitypub/ApLoggerService.ts create mode 100644 packages/backend/src/core/activitypub/ApMfmService.ts create mode 100644 packages/backend/src/core/activitypub/ApRendererService.ts create mode 100644 packages/backend/src/core/activitypub/ApRequestService.ts create mode 100644 packages/backend/src/core/activitypub/ApResolverService.ts create mode 100644 packages/backend/src/core/activitypub/LdSignatureService.ts create mode 100644 packages/backend/src/core/activitypub/misc/contexts.ts create mode 100644 packages/backend/src/core/activitypub/models/ApImageService.ts create mode 100644 packages/backend/src/core/activitypub/models/ApMentionService.ts create mode 100644 packages/backend/src/core/activitypub/models/ApNoteService.ts create mode 100644 packages/backend/src/core/activitypub/models/ApPersonService.ts create mode 100644 packages/backend/src/core/activitypub/models/ApQuestionService.ts create mode 100644 packages/backend/src/core/activitypub/models/icon.ts create mode 100644 packages/backend/src/core/activitypub/models/identifier.ts create mode 100644 packages/backend/src/core/activitypub/models/tag.ts create mode 100644 packages/backend/src/core/activitypub/type.ts delete mode 100644 packages/backend/src/core/queue/QueueModule.ts delete mode 100644 packages/backend/src/core/remote/RemoteLoggerService.ts delete mode 100644 packages/backend/src/core/remote/ResolveUserService.ts delete mode 100644 packages/backend/src/core/remote/WebfingerService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApAudienceService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApDbResolverService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApInboxService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApLoggerService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApMfmService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApRendererService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApRequestService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/ApResolverService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/LdSignatureService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/misc/contexts.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/ApImageService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/ApMentionService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/ApNoteService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/ApPersonService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/icon.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/identifier.ts delete mode 100644 packages/backend/src/core/remote/activitypub/models/tag.ts delete mode 100644 packages/backend/src/core/remote/activitypub/type.ts (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/core/AccountUpdateService.ts b/packages/backend/src/core/AccountUpdateService.ts index 6fe0e05c6d..a5ab4fdfce 100644 --- a/packages/backend/src/core/AccountUpdateService.ts +++ b/packages/backend/src/core/AccountUpdateService.ts @@ -3,10 +3,10 @@ import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type { User } from '@/models/entities/User.js'; -import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { RelayService } from '@/core/RelayService.js'; -import { ApDeliverManagerService } from '@/core/remote/activitypub/ApDeliverManagerService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; +import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; @Injectable() export class AccountUpdateService { diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index af76767f31..8046ba5311 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -11,7 +11,7 @@ import { Cache } from '@/misc/cache.js'; import type { Packed } from '@/misc/schema.js'; import { DI } from '@/di-symbols.js'; import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; -import { UtilityService } from './UtilityService.js'; +import { UtilityService } from '@/core/UtilityService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index b2bc24ac2c..b60271812c 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; -import { HttpRequestService } from './HttpRequestService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; type CaptchaResponse = { success: boolean; diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index da07728d22..085addaa05 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -94,25 +94,25 @@ import { UserEntityService } from './entities/UserEntityService.js'; import { UserGroupEntityService } from './entities/UserGroupEntityService.js'; import { UserGroupInvitationEntityService } from './entities/UserGroupInvitationEntityService.js'; import { UserListEntityService } from './entities/UserListEntityService.js'; -import { ApAudienceService } from './remote/activitypub/ApAudienceService.js'; -import { ApDbResolverService } from './remote/activitypub/ApDbResolverService.js'; -import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js'; -import { ApInboxService } from './remote/activitypub/ApInboxService.js'; -import { ApLoggerService } from './remote/activitypub/ApLoggerService.js'; -import { ApMfmService } from './remote/activitypub/ApMfmService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { ApRequestService } from './remote/activitypub/ApRequestService.js'; -import { ApResolverService } from './remote/activitypub/ApResolverService.js'; -import { LdSignatureService } from './remote/activitypub/LdSignatureService.js'; -import { RemoteLoggerService } from './remote/RemoteLoggerService.js'; -import { ResolveUserService } from './remote/ResolveUserService.js'; -import { WebfingerService } from './remote/WebfingerService.js'; -import { ApImageService } from './remote/activitypub/models/ApImageService.js'; -import { ApMentionService } from './remote/activitypub/models/ApMentionService.js'; -import { ApNoteService } from './remote/activitypub/models/ApNoteService.js'; -import { ApPersonService } from './remote/activitypub/models/ApPersonService.js'; -import { ApQuestionService } from './remote/activitypub/models/ApQuestionService.js'; -import { QueueModule } from './queue/QueueModule.js'; +import { ApAudienceService } from './activitypub/ApAudienceService.js'; +import { ApDbResolverService } from './activitypub/ApDbResolverService.js'; +import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js'; +import { ApInboxService } from './activitypub/ApInboxService.js'; +import { ApLoggerService } from './activitypub/ApLoggerService.js'; +import { ApMfmService } from './activitypub/ApMfmService.js'; +import { ApRendererService } from './activitypub/ApRendererService.js'; +import { ApRequestService } from './activitypub/ApRequestService.js'; +import { ApResolverService } from './activitypub/ApResolverService.js'; +import { LdSignatureService } from './activitypub/LdSignatureService.js'; +import { RemoteLoggerService } from './RemoteLoggerService.js'; +import { RemoteUserResolveService } from './RemoteUserResolveService.js'; +import { WebfingerService } from './WebfingerService.js'; +import { ApImageService } from './activitypub/models/ApImageService.js'; +import { ApMentionService } from './activitypub/models/ApMentionService.js'; +import { ApNoteService } from './activitypub/models/ApNoteService.js'; +import { ApPersonService } from './activitypub/models/ApPersonService.js'; +import { ApQuestionService } from './activitypub/models/ApQuestionService.js'; +import { QueueModule } from './QueueModule.js'; import { QueueService } from './QueueService.js'; import { LoggerService } from './LoggerService.js'; import type { Provider } from '@nestjs/common'; @@ -226,7 +226,7 @@ const $ApRequestService: Provider = { provide: 'ApRequestService', useExisting: const $ApResolverService: Provider = { provide: 'ApResolverService', useExisting: ApResolverService }; const $LdSignatureService: Provider = { provide: 'LdSignatureService', useExisting: LdSignatureService }; const $RemoteLoggerService: Provider = { provide: 'RemoteLoggerService', useExisting: RemoteLoggerService }; -const $ResolveUserService: Provider = { provide: 'ResolveUserService', useExisting: ResolveUserService }; +const $RemoteUserResolveService: Provider = { provide: 'RemoteUserResolveService', useExisting: RemoteUserResolveService }; const $WebfingerService: Provider = { provide: 'WebfingerService', useExisting: WebfingerService }; const $ApImageService: Provider = { provide: 'ApImageService', useExisting: ApImageService }; const $ApMentionService: Provider = { provide: 'ApMentionService', useExisting: ApMentionService }; @@ -346,7 +346,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ApResolverService, LdSignatureService, RemoteLoggerService, - ResolveUserService, + RemoteUserResolveService, WebfingerService, ApImageService, ApMentionService, @@ -462,7 +462,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $ApResolverService, $LdSignatureService, $RemoteLoggerService, - $ResolveUserService, + $RemoteUserResolveService, $WebfingerService, $ApImageService, $ApMentionService, @@ -578,7 +578,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ApResolverService, LdSignatureService, RemoteLoggerService, - ResolveUserService, + RemoteUserResolveService, WebfingerService, ApImageService, ApMentionService, @@ -693,7 +693,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $ApResolverService, $LdSignatureService, $RemoteLoggerService, - $ResolveUserService, + $RemoteUserResolveService, $WebfingerService, $ApImageService, $ApMentionService, diff --git a/packages/backend/src/core/CreateNotificationService.ts b/packages/backend/src/core/CreateNotificationService.ts index feb82dcbf9..504661c3bd 100644 --- a/packages/backend/src/core/CreateNotificationService.ts +++ b/packages/backend/src/core/CreateNotificationService.ts @@ -5,8 +5,8 @@ import type { Notification } from '@/models/entities/Notification.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; -import { NotificationEntityService } from './entities/NotificationEntityService.js'; -import { PushNotificationService } from './PushNotificationService.js'; +import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js'; +import { PushNotificationService } from '@/core/PushNotificationService.js'; @Injectable() export class CreateNotificationService { diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index e1355fff07..3319f3efa8 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -10,8 +10,8 @@ import { Cache } from '@/misc/cache.js'; import { query } from '@/misc/prelude/url.js'; import type { Note } from '@/models/entities/Note.js'; import type { EmojisRepository } from '@/models/index.js'; -import { UtilityService } from './UtilityService.js'; -import { ReactionService } from './ReactionService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { ReactionService } from '@/core/ReactionService.js'; /** * 添付用絵文字情報 diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index e0bdd29c0f..1d2ba5df8c 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -28,9 +28,9 @@ import InstanceChart from '@/core/chart/charts/instance.js'; import { DownloadService } from '@/core/DownloadService.js'; import { S3Service } from '@/core/S3Service.js'; import { InternalStorageService } from '@/core/InternalStorageService.js'; -import { DriveFileEntityService } from './entities/DriveFileEntityService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { FileInfoService } from './FileInfoService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { FileInfoService } from '@/core/FileInfoService.js'; import type S3 from 'aws-sdk/clients/s3.js'; type AddFileArgs = { diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index b98a41f757..a05c95a2ae 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -4,7 +4,7 @@ import type { Instance } from '@/models/entities/Instance.js'; import { Cache } from '@/misc/cache.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; -import { UtilityService } from './UtilityService.js'; +import { UtilityService } from '@/core/UtilityService.js'; @Injectable() export class FederatedInstanceService { diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 184404123c..b92ebe6059 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -9,7 +9,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import type Logger from '@/logger.js'; import { DI } from '@/di-symbols.js'; import { LoggerService } from '@/core/LoggerService.js'; -import { HttpRequestService } from './HttpRequestService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; import type { DOMWindow } from 'jsdom'; type NodeInfo = { diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts index 83950aa890..5ca058e9a4 100644 --- a/packages/backend/src/core/HashtagService.ts +++ b/packages/backend/src/core/HashtagService.ts @@ -6,7 +6,7 @@ import { IdService } from '@/core/IdService.js'; import type { Hashtag } from '@/models/entities/Hashtag.js'; import HashtagChart from '@/core/chart/charts/hashtag.js'; import type { HashtagsRepository, UsersRepository } from '@/models/index.js'; -import { UserEntityService } from './entities/UserEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; @Injectable() export class HashtagService { diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts index fa906df4a2..f35a28147d 100644 --- a/packages/backend/src/core/InstanceActorService.ts +++ b/packages/backend/src/core/InstanceActorService.ts @@ -4,7 +4,7 @@ import type { ILocalUser } from '@/models/entities/User.js'; import type { UsersRepository } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; import { DI } from '@/di-symbols.js'; -import { CreateSystemUserService } from './CreateSystemUserService.js'; +import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; const ACTOR_USERNAME = 'instance.actor' as const; diff --git a/packages/backend/src/core/MessagingService.ts b/packages/backend/src/core/MessagingService.ts index 0603da0651..9de28ad8db 100644 --- a/packages/backend/src/core/MessagingService.ts +++ b/packages/backend/src/core/MessagingService.ts @@ -11,12 +11,12 @@ import { QueueService } from '@/core/QueueService.js'; import { toArray } from '@/misc/prelude/array.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { MessagingMessagesRepository, MutingsRepository, UserGroupJoiningsRepository, UsersRepository } from '@/models/index.js'; -import { IdService } from './IdService.js'; -import { GlobalEventService } from './GlobalEventService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { MessagingMessageEntityService } from './entities/MessagingMessageEntityService.js'; -import { PushNotificationService } from './PushNotificationService.js'; +import { IdService } from '@/core/IdService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; +import { PushNotificationService } from '@/core/PushNotificationService.js'; @Injectable() export class MessagingService { diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index a23e105674..cf1566a5e8 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -34,12 +34,12 @@ import { WebhookService } from '@/core/WebhookService.js'; import { HashtagService } from '@/core/HashtagService.js'; import { AntennaService } from '@/core/AntennaService.js'; import { QueueService } from '@/core/QueueService.js'; -import { NoteEntityService } from './entities/NoteEntityService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { NoteReadService } from './NoteReadService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { ResolveUserService } from './remote/ResolveUserService.js'; -import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; +import { NoteReadService } from '@/core/NoteReadService.js'; +import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5); @@ -179,7 +179,7 @@ export class NoteCreateService { private hashtagService: HashtagService, private antennaService: AntennaService, private webhookService: WebhookService, - private resolveUserService: ResolveUserService, + private remoteUserResolveService: RemoteUserResolveService, private apDeliverManagerService: ApDeliverManagerService, private apRendererService: ApRendererService, private notesChart: NotesChart, @@ -726,7 +726,7 @@ export class NoteCreateService { const mentions = extractMentions(tokens); let mentionedUsers = (await Promise.all(mentions.map(m => - this.resolveUserService.resolveUser(m.username, m.host ?? user.host).catch(() => null), + this.remoteUserResolveService.resolveUser(m.username, m.host ?? user.host).catch(() => null), ))).filter(x => x != null) as User[]; // Drop duplicate users diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index ccc583c5b6..ce6e755a7e 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -11,10 +11,10 @@ import NotesChart from '@/core/chart/charts/notes.js'; import PerUserNotesChart from '@/core/chart/charts/per-user-notes.js'; import InstanceChart from '@/core/chart/charts/instance.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { NoteEntityService } from './entities/NoteEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @Injectable() export class NoteDeleteService { diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts index 8c4a761ba6..a04b52fe4c 100644 --- a/packages/backend/src/core/NotePiningService.ts +++ b/packages/backend/src/core/NotePiningService.ts @@ -8,9 +8,9 @@ import { IdService } from '@/core/IdService.js'; import type { UserNotePining } from '@/models/entities/UserNotePining.js'; import { RelayService } from '@/core/RelayService.js'; import type { Config } from '@/config.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; @Injectable() export class NotePiningService { diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index 2c84e1d4d5..e0feaa957d 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -8,7 +8,7 @@ import type { Note } from '@/models/entities/Note.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { UsersRepository, NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository, FollowingsRepository, ChannelFollowingsRepository, AntennaNotesRepository } from '@/models/index.js'; -import { UserEntityService } from './entities/UserEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NotificationService } from './NotificationService.js'; import { AntennaService } from './AntennaService.js'; diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index 2606ca4de0..8bbc95b02d 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -5,7 +5,7 @@ import type { NotificationsRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import type { Notification } from '@/models/entities/Notification.js'; -import { UserEntityService } from './entities/UserEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalEventService } from './GlobalEventService.js'; import { PushNotificationService } from './PushNotificationService.js'; diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index e3e12b5320..287ce8ada4 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -8,9 +8,9 @@ import type { CacheableUser } from '@/models/entities/User.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { CreateNotificationService } from '@/core/CreateNotificationService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; @Injectable() export class PollService { diff --git a/packages/backend/src/core/ProxyAccountService.ts b/packages/backend/src/core/ProxyAccountService.ts index 07d8d0dbd5..4cbdadd029 100644 --- a/packages/backend/src/core/ProxyAccountService.ts +++ b/packages/backend/src/core/ProxyAccountService.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UsersRepository } from '@/models/index.js'; import type { ILocalUser, User } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; -import { MetaService } from './MetaService.js'; +import { MetaService } from '@/core/MetaService.js'; @Injectable() export class ProxyAccountService { diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index 5eaaed00eb..98e0841799 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -5,7 +5,7 @@ import type { Config } from '@/config.js'; import type { Packed } from '@/misc/schema'; import { getNoteSummary } from '@/misc/get-note-summary.js'; import type { SwSubscriptionsRepository } from '@/models/index.js'; -import { MetaService } from './MetaService.js'; +import { MetaService } from '@/core/MetaService.js'; // Defined also packages/sw/types.ts#L14-L21 type pushNotificationsTypes = { diff --git a/packages/backend/src/core/QueueModule.ts b/packages/backend/src/core/QueueModule.ts new file mode 100644 index 0000000000..edd843977b --- /dev/null +++ b/packages/backend/src/core/QueueModule.ts @@ -0,0 +1,112 @@ +import { Module } from '@nestjs/common'; +import Bull from 'bull'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import type { Provider } from '@nestjs/common'; +import type { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData, WebhookDeliverJobData } from '../queue/types.js'; + +function q(config: Config, name: string, limitPerSec = -1) { + return new Bull(name, { + redis: { + port: config.redis.port, + host: config.redis.host, + family: config.redis.family == null ? 0 : config.redis.family, + password: config.redis.pass, + db: config.redis.db ?? 0, + }, + prefix: config.redis.prefix ? `${config.redis.prefix}:queue` : 'queue', + limiter: limitPerSec > 0 ? { + max: limitPerSec, + duration: 1000, + } : undefined, + settings: { + backoffStrategies: { + apBackoff, + }, + }, + }); +} + +// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019 +function apBackoff(attemptsMade: number, err: Error) { + const baseDelay = 60 * 1000; // 1min + const maxBackoff = 8 * 60 * 60 * 1000; // 8hours + let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay; + backoff = Math.min(backoff, maxBackoff); + backoff += Math.round(backoff * Math.random() * 0.2); + return backoff; +} + +export type SystemQueue = Bull.Queue>; +export type EndedPollNotificationQueue = Bull.Queue; +export type DeliverQueue = Bull.Queue; +export type InboxQueue = Bull.Queue; +export type DbQueue = Bull.Queue; +export type ObjectStorageQueue = Bull.Queue; +export type WebhookDeliverQueue = Bull.Queue; + +const $system: Provider = { + provide: 'queue:system', + useFactory: (config: Config) => q(config, 'system'), + inject: [DI.config], +}; + +const $endedPollNotification: Provider = { + provide: 'queue:endedPollNotification', + useFactory: (config: Config) => q(config, 'endedPollNotification'), + inject: [DI.config], +}; + +const $deliver: Provider = { + provide: 'queue:deliver', + useFactory: (config: Config) => q(config, 'deliver', config.deliverJobPerSec ?? 128), + inject: [DI.config], +}; + +const $inbox: Provider = { + provide: 'queue:inbox', + useFactory: (config: Config) => q(config, 'inbox', config.inboxJobPerSec ?? 16), + inject: [DI.config], +}; + +const $db: Provider = { + provide: 'queue:db', + useFactory: (config: Config) => q(config, 'db'), + inject: [DI.config], +}; + +const $objectStorage: Provider = { + provide: 'queue:objectStorage', + useFactory: (config: Config) => q(config, 'objectStorage'), + inject: [DI.config], +}; + +const $webhookDeliver: Provider = { + provide: 'queue:webhookDeliver', + useFactory: (config: Config) => q(config, 'webhookDeliver', 64), + inject: [DI.config], +}; + +@Module({ + imports: [ + ], + providers: [ + $system, + $endedPollNotification, + $deliver, + $inbox, + $db, + $objectStorage, + $webhookDeliver, + ], + exports: [ + $system, + $endedPollNotification, + $deliver, + $inbox, + $db, + $objectStorage, + $webhookDeliver, + ], +}) +export class QueueModule {} diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index d9ad26747f..a27d68ee19 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -1,11 +1,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { v4 as uuid } from 'uuid'; -import type { IActivity } from '@/core/remote/activitypub/type.js'; +import type { IActivity } from '@/core/activitypub/type.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Webhook, webhookEventTypes } from '@/models/entities/Webhook.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; -import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from './queue/QueueModule.js'; +import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js'; import type { ThinUser } from '../queue/types.js'; import type httpSignature from '@peertube/http-signature'; diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index d5b3c0e799..7a9724e7dd 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -12,11 +12,11 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import PerUserReactionsChart from '@/core/chart/charts/per-user-reactions.js'; import { emojiRegex } from '@/misc/emoji-regex.js'; -import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js'; -import { NoteEntityService } from './entities/NoteEntityService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { MetaService } from './MetaService.js'; +import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { MetaService } from '@/core/MetaService.js'; import { UtilityService } from './UtilityService.js'; const legacies: Record = { diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts index 3c67e0573f..7951edddcb 100644 --- a/packages/backend/src/core/RelayService.ts +++ b/packages/backend/src/core/RelayService.ts @@ -7,7 +7,7 @@ import { Cache } from '@/misc/cache.js'; import type { Relay } from '@/models/entities/Relay.js'; import { QueueService } from '@/core/QueueService.js'; import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; -import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { DI } from '@/di-symbols.js'; import { deepClone } from '@/misc/clone.js'; diff --git a/packages/backend/src/core/RemoteLoggerService.ts b/packages/backend/src/core/RemoteLoggerService.ts new file mode 100644 index 0000000000..68246466c8 --- /dev/null +++ b/packages/backend/src/core/RemoteLoggerService.ts @@ -0,0 +1,14 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type Logger from '@/logger.js'; +import { LoggerService } from '@/core/LoggerService.js'; + +@Injectable() +export class RemoteLoggerService { + public logger: Logger; + + constructor( + private loggerService: LoggerService, + ) { + this.logger = this.loggerService.getLogger('remote', 'cyan'); + } +} diff --git a/packages/backend/src/core/RemoteUserResolveService.ts b/packages/backend/src/core/RemoteUserResolveService.ts new file mode 100644 index 0000000000..809b50f6e9 --- /dev/null +++ b/packages/backend/src/core/RemoteUserResolveService.ts @@ -0,0 +1,132 @@ +import { URL } from 'node:url'; +import { Inject, Injectable } from '@nestjs/common'; +import chalk from 'chalk'; +import { IsNull } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { UsersRepository } from '@/models/index.js'; +import type { IRemoteUser, User } from '@/models/entities/User.js'; +import type { Config } from '@/config.js'; +import type Logger from '@/logger.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { WebfingerService } from '@/core/WebfingerService.js'; +import { RemoteLoggerService } from '@/core/RemoteLoggerService.js'; +import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; + +@Injectable() +export class RemoteUserResolveService { + private logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private utilityService: UtilityService, + private webfingerService: WebfingerService, + private remoteLoggerService: RemoteLoggerService, + private apPersonService: ApPersonService, + ) { + this.logger = this.remoteLoggerService.logger.createSubLogger('resolve-user'); + } + + public async resolveUser(username: string, host: string | null): Promise { + const usernameLower = username.toLowerCase(); + + if (host == null) { + this.logger.info(`return local user: ${usernameLower}`); + return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => { + if (u == null) { + throw new Error('user not found'); + } else { + return u; + } + }); + } + + host = this.utilityService.toPuny(host); + + if (this.config.host === host) { + this.logger.info(`return local user: ${usernameLower}`); + return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => { + if (u == null) { + throw new Error('user not found'); + } else { + return u; + } + }); + } + + const user = await this.usersRepository.findOneBy({ usernameLower, host }) as IRemoteUser | null; + + const acctLower = `${usernameLower}@${host}`; + + if (user == null) { + const self = await this.resolveSelf(acctLower); + + this.logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`); + return await this.apPersonService.createPerson(self.href); + } + + // ユーザー情報が古い場合は、WebFilgerからやりなおして返す + if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + // 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する + await this.usersRepository.update(user.id, { + lastFetchedAt: new Date(), + }); + + this.logger.info(`try resync: ${acctLower}`); + const self = await this.resolveSelf(acctLower); + + if (user.uri !== self.href) { + // if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping. + this.logger.info(`uri missmatch: ${acctLower}`); + this.logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`); + + // validate uri + const uri = new URL(self.href); + if (uri.hostname !== host) { + throw new Error('Invalid uri'); + } + + await this.usersRepository.update({ + usernameLower, + host: host, + }, { + uri: self.href, + }); + } else { + this.logger.info(`uri is fine: ${acctLower}`); + } + + await this.apPersonService.updatePerson(self.href); + + this.logger.info(`return resynced remote user: ${acctLower}`); + return await this.usersRepository.findOneBy({ uri: self.href }).then(u => { + if (u == null) { + throw new Error('user not found'); + } else { + return u; + } + }); + } + + this.logger.info(`return existing remote user: ${acctLower}`); + return user; + } + + private async resolveSelf(acctLower: string) { + this.logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); + const finger = await this.webfingerService.webfinger(acctLower).catch(err => { + this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`); + throw new Error(`Failed to WebFinger for ${acctLower}: ${ err.statusCode ?? err.message }`); + }); + const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self'); + if (!self) { + this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`); + throw new Error('self link not found'); + } + return self; + } +} diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts index 723a79dc59..1374ee06c8 100644 --- a/packages/backend/src/core/S3Service.ts +++ b/packages/backend/src/core/S3Service.ts @@ -4,7 +4,7 @@ import S3 from 'aws-sdk/clients/s3.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import type { Meta } from '@/models/entities/Meta.js'; -import { HttpRequestService } from './HttpRequestService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; @Injectable() export class S3Service { diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 2239d5fd83..1e34d9e4f8 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -12,7 +12,7 @@ import { UserKeypair } from '@/models/entities/UserKeypair.js'; import { UsedUsername } from '@/models/entities/UsedUsername.js'; import generateUserToken from '@/misc/generate-native-user-token.js'; import UsersChart from './chart/charts/users.js'; -import { UserEntityService } from './entities/UserEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UtilityService } from './UtilityService.js'; @Injectable() diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index b7a434684e..3399bb510f 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -10,10 +10,10 @@ import { DI } from '@/di-symbols.js'; import logger from '@/logger.js'; import type { UsersRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; import Logger from '@/logger.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { WebhookService } from './WebhookService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { LoggerService } from './LoggerService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import { WebhookService } from '@/core/WebhookService.js'; @Injectable() export class UserBlockingService { diff --git a/packages/backend/src/core/UserCacheService.ts b/packages/backend/src/core/UserCacheService.ts index b7166010ee..25a600a8da 100644 --- a/packages/backend/src/core/UserCacheService.ts +++ b/packages/backend/src/core/UserCacheService.ts @@ -4,7 +4,7 @@ import type { UsersRepository } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; import type { CacheableLocalUser, CacheableUser, ILocalUser } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; -import { UserEntityService } from './entities/UserEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 31e08c1366..2f51e2a9df 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -14,8 +14,8 @@ import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { DI } from '@/di-symbols.js'; import type { BlockingsRepository, FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import Logger from '../logger.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; const logger = new Logger('following/create'); diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index b1d01a1565..1d1ead5a1f 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -7,8 +7,8 @@ import { IdService } from '@/core/IdService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; -import { UserEntityService } from './entities/UserEntityService.js'; -import { ProxyAccountService } from './ProxyAccountService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ProxyAccountService } from '@/core/ProxyAccountService.js'; @Injectable() export class UserListService { diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index 82c2e98236..02f686bab6 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -6,8 +6,8 @@ import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; -import { ApRendererService } from './remote/activitypub/ApRendererService.js'; -import { UserEntityService } from './entities/UserEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; @Injectable() export class UserSuspendService { diff --git a/packages/backend/src/core/WebfingerService.ts b/packages/backend/src/core/WebfingerService.ts new file mode 100644 index 0000000000..d2a88be583 --- /dev/null +++ b/packages/backend/src/core/WebfingerService.ts @@ -0,0 +1,48 @@ +import { URL } from 'node:url'; +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import { query as urlQuery } from '@/misc/prelude/url.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; + +type ILink = { + href: string; + rel?: string; +}; + +type IWebFinger = { + links: ILink[]; + subject: string; +}; + +@Injectable() +export class WebfingerService { + constructor( + @Inject(DI.config) + private config: Config, + + private httpRequestService: HttpRequestService, + ) { + } + + public async webfinger(query: string): Promise { + const url = this.genUrl(query); + + return await this.httpRequestService.getJson(url, 'application/jrd+json, application/json') as IWebFinger; + } + + private genUrl(query: string): string { + if (query.match(/^https?:\/\//)) { + const u = new URL(query); + return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query }); + } + + const m = query.match(/^([^@]+)@(.*)/); + if (m) { + const hostname = m[2]; + return `https://${hostname}/.well-known/webfinger?` + urlQuery({ resource: `acct:${query}` }); + } + + throw new Error(`Invalid query (${query})`); + } +} diff --git a/packages/backend/src/core/activitypub/ApAudienceService.ts b/packages/backend/src/core/activitypub/ApAudienceService.ts new file mode 100644 index 0000000000..744017aa3a --- /dev/null +++ b/packages/backend/src/core/activitypub/ApAudienceService.ts @@ -0,0 +1,104 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { In } from 'typeorm'; +import promiseLimit from 'promise-limit'; +import { DI } from '@/di-symbols.js'; +import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js'; +import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js'; +import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; +import { ApPersonService } from './models/ApPersonService.js'; +import type { ApObject } from './type.js'; +import type { Resolver } from './ApResolverService.js'; + +type Visibility = 'public' | 'home' | 'followers' | 'specified'; + +type AudienceInfo = { + visibility: Visibility, + mentionedUsers: CacheableUser[], + visibleUsers: CacheableUser[], +}; + +@Injectable() +export class ApAudienceService { + constructor( + private apPersonService: ApPersonService, + ) { + } + + public async parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { + const toGroups = this.groupingAudience(getApIds(to), actor); + const ccGroups = this.groupingAudience(getApIds(cc), actor); + + const others = unique(concat([toGroups.other, ccGroups.other])); + + const limit = promiseLimit(2); + const mentionedUsers = (await Promise.all( + others.map(id => limit(() => this.apPersonService.resolvePerson(id, resolver).catch(() => null))), + )).filter((x): x is CacheableUser => x != null); + + if (toGroups.public.length > 0) { + return { + visibility: 'public', + mentionedUsers, + visibleUsers: [], + }; + } + + if (ccGroups.public.length > 0) { + return { + visibility: 'home', + mentionedUsers, + visibleUsers: [], + }; + } + + if (toGroups.followers.length > 0) { + return { + visibility: 'followers', + mentionedUsers, + visibleUsers: [], + }; + } + + return { + visibility: 'specified', + mentionedUsers, + visibleUsers: mentionedUsers, + }; + } + + private groupingAudience(ids: string[], actor: CacheableRemoteUser) { + const groups = { + public: [] as string[], + followers: [] as string[], + other: [] as string[], + }; + + for (const id of ids) { + if (this.isPublic(id)) { + groups.public.push(id); + } else if (this.isFollowers(id, actor)) { + groups.followers.push(id); + } else { + groups.other.push(id); + } + } + + groups.other = unique(groups.other); + + return groups; + } + + private isPublic(id: string) { + return [ + 'https://www.w3.org/ns/activitystreams#Public', + 'as#Public', + 'Public', + ].includes(id); + } + + private isFollowers(id: string, actor: CacheableRemoteUser) { + return ( + id === (actor.followersUri ?? `${actor.uri}/followers`) + ); + } +} diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts new file mode 100644 index 0000000000..77d200c3c8 --- /dev/null +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -0,0 +1,179 @@ +import { Inject, Injectable } from '@nestjs/common'; +import escapeRegexp from 'escape-regexp'; +import { DI } from '@/di-symbols.js'; +import type { MessagingMessagesRepository, NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js'; +import { Cache } from '@/misc/cache.js'; +import type { UserPublickey } from '@/models/entities/UserPublickey.js'; +import { UserCacheService } from '@/core/UserCacheService.js'; +import type { Note } from '@/models/entities/Note.js'; +import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; +import { getApId } from './type.js'; +import { ApPersonService } from './models/ApPersonService.js'; +import type { IObject } from './type.js'; + +export type UriParseResult = { + /** wether the URI was generated by us */ + local: true; + /** id in DB */ + id: string; + /** hint of type, e.g. "notes", "users" */ + type: string; + /** any remaining text after type and id, not including the slash after id. undefined if empty */ + rest?: string; +} | { + /** wether the URI was generated by us */ + local: false; + /** uri in DB */ + uri: string; +}; + +@Injectable() +export class ApDbResolverService { + private publicKeyCache: Cache; + private publicKeyByUserIdCache: Cache; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.userPublickeysRepository) + private userPublickeysRepository: UserPublickeysRepository, + + private userCacheService: UserCacheService, + private apPersonService: ApPersonService, + ) { + this.publicKeyCache = new Cache(Infinity); + this.publicKeyByUserIdCache = new Cache(Infinity); + } + + public parseUri(value: string | IObject): UriParseResult { + const uri = getApId(value); + + // the host part of a URL is case insensitive, so use the 'i' flag. + const localRegex = new RegExp('^' + escapeRegexp(this.config.url) + '/(\\w+)/(\\w+)(?:\/(.+))?', 'i'); + const matchLocal = uri.match(localRegex); + + if (matchLocal) { + return { + local: true, + type: matchLocal[1], + id: matchLocal[2], + rest: matchLocal[3], + }; + } else { + return { + local: false, + uri, + }; + } + } + + /** + * AP Note => Misskey Note in DB + */ + public async getNoteFromApId(value: string | IObject): Promise { + const parsed = this.parseUri(value); + + if (parsed.local) { + if (parsed.type !== 'notes') return null; + + return await this.notesRepository.findOneBy({ + id: parsed.id, + }); + } else { + return await this.notesRepository.findOneBy({ + uri: parsed.uri, + }); + } + } + + public async getMessageFromApId(value: string | IObject): Promise { + const parsed = this.parseUri(value); + + if (parsed.local) { + if (parsed.type !== 'notes') return null; + + return await this.messagingMessagesRepository.findOneBy({ + id: parsed.id, + }); + } else { + return await this.messagingMessagesRepository.findOneBy({ + uri: parsed.uri, + }); + } + } + + /** + * AP Person => Misskey User in DB + */ + public async getUserFromApId(value: string | IObject): Promise { + const parsed = this.parseUri(value); + + if (parsed.local) { + if (parsed.type !== 'users') return null; + + return await this.userCacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({ + id: parsed.id, + }).then(x => x ?? undefined)) ?? null; + } else { + return await this.userCacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({ + uri: parsed.uri, + })); + } + } + + /** + * AP KeyId => Misskey User and Key + */ + public async getAuthUserFromKeyId(keyId: string): Promise<{ + user: CacheableRemoteUser; + key: UserPublickey; + } | null> { + const key = await this.publicKeyCache.fetch(keyId, async () => { + const key = await this.userPublickeysRepository.findOneBy({ + keyId, + }); + + if (key == null) return null; + + return key; + }, key => key != null); + + if (key == null) return null; + + return { + user: await this.userCacheService.userByIdCache.fetch(key.userId, () => this.usersRepository.findOneByOrFail({ id: key.userId })) as CacheableRemoteUser, + key, + }; + } + + /** + * AP Actor id => Misskey User and Key + */ + public async getAuthUserFromApId(uri: string): Promise<{ + user: CacheableRemoteUser; + key: UserPublickey | null; + } | null> { + const user = await this.apPersonService.resolvePerson(uri) as CacheableRemoteUser; + + if (user == null) return null; + + const key = await this.publicKeyByUserIdCache.fetch(user.id, () => this.userPublickeysRepository.findOneBy({ userId: user.id }), v => v != null); + + return { + user, + key, + }; + } +} diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts new file mode 100644 index 0000000000..6fc75a0397 --- /dev/null +++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts @@ -0,0 +1,199 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { IsNull, Not } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; +import { QueueService } from '@/core/QueueService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; + +interface IRecipe { + type: string; +} + +interface IFollowersRecipe extends IRecipe { + type: 'Followers'; +} + +interface IDirectRecipe extends IRecipe { + type: 'Direct'; + to: IRemoteUser; +} + +const isFollowers = (recipe: any): recipe is IFollowersRecipe => + recipe.type === 'Followers'; + +const isDirect = (recipe: any): recipe is IDirectRecipe => + recipe.type === 'Direct'; + +@Injectable() +export class ApDeliverManagerService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + private queueService: QueueService, + ) { + } + + /** + * Deliver activity to followers + * @param activity Activity + * @param from Followee + */ + public async deliverToFollowers(actor: { id: ILocalUser['id']; host: null; }, activity: any) { + const manager = new DeliverManager( + this.userEntityService, + this.followingsRepository, + this.queueService, + actor, + activity, + ); + manager.addFollowersRecipe(); + await manager.execute(); + } + + /** + * Deliver activity to user + * @param activity Activity + * @param to Target user + */ + public async deliverToUser(actor: { id: ILocalUser['id']; host: null; }, activity: any, to: IRemoteUser) { + const manager = new DeliverManager( + this.userEntityService, + this.followingsRepository, + this.queueService, + actor, + activity, + ); + manager.addDirectRecipe(to); + await manager.execute(); + } + + public createDeliverManager(actor: { id: User['id']; host: null; }, activity: any) { + return new DeliverManager( + this.userEntityService, + this.followingsRepository, + this.queueService, + + actor, + activity, + ); + } +} + +class DeliverManager { + private actor: { id: User['id']; host: null; }; + private activity: any; + private recipes: IRecipe[] = []; + + /** + * Constructor + * @param actor Actor + * @param activity Activity to deliver + */ + constructor( + private userEntityService: UserEntityService, + private followingsRepository: FollowingsRepository, + private queueService: QueueService, + + actor: { id: User['id']; host: null; }, + activity: any, + ) { + this.actor = actor; + this.activity = activity; + } + + /** + * Add recipe for followers deliver + */ + public addFollowersRecipe() { + const deliver = { + type: 'Followers', + } as IFollowersRecipe; + + this.addRecipe(deliver); + } + + /** + * Add recipe for direct deliver + * @param to To + */ + public addDirectRecipe(to: IRemoteUser) { + const recipe = { + type: 'Direct', + to, + } as IDirectRecipe; + + this.addRecipe(recipe); + } + + /** + * Add recipe + * @param recipe Recipe + */ + public addRecipe(recipe: IRecipe) { + this.recipes.push(recipe); + } + + /** + * Execute delivers + */ + public async execute() { + if (!this.userEntityService.isLocalUser(this.actor)) return; + + const inboxes = new Set(); + + /* + build inbox list + + Process follower recipes first to avoid duplication when processing + direct recipes later. + */ + if (this.recipes.some(r => isFollowers(r))) { + // followers deliver + // TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう + // ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう? + const followers = await this.followingsRepository.find({ + where: { + followeeId: this.actor.id, + followerHost: Not(IsNull()), + }, + select: { + followerSharedInbox: true, + followerInbox: true, + }, + }) as { + followerSharedInbox: string | null; + followerInbox: string; + }[]; + + for (const following of followers) { + const inbox = following.followerSharedInbox ?? following.followerInbox; + inboxes.add(inbox); + } + } + + this.recipes.filter((recipe): recipe is IDirectRecipe => + // followers recipes have already been processed + isDirect(recipe) + // check that shared inbox has not been added yet + && !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox)) + // check that they actually have an inbox + && recipe.to.inbox != null, + ) + .forEach(recipe => inboxes.add(recipe.to.inbox!)); + + // deliver + for (const inbox of inboxes) { + this.queueService.deliver(this.actor, this.activity, inbox); + } + } +} diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts new file mode 100644 index 0000000000..3da384ec2d --- /dev/null +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -0,0 +1,740 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { In } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import type { CacheableRemoteUser } from '@/models/entities/User.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { ReactionService } from '@/core/ReactionService.js'; +import { RelayService } from '@/core/RelayService.js'; +import { NotePiningService } from '@/core/NotePiningService.js'; +import { UserBlockingService } from '@/core/UserBlockingService.js'; +import { NoteDeleteService } from '@/core/NoteDeleteService.js'; +import { NoteCreateService } from '@/core/NoteCreateService.js'; +import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js'; +import { AppLockService } from '@/core/AppLockService.js'; +import type Logger from '@/logger.js'; +import { MetaService } from '@/core/MetaService.js'; +import { IdService } from '@/core/IdService.js'; +import { StatusError } from '@/misc/status-error.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { QueueService } from '@/core/QueueService.js'; +import { MessagingService } from '@/core/MessagingService.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, MessagingMessagesRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js'; +import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; +import { ApNoteService } from './models/ApNoteService.js'; +import { ApLoggerService } from './ApLoggerService.js'; +import { ApDbResolverService } from './ApDbResolverService.js'; +import { ApResolverService } from './ApResolverService.js'; +import { ApAudienceService } from './ApAudienceService.js'; +import { ApPersonService } from './models/ApPersonService.js'; +import { ApQuestionService } from './models/ApQuestionService.js'; +import type { Resolver } from './ApResolverService.js'; +import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IRead, IReject, IRemove, IUndo, IUpdate } from './type.js'; + +@Injectable() +export class ApInboxService { + private logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + @Inject(DI.abuseUserReportsRepository) + private abuseUserReportsRepository: AbuseUserReportsRepository, + + @Inject(DI.followRequestsRepository) + private followRequestsRepository: FollowRequestsRepository, + + private userEntityService: UserEntityService, + private noteEntityService: NoteEntityService, + private utilityService: UtilityService, + private idService: IdService, + private metaService: MetaService, + private userFollowingService: UserFollowingService, + private apAudienceService: ApAudienceService, + private reactionService: ReactionService, + private relayService: RelayService, + private notePiningService: NotePiningService, + private userBlockingService: UserBlockingService, + private noteCreateService: NoteCreateService, + private noteDeleteService: NoteDeleteService, + private appLockService: AppLockService, + private apResolverService: ApResolverService, + private apDbResolverService: ApDbResolverService, + private apLoggerService: ApLoggerService, + private apNoteService: ApNoteService, + private apPersonService: ApPersonService, + private apQuestionService: ApQuestionService, + private queueService: QueueService, + private messagingService: MessagingService, + ) { + this.logger = this.apLoggerService.logger; + } + + public async performActivity(actor: CacheableRemoteUser, activity: IObject) { + if (isCollectionOrOrderedCollection(activity)) { + const resolver = this.apResolverService.createResolver(); + for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) { + const act = await resolver.resolve(item); + try { + await this.performOneActivity(actor, act); + } catch (err) { + if (err instanceof Error || typeof err === 'string') { + this.logger.error(err); + } + } + } + } else { + await this.performOneActivity(actor, activity); + } + + // ついでにリモートユーザーの情報が古かったら更新しておく + if (actor.uri) { + if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + setImmediate(() => { + this.apPersonService.updatePerson(actor.uri!); + }); + } + } + } + + public async performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise { + if (actor.isSuspended) return; + + if (isCreate(activity)) { + await this.create(actor, activity); + } else if (isDelete(activity)) { + await this.delete(actor, activity); + } else if (isUpdate(activity)) { + await this.update(actor, activity); + } else if (isRead(activity)) { + await this.read(actor, activity); + } else if (isFollow(activity)) { + await this.follow(actor, activity); + } else if (isAccept(activity)) { + await this.accept(actor, activity); + } else if (isReject(activity)) { + await this.reject(actor, activity); + } else if (isAdd(activity)) { + await this.add(actor, activity).catch(err => this.logger.error(err)); + } else if (isRemove(activity)) { + await this.remove(actor, activity).catch(err => this.logger.error(err)); + } else if (isAnnounce(activity)) { + await this.announce(actor, activity); + } else if (isLike(activity)) { + await this.like(actor, activity); + } else if (isUndo(activity)) { + await this.undo(actor, activity); + } else if (isBlock(activity)) { + await this.block(actor, activity); + } else if (isFlag(activity)) { + await this.flag(actor, activity); + } else { + this.logger.warn(`unrecognized activity type: ${(activity as any).type}`); + } + } + + private async follow(actor: CacheableRemoteUser, activity: IFollow): Promise { + const followee = await this.apDbResolverService.getUserFromApId(activity.object); + + if (followee == null) { + return 'skip: followee not found'; + } + + if (followee.host != null) { + return 'skip: フォローしようとしているユーザーはローカルユーザーではありません'; + } + + await this.userFollowingService.follow(actor, followee, activity.id); + return 'ok'; + } + + private async like(actor: CacheableRemoteUser, activity: ILike): Promise { + const targetUri = getApId(activity.object); + + const note = await this.apNoteService.fetchNote(targetUri); + if (!note) return `skip: target note not found ${targetUri}`; + + await this.apNoteService.extractEmojis(activity.tag ?? [], actor.host).catch(() => null); + + return await this.reactionService.create(actor, note, activity._misskey_reaction ?? activity.content ?? activity.name).catch(err => { + if (err.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') { + return 'skip: already reacted'; + } else { + throw err; + } + }).then(() => 'ok'); + } + + private async read(actor: CacheableRemoteUser, activity: IRead): Promise { + const id = await getApId(activity.object); + + if (!this.utilityService.isSelfHost(this.utilityService.extractDbHost(id))) { + return `skip: Read to foreign host (${id})`; + } + + const messageId = id.split('/').pop(); + + const message = await this.messagingMessagesRepository.findOneBy({ id: messageId }); + if (message == null) { + return 'skip: message not found'; + } + + if (actor.id !== message.recipientId) { + return 'skip: actor is not a message recipient'; + } + + await this.messagingService.readUserMessagingMessage(message.recipientId!, message.userId, [message.id]); + return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`; + } + + private async accept(actor: CacheableRemoteUser, activity: IAccept): Promise { + const uri = activity.id ?? activity; + + this.logger.info(`Accept: ${uri}`); + + const resolver = this.apResolverService.createResolver(); + + const object = await resolver.resolve(activity.object).catch(err => { + this.logger.error(`Resolution failed: ${err}`); + throw err; + }); + + if (isFollow(object)) return await this.acceptFollow(actor, object); + + return `skip: Unknown Accept type: ${getApType(object)}`; + } + + private async acceptFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { + // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある + + const follower = await this.apDbResolverService.getUserFromApId(activity.actor); + + if (follower == null) { + return 'skip: follower not found'; + } + + if (follower.host != null) { + return 'skip: follower is not a local user'; + } + + // relay + const match = activity.id?.match(/follow-relay\/(\w+)/); + if (match) { + return await this.relayService.relayAccepted(match[1]); + } + + await this.userFollowingService.acceptFollowRequest(actor, follower); + return 'ok'; + } + + private async add(actor: CacheableRemoteUser, activity: IAdd): Promise { + if ('actor' in activity && actor.uri !== activity.actor) { + throw new Error('invalid actor'); + } + + if (activity.target == null) { + throw new Error('target is null'); + } + + if (activity.target === actor.featured) { + const note = await this.apNoteService.resolveNote(activity.object); + if (note == null) throw new Error('note not found'); + await this.notePiningService.addPinned(actor, note.id); + return; + } + + throw new Error(`unknown target: ${activity.target}`); + } + + private async announce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { + const uri = getApId(activity); + + this.logger.info(`Announce: ${uri}`); + + const targetUri = getApId(activity.object); + + this.announceNote(actor, activity, targetUri); + } + + private async announceNote(actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { + const uri = getApId(activity); + + if (actor.isSuspended) { + return; + } + + // アナウンス先をブロックしてたら中断 + const meta = await this.metaService.fetch(); + if (meta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) return; + + const unlock = await this.appLockService.getApLock(uri); + + try { + // 既に同じURIを持つものが登録されていないかチェック + const exist = await this.apNoteService.fetchNote(uri); + if (exist) { + return; + } + + // Announce対象をresolve + let renote; + try { + renote = await this.apNoteService.resolveNote(targetUri); + if (renote == null) throw new Error('announce target is null'); + } catch (err) { + // 対象が4xxならスキップ + if (err instanceof StatusError) { + if (err.isClientError) { + this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`); + return; + } + + this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode ?? err}`); + } + throw err; + } + + if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) { + this.logger.warn('skip: invalid actor for this activity'); + return; + } + + this.logger.info(`Creating the (Re)Note: ${uri}`); + + const activityAudience = await this.apAudienceService.parseAudience(actor, activity.to, activity.cc); + + await this.noteCreateService.create(actor, { + createdAt: activity.published ? new Date(activity.published) : null, + renote, + visibility: activityAudience.visibility, + visibleUsers: activityAudience.visibleUsers, + uri, + }); + } finally { + unlock(); + } + } + + private async block(actor: CacheableRemoteUser, activity: IBlock): Promise { + // ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず + + const blockee = await this.apDbResolverService.getUserFromApId(activity.object); + + if (blockee == null) { + return 'skip: blockee not found'; + } + + if (blockee.host != null) { + return 'skip: ブロックしようとしているユーザーはローカルユーザーではありません'; + } + + await this.userBlockingService.block(await this.usersRepository.findOneByOrFail({ id: actor.id }), await this.usersRepository.findOneByOrFail({ id: blockee.id })); + return 'ok'; + } + + private async create(actor: CacheableRemoteUser, activity: ICreate): Promise { + const uri = getApId(activity); + + this.logger.info(`Create: ${uri}`); + + // copy audiences between activity <=> object. + if (typeof activity.object === 'object') { + const to = unique(concat([toArray(activity.to), toArray(activity.object.to)])); + const cc = unique(concat([toArray(activity.cc), toArray(activity.object.cc)])); + + activity.to = to; + activity.cc = cc; + activity.object.to = to; + activity.object.cc = cc; + } + + // If there is no attributedTo, use Activity actor. + if (typeof activity.object === 'object' && !activity.object.attributedTo) { + activity.object.attributedTo = activity.actor; + } + + const resolver = this.apResolverService.createResolver(); + + const object = await resolver.resolve(activity.object).catch(e => { + this.logger.error(`Resolution failed: ${e}`); + throw e; + }); + + if (isPost(object)) { + this.createNote(resolver, actor, object, false, activity); + } else { + this.logger.warn(`Unknown type: ${getApType(object)}`); + } + } + + private async createNote(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { + const uri = getApId(note); + + if (typeof note === 'object') { + if (actor.uri !== note.attributedTo) { + return 'skip: actor.uri !== note.attributedTo'; + } + + if (typeof note.id === 'string') { + if (this.utilityService.extractDbHost(actor.uri) !== this.utilityService.extractDbHost(note.id)) { + return 'skip: host in actor.uri !== note.id'; + } + } + } + + const unlock = await this.appLockService.getApLock(uri); + + try { + const exist = await this.apNoteService.fetchNote(note); + if (exist) return 'skip: note exists'; + + await this.apNoteService.createNote(note, resolver, silent); + return 'ok'; + } catch (err) { + if (err instanceof StatusError && err.isClientError) { + return `skip ${err.statusCode}`; + } else { + throw err; + } + } finally { + unlock(); + } + } + + private async delete(actor: CacheableRemoteUser, activity: IDelete): Promise { + if ('actor' in activity && actor.uri !== activity.actor) { + throw new Error('invalid actor'); + } + + // 削除対象objectのtype + let formerType: string | undefined; + + if (typeof activity.object === 'string') { + // typeが不明だけど、どうせ消えてるのでremote resolveしない + formerType = undefined; + } else { + const object = activity.object as IObject; + if (isTombstone(object)) { + formerType = toSingle(object.formerType); + } else { + formerType = toSingle(object.type); + } + } + + const uri = getApId(activity.object); + + // type不明でもactorとobjectが同じならばそれはPersonに違いない + if (!formerType && actor.uri === uri) { + formerType = 'Person'; + } + + // それでもなかったらおそらくNote + if (!formerType) { + formerType = 'Note'; + } + + if (validPost.includes(formerType)) { + return await this.deleteNote(actor, uri); + } else if (validActor.includes(formerType)) { + return await this.deleteActor(actor, uri); + } else { + return `Unknown type ${formerType}`; + } + } + + private async deleteActor(actor: CacheableRemoteUser, uri: string): Promise { + this.logger.info(`Deleting the Actor: ${uri}`); + + if (actor.uri !== uri) { + return `skip: delete actor ${actor.uri} !== ${uri}`; + } + + const user = await this.usersRepository.findOneByOrFail({ id: actor.id }); + if (user.isDeleted) { + this.logger.info('skip: already deleted'); + } + + const job = await this.queueService.createDeleteAccountJob(actor); + + await this.usersRepository.update(actor.id, { + isDeleted: true, + }); + + return `ok: queued ${job.name} ${job.id}`; + } + + private async deleteNote(actor: CacheableRemoteUser, uri: string): Promise { + this.logger.info(`Deleting the Note: ${uri}`); + + const unlock = await this.appLockService.getApLock(uri); + + try { + const note = await this.apDbResolverService.getNoteFromApId(uri); + + if (note == null) { + const message = await this.apDbResolverService.getMessageFromApId(uri); + if (message == null) return 'message not found'; + + if (message.userId !== actor.id) { + return '投稿を削除しようとしているユーザーは投稿の作成者ではありません'; + } + + await this.messagingService.deleteMessage(message); + + return 'ok: message deleted'; + } + + if (note.userId !== actor.id) { + return '投稿を削除しようとしているユーザーは投稿の作成者ではありません'; + } + + await this.noteDeleteService.delete(actor, note); + return 'ok: note deleted'; + } finally { + unlock(); + } + } + + private async flag(actor: CacheableRemoteUser, activity: IFlag): Promise { + // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので + // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する + const uris = getApIds(activity.object); + + const userIds = uris.filter(uri => uri.startsWith(this.config.url + '/users/')).map(uri => uri.split('/').pop()!); + const users = await this.usersRepository.findBy({ + id: In(userIds), + }); + if (users.length < 1) return 'skip'; + + await this.abuseUserReportsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + targetUserId: users[0].id, + targetUserHost: users[0].host, + reporterId: actor.id, + reporterHost: actor.host, + comment: `${activity.content}\n${JSON.stringify(uris, null, 2)}`, + }); + + return 'ok'; + } + + private async reject(actor: CacheableRemoteUser, activity: IReject): Promise { + const uri = activity.id ?? activity; + + this.logger.info(`Reject: ${uri}`); + + const resolver = this.apResolverService.createResolver(); + + const object = await resolver.resolve(activity.object).catch(e => { + this.logger.error(`Resolution failed: ${e}`); + throw e; + }); + + if (isFollow(object)) return await this.rejectFollow(actor, object); + + return `skip: Unknown Reject type: ${getApType(object)}`; + } + + private async rejectFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { + // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある + + const follower = await this.apDbResolverService.getUserFromApId(activity.actor); + + if (follower == null) { + return 'skip: follower not found'; + } + + if (!this.userEntityService.isLocalUser(follower)) { + return 'skip: follower is not a local user'; + } + + // relay + const match = activity.id?.match(/follow-relay\/(\w+)/); + if (match) { + return await this.relayService.relayRejected(match[1]); + } + + await this.userFollowingService.remoteReject(actor, follower); + return 'ok'; + } + + private async remove(actor: CacheableRemoteUser, activity: IRemove): Promise { + if ('actor' in activity && actor.uri !== activity.actor) { + throw new Error('invalid actor'); + } + + if (activity.target == null) { + throw new Error('target is null'); + } + + if (activity.target === actor.featured) { + const note = await this.apNoteService.resolveNote(activity.object); + if (note == null) throw new Error('note not found'); + await this.notePiningService.removePinned(actor, note.id); + return; + } + + throw new Error(`unknown target: ${activity.target}`); + } + + private async undo(actor: CacheableRemoteUser, activity: IUndo): Promise { + if ('actor' in activity && actor.uri !== activity.actor) { + throw new Error('invalid actor'); + } + + const uri = activity.id ?? activity; + + this.logger.info(`Undo: ${uri}`); + + const resolver = this.apResolverService.createResolver(); + + const object = await resolver.resolve(activity.object).catch(e => { + this.logger.error(`Resolution failed: ${e}`); + throw e; + }); + + if (isFollow(object)) return await this.undoFollow(actor, object); + if (isBlock(object)) return await this.undoBlock(actor, object); + if (isLike(object)) return await this.undoLike(actor, object); + if (isAnnounce(object)) return await this.undoAnnounce(actor, object); + if (isAccept(object)) return await this.undoAccept(actor, object); + + return `skip: unknown object type ${getApType(object)}`; + } + + private async undoAccept(actor: CacheableRemoteUser, activity: IAccept): Promise { + const follower = await this.apDbResolverService.getUserFromApId(activity.object); + if (follower == null) { + return 'skip: follower not found'; + } + + const following = await this.followingsRepository.findOneBy({ + followerId: follower.id, + followeeId: actor.id, + }); + + if (following) { + await this.userFollowingService.unfollow(follower, actor); + return 'ok: unfollowed'; + } + + return 'skip: フォローされていない'; + } + + private async undoAnnounce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { + const uri = getApId(activity); + + const note = await this.notesRepository.findOneBy({ + uri, + userId: actor.id, + }); + + if (!note) return 'skip: no such Announce'; + + await this.noteDeleteService.delete(actor, note); + return 'ok: deleted'; + } + + private async undoBlock(actor: CacheableRemoteUser, activity: IBlock): Promise { + const blockee = await this.apDbResolverService.getUserFromApId(activity.object); + + if (blockee == null) { + return 'skip: blockee not found'; + } + + if (blockee.host != null) { + return 'skip: ブロック解除しようとしているユーザーはローカルユーザーではありません'; + } + + await this.userBlockingService.unblock(await this.usersRepository.findOneByOrFail({ id: actor.id }), blockee); + return 'ok'; + } + + private async undoFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { + const followee = await this.apDbResolverService.getUserFromApId(activity.object); + if (followee == null) { + return 'skip: followee not found'; + } + + if (followee.host != null) { + return 'skip: フォロー解除しようとしているユーザーはローカルユーザーではありません'; + } + + const req = await this.followRequestsRepository.findOneBy({ + followerId: actor.id, + followeeId: followee.id, + }); + + const following = await this.followingsRepository.findOneBy({ + followerId: actor.id, + followeeId: followee.id, + }); + + if (req) { + await this.userFollowingService.cancelFollowRequest(followee, actor); + return 'ok: follow request canceled'; + } + + if (following) { + await this.userFollowingService.unfollow(actor, followee); + return 'ok: unfollowed'; + } + + return 'skip: リクエストもフォローもされていない'; + } + + private async undoLike(actor: CacheableRemoteUser, activity: ILike): Promise { + const targetUri = getApId(activity.object); + + const note = await this.apNoteService.fetchNote(targetUri); + if (!note) return `skip: target note not found ${targetUri}`; + + await this.reactionService.delete(actor, note).catch(e => { + if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') return; + throw e; + }); + + return 'ok'; + } + + private async update(actor: CacheableRemoteUser, activity: IUpdate): Promise { + if ('actor' in activity && actor.uri !== activity.actor) { + return 'skip: invalid actor'; + } + + this.logger.debug('Update'); + + const resolver = this.apResolverService.createResolver(); + + const object = await resolver.resolve(activity.object).catch(e => { + this.logger.error(`Resolution failed: ${e}`); + throw e; + }); + + if (isActor(object)) { + await this.apPersonService.updatePerson(actor.uri!, resolver, object); + return 'ok: Person updated'; + } else if (getApType(object) === 'Question') { + await this.apQuestionService.updateQuestion(object, resolver).catch(err => console.error(err)); + return 'ok: Question updated'; + } else { + return `skip: Unknown type: ${getApType(object)}`; + } + } +} diff --git a/packages/backend/src/core/activitypub/ApLoggerService.ts b/packages/backend/src/core/activitypub/ApLoggerService.ts new file mode 100644 index 0000000000..a742cc42da --- /dev/null +++ b/packages/backend/src/core/activitypub/ApLoggerService.ts @@ -0,0 +1,14 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type Logger from '@/logger.js'; +import { RemoteLoggerService } from '@/core/RemoteLoggerService.js'; + +@Injectable() +export class ApLoggerService { + public logger: Logger; + + constructor( + private remoteLoggerService: RemoteLoggerService, + ) { + this.logger = this.remoteLoggerService.logger.createSubLogger('ap', 'magenta'); + } +} diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts new file mode 100644 index 0000000000..8804fde64a --- /dev/null +++ b/packages/backend/src/core/activitypub/ApMfmService.ts @@ -0,0 +1,30 @@ +import { Inject, Injectable } from '@nestjs/common'; +import * as mfm from 'mfm-js'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import { MfmService } from '@/core/MfmService.js'; +import type { Note } from '@/models/entities/Note.js'; +import { extractApHashtagObjects } from './models/tag.js'; +import type { IObject } from './type.js'; + +@Injectable() +export class ApMfmService { + constructor( + @Inject(DI.config) + private config: Config, + + private mfmService: MfmService, + ) { + } + + public htmlToMfm(html: string, tag?: IObject | IObject[]) { + const hashtagNames = extractApHashtagObjects(tag).map(x => x.name).filter((x): x is string => x != null); + + return this.mfmService.fromHtml(html, hashtagNames); + } + + public getNoteHtml(note: Note) { + if (!note.text) return ''; + return this.mfmService.toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); + } +} diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts new file mode 100644 index 0000000000..38a92567c3 --- /dev/null +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -0,0 +1,703 @@ +import { createPublicKey } from 'node:crypto'; +import { Inject, Injectable } from '@nestjs/common'; +import { In, IsNull } from 'typeorm'; +import { v4 as uuid } from 'uuid'; +import * as mfm from 'mfm-js'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; +import type { IMentionedRemoteUsers, Note } from '@/models/entities/Note.js'; +import type { Blocking } from '@/models/entities/Blocking.js'; +import type { Relay } from '@/models/entities/Relay.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { NoteReaction } from '@/models/entities/NoteReaction.js'; +import type { Emoji } from '@/models/entities/Emoji.js'; +import type { Poll } from '@/models/entities/Poll.js'; +import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; +import type { PollVote } from '@/models/entities/PollVote.js'; +import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; +import { MfmService } from '@/core/MfmService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import type { UserKeypair } from '@/models/entities/UserKeypair.js'; +import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, EmojisRepository, PollsRepository } from '@/models/index.js'; +import { LdSignatureService } from './LdSignatureService.js'; +import { ApMfmService } from './ApMfmService.js'; +import type { IActivity, IObject } from './type.js'; +import type { IIdentifier } from './models/identifier.js'; + +@Injectable() +export class ApRendererService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + private userEntityService: UserEntityService, + private driveFileEntityService: DriveFileEntityService, + private ldSignatureService: LdSignatureService, + private userKeypairStoreService: UserKeypairStoreService, + private apMfmService: ApMfmService, + private mfmService: MfmService, + ) { + } + + public renderAccept(object: any, user: { id: User['id']; host: null }) { + return { + type: 'Accept', + actor: `${this.config.url}/users/${user.id}`, + object, + }; + } + + public renderAdd(user: ILocalUser, target: any, object: any) { + return { + type: 'Add', + actor: `${this.config.url}/users/${user.id}`, + target, + object, + }; + } + + public renderAnnounce(object: any, note: Note) { + const attributedTo = `${this.config.url}/users/${note.userId}`; + + let to: string[] = []; + let cc: string[] = []; + + if (note.visibility === 'public') { + to = ['https://www.w3.org/ns/activitystreams#Public']; + cc = [`${attributedTo}/followers`]; + } else if (note.visibility === 'home') { + to = [`${attributedTo}/followers`]; + cc = ['https://www.w3.org/ns/activitystreams#Public']; + } else { + return null; + } + + return { + id: `${this.config.url}/notes/${note.id}/activity`, + actor: `${this.config.url}/users/${note.userId}`, + type: 'Announce', + published: note.createdAt.toISOString(), + to, + cc, + object, + }; + } + + /** + * Renders a block into its ActivityPub representation. + * + * @param block The block to be rendered. The blockee relation must be loaded. + */ + public renderBlock(block: Blocking) { + if (block.blockee?.uri == null) { + throw new Error('renderBlock: missing blockee uri'); + } + + return { + type: 'Block', + id: `${this.config.url}/blocks/${block.id}`, + actor: `${this.config.url}/users/${block.blockerId}`, + object: block.blockee.uri, + }; + } + + public renderCreate(object: any, note: Note) { + const activity = { + id: `${this.config.url}/notes/${note.id}/activity`, + actor: `${this.config.url}/users/${note.userId}`, + type: 'Create', + published: note.createdAt.toISOString(), + object, + } as any; + + if (object.to) activity.to = object.to; + if (object.cc) activity.cc = object.cc; + + return activity; + } + + public renderDelete(object: any, user: { id: User['id']; host: null }) { + return { + type: 'Delete', + actor: `${this.config.url}/users/${user.id}`, + object, + published: new Date().toISOString(), + }; + } + + public renderDocument(file: DriveFile) { + return { + type: 'Document', + mediaType: file.type, + url: this.driveFileEntityService.getPublicUrl(file), + name: file.comment, + }; + } + + public renderEmoji(emoji: Emoji) { + return { + id: `${this.config.url}/emojis/${emoji.name}`, + type: 'Emoji', + name: `:${emoji.name}:`, + updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : new Date().toISOString, + icon: { + type: 'Image', + mediaType: emoji.type ?? 'image/png', + url: emoji.publicUrl ?? emoji.originalUrl, // ?? emoji.originalUrl してるのは後方互換性のため + }, + }; + } + + // to anonymise reporters, the reporting actor must be a system user + // object has to be a uri or array of uris + public renderFlag(user: ILocalUser, object: [string], content: string) { + return { + type: 'Flag', + actor: `${this.config.url}/users/${user.id}`, + content, + object, + }; + } + + public renderFollowRelay(relay: Relay, relayActor: ILocalUser) { + const follow = { + id: `${this.config.url}/activities/follow-relay/${relay.id}`, + type: 'Follow', + actor: `${this.config.url}/users/${relayActor.id}`, + object: 'https://www.w3.org/ns/activitystreams#Public', + }; + + return follow; + } + + /** + * Convert (local|remote)(Follower|Followee)ID to URL + * @param id Follower|Followee ID + */ + public async renderFollowUser(id: User['id']) { + const user = await this.usersRepository.findOneByOrFail({ id: id }); + return this.userEntityService.isLocalUser(user) ? `${this.config.url}/users/${user.id}` : user.uri; + } + + public renderFollow( + follower: { id: User['id']; host: User['host']; uri: User['host'] }, + followee: { id: User['id']; host: User['host']; uri: User['host'] }, + requestId?: string, + ) { + const follow = { + id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`, + type: 'Follow', + actor: this.userEntityService.isLocalUser(follower) ? `${this.config.url}/users/${follower.id}` : follower.uri, + object: this.userEntityService.isLocalUser(followee) ? `${this.config.url}/users/${followee.id}` : followee.uri, + } as any; + + return follow; + } + + public renderHashtag(tag: string) { + return { + type: 'Hashtag', + href: `${this.config.url}/tags/${encodeURIComponent(tag)}`, + name: `#${tag}`, + }; + } + + public renderImage(file: DriveFile) { + return { + type: 'Image', + url: this.driveFileEntityService.getPublicUrl(file), + sensitive: file.isSensitive, + name: file.comment, + }; + } + + public renderKey(user: ILocalUser, key: UserKeypair, postfix?: string) { + return { + id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`, + type: 'Key', + owner: `${this.config.url}/users/${user.id}`, + publicKeyPem: createPublicKey(key.publicKey).export({ + type: 'spki', + format: 'pem', + }), + }; + } + + public async renderLike(noteReaction: NoteReaction, note: { uri: string | null }) { + const reaction = noteReaction.reaction; + + const object = { + type: 'Like', + id: `${this.config.url}/likes/${noteReaction.id}`, + actor: `${this.config.url}/users/${noteReaction.userId}`, + object: note.uri ? note.uri : `${this.config.url}/notes/${noteReaction.noteId}`, + content: reaction, + _misskey_reaction: reaction, + } as any; + + if (reaction.startsWith(':')) { + const name = reaction.replace(/:/g, ''); + const emoji = await this.emojisRepository.findOneBy({ + name, + host: IsNull(), + }); + + if (emoji) object.tag = [this.renderEmoji(emoji)]; + } + + return object; + } + + public renderMention(mention: User) { + return { + type: 'Mention', + href: this.userEntityService.isRemoteUser(mention) ? mention.uri : `${this.config.url}/users/${(mention as ILocalUser).id}`, + name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as ILocalUser).username}`, + }; + } + + public async renderNote(note: Note, dive = true, isTalk = false): Promise { + const getPromisedFiles = async (ids: string[]) => { + if (!ids || ids.length === 0) return []; + const items = await this.driveFilesRepository.findBy({ id: In(ids) }); + return ids.map(id => items.find(item => item.id === id)).filter(item => item != null) as DriveFile[]; + }; + + let inReplyTo; + let inReplyToNote: Note | null; + + if (note.replyId) { + inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId }); + + if (inReplyToNote != null) { + const inReplyToUser = await this.usersRepository.findOneBy({ id: inReplyToNote.userId }); + + if (inReplyToUser != null) { + if (inReplyToNote.uri) { + inReplyTo = inReplyToNote.uri; + } else { + if (dive) { + inReplyTo = await this.renderNote(inReplyToNote, false); + } else { + inReplyTo = `${this.config.url}/notes/${inReplyToNote.id}`; + } + } + } + } + } else { + inReplyTo = null; + } + + let quote; + + if (note.renoteId) { + const renote = await this.notesRepository.findOneBy({ id: note.renoteId }); + + if (renote) { + quote = renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`; + } + } + + const attributedTo = `${this.config.url}/users/${note.userId}`; + + const mentions = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); + + let to: string[] = []; + let cc: string[] = []; + + if (note.visibility === 'public') { + to = ['https://www.w3.org/ns/activitystreams#Public']; + cc = [`${attributedTo}/followers`].concat(mentions); + } else if (note.visibility === 'home') { + to = [`${attributedTo}/followers`]; + cc = ['https://www.w3.org/ns/activitystreams#Public'].concat(mentions); + } else if (note.visibility === 'followers') { + to = [`${attributedTo}/followers`]; + cc = mentions; + } else { + to = mentions; + } + + const mentionedUsers = note.mentions.length > 0 ? await this.usersRepository.findBy({ + id: In(note.mentions), + }) : []; + + const hashtagTags = (note.tags ?? []).map(tag => this.renderHashtag(tag)); + const mentionTags = mentionedUsers.map(u => this.renderMention(u)); + + const files = await getPromisedFiles(note.fileIds); + + const text = note.text ?? ''; + let poll: Poll | null = null; + + if (note.hasPoll) { + poll = await this.pollsRepository.findOneBy({ noteId: note.id }); + } + + let apText = text; + + if (quote) { + apText += `\n\nRE: ${quote}`; + } + + const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw; + + const content = this.apMfmService.getNoteHtml(Object.assign({}, note, { + text: apText, + })); + + const emojis = await this.getEmojis(note.emojis); + const apemojis = emojis.map(emoji => this.renderEmoji(emoji)); + + const tag = [ + ...hashtagTags, + ...mentionTags, + ...apemojis, + ]; + + const asPoll = poll ? { + type: 'Question', + content: this.apMfmService.getNoteHtml(Object.assign({}, note, { + text: text, + })), + [poll.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt, + [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ + type: 'Note', + name: text, + replies: { + type: 'Collection', + totalItems: poll!.votes[i], + }, + })), + } : {}; + + const asTalk = isTalk ? { + _misskey_talk: true, + } : {}; + + return { + id: `${this.config.url}/notes/${note.id}`, + type: 'Note', + attributedTo, + summary: summary ?? undefined, + content: content ?? undefined, + _misskey_content: text, + source: { + content: text, + mediaType: 'text/x.misskeymarkdown', + }, + _misskey_quote: quote, + quoteUrl: quote, + published: note.createdAt.toISOString(), + to, + cc, + inReplyTo, + attachment: files.map(x => this.renderDocument(x)), + sensitive: note.cw != null || files.some(file => file.isSensitive), + tag, + ...asPoll, + ...asTalk, + }; + } + + public async renderPerson(user: ILocalUser) { + const id = `${this.config.url}/users/${user.id}`; + const isSystem = !!user.username.match(/\./); + + const [avatar, banner, profile] = await Promise.all([ + user.avatarId ? this.driveFilesRepository.findOneBy({ id: user.avatarId }) : Promise.resolve(undefined), + user.bannerId ? this.driveFilesRepository.findOneBy({ id: user.bannerId }) : Promise.resolve(undefined), + this.userProfilesRepository.findOneByOrFail({ userId: user.id }), + ]); + + const attachment: { + type: 'PropertyValue', + name: string, + value: string, + identifier?: IIdentifier, + }[] = []; + + if (profile.fields) { + for (const field of profile.fields) { + attachment.push({ + type: 'PropertyValue', + name: field.name, + value: (field.value != null && field.value.match(/^https?:/)) + ? `${new URL(field.value).href}` + : field.value, + }); + } + } + + const emojis = await this.getEmojis(user.emojis); + const apemojis = emojis.map(emoji => this.renderEmoji(emoji)); + + const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag)); + + const tag = [ + ...apemojis, + ...hashtagTags, + ]; + + const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); + + const person = { + type: isSystem ? 'Application' : user.isBot ? 'Service' : 'Person', + id, + inbox: `${id}/inbox`, + outbox: `${id}/outbox`, + followers: `${id}/followers`, + following: `${id}/following`, + featured: `${id}/collections/featured`, + sharedInbox: `${this.config.url}/inbox`, + endpoints: { sharedInbox: `${this.config.url}/inbox` }, + url: `${this.config.url}/@${user.username}`, + preferredUsername: user.username, + name: user.name, + summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null, + icon: avatar ? this.renderImage(avatar) : null, + image: banner ? this.renderImage(banner) : null, + tag, + manuallyApprovesFollowers: user.isLocked, + discoverable: !!user.isExplorable, + publicKey: this.renderKey(user, keypair, '#main-key'), + isCat: user.isCat, + attachment: attachment.length ? attachment : undefined, + } as any; + + if (profile.birthday) { + person['vcard:bday'] = profile.birthday; + } + + if (profile.location) { + person['vcard:Address'] = profile.location; + } + + return person; + } + + public async renderQuestion(user: { id: User['id'] }, note: Note, poll: Poll) { + const question = { + type: 'Question', + id: `${this.config.url}/questions/${note.id}`, + actor: `${this.config.url}/users/${user.id}`, + content: note.text ?? '', + [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ + name: text, + _misskey_votes: poll.votes[i], + replies: { + type: 'Collection', + totalItems: poll.votes[i], + }, + })), + }; + + return question; + } + + public renderRead(user: { id: User['id'] }, message: MessagingMessage) { + return { + type: 'Read', + actor: `${this.config.url}/users/${user.id}`, + object: message.uri, + }; + } + + public renderReject(object: any, user: { id: User['id'] }) { + return { + type: 'Reject', + actor: `${this.config.url}/users/${user.id}`, + object, + }; + } + + public renderRemove(user: { id: User['id'] }, target: any, object: any) { + return { + type: 'Remove', + actor: `${this.config.url}/users/${user.id}`, + target, + object, + }; + } + + public renderTombstone(id: string) { + return { + id, + type: 'Tombstone', + }; + } + + public renderUndo(object: any, user: { id: User['id'] }) { + if (object == null) return null; + const id = typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined; + + return { + type: 'Undo', + ...(id ? { id } : {}), + actor: `${this.config.url}/users/${user.id}`, + object, + published: new Date().toISOString(), + }; + } + + public renderUpdate(object: any, user: { id: User['id'] }) { + const activity = { + id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`, + actor: `${this.config.url}/users/${user.id}`, + type: 'Update', + to: ['https://www.w3.org/ns/activitystreams#Public'], + object, + published: new Date().toISOString(), + } as any; + + return activity; + } + + public renderVote(user: { id: User['id'] }, vote: PollVote, note: Note, poll: Poll, pollOwner: IRemoteUser) { + return { + id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`, + actor: `${this.config.url}/users/${user.id}`, + type: 'Create', + to: [pollOwner.uri], + published: new Date().toISOString(), + object: { + id: `${this.config.url}/users/${user.id}#votes/${vote.id}`, + type: 'Note', + attributedTo: `${this.config.url}/users/${user.id}`, + to: [pollOwner.uri], + inReplyTo: note.uri, + name: poll.choices[vote.choice], + }, + }; + } + + public renderActivity(x: any): IActivity | null { + if (x == null) return null; + + if (typeof x === 'object' && x.id == null) { + x.id = `${this.config.url}/${uuid()}`; + } + + return Object.assign({ + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', + { + // as non-standards + manuallyApprovesFollowers: 'as:manuallyApprovesFollowers', + sensitive: 'as:sensitive', + Hashtag: 'as:Hashtag', + quoteUrl: 'as:quoteUrl', + // Mastodon + toot: 'http://joinmastodon.org/ns#', + Emoji: 'toot:Emoji', + featured: 'toot:featured', + discoverable: 'toot:discoverable', + // schema + schema: 'http://schema.org#', + PropertyValue: 'schema:PropertyValue', + value: 'schema:value', + // Misskey + misskey: 'https://misskey-hub.net/ns#', + '_misskey_content': 'misskey:_misskey_content', + '_misskey_quote': 'misskey:_misskey_quote', + '_misskey_reaction': 'misskey:_misskey_reaction', + '_misskey_votes': 'misskey:_misskey_votes', + '_misskey_talk': 'misskey:_misskey_talk', + 'isCat': 'misskey:isCat', + // vcard + vcard: 'http://www.w3.org/2006/vcard/ns#', + }, + ], + }, x); + } + + public async attachLdSignature(activity: any, user: { id: User['id']; host: null; }): Promise { + const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); + + const ldSignature = this.ldSignatureService.use(); + ldSignature.debug = false; + activity = await ldSignature.signRsaSignature2017(activity, keypair.privateKey, `${this.config.url}/users/${user.id}#main-key`); + + return activity; + } + + /** + * Render OrderedCollectionPage + * @param id URL of self + * @param totalItems Number of total items + * @param orderedItems Items + * @param partOf URL of base + * @param prev URL of prev page (optional) + * @param next URL of next page (optional) + */ + public renderOrderedCollectionPage(id: string, totalItems: any, orderedItems: any, partOf: string, prev?: string, next?: string) { + const page = { + id, + partOf, + type: 'OrderedCollectionPage', + totalItems, + orderedItems, + } as any; + + if (prev) page.prev = prev; + if (next) page.next = next; + + return page; + } + + /** + * Render OrderedCollection + * @param id URL of self + * @param totalItems Total number of items + * @param first URL of first page (optional) + * @param last URL of last page (optional) + * @param orderedItems attached objects (optional) + */ + public renderOrderedCollection(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: IObject[]) { + const page: any = { + id, + type: 'OrderedCollection', + totalItems, + }; + + if (first) page.first = first; + if (last) page.last = last; + if (orderedItems) page.orderedItems = orderedItems; + + return page; + } + + private async getEmojis(names: string[]): Promise { + if (names == null || names.length === 0) return []; + + const emojis = await Promise.all( + names.map(name => this.emojisRepository.findOneBy({ + name, + host: IsNull(), + })), + ); + + return emojis.filter(emoji => emoji != null) as Emoji[]; + } +} diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts new file mode 100644 index 0000000000..baad46d668 --- /dev/null +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -0,0 +1,182 @@ +import * as crypto from 'node:crypto'; +import { URL } from 'node:url'; +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import type { User } from '@/models/entities/User.js'; +import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; + +type Request = { + url: string; + method: string; + headers: Record; +}; + +type Signed = { + request: Request; + signingString: string; + signature: string; + signatureHeader: string; +}; + +type PrivateKey = { + privateKeyPem: string; + keyId: string; +}; + +@Injectable() +export class ApRequestService { + constructor( + @Inject(DI.config) + private config: Config, + + private userKeypairStoreService: UserKeypairStoreService, + private httpRequestService: HttpRequestService, + ) { + } + + private createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record }): Signed { + const u = new URL(args.url); + const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`; + + const request: Request = { + url: u.href, + method: 'POST', + headers: this.objectAssignWithLcKey({ + 'Date': new Date().toUTCString(), + 'Host': u.hostname, + 'Content-Type': 'application/activity+json', + 'Digest': digestHeader, + }, args.additionalHeaders), + }; + + const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']); + + return { + request, + signingString: result.signingString, + signature: result.signature, + signatureHeader: result.signatureHeader, + }; + } + + private createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record }): Signed { + const u = new URL(args.url); + + const request: Request = { + url: u.href, + method: 'GET', + headers: this.objectAssignWithLcKey({ + 'Accept': 'application/activity+json, application/ld+json', + 'Date': new Date().toUTCString(), + 'Host': new URL(args.url).hostname, + }, args.additionalHeaders), + }; + + const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']); + + return { + request, + signingString: result.signingString, + signature: result.signature, + signatureHeader: result.signatureHeader, + }; + } + + private signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed { + const signingString = this.genSigningString(request, includeHeaders); + const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64'); + const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`; + + request.headers = this.objectAssignWithLcKey(request.headers, { + Signature: signatureHeader, + }); + + return { + request, + signingString, + signature, + signatureHeader, + }; + } + + private genSigningString(request: Request, includeHeaders: string[]): string { + request.headers = this.lcObjectKey(request.headers); + + const results: string[] = []; + + for (const key of includeHeaders.map(x => x.toLowerCase())) { + if (key === '(request-target)') { + results.push(`(request-target): ${request.method.toLowerCase()} ${new URL(request.url).pathname}`); + } else { + results.push(`${key}: ${request.headers[key]}`); + } + } + + return results.join('\n'); + } + + private lcObjectKey(src: Record): Record { + const dst: Record = {}; + for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key]; + return dst; + } + + private objectAssignWithLcKey(a: Record, b: Record): Record { + return Object.assign(this.lcObjectKey(a), this.lcObjectKey(b)); + } + + public async signedPost(user: { id: User['id'] }, url: string, object: any) { + const body = JSON.stringify(object); + + const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); + + const req = this.createSignedPost({ + key: { + privateKeyPem: keypair.privateKey, + keyId: `${this.config.url}/users/${user.id}#main-key`, + }, + url, + body, + additionalHeaders: { + 'User-Agent': this.config.userAgent, + }, + }); + + await this.httpRequestService.getResponse({ + url, + method: req.request.method, + headers: req.request.headers, + body, + }); + } + + /** + * Get AP object with http-signature + * @param user http-signature user + * @param url URL to fetch + */ + public async signedGet(url: string, user: { id: User['id'] }) { + const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); + + const req = this.createSignedGet({ + key: { + privateKeyPem: keypair.privateKey, + keyId: `${this.config.url}/users/${user.id}#main-key`, + }, + url, + additionalHeaders: { + 'User-Agent': this.config.userAgent, + }, + }); + + const res = await this.httpRequestService.getResponse({ + url, + method: req.request.method, + headers: req.request.headers, + }); + + return await res.json(); + } +} diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts new file mode 100644 index 0000000000..bcdb9383d1 --- /dev/null +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -0,0 +1,195 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type { ILocalUser } from '@/models/entities/User.js'; +import { InstanceActorService } from '@/core/InstanceActorService.js'; +import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import { MetaService } from '@/core/MetaService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { DI } from '@/di-symbols.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { isCollectionOrOrderedCollection } from './type.js'; +import { ApDbResolverService } from './ApDbResolverService.js'; +import { ApRendererService } from './ApRendererService.js'; +import { ApRequestService } from './ApRequestService.js'; +import type { IObject, ICollection, IOrderedCollection } from './type.js'; + +@Injectable() +export class ApResolverService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + @Inject(DI.noteReactionsRepository) + private noteReactionsRepository: NoteReactionsRepository, + + private utilityService: UtilityService, + private instanceActorService: InstanceActorService, + private metaService: MetaService, + private apRequestService: ApRequestService, + private httpRequestService: HttpRequestService, + private apRendererService: ApRendererService, + private apDbResolverService: ApDbResolverService, + ) { + } + + public createResolver(): Resolver { + return new Resolver( + this.config, + this.usersRepository, + this.notesRepository, + this.pollsRepository, + this.noteReactionsRepository, + this.utilityService, + this.instanceActorService, + this.metaService, + this.apRequestService, + this.httpRequestService, + this.apRendererService, + this.apDbResolverService, + ); + } +} + +export class Resolver { + private history: Set; + private user?: ILocalUser; + + constructor( + private config: Config, + private usersRepository: UsersRepository, + private notesRepository: NotesRepository, + private pollsRepository: PollsRepository, + private noteReactionsRepository: NoteReactionsRepository, + private utilityService: UtilityService, + private instanceActorService: InstanceActorService, + private metaService: MetaService, + private apRequestService: ApRequestService, + private httpRequestService: HttpRequestService, + private apRendererService: ApRendererService, + private apDbResolverService: ApDbResolverService, + private recursionLimit = 100 + ) { + this.history = new Set(); + } + + public getHistory(): string[] { + return Array.from(this.history); + } + + public async resolveCollection(value: string | IObject): Promise { + const collection = typeof value === 'string' + ? await this.resolve(value) + : value; + + if (isCollectionOrOrderedCollection(collection)) { + return collection; + } else { + throw new Error(`unrecognized collection type: ${collection.type}`); + } + } + + public async resolve(value: string | IObject): Promise { + if (value == null) { + throw new Error('resolvee is null (or undefined)'); + } + + if (typeof value !== 'string') { + return value; + } + + if (value.includes('#')) { + // URLs with fragment parts cannot be resolved correctly because + // the fragment part does not get transmitted over HTTP(S). + // Avoid strange behaviour by not trying to resolve these at all. + throw new Error(`cannot resolve URL with fragment: ${value}`); + } + + if (this.history.has(value)) { + throw new Error('cannot resolve already resolved one'); + } + + if (this.history.size > this.recursionLimit) { + throw new Error(`hit recursion limit: ${this.utilityService.extractDbHost(value)}`); + } + + this.history.add(value); + + const host = this.utilityService.extractDbHost(value); + if (this.utilityService.isSelfHost(host)) { + return await this.resolveLocal(value); + } + + const meta = await this.metaService.fetch(); + if (meta.blockedHosts.includes(host)) { + throw new Error('Instance is blocked'); + } + + if (this.config.signToActivityPubGet && !this.user) { + this.user = await this.instanceActorService.getInstanceActor(); + } + + const object = (this.user + ? await this.apRequestService.signedGet(value, this.user) + : await this.httpRequestService.getJson(value, 'application/activity+json, application/ld+json')) as IObject; + + if (object == null || ( + Array.isArray(object['@context']) ? + !(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') : + object['@context'] !== 'https://www.w3.org/ns/activitystreams' + )) { + throw new Error('invalid response'); + } + + return object; + } + + private resolveLocal(url: string): Promise { + const parsed = this.apDbResolverService.parseUri(url); + if (!parsed.local) throw new Error('resolveLocal: not local'); + + switch (parsed.type) { + case 'notes': + return this.notesRepository.findOneByOrFail({ id: parsed.id }) + .then(note => { + if (parsed.rest === 'activity') { + // this refers to the create activity and not the note itself + return this.apRendererService.renderActivity(this.apRendererService.renderCreate(this.apRendererService.renderNote(note), note)); + } else { + return this.apRendererService.renderNote(note); + } + }); + case 'users': + return this.usersRepository.findOneByOrFail({ id: parsed.id }) + .then(user => this.apRendererService.renderPerson(user as ILocalUser)); + case 'questions': + // Polls are indexed by the note they are attached to. + return Promise.all([ + this.notesRepository.findOneByOrFail({ id: parsed.id }), + this.pollsRepository.findOneByOrFail({ noteId: parsed.id }), + ]) + .then(([note, poll]) => this.apRendererService.renderQuestion({ id: note.userId }, note, poll)); + case 'likes': + return this.noteReactionsRepository.findOneByOrFail({ id: parsed.id }).then(reaction => + this.apRendererService.renderActivity(this.apRendererService.renderLike(reaction, { uri: null }))!); + case 'follows': + // rest should be + if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI'); + + return Promise.all( + [parsed.id, parsed.rest].map(id => this.usersRepository.findOneByOrFail({ id })), + ) + .then(([follower, followee]) => this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee, url))); + default: + throw new Error(`resolveLocal: type ${parsed.type} unhandled`); + } + } +} diff --git a/packages/backend/src/core/activitypub/LdSignatureService.ts b/packages/backend/src/core/activitypub/LdSignatureService.ts new file mode 100644 index 0000000000..ea39f15b2b --- /dev/null +++ b/packages/backend/src/core/activitypub/LdSignatureService.ts @@ -0,0 +1,147 @@ +import * as crypto from 'node:crypto'; +import { Inject, Injectable } from '@nestjs/common'; +import fetch from 'node-fetch'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { CONTEXTS } from './misc/contexts.js'; + +// RsaSignature2017 based from https://github.com/transmute-industries/RsaSignature2017 + +@Injectable() +export class LdSignatureService { + constructor( + private httpRequestService: HttpRequestService, + ) { + } + + public use(): LdSignature { + return new LdSignature(this.httpRequestService); + } +} + +class LdSignature { + public debug = false; + public preLoad = true; + public loderTimeout = 10 * 1000; + + constructor( + private httpRequestService: HttpRequestService, + ) { + } + + public async signRsaSignature2017(data: any, privateKey: string, creator: string, domain?: string, created?: Date): Promise { + const options = { + type: 'RsaSignature2017', + creator, + domain, + nonce: crypto.randomBytes(16).toString('hex'), + created: (created ?? new Date()).toISOString(), + } as { + type: string; + creator: string; + domain?: string; + nonce: string; + created: string; + }; + + if (!domain) { + delete options.domain; + } + + const toBeSigned = await this.createVerifyData(data, options); + + const signer = crypto.createSign('sha256'); + signer.update(toBeSigned); + signer.end(); + + const signature = signer.sign(privateKey); + + return { + ...data, + signature: { + ...options, + signatureValue: signature.toString('base64'), + }, + }; + } + + public async verifyRsaSignature2017(data: any, publicKey: string): Promise { + const toBeSigned = await this.createVerifyData(data, data.signature); + const verifier = crypto.createVerify('sha256'); + verifier.update(toBeSigned); + return verifier.verify(publicKey, data.signature.signatureValue, 'base64'); + } + + public async createVerifyData(data: any, options: any) { + const transformedOptions = { + ...options, + '@context': 'https://w3id.org/identity/v1', + }; + delete transformedOptions['type']; + delete transformedOptions['id']; + delete transformedOptions['signatureValue']; + const canonizedOptions = await this.normalize(transformedOptions); + const optionsHash = this.sha256(canonizedOptions.toString()); + const transformedData = { ...data }; + delete transformedData['signature']; + const cannonidedData = await this.normalize(transformedData); + if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`); + const documentHash = this.sha256(cannonidedData.toString()); + const verifyData = `${optionsHash}${documentHash}`; + return verifyData; + } + + public async normalize(data: any) { + const customLoader = this.getLoader(); + return 42; + } + + private getLoader() { + return async (url: string): Promise => { + if (!url.match('^https?\:\/\/')) throw `Invalid URL ${url}`; + + if (this.preLoad) { + if (url in CONTEXTS) { + if (this.debug) console.debug(`HIT: ${url}`); + return { + contextUrl: null, + document: CONTEXTS[url], + documentUrl: url, + }; + } + } + + if (this.debug) console.debug(`MISS: ${url}`); + const document = await this.fetchDocument(url); + return { + contextUrl: null, + document: document, + documentUrl: url, + }; + }; + } + + private async fetchDocument(url: string) { + const json = await fetch(url, { + headers: { + Accept: 'application/ld+json, application/json', + }, + // TODO + //timeout: this.loderTimeout, + agent: u => u.protocol === 'http:' ? this.httpRequestService.httpAgent : this.httpRequestService.httpsAgent, + }).then(res => { + if (!res.ok) { + throw `${res.status} ${res.statusText}`; + } else { + return res.json(); + } + }); + + return json; + } + + public sha256(data: string): string { + const hash = crypto.createHash('sha256'); + hash.update(data); + return hash.digest('hex'); + } +} diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts new file mode 100644 index 0000000000..aee0d3629c --- /dev/null +++ b/packages/backend/src/core/activitypub/misc/contexts.ts @@ -0,0 +1,526 @@ +/* eslint:disable:quotemark indent */ +const id_v1 = { + '@context': { + 'id': '@id', + 'type': '@type', + + 'cred': 'https://w3id.org/credentials#', + 'dc': 'http://purl.org/dc/terms/', + 'identity': 'https://w3id.org/identity#', + 'perm': 'https://w3id.org/permissions#', + 'ps': 'https://w3id.org/payswarm#', + 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#', + 'sec': 'https://w3id.org/security#', + 'schema': 'http://schema.org/', + 'xsd': 'http://www.w3.org/2001/XMLSchema#', + + 'Group': 'https://www.w3.org/ns/activitystreams#Group', + + 'claim': { '@id': 'cred:claim', '@type': '@id' }, + 'credential': { '@id': 'cred:credential', '@type': '@id' }, + 'issued': { '@id': 'cred:issued', '@type': 'xsd:dateTime' }, + 'issuer': { '@id': 'cred:issuer', '@type': '@id' }, + 'recipient': { '@id': 'cred:recipient', '@type': '@id' }, + 'Credential': 'cred:Credential', + 'CryptographicKeyCredential': 'cred:CryptographicKeyCredential', + + 'about': { '@id': 'schema:about', '@type': '@id' }, + 'address': { '@id': 'schema:address', '@type': '@id' }, + 'addressCountry': 'schema:addressCountry', + 'addressLocality': 'schema:addressLocality', + 'addressRegion': 'schema:addressRegion', + 'comment': 'rdfs:comment', + 'created': { '@id': 'dc:created', '@type': 'xsd:dateTime' }, + 'creator': { '@id': 'dc:creator', '@type': '@id' }, + 'description': 'schema:description', + 'email': 'schema:email', + 'familyName': 'schema:familyName', + 'givenName': 'schema:givenName', + 'image': { '@id': 'schema:image', '@type': '@id' }, + 'label': 'rdfs:label', + 'name': 'schema:name', + 'postalCode': 'schema:postalCode', + 'streetAddress': 'schema:streetAddress', + 'title': 'dc:title', + 'url': { '@id': 'schema:url', '@type': '@id' }, + 'Person': 'schema:Person', + 'PostalAddress': 'schema:PostalAddress', + 'Organization': 'schema:Organization', + + 'identityService': { '@id': 'identity:identityService', '@type': '@id' }, + 'idp': { '@id': 'identity:idp', '@type': '@id' }, + 'Identity': 'identity:Identity', + + 'paymentProcessor': 'ps:processor', + 'preferences': { '@id': 'ps:preferences', '@type': '@vocab' }, + + 'cipherAlgorithm': 'sec:cipherAlgorithm', + 'cipherData': 'sec:cipherData', + 'cipherKey': 'sec:cipherKey', + 'digestAlgorithm': 'sec:digestAlgorithm', + 'digestValue': 'sec:digestValue', + 'domain': 'sec:domain', + 'expires': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, + 'initializationVector': 'sec:initializationVector', + 'member': { '@id': 'schema:member', '@type': '@id' }, + 'memberOf': { '@id': 'schema:memberOf', '@type': '@id' }, + 'nonce': 'sec:nonce', + 'normalizationAlgorithm': 'sec:normalizationAlgorithm', + 'owner': { '@id': 'sec:owner', '@type': '@id' }, + 'password': 'sec:password', + 'privateKey': { '@id': 'sec:privateKey', '@type': '@id' }, + 'privateKeyPem': 'sec:privateKeyPem', + 'publicKey': { '@id': 'sec:publicKey', '@type': '@id' }, + 'publicKeyPem': 'sec:publicKeyPem', + 'publicKeyService': { '@id': 'sec:publicKeyService', '@type': '@id' }, + 'revoked': { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, + 'signature': 'sec:signature', + 'signatureAlgorithm': 'sec:signatureAlgorithm', + 'signatureValue': 'sec:signatureValue', + 'CryptographicKey': 'sec:Key', + 'EncryptedMessage': 'sec:EncryptedMessage', + 'GraphSignature2012': 'sec:GraphSignature2012', + 'LinkedDataSignature2015': 'sec:LinkedDataSignature2015', + + 'accessControl': { '@id': 'perm:accessControl', '@type': '@id' }, + 'writePermission': { '@id': 'perm:writePermission', '@type': '@id' }, + }, +}; + +const security_v1 = { + '@context': { + 'id': '@id', + 'type': '@type', + + 'dc': 'http://purl.org/dc/terms/', + 'sec': 'https://w3id.org/security#', + 'xsd': 'http://www.w3.org/2001/XMLSchema#', + + 'EcdsaKoblitzSignature2016': 'sec:EcdsaKoblitzSignature2016', + 'Ed25519Signature2018': 'sec:Ed25519Signature2018', + 'EncryptedMessage': 'sec:EncryptedMessage', + 'GraphSignature2012': 'sec:GraphSignature2012', + 'LinkedDataSignature2015': 'sec:LinkedDataSignature2015', + 'LinkedDataSignature2016': 'sec:LinkedDataSignature2016', + 'CryptographicKey': 'sec:Key', + + 'authenticationTag': 'sec:authenticationTag', + 'canonicalizationAlgorithm': 'sec:canonicalizationAlgorithm', + 'cipherAlgorithm': 'sec:cipherAlgorithm', + 'cipherData': 'sec:cipherData', + 'cipherKey': 'sec:cipherKey', + 'created': { '@id': 'dc:created', '@type': 'xsd:dateTime' }, + 'creator': { '@id': 'dc:creator', '@type': '@id' }, + 'digestAlgorithm': 'sec:digestAlgorithm', + 'digestValue': 'sec:digestValue', + 'domain': 'sec:domain', + 'encryptionKey': 'sec:encryptionKey', + 'expiration': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, + 'expires': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, + 'initializationVector': 'sec:initializationVector', + 'iterationCount': 'sec:iterationCount', + 'nonce': 'sec:nonce', + 'normalizationAlgorithm': 'sec:normalizationAlgorithm', + 'owner': { '@id': 'sec:owner', '@type': '@id' }, + 'password': 'sec:password', + 'privateKey': { '@id': 'sec:privateKey', '@type': '@id' }, + 'privateKeyPem': 'sec:privateKeyPem', + 'publicKey': { '@id': 'sec:publicKey', '@type': '@id' }, + 'publicKeyBase58': 'sec:publicKeyBase58', + 'publicKeyPem': 'sec:publicKeyPem', + 'publicKeyWif': 'sec:publicKeyWif', + 'publicKeyService': { '@id': 'sec:publicKeyService', '@type': '@id' }, + 'revoked': { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, + 'salt': 'sec:salt', + 'signature': 'sec:signature', + 'signatureAlgorithm': 'sec:signingAlgorithm', + 'signatureValue': 'sec:signatureValue', + }, +}; + +const activitystreams = { + '@context': { + '@vocab': '_:', + 'xsd': 'http://www.w3.org/2001/XMLSchema#', + 'as': 'https://www.w3.org/ns/activitystreams#', + 'ldp': 'http://www.w3.org/ns/ldp#', + 'vcard': 'http://www.w3.org/2006/vcard/ns#', + 'id': '@id', + 'type': '@type', + 'Accept': 'as:Accept', + 'Activity': 'as:Activity', + 'IntransitiveActivity': 'as:IntransitiveActivity', + 'Add': 'as:Add', + 'Announce': 'as:Announce', + 'Application': 'as:Application', + 'Arrive': 'as:Arrive', + 'Article': 'as:Article', + 'Audio': 'as:Audio', + 'Block': 'as:Block', + 'Collection': 'as:Collection', + 'CollectionPage': 'as:CollectionPage', + 'Relationship': 'as:Relationship', + 'Create': 'as:Create', + 'Delete': 'as:Delete', + 'Dislike': 'as:Dislike', + 'Document': 'as:Document', + 'Event': 'as:Event', + 'Follow': 'as:Follow', + 'Flag': 'as:Flag', + 'Group': 'as:Group', + 'Ignore': 'as:Ignore', + 'Image': 'as:Image', + 'Invite': 'as:Invite', + 'Join': 'as:Join', + 'Leave': 'as:Leave', + 'Like': 'as:Like', + 'Link': 'as:Link', + 'Mention': 'as:Mention', + 'Note': 'as:Note', + 'Object': 'as:Object', + 'Offer': 'as:Offer', + 'OrderedCollection': 'as:OrderedCollection', + 'OrderedCollectionPage': 'as:OrderedCollectionPage', + 'Organization': 'as:Organization', + 'Page': 'as:Page', + 'Person': 'as:Person', + 'Place': 'as:Place', + 'Profile': 'as:Profile', + 'Question': 'as:Question', + 'Reject': 'as:Reject', + 'Remove': 'as:Remove', + 'Service': 'as:Service', + 'TentativeAccept': 'as:TentativeAccept', + 'TentativeReject': 'as:TentativeReject', + 'Tombstone': 'as:Tombstone', + 'Undo': 'as:Undo', + 'Update': 'as:Update', + 'Video': 'as:Video', + 'View': 'as:View', + 'Listen': 'as:Listen', + 'Read': 'as:Read', + 'Move': 'as:Move', + 'Travel': 'as:Travel', + 'IsFollowing': 'as:IsFollowing', + 'IsFollowedBy': 'as:IsFollowedBy', + 'IsContact': 'as:IsContact', + 'IsMember': 'as:IsMember', + 'subject': { + '@id': 'as:subject', + '@type': '@id', + }, + 'relationship': { + '@id': 'as:relationship', + '@type': '@id', + }, + 'actor': { + '@id': 'as:actor', + '@type': '@id', + }, + 'attributedTo': { + '@id': 'as:attributedTo', + '@type': '@id', + }, + 'attachment': { + '@id': 'as:attachment', + '@type': '@id', + }, + 'bcc': { + '@id': 'as:bcc', + '@type': '@id', + }, + 'bto': { + '@id': 'as:bto', + '@type': '@id', + }, + 'cc': { + '@id': 'as:cc', + '@type': '@id', + }, + 'context': { + '@id': 'as:context', + '@type': '@id', + }, + 'current': { + '@id': 'as:current', + '@type': '@id', + }, + 'first': { + '@id': 'as:first', + '@type': '@id', + }, + 'generator': { + '@id': 'as:generator', + '@type': '@id', + }, + 'icon': { + '@id': 'as:icon', + '@type': '@id', + }, + 'image': { + '@id': 'as:image', + '@type': '@id', + }, + 'inReplyTo': { + '@id': 'as:inReplyTo', + '@type': '@id', + }, + 'items': { + '@id': 'as:items', + '@type': '@id', + }, + 'instrument': { + '@id': 'as:instrument', + '@type': '@id', + }, + 'orderedItems': { + '@id': 'as:items', + '@type': '@id', + '@container': '@list', + }, + 'last': { + '@id': 'as:last', + '@type': '@id', + }, + 'location': { + '@id': 'as:location', + '@type': '@id', + }, + 'next': { + '@id': 'as:next', + '@type': '@id', + }, + 'object': { + '@id': 'as:object', + '@type': '@id', + }, + 'oneOf': { + '@id': 'as:oneOf', + '@type': '@id', + }, + 'anyOf': { + '@id': 'as:anyOf', + '@type': '@id', + }, + 'closed': { + '@id': 'as:closed', + '@type': 'xsd:dateTime', + }, + 'origin': { + '@id': 'as:origin', + '@type': '@id', + }, + 'accuracy': { + '@id': 'as:accuracy', + '@type': 'xsd:float', + }, + 'prev': { + '@id': 'as:prev', + '@type': '@id', + }, + 'preview': { + '@id': 'as:preview', + '@type': '@id', + }, + 'replies': { + '@id': 'as:replies', + '@type': '@id', + }, + 'result': { + '@id': 'as:result', + '@type': '@id', + }, + 'audience': { + '@id': 'as:audience', + '@type': '@id', + }, + 'partOf': { + '@id': 'as:partOf', + '@type': '@id', + }, + 'tag': { + '@id': 'as:tag', + '@type': '@id', + }, + 'target': { + '@id': 'as:target', + '@type': '@id', + }, + 'to': { + '@id': 'as:to', + '@type': '@id', + }, + 'url': { + '@id': 'as:url', + '@type': '@id', + }, + 'altitude': { + '@id': 'as:altitude', + '@type': 'xsd:float', + }, + 'content': 'as:content', + 'contentMap': { + '@id': 'as:content', + '@container': '@language', + }, + 'name': 'as:name', + 'nameMap': { + '@id': 'as:name', + '@container': '@language', + }, + 'duration': { + '@id': 'as:duration', + '@type': 'xsd:duration', + }, + 'endTime': { + '@id': 'as:endTime', + '@type': 'xsd:dateTime', + }, + 'height': { + '@id': 'as:height', + '@type': 'xsd:nonNegativeInteger', + }, + 'href': { + '@id': 'as:href', + '@type': '@id', + }, + 'hreflang': 'as:hreflang', + 'latitude': { + '@id': 'as:latitude', + '@type': 'xsd:float', + }, + 'longitude': { + '@id': 'as:longitude', + '@type': 'xsd:float', + }, + 'mediaType': 'as:mediaType', + 'published': { + '@id': 'as:published', + '@type': 'xsd:dateTime', + }, + 'radius': { + '@id': 'as:radius', + '@type': 'xsd:float', + }, + 'rel': 'as:rel', + 'startIndex': { + '@id': 'as:startIndex', + '@type': 'xsd:nonNegativeInteger', + }, + 'startTime': { + '@id': 'as:startTime', + '@type': 'xsd:dateTime', + }, + 'summary': 'as:summary', + 'summaryMap': { + '@id': 'as:summary', + '@container': '@language', + }, + 'totalItems': { + '@id': 'as:totalItems', + '@type': 'xsd:nonNegativeInteger', + }, + 'units': 'as:units', + 'updated': { + '@id': 'as:updated', + '@type': 'xsd:dateTime', + }, + 'width': { + '@id': 'as:width', + '@type': 'xsd:nonNegativeInteger', + }, + 'describes': { + '@id': 'as:describes', + '@type': '@id', + }, + 'formerType': { + '@id': 'as:formerType', + '@type': '@id', + }, + 'deleted': { + '@id': 'as:deleted', + '@type': 'xsd:dateTime', + }, + 'inbox': { + '@id': 'ldp:inbox', + '@type': '@id', + }, + 'outbox': { + '@id': 'as:outbox', + '@type': '@id', + }, + 'following': { + '@id': 'as:following', + '@type': '@id', + }, + 'followers': { + '@id': 'as:followers', + '@type': '@id', + }, + 'streams': { + '@id': 'as:streams', + '@type': '@id', + }, + 'preferredUsername': 'as:preferredUsername', + 'endpoints': { + '@id': 'as:endpoints', + '@type': '@id', + }, + 'uploadMedia': { + '@id': 'as:uploadMedia', + '@type': '@id', + }, + 'proxyUrl': { + '@id': 'as:proxyUrl', + '@type': '@id', + }, + 'liked': { + '@id': 'as:liked', + '@type': '@id', + }, + 'oauthAuthorizationEndpoint': { + '@id': 'as:oauthAuthorizationEndpoint', + '@type': '@id', + }, + 'oauthTokenEndpoint': { + '@id': 'as:oauthTokenEndpoint', + '@type': '@id', + }, + 'provideClientKey': { + '@id': 'as:provideClientKey', + '@type': '@id', + }, + 'signClientKey': { + '@id': 'as:signClientKey', + '@type': '@id', + }, + 'sharedInbox': { + '@id': 'as:sharedInbox', + '@type': '@id', + }, + 'Public': { + '@id': 'as:Public', + '@type': '@id', + }, + 'source': 'as:source', + 'likes': { + '@id': 'as:likes', + '@type': '@id', + }, + 'shares': { + '@id': 'as:shares', + '@type': '@id', + }, + 'alsoKnownAs': { + '@id': 'as:alsoKnownAs', + '@type': '@id', + }, + }, +}; + +export const CONTEXTS: Record = { + 'https://w3id.org/identity/v1': id_v1, + 'https://w3id.org/security/v1': security_v1, + 'https://www.w3.org/ns/activitystreams': activitystreams, +}; diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts new file mode 100644 index 0000000000..9bf87f19d4 --- /dev/null +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -0,0 +1,90 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { DriveFilesRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { CacheableRemoteUser } from '@/models/entities/User.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import { MetaService } from '@/core/MetaService.js'; +import { truncate } from '@/misc/truncate.js'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; +import { DriveService } from '@/core/DriveService.js'; +import type Logger from '@/logger.js'; +import { ApResolverService } from '../ApResolverService.js'; +import { ApLoggerService } from '../ApLoggerService.js'; + +@Injectable() +export class ApImageService { + private logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private metaService: MetaService, + private apResolverService: ApResolverService, + private driveService: DriveService, + private apLoggerService: ApLoggerService, + ) { + this.logger = this.apLoggerService.logger; + } + + /** + * Imageを作成します。 + */ + public async createImage(actor: CacheableRemoteUser, value: any): Promise { + // 投稿者が凍結されていたらスキップ + if (actor.isSuspended) { + throw new Error('actor has been suspended'); + } + + const image = await this.apResolverService.createResolver().resolve(value) as any; + + if (image.url == null) { + throw new Error('invalid image: url not privided'); + } + + this.logger.info(`Creating the Image: ${image.url}`); + + const instance = await this.metaService.fetch(); + + let file = await this.driveService.uploadFromUrl({ + url: image.url, + user: actor, + uri: image.url, + sensitive: image.sensitive, + isLink: !instance.cacheRemoteFiles, + comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH), + }); + + if (file.isLink) { + // URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、 + // URLを更新する + if (file.url !== image.url) { + await this.driveFilesRepository.update({ id: file.id }, { + url: image.url, + uri: image.url, + }); + + file = await this.driveFilesRepository.findOneByOrFail({ id: file.id }); + } + } + + return file; + } + + /** + * Imageを解決します。 + * + * Misskeyに対象のImageが登録されていればそれを返し、そうでなければ + * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 + */ + public async resolveImage(actor: CacheableRemoteUser, value: any): Promise { + // TODO + + // リモートサーバーからフェッチしてきて登録 + return await this.createImage(actor, value); + } +} diff --git a/packages/backend/src/core/activitypub/models/ApMentionService.ts b/packages/backend/src/core/activitypub/models/ApMentionService.ts new file mode 100644 index 0000000000..1275e24c62 --- /dev/null +++ b/packages/backend/src/core/activitypub/models/ApMentionService.ts @@ -0,0 +1,39 @@ +import { Inject, Injectable } from '@nestjs/common'; +import promiseLimit from 'promise-limit'; +import { DI } from '@/di-symbols.js'; +import type { UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import { toArray, unique } from '@/misc/prelude/array.js'; +import type { CacheableUser } from '@/models/entities/User.js'; +import { isMention } from '../type.js'; +import { ApResolverService, Resolver } from '../ApResolverService.js'; +import { ApPersonService } from './ApPersonService.js'; +import type { IObject, IApMention } from '../type.js'; + +@Injectable() +export class ApMentionService { + constructor( + @Inject(DI.config) + private config: Config, + + private apResolverService: ApResolverService, + private apPersonService: ApPersonService, + ) { + } + + public async extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) { + const hrefs = unique(this.extractApMentionObjects(tags).map(x => x.href as string)); + + const limit = promiseLimit(2); + const mentionedUsers = (await Promise.all( + hrefs.map(x => limit(() => this.apPersonService.resolvePerson(x, resolver).catch(() => null))), + )).filter((x): x is CacheableUser => x != null); + + return mentionedUsers; + } + + public extractApMentionObjects(tags: IObject | IObject[] | null | undefined): IApMention[] { + if (tags == null) return []; + return toArray(tags).filter(isMention); + } +} diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts new file mode 100644 index 0000000000..7cf6725a38 --- /dev/null +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -0,0 +1,403 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import promiseLimit from 'promise-limit'; +import { DI } from '@/di-symbols.js'; +import type { MessagingMessagesRepository, PollsRepository, EmojisRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { CacheableRemoteUser } from '@/models/entities/User.js'; +import type { Note } from '@/models/entities/Note.js'; +import { toArray, toSingle, unique } from '@/misc/prelude/array.js'; +import type { Emoji } from '@/models/entities/Emoji.js'; +import { MetaService } from '@/core/MetaService.js'; +import { AppLockService } from '@/core/AppLockService.js'; +import type { DriveFile } from '@/models/entities/DriveFile.js'; +import { NoteCreateService } from '@/core/NoteCreateService.js'; +import type Logger from '@/logger.js'; +import { IdService } from '@/core/IdService.js'; +import { PollService } from '@/core/PollService.js'; +import { StatusError } from '@/misc/status-error.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { MessagingService } from '@/core/MessagingService.js'; +import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js'; +// eslint-disable-next-line @typescript-eslint/consistent-type-imports +import { ApLoggerService } from '../ApLoggerService.js'; +import { ApMfmService } from '../ApMfmService.js'; +import { ApDbResolverService } from '../ApDbResolverService.js'; +import { ApResolverService } from '../ApResolverService.js'; +import { ApAudienceService } from '../ApAudienceService.js'; +import { ApPersonService } from './ApPersonService.js'; +import { extractApHashtags } from './tag.js'; +import { ApMentionService } from './ApMentionService.js'; +import { ApQuestionService } from './ApQuestionService.js'; +import { ApImageService } from './ApImageService.js'; +import type { Resolver } from '../ApResolverService.js'; +import type { IObject, IPost } from '../type.js'; + +@Injectable() +export class ApNoteService { + private logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + + @Inject(DI.messagingMessagesRepository) + private messagingMessagesRepository: MessagingMessagesRepository, + + private idService: IdService, + private apMfmService: ApMfmService, + private apResolverService: ApResolverService, + + // 循環参照のため / for circular dependency + @Inject(forwardRef(() => ApPersonService)) + private apPersonService: ApPersonService, + + private utilityService: UtilityService, + private apAudienceService: ApAudienceService, + private apMentionService: ApMentionService, + private apImageService: ApImageService, + private apQuestionService: ApQuestionService, + private metaService: MetaService, + private messagingService: MessagingService, + private appLockService: AppLockService, + private pollService: PollService, + private noteCreateService: NoteCreateService, + private apDbResolverService: ApDbResolverService, + private apLoggerService: ApLoggerService, + ) { + this.logger = this.apLoggerService.logger; + } + + public validateNote(object: any, uri: string) { + const expectHost = this.utilityService.extractDbHost(uri); + + if (object == null) { + return new Error('invalid Note: object is null'); + } + + if (!validPost.includes(getApType(object))) { + return new Error(`invalid Note: invalid object type ${getApType(object)}`); + } + + if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) { + return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`); + } + + if (object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo)) !== expectHost) { + return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.attributedTo)}`); + } + + return null; + } + + /** + * Noteをフェッチします。 + * + * Misskeyに対象のNoteが登録されていればそれを返します。 + */ + public async fetchNote(object: string | IObject): Promise { + return await this.apDbResolverService.getNoteFromApId(object); + } + + /** + * Noteを作成します。 + */ + public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise { + if (resolver == null) resolver = this.apResolverService.createResolver(); + + const object: any = await resolver.resolve(value); + + const entryUri = getApId(value); + const err = this.validateNote(object, entryUri); + if (err) { + this.logger.error(`${err.message}`, { + resolver: { + history: resolver.getHistory(), + }, + value: value, + object: object, + }); + throw new Error('invalid note'); + } + + const note: IPost = object; + + this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); + + this.logger.info(`Creating the Note: ${note.id}`); + + // 投稿者をフェッチ + const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser; + + // 投稿者が凍結されていたらスキップ + if (actor.isSuspended) { + throw new Error('actor has been suspended'); + } + + const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver); + let visibility = noteAudience.visibility; + const visibleUsers = noteAudience.visibleUsers; + + // Audience (to, cc) が指定されてなかった場合 + if (visibility === 'specified' && visibleUsers.length === 0) { + if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している + // こちらから匿名GET出来たものならばpublic + visibility = 'public'; + } + } + + let isMessaging = note._misskey_talk && visibility === 'specified'; + + const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver); + const apHashtags = await extractApHashtags(note.tag); + + // 添付ファイル + // TODO: attachmentは必ずしもImageではない + // TODO: attachmentは必ずしも配列ではない + // Noteがsensitiveなら添付もsensitiveにする + const limit = promiseLimit(2); + + note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : []; + const files = note.attachment + .map(attach => attach.sensitive = note.sensitive) + ? (await Promise.all(note.attachment.map(x => limit(() => this.apImageService.resolveImage(actor, x)) as Promise))) + .filter(image => image != null) + : []; + + // リプライ + const reply: Note | null = note.inReplyTo + ? await this.resolveNote(note.inReplyTo, resolver).then(x => { + if (x == null) { + this.logger.warn('Specified inReplyTo, but nout found'); + throw new Error('inReplyTo not found'); + } else { + return x; + } + }).catch(async err => { + // トークだったらinReplyToのエラーは無視 + const uri = getApId(note.inReplyTo); + if (uri.startsWith(this.config.url + '/')) { + const id = uri.split('/').pop(); + const talk = await this.messagingMessagesRepository.findOneBy({ id }); + if (talk) { + isMessaging = true; + return null; + } + } + + this.logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`); + throw err; + }) + : null; + + // 引用 + let quote: Note | undefined | null; + + if (note._misskey_quote || note.quoteUrl) { + const tryResolveNote = async (uri: string): Promise<{ + status: 'ok'; + res: Note | null; + } | { + status: 'permerror' | 'temperror'; + }> => { + if (typeof uri !== 'string' || !uri.match(/^https?:/)) return { status: 'permerror' }; + try { + const res = await this.resolveNote(uri); + if (res) { + return { + status: 'ok', + res, + }; + } else { + return { + status: 'permerror', + }; + } + } catch (e) { + return { + status: (e instanceof StatusError && e.isClientError) ? 'permerror' : 'temperror', + }; + } + }; + + const uris = unique([note._misskey_quote, note.quoteUrl].filter((x): x is string => typeof x === 'string')); + const results = await Promise.all(uris.map(uri => tryResolveNote(uri))); + + quote = results.filter((x): x is { status: 'ok', res: Note | null } => x.status === 'ok').map(x => x.res).find(x => x); + if (!quote) { + if (results.some(x => x.status === 'temperror')) { + throw 'quote resolve failed'; + } + } + } + + const cw = note.summary === '' ? null : note.summary; + + // テキストのパース + let text: string | null = null; + if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') { + text = note.source.content; + } else if (typeof note._misskey_content !== 'undefined') { + text = note._misskey_content; + } else if (typeof note.content === 'string') { + text = this.apMfmService.htmlToMfm(note.content, note.tag); + } + + // vote + if (reply && reply.hasPoll) { + const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); + + const tryCreateVote = async (name: string, index: number): Promise => { + if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { + this.logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); + } else if (index >= 0) { + this.logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); + await this.pollService.vote(actor, reply, index); + + // リモートフォロワーにUpdate配信 + this.pollService.deliverQuestionUpdate(reply.id); + } + return null; + }; + + if (note.name) { + return await tryCreateVote(note.name, poll.choices.findIndex(x => x === note.name)); + } + } + + const emojis = await this.extractEmojis(note.tag ?? [], actor.host).catch(e => { + this.logger.info(`extractEmojis: ${e}`); + return [] as Emoji[]; + }); + + const apEmojis = emojis.map(emoji => emoji.name); + + const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined); + + if (isMessaging) { + for (const recipient of visibleUsers) { + await this.messagingService.createMessage(actor, recipient, undefined, text ?? undefined, (files && files.length > 0) ? files[0] : null, object.id); + return null; + } + } + + return await this.noteCreateService.create(actor, { + createdAt: note.published ? new Date(note.published) : null, + files, + reply, + renote: quote, + name: note.name, + cw, + text, + localOnly: false, + visibility, + visibleUsers, + apMentions, + apHashtags, + apEmojis, + poll, + uri: note.id, + url: getOneApHrefNullable(note.url), + }, silent); + } + + /** + * Noteを解決します。 + * + * Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ + * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 + */ + public async resolveNote(value: string | IObject, resolver?: Resolver): Promise { + const uri = typeof value === 'string' ? value : value.id; + if (uri == null) throw new Error('missing uri'); + + // ブロックしてたら中断 + const meta = await this.metaService.fetch(); + if (meta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) throw { statusCode: 451 }; + + const unlock = await this.appLockService.getApLock(uri); + + try { + //#region このサーバーに既に登録されていたらそれを返す + const exist = await this.fetchNote(uri); + + if (exist) { + return exist; + } + //#endregion + + if (uri.startsWith(this.config.url)) { + throw new StatusError('cannot resolve local note', 400, 'cannot resolve local note'); + } + + // リモートサーバーからフェッチしてきて登録 + // ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが + // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 + return await this.createNote(uri, resolver, true); + } finally { + unlock(); + } + } + + public async extractEmojis(tags: IObject | IObject[], host: string): Promise { + host = this.utilityService.toPuny(host); + + if (!tags) return []; + + const eomjiTags = toArray(tags).filter(isEmoji); + + return await Promise.all(eomjiTags.map(async tag => { + const name = tag.name!.replace(/^:/, '').replace(/:$/, ''); + tag.icon = toSingle(tag.icon); + + const exists = await this.emojisRepository.findOneBy({ + host, + name, + }); + + if (exists) { + if ((tag.updated != null && exists.updatedAt == null) + || (tag.id != null && exists.uri == null) + || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt) + || (tag.icon!.url !== exists.originalUrl) + ) { + await this.emojisRepository.update({ + host, + name, + }, { + uri: tag.id, + originalUrl: tag.icon!.url, + publicUrl: tag.icon!.url, + updatedAt: new Date(), + }); + + return await this.emojisRepository.findOneBy({ + host, + name, + }) as Emoji; + } + + return exists; + } + + this.logger.info(`register emoji host=${host}, name=${name}`); + + return await this.emojisRepository.insert({ + id: this.idService.genId(), + host, + name, + uri: tag.id, + originalUrl: tag.icon!.url, + publicUrl: tag.icon!.url, + updatedAt: new Date(), + aliases: [], + } as Partial).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0])); + })); + } +} diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts new file mode 100644 index 0000000000..f9d6f42ef6 --- /dev/null +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -0,0 +1,594 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import promiseLimit from 'promise-limit'; +import { DataSource } from 'typeorm'; +import { ModuleRef } from '@nestjs/core'; +import { DI } from '@/di-symbols.js'; +import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { CacheableUser, IRemoteUser } from '@/models/entities/User.js'; +import { User } from '@/models/entities/User.js'; +import { truncate } from '@/misc/truncate.js'; +import type { UserCacheService } from '@/core/UserCacheService.js'; +import { normalizeForSearch } from '@/misc/normalize-for-search.js'; +import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; +import type Logger from '@/logger.js'; +import type { Note } from '@/models/entities/Note.js'; +import type { IdService } from '@/core/IdService.js'; +import type { MfmService } from '@/core/MfmService.js'; +import type { Emoji } from '@/models/entities/Emoji.js'; +import { toArray } from '@/misc/prelude/array.js'; +import type { GlobalEventService } from '@/core/GlobalEventService.js'; +import type { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; +import type { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; +import { UserProfile } from '@/models/entities/UserProfile.js'; +import { UserPublickey } from '@/models/entities/UserPublickey.js'; +import type UsersChart from '@/core/chart/charts/users.js'; +import type InstanceChart from '@/core/chart/charts/instance.js'; +import type { HashtagService } from '@/core/HashtagService.js'; +import { UserNotePining } from '@/models/entities/UserNotePining.js'; +import { StatusError } from '@/misc/status-error.js'; +import type { UtilityService } from '@/core/UtilityService.js'; +import type { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js'; +import { extractApHashtags } from './tag.js'; +import type { OnModuleInit } from '@nestjs/common'; +import type { ApNoteService } from './ApNoteService.js'; +import type { ApMfmService } from '../ApMfmService.js'; +import type { ApResolverService, Resolver } from '../ApResolverService.js'; +import type { ApLoggerService } from '../ApLoggerService.js'; +// eslint-disable-next-line @typescript-eslint/consistent-type-imports +import type { ApImageService } from './ApImageService.js'; +import type { IActor, IObject, IApPropertyValue } from '../type.js'; + +const nameLength = 128; +const summaryLength = 2048; + +const services: { + [x: string]: (id: string, username: string) => any +} = { + 'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }), + 'misskey:authentication:github': (id, login) => ({ id, login }), + 'misskey:authentication:discord': (id, name) => $discord(id, name), +}; + +const $discord = (id: string, name: string) => { + if (typeof name !== 'string') { + name = 'unknown#0000'; + } + const [username, discriminator] = name.split('#'); + return { id, username, discriminator }; +}; + +function addService(target: { [x: string]: any }, source: IApPropertyValue) { + const service = services[source.name]; + + if (typeof source.value !== 'string') { + source.value = 'unknown'; + } + + const [id, username] = source.value.split('@'); + + if (service) { + target[source.name.split(':')[2]] = service(id, username); + } +} + +@Injectable() +export class ApPersonService implements OnModuleInit { + private utilityService: UtilityService; + private userEntityService: UserEntityService; + private idService: IdService; + private globalEventService: GlobalEventService; + private federatedInstanceService: FederatedInstanceService; + private fetchInstanceMetadataService: FetchInstanceMetadataService; + private userCacheService: UserCacheService; + private apResolverService: ApResolverService; + private apNoteService: ApNoteService; + private apImageService: ApImageService; + private apMfmService: ApMfmService; + private mfmService: MfmService; + private hashtagService: HashtagService; + private usersChart: UsersChart; + private instanceChart: InstanceChart; + private apLoggerService: ApLoggerService; + private logger: Logger; + + constructor( + private moduleRef: ModuleRef, + + @Inject(DI.config) + private config: Config, + + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.userPublickeysRepository) + private userPublickeysRepository: UserPublickeysRepository, + + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + //private utilityService: UtilityService, + //private userEntityService: UserEntityService, + //private idService: IdService, + //private globalEventService: GlobalEventService, + //private federatedInstanceService: FederatedInstanceService, + //private fetchInstanceMetadataService: FetchInstanceMetadataService, + //private userCacheService: UserCacheService, + //private apResolverService: ApResolverService, + //private apNoteService: ApNoteService, + //private apImageService: ApImageService, + //private apMfmService: ApMfmService, + //private mfmService: MfmService, + //private hashtagService: HashtagService, + //private usersChart: UsersChart, + //private instanceChart: InstanceChart, + //private apLoggerService: ApLoggerService, + ) { + } + + onModuleInit() { + this.utilityService = this.moduleRef.get('UtilityService'); + this.userEntityService = this.moduleRef.get('UserEntityService'); + this.idService = this.moduleRef.get('IdService'); + this.globalEventService = this.moduleRef.get('GlobalEventService'); + this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService'); + this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService'); + this.userCacheService = this.moduleRef.get('UserCacheService'); + this.apResolverService = this.moduleRef.get('ApResolverService'); + this.apNoteService = this.moduleRef.get('ApNoteService'); + this.apImageService = this.moduleRef.get('ApImageService'); + this.apMfmService = this.moduleRef.get('ApMfmService'); + this.mfmService = this.moduleRef.get('MfmService'); + this.hashtagService = this.moduleRef.get('HashtagService'); + this.usersChart = this.moduleRef.get('UsersChart'); + this.instanceChart = this.moduleRef.get('InstanceChart'); + this.apLoggerService = this.moduleRef.get('ApLoggerService'); + this.logger = this.apLoggerService.logger; + } + + /** + * Validate and convert to actor object + * @param x Fetched object + * @param uri Fetch target URI + */ + private validateActor(x: IObject, uri: string): IActor { + const expectHost = this.utilityService.toPuny(new URL(uri).hostname); + + if (x == null) { + throw new Error('invalid Actor: object is null'); + } + + if (!isActor(x)) { + throw new Error(`invalid Actor type '${x.type}'`); + } + + if (!(typeof x.id === 'string' && x.id.length > 0)) { + throw new Error('invalid Actor: wrong id'); + } + + if (!(typeof x.inbox === 'string' && x.inbox.length > 0)) { + throw new Error('invalid Actor: wrong inbox'); + } + + if (!(typeof x.preferredUsername === 'string' && x.preferredUsername.length > 0 && x.preferredUsername.length <= 128 && /^\w([\w-.]*\w)?$/.test(x.preferredUsername))) { + throw new Error('invalid Actor: wrong username'); + } + + // These fields are only informational, and some AP software allows these + // fields to be very long. If they are too long, we cut them off. This way + // we can at least see these users and their activities. + if (x.name) { + if (!(typeof x.name === 'string' && x.name.length > 0)) { + throw new Error('invalid Actor: wrong name'); + } + x.name = truncate(x.name, nameLength); + } + if (x.summary) { + if (!(typeof x.summary === 'string' && x.summary.length > 0)) { + throw new Error('invalid Actor: wrong summary'); + } + x.summary = truncate(x.summary, summaryLength); + } + + const idHost = this.utilityService.toPuny(new URL(x.id!).hostname); + if (idHost !== expectHost) { + throw new Error('invalid Actor: id has different host'); + } + + if (x.publicKey) { + if (typeof x.publicKey.id !== 'string') { + throw new Error('invalid Actor: publicKey.id is not a string'); + } + + const publicKeyIdHost = this.utilityService.toPuny(new URL(x.publicKey.id).hostname); + if (publicKeyIdHost !== expectHost) { + throw new Error('invalid Actor: publicKey.id has different host'); + } + } + + return x; + } + + /** + * Personをフェッチします。 + * + * Misskeyに対象のPersonが登録されていればそれを返します。 + */ + public async fetchPerson(uri: string, resolver?: Resolver): Promise { + if (typeof uri !== 'string') throw new Error('uri is not string'); + + const cached = this.userCacheService.uriPersonCache.get(uri); + if (cached) return cached; + + // URIがこのサーバーを指しているならデータベースからフェッチ + if (uri.startsWith(this.config.url + '/')) { + const id = uri.split('/').pop(); + const u = await this.usersRepository.findOneBy({ id }); + if (u) this.userCacheService.uriPersonCache.set(uri, u); + return u; + } + + //#region このサーバーに既に登録されていたらそれを返す + const exist = await this.usersRepository.findOneBy({ uri }); + + if (exist) { + this.userCacheService.uriPersonCache.set(uri, exist); + return exist; + } + //#endregion + + return null; + } + + /** + * Personを作成します。 + */ + public async createPerson(uri: string, resolver?: Resolver): Promise { + if (typeof uri !== 'string') throw new Error('uri is not string'); + + if (uri.startsWith(this.config.url)) { + throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user'); + } + + if (resolver == null) resolver = this.apResolverService.createResolver(); + + const object = await resolver.resolve(uri) as any; + + const person = this.validateActor(object, uri); + + this.logger.info(`Creating the Person: ${person.id}`); + + const host = this.utilityService.toPuny(new URL(object.id).hostname); + + const { fields } = this.analyzeAttachments(person.attachment ?? []); + + const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32); + + const isBot = getApType(object) === 'Service'; + + const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); + + // Create user + let user: IRemoteUser; + try { + // Start transaction + await this.db.transaction(async transactionalEntityManager => { + user = await transactionalEntityManager.save(new User({ + id: this.idService.genId(), + avatarId: null, + bannerId: null, + createdAt: new Date(), + lastFetchedAt: new Date(), + name: truncate(person.name, nameLength), + isLocked: !!person.manuallyApprovesFollowers, + isExplorable: !!person.discoverable, + username: person.preferredUsername, + usernameLower: person.preferredUsername!.toLowerCase(), + host, + inbox: person.inbox, + sharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), + followersUri: person.followers ? getApId(person.followers) : undefined, + featured: person.featured ? getApId(person.featured) : undefined, + uri: person.id, + tags, + isBot, + isCat: (person as any).isCat === true, + showTimelineReplies: false, + })) as IRemoteUser; + + await transactionalEntityManager.save(new UserProfile({ + userId: user.id, + description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, + url: getOneApHrefNullable(person.url), + fields, + birthday: bday ? bday[0] : null, + location: person['vcard:Address'] ?? null, + userHost: host, + })); + + if (person.publicKey) { + await transactionalEntityManager.save(new UserPublickey({ + userId: user.id, + keyId: person.publicKey.id, + keyPem: person.publicKey.publicKeyPem, + })); + } + }); + } catch (e) { + // duplicate key error + if (isDuplicateKeyValueError(e)) { + // /users/@a => /users/:id のように入力がaliasなときにエラーになることがあるのを対応 + const u = await this.usersRepository.findOneBy({ + uri: person.id, + }); + + if (u) { + user = u as IRemoteUser; + } else { + throw new Error('already registered'); + } + } else { + this.logger.error(e instanceof Error ? e : new Error(e as string)); + throw e; + } + } + + // Register host + this.federatedInstanceService.registerOrFetchInstanceDoc(host).then(i => { + this.instancesRepository.increment({ id: i.id }, 'usersCount', 1); + this.instanceChart.newUser(i.host); + this.fetchInstanceMetadataService.fetchInstanceMetadata(i); + }); + + this.usersChart.update(user!, true); + + // ハッシュタグ更新 + this.hashtagService.updateUsertags(user!, tags); + + //#region アバターとヘッダー画像をフェッチ + const [avatar, banner] = await Promise.all([ + person.icon, + person.image, + ].map(img => + img == null + ? Promise.resolve(null) + : this.apImageService.resolveImage(user!, img).catch(() => null), + )); + + const avatarId = avatar ? avatar.id : null; + const bannerId = banner ? banner.id : null; + + await this.usersRepository.update(user!.id, { + avatarId, + bannerId, + }); + + user!.avatarId = avatarId; + user!.bannerId = bannerId; + //#endregion + + //#region カスタム絵文字取得 + const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host).catch(err => { + this.logger.info(`extractEmojis: ${err}`); + return [] as Emoji[]; + }); + + const emojiNames = emojis.map(emoji => emoji.name); + + await this.usersRepository.update(user!.id, { + emojis: emojiNames, + }); + //#endregion + + await this.updateFeatured(user!.id, resolver).catch(err => this.logger.error(err)); + + return user!; + } + + /** + * Personの情報を更新します。 + * Misskeyに対象のPersonが登録されていなければ無視します。 + * @param uri URI of Person + * @param resolver Resolver + * @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します) + */ + public async updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise { + if (typeof uri !== 'string') throw new Error('uri is not string'); + + // URIがこのサーバーを指しているならスキップ + if (uri.startsWith(this.config.url + '/')) { + return; + } + + //#region このサーバーに既に登録されているか + const exist = await this.usersRepository.findOneBy({ uri }) as IRemoteUser; + + if (exist == null) { + return; + } + //#endregion + + if (resolver == null) resolver = this.apResolverService.createResolver(); + + const object = hint ?? await resolver.resolve(uri); + + const person = this.validateActor(object, uri); + + this.logger.info(`Updating the Person: ${person.id}`); + + // アバターとヘッダー画像をフェッチ + const [avatar, banner] = await Promise.all([ + person.icon, + person.image, + ].map(img => + img == null + ? Promise.resolve(null) + : this.apImageService.resolveImage(exist, img).catch(() => null), + )); + + // カスタム絵文字取得 + const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => { + this.logger.info(`extractEmojis: ${e}`); + return [] as Emoji[]; + }); + + const emojiNames = emojis.map(emoji => emoji.name); + + const { fields } = this.analyzeAttachments(person.attachment ?? []); + + const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32); + + const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); + + const updates = { + lastFetchedAt: new Date(), + inbox: person.inbox, + sharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), + followersUri: person.followers ? getApId(person.followers) : undefined, + featured: person.featured, + emojis: emojiNames, + name: truncate(person.name, nameLength), + tags, + isBot: getApType(object) === 'Service', + isCat: (person as any).isCat === true, + isLocked: !!person.manuallyApprovesFollowers, + isExplorable: !!person.discoverable, + } as Partial; + + if (avatar) { + updates.avatarId = avatar.id; + } + + if (banner) { + updates.bannerId = banner.id; + } + + // Update user + await this.usersRepository.update(exist.id, updates); + + if (person.publicKey) { + await this.userPublickeysRepository.update({ userId: exist.id }, { + keyId: person.publicKey.id, + keyPem: person.publicKey.publicKeyPem, + }); + } + + await this.userProfilesRepository.update({ userId: exist.id }, { + url: getOneApHrefNullable(person.url), + fields, + description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, + birthday: bday ? bday[0] : null, + location: person['vcard:Address'] ?? null, + }); + + this.globalEventService.publishInternalEvent('remoteUserUpdated', { id: exist.id }); + + // ハッシュタグ更新 + this.hashtagService.updateUsertags(exist, tags); + + // 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする + await this.followingsRepository.update({ + followerId: exist.id, + }, { + followerSharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), + }); + + await this.updateFeatured(exist.id, resolver).catch(err => this.logger.error(err)); + } + + /** + * Personを解決します。 + * + * Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ + * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 + */ + public async resolvePerson(uri: string, resolver?: Resolver): Promise { + if (typeof uri !== 'string') throw new Error('uri is not string'); + + //#region このサーバーに既に登録されていたらそれを返す + const exist = await this.fetchPerson(uri); + + if (exist) { + return exist; + } + //#endregion + + // リモートサーバーからフェッチしてきて登録 + if (resolver == null) resolver = this.apResolverService.createResolver(); + return await this.createPerson(uri, resolver); + } + + public analyzeAttachments(attachments: IObject | IObject[] | undefined) { + const fields: { + name: string, + value: string + }[] = []; + const services: { [x: string]: any } = {}; + + if (Array.isArray(attachments)) { + for (const attachment of attachments.filter(isPropertyValue)) { + if (isPropertyValue(attachment.identifier)) { + addService(services, attachment.identifier); + } else { + fields.push({ + name: attachment.name, + value: this.mfmService.fromHtml(attachment.value), + }); + } + } + } + + return { fields, services }; + } + + public async updateFeatured(userId: User['id'], resolver?: Resolver) { + const user = await this.usersRepository.findOneByOrFail({ id: userId }); + if (!this.userEntityService.isRemoteUser(user)) return; + if (!user.featured) return; + + this.logger.info(`Updating the featured: ${user.uri}`); + + if (resolver == null) resolver = this.apResolverService.createResolver(); + + // Resolve to (Ordered)Collection Object + const collection = await resolver.resolveCollection(user.featured); + if (!isCollectionOrOrderedCollection(collection)) throw new Error('Object is not Collection or OrderedCollection'); + + // Resolve to Object(may be Note) arrays + const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems; + const items = await Promise.all(toArray(unresolvedItems).map(x => resolver.resolve(x))); + + // Resolve and regist Notes + const limit = promiseLimit(2); + const featuredNotes = await Promise.all(items + .filter(item => getApType(item) === 'Note') // TODO: Noteでなくてもいいかも + .slice(0, 5) + .map(item => limit(() => this.apNoteService.resolveNote(item, resolver)))); + + await this.db.transaction(async transactionalEntityManager => { + await transactionalEntityManager.delete(UserNotePining, { userId: user.id }); + + // とりあえずidを別の時間で生成して順番を維持 + let td = 0; + for (const note of featuredNotes.filter(note => note != null)) { + td -= 1000; + transactionalEntityManager.insert(UserNotePining, { + id: this.idService.genId(new Date(Date.now() + td)), + createdAt: new Date(), + userId: user.id, + noteId: note!.id, + }); + } + }); + } +} diff --git a/packages/backend/src/core/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/activitypub/models/ApQuestionService.ts new file mode 100644 index 0000000000..5793b98353 --- /dev/null +++ b/packages/backend/src/core/activitypub/models/ApQuestionService.ts @@ -0,0 +1,109 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { NotesRepository, PollsRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { IPoll } from '@/models/entities/Poll.js'; +import type Logger from '@/logger.js'; +import { isQuestion } from '../type.js'; +import { ApLoggerService } from '../ApLoggerService.js'; +import { ApResolverService } from '../ApResolverService.js'; +import type { Resolver } from '../ApResolverService.js'; +import type { IObject, IQuestion } from '../type.js'; + +@Injectable() +export class ApQuestionService { + private logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + private apResolverService: ApResolverService, + private apLoggerService: ApLoggerService, + ) { + this.logger = this.apLoggerService.logger; + } + + public async extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise { + if (resolver == null) resolver = this.apResolverService.createResolver(); + + const question = await resolver.resolve(source); + + if (!isQuestion(question)) { + throw new Error('invalid type'); + } + + const multiple = !question.oneOf; + const expiresAt = question.endTime ? new Date(question.endTime) : question.closed ? new Date(question.closed) : null; + + if (multiple && !question.anyOf) { + throw new Error('invalid question'); + } + + const choices = question[multiple ? 'anyOf' : 'oneOf']! + .map((x, i) => x.name!); + + const votes = question[multiple ? 'anyOf' : 'oneOf']! + .map((x, i) => x.replies && x.replies.totalItems || x._misskey_votes || 0); + + return { + choices, + votes, + multiple, + expiresAt, + }; + } + + /** + * Update votes of Question + * @param uri URI of AP Question object + * @returns true if updated + */ + public async updateQuestion(value: any, resolver?: Resolver) { + const uri = typeof value === 'string' ? value : value.id; + + // URIがこのサーバーを指しているならスキップ + if (uri.startsWith(this.config.url + '/')) throw new Error('uri points local'); + + //#region このサーバーに既に登録されているか + const note = await this.notesRepository.findOneBy({ uri }); + if (note == null) throw new Error('Question is not registed'); + + const poll = await this.pollsRepository.findOneBy({ noteId: note.id }); + if (poll == null) throw new Error('Question is not registed'); + //#endregion + + // resolve new Question object + if (resolver == null) resolver = this.apResolverService.createResolver(); + const question = await resolver.resolve(value) as IQuestion; + this.logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); + + if (question.type !== 'Question') throw new Error('object is not a Question'); + + const apChoices = question.oneOf ?? question.anyOf; + + let changed = false; + + for (const choice of poll.choices) { + const oldCount = poll.votes[poll.choices.indexOf(choice)]; + const newCount = apChoices!.filter(ap => ap.name === choice)[0].replies!.totalItems; + + if (oldCount !== newCount) { + changed = true; + poll.votes[poll.choices.indexOf(choice)] = newCount; + } + } + + await this.pollsRepository.update({ noteId: note.id }, { + votes: poll.votes, + }); + + return changed; + } +} diff --git a/packages/backend/src/core/activitypub/models/icon.ts b/packages/backend/src/core/activitypub/models/icon.ts new file mode 100644 index 0000000000..50794a937d --- /dev/null +++ b/packages/backend/src/core/activitypub/models/icon.ts @@ -0,0 +1,5 @@ +export type IIcon = { + type: string; + mediaType?: string; + url?: string; +}; diff --git a/packages/backend/src/core/activitypub/models/identifier.ts b/packages/backend/src/core/activitypub/models/identifier.ts new file mode 100644 index 0000000000..f6c3bb8c88 --- /dev/null +++ b/packages/backend/src/core/activitypub/models/identifier.ts @@ -0,0 +1,5 @@ +export type IIdentifier = { + type: string; + name: string; + value: string; +}; diff --git a/packages/backend/src/core/activitypub/models/tag.ts b/packages/backend/src/core/activitypub/models/tag.ts new file mode 100644 index 0000000000..803846a0b0 --- /dev/null +++ b/packages/backend/src/core/activitypub/models/tag.ts @@ -0,0 +1,19 @@ +import { toArray } from '@/misc/prelude/array.js'; +import { isHashtag } from '../type.js'; +import type { IObject, IApHashtag } from '../type.js'; + +export function extractApHashtags(tags: IObject | IObject[] | null | undefined) { + if (tags == null) return []; + + const hashtags = extractApHashtagObjects(tags); + + return hashtags.map(tag => { + const m = tag.name.match(/^#(.+)/); + return m ? m[1] : null; + }).filter((x): x is string => x != null); +} + +export function extractApHashtagObjects(tags: IObject | IObject[] | null | undefined): IApHashtag[] { + if (tags == null) return []; + return toArray(tags).filter(isHashtag); +} diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts new file mode 100644 index 0000000000..dcc5110aa5 --- /dev/null +++ b/packages/backend/src/core/activitypub/type.ts @@ -0,0 +1,296 @@ +export type obj = { [x: string]: any }; +export type ApObject = IObject | string | (IObject | string)[]; + +export interface IObject { + '@context': string | string[] | obj | obj[]; + type: string | string[]; + id?: string; + summary?: string; + published?: string; + cc?: ApObject; + to?: ApObject; + attributedTo: ApObject; + attachment?: any[]; + inReplyTo?: any; + replies?: ICollection; + content?: string; + name?: string; + startTime?: Date; + endTime?: Date; + icon?: any; + image?: any; + url?: ApObject; + href?: string; + tag?: IObject | IObject[]; + sensitive?: boolean; +} + +/** + * Get array of ActivityStreams Objects id + */ +export function getApIds(value: ApObject | undefined): string[] { + if (value == null) return []; + const array = Array.isArray(value) ? value : [value]; + return array.map(x => getApId(x)); +} + +/** + * Get first ActivityStreams Object id + */ +export function getOneApId(value: ApObject): string { + const firstOne = Array.isArray(value) ? value[0] : value; + return getApId(firstOne); +} + +/** + * Get ActivityStreams Object id + */ +export function getApId(value: string | IObject): string { + if (typeof value === 'string') return value; + if (typeof value.id === 'string') return value.id; + throw new Error('cannot detemine id'); +} + +/** + * Get ActivityStreams Object type + */ +export function getApType(value: IObject): string { + if (typeof value.type === 'string') return value.type; + if (Array.isArray(value.type) && typeof value.type[0] === 'string') return value.type[0]; + throw new Error('cannot detect type'); +} + +export function getOneApHrefNullable(value: ApObject | undefined): string | undefined { + const firstOne = Array.isArray(value) ? value[0] : value; + return getApHrefNullable(firstOne); +} + +export function getApHrefNullable(value: string | IObject | undefined): string | undefined { + if (typeof value === 'string') return value; + if (typeof value?.href === 'string') return value.href; + return undefined; +} + +export interface IActivity extends IObject { + //type: 'Activity'; + actor: IObject | string; + object: IObject | string; + target?: IObject | string; + /** LD-Signature */ + signature?: { + type: string; + created: Date; + creator: string; + domain?: string; + nonce?: string; + signatureValue: string; + }; +} + +export interface ICollection extends IObject { + type: 'Collection'; + totalItems: number; + items: ApObject; +} + +export interface IOrderedCollection extends IObject { + type: 'OrderedCollection'; + totalItems: number; + orderedItems: ApObject; +} + +export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video', 'Event']; + +export const isPost = (object: IObject): object is IPost => + validPost.includes(getApType(object)); + +export interface IPost extends IObject { + type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video' | 'Event'; + source?: { + content: string; + mediaType: string; + }; + _misskey_quote?: string; + _misskey_content?: string; + quoteUrl?: string; + _misskey_talk?: boolean; +} + +export interface IQuestion extends IObject { + type: 'Note' | 'Question'; + source?: { + content: string; + mediaType: string; + }; + _misskey_quote?: string; + quoteUrl?: string; + oneOf?: IQuestionChoice[]; + anyOf?: IQuestionChoice[]; + endTime?: Date; + closed?: Date; +} + +export const isQuestion = (object: IObject): object is IQuestion => + getApType(object) === 'Note' || getApType(object) === 'Question'; + +interface IQuestionChoice { + name?: string; + replies?: ICollection; + _misskey_votes?: number; +} +export interface ITombstone extends IObject { + type: 'Tombstone'; + formerType?: string; + deleted?: Date; +} + +export const isTombstone = (object: IObject): object is ITombstone => + getApType(object) === 'Tombstone'; + +export const validActor = ['Person', 'Service', 'Group', 'Organization', 'Application']; + +export const isActor = (object: IObject): object is IActor => + validActor.includes(getApType(object)); + +export interface IActor extends IObject { + type: 'Person' | 'Service' | 'Organization' | 'Group' | 'Application'; + name?: string; + preferredUsername?: string; + manuallyApprovesFollowers?: boolean; + discoverable?: boolean; + inbox: string; + sharedInbox?: string; // 後方互換性のため + publicKey?: { + id: string; + publicKeyPem: string; + }; + followers?: string | ICollection | IOrderedCollection; + following?: string | ICollection | IOrderedCollection; + featured?: string | IOrderedCollection; + outbox: string | IOrderedCollection; + endpoints?: { + sharedInbox?: string; + }; + 'vcard:bday'?: string; + 'vcard:Address'?: string; +} + +export const isCollection = (object: IObject): object is ICollection => + getApType(object) === 'Collection'; + +export const isOrderedCollection = (object: IObject): object is IOrderedCollection => + getApType(object) === 'OrderedCollection'; + +export const isCollectionOrOrderedCollection = (object: IObject): object is ICollection | IOrderedCollection => + isCollection(object) || isOrderedCollection(object); + +export interface IApPropertyValue extends IObject { + type: 'PropertyValue'; + identifier: IApPropertyValue; + name: string; + value: string; +} + +export const isPropertyValue = (object: IObject): object is IApPropertyValue => + object && + getApType(object) === 'PropertyValue' && + typeof object.name === 'string' && + typeof (object as any).value === 'string'; + +export interface IApMention extends IObject { + type: 'Mention'; + href: string; +} + +export const isMention = (object: IObject): object is IApMention => + getApType(object) === 'Mention' && + typeof object.href === 'string'; + +export interface IApHashtag extends IObject { + type: 'Hashtag'; + name: string; +} + +export const isHashtag = (object: IObject): object is IApHashtag => + getApType(object) === 'Hashtag' && + typeof object.name === 'string'; + +export interface IApEmoji extends IObject { + type: 'Emoji'; + updated: Date; +} + +export const isEmoji = (object: IObject): object is IApEmoji => + getApType(object) === 'Emoji' && !Array.isArray(object.icon) && object.icon.url != null; + +export interface ICreate extends IActivity { + type: 'Create'; +} + +export interface IDelete extends IActivity { + type: 'Delete'; +} + +export interface IUpdate extends IActivity { + type: 'Update'; +} + +export interface IRead extends IActivity { + type: 'Read'; +} + +export interface IUndo extends IActivity { + type: 'Undo'; +} + +export interface IFollow extends IActivity { + type: 'Follow'; +} + +export interface IAccept extends IActivity { + type: 'Accept'; +} + +export interface IReject extends IActivity { + type: 'Reject'; +} + +export interface IAdd extends IActivity { + type: 'Add'; +} + +export interface IRemove extends IActivity { + type: 'Remove'; +} + +export interface ILike extends IActivity { + type: 'Like' | 'EmojiReaction' | 'EmojiReact'; + _misskey_reaction?: string; +} + +export interface IAnnounce extends IActivity { + type: 'Announce'; +} + +export interface IBlock extends IActivity { + type: 'Block'; +} + +export interface IFlag extends IActivity { + type: 'Flag'; +} + +export const isCreate = (object: IObject): object is ICreate => getApType(object) === 'Create'; +export const isDelete = (object: IObject): object is IDelete => getApType(object) === 'Delete'; +export const isUpdate = (object: IObject): object is IUpdate => getApType(object) === 'Update'; +export const isRead = (object: IObject): object is IRead => getApType(object) === 'Read'; +export const isUndo = (object: IObject): object is IUndo => getApType(object) === 'Undo'; +export const isFollow = (object: IObject): object is IFollow => getApType(object) === 'Follow'; +export const isAccept = (object: IObject): object is IAccept => getApType(object) === 'Accept'; +export const isReject = (object: IObject): object is IReject => getApType(object) === 'Reject'; +export const isAdd = (object: IObject): object is IAdd => getApType(object) === 'Add'; +export const isRemove = (object: IObject): object is IRemove => getApType(object) === 'Remove'; +export const isLike = (object: IObject): object is ILike => getApType(object) === 'Like' || getApType(object) === 'EmojiReaction' || getApType(object) === 'EmojiReact'; +export const isAnnounce = (object: IObject): object is IAnnounce => getApType(object) === 'Announce'; +export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block'; +export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag'; diff --git a/packages/backend/src/core/chart/charts/active-users.ts b/packages/backend/src/core/chart/charts/active-users.ts index 40c60910ea..6683d76587 100644 --- a/packages/backend/src/core/chart/charts/active-users.ts +++ b/packages/backend/src/core/chart/charts/active-users.ts @@ -5,7 +5,7 @@ import type { User } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/active-users.js'; +import { name, schema } from '@/core/entities/active-users.js'; import type { KVs } from '../core.js'; const week = 1000 * 60 * 60 * 24 * 7; diff --git a/packages/backend/src/core/chart/charts/ap-request.ts b/packages/backend/src/core/chart/charts/ap-request.ts index 4b91fbbf18..1de21a6a16 100644 --- a/packages/backend/src/core/chart/charts/ap-request.ts +++ b/packages/backend/src/core/chart/charts/ap-request.ts @@ -4,7 +4,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/ap-request.js'; +import { name, schema } from '@/core/entities/ap-request.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/drive.ts b/packages/backend/src/core/chart/charts/drive.ts index 494dfbbe57..638e31ac8d 100644 --- a/packages/backend/src/core/chart/charts/drive.ts +++ b/packages/backend/src/core/chart/charts/drive.ts @@ -5,7 +5,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/drive.js'; +import { name, schema } from '@/core/entities/drive.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts index 21e4cedea3..75a565cebc 100644 --- a/packages/backend/src/core/chart/charts/federation.ts +++ b/packages/backend/src/core/chart/charts/federation.ts @@ -6,7 +6,7 @@ import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/federation.js'; +import { name, schema } from '@/core/entities/federation.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/hashtag.ts b/packages/backend/src/core/chart/charts/hashtag.ts index 8b8c795cfd..ff83b8aa5d 100644 --- a/packages/backend/src/core/chart/charts/hashtag.ts +++ b/packages/backend/src/core/chart/charts/hashtag.ts @@ -6,7 +6,7 @@ import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/hashtag.js'; +import { name, schema } from '@/core/entities/hashtag.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/instance.ts b/packages/backend/src/core/chart/charts/instance.ts index 2e0f4c7126..41a35a2123 100644 --- a/packages/backend/src/core/chart/charts/instance.ts +++ b/packages/backend/src/core/chart/charts/instance.ts @@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js'; import { UtilityService } from '@/core/UtilityService.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/instance.js'; +import { name, schema } from '@/core/entities/instance.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/notes.ts b/packages/backend/src/core/chart/charts/notes.ts index 2153cfe4b4..083b0d5519 100644 --- a/packages/backend/src/core/chart/charts/notes.ts +++ b/packages/backend/src/core/chart/charts/notes.ts @@ -6,7 +6,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/notes.js'; +import { name, schema } from '@/core/entities/notes.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/per-user-drive.ts b/packages/backend/src/core/chart/charts/per-user-drive.ts index a44460bb4e..9b2e2d3b5a 100644 --- a/packages/backend/src/core/chart/charts/per-user-drive.ts +++ b/packages/backend/src/core/chart/charts/per-user-drive.ts @@ -7,7 +7,7 @@ import { DI } from '@/di-symbols.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/per-user-drive.js'; +import { name, schema } from '@/core/entities/per-user-drive.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/per-user-following.ts b/packages/backend/src/core/chart/charts/per-user-following.ts index 5ea08a0872..6bd6f1a7dc 100644 --- a/packages/backend/src/core/chart/charts/per-user-following.ts +++ b/packages/backend/src/core/chart/charts/per-user-following.ts @@ -7,7 +7,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { FollowingsRepository } from '@/models/index.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/per-user-following.js'; +import { name, schema } from '@/core/entities/per-user-following.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/per-user-notes.ts b/packages/backend/src/core/chart/charts/per-user-notes.ts index 5c14309d89..53bacd434a 100644 --- a/packages/backend/src/core/chart/charts/per-user-notes.ts +++ b/packages/backend/src/core/chart/charts/per-user-notes.ts @@ -7,7 +7,7 @@ import { DI } from '@/di-symbols.js'; import type { NotesRepository } from '@/models/index.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/per-user-notes.js'; +import { name, schema } from '@/core/entities/per-user-notes.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/per-user-reactions.ts b/packages/backend/src/core/chart/charts/per-user-reactions.ts index 4160219720..78a7be0383 100644 --- a/packages/backend/src/core/chart/charts/per-user-reactions.ts +++ b/packages/backend/src/core/chart/charts/per-user-reactions.ts @@ -7,7 +7,7 @@ import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/per-user-reactions.js'; +import { name, schema } from '@/core/entities/per-user-reactions.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/test-grouped.ts b/packages/backend/src/core/chart/charts/test-grouped.ts index bc215f3942..95585ec93e 100644 --- a/packages/backend/src/core/chart/charts/test-grouped.ts +++ b/packages/backend/src/core/chart/charts/test-grouped.ts @@ -4,7 +4,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; import Chart from '../core.js'; -import { name, schema } from './entities/test-grouped.js'; +import { name, schema } from '@/core/entities/test-grouped.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/test-intersection.ts b/packages/backend/src/core/chart/charts/test-intersection.ts index a074a7dded..c404a211a5 100644 --- a/packages/backend/src/core/chart/charts/test-intersection.ts +++ b/packages/backend/src/core/chart/charts/test-intersection.ts @@ -4,7 +4,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; import Chart from '../core.js'; -import { name, schema } from './entities/test-intersection.js'; +import { name, schema } from '@/core/entities/test-intersection.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/test-unique.ts b/packages/backend/src/core/chart/charts/test-unique.ts index 4d3e2f2403..5430db852b 100644 --- a/packages/backend/src/core/chart/charts/test-unique.ts +++ b/packages/backend/src/core/chart/charts/test-unique.ts @@ -4,7 +4,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; import Chart from '../core.js'; -import { name, schema } from './entities/test-unique.js'; +import { name, schema } from '@/core/entities/test-unique.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/test.ts b/packages/backend/src/core/chart/charts/test.ts index 72caf79e0f..7510b533c2 100644 --- a/packages/backend/src/core/chart/charts/test.ts +++ b/packages/backend/src/core/chart/charts/test.ts @@ -4,7 +4,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; import Chart from '../core.js'; -import { name, schema } from './entities/test.js'; +import { name, schema } from '@/core/entities/test.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/chart/charts/users.ts b/packages/backend/src/core/chart/charts/users.ts index f0359968eb..0731617354 100644 --- a/packages/backend/src/core/chart/charts/users.ts +++ b/packages/backend/src/core/chart/charts/users.ts @@ -7,7 +7,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { UsersRepository } from '@/models/index.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; -import { name, schema } from './entities/users.js'; +import { name, schema } from '@/core/entities/users.js'; import type { KVs } from '../core.js'; /** diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index c54285d9df..ebf6116f27 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -6,7 +6,7 @@ import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { Instance } from '@/models/entities/Instance.js'; -import { MetaService } from '../MetaService.js'; +import { MetaService } from '.@/core/MetaService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() diff --git a/packages/backend/src/core/queue/QueueModule.ts b/packages/backend/src/core/queue/QueueModule.ts deleted file mode 100644 index 3a271ea37f..0000000000 --- a/packages/backend/src/core/queue/QueueModule.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { Module } from '@nestjs/common'; -import Bull from 'bull'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import type { Provider } from '@nestjs/common'; -import type { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData, WebhookDeliverJobData } from '../../queue/types.js'; - -function q(config: Config, name: string, limitPerSec = -1) { - return new Bull(name, { - redis: { - port: config.redis.port, - host: config.redis.host, - family: config.redis.family == null ? 0 : config.redis.family, - password: config.redis.pass, - db: config.redis.db ?? 0, - }, - prefix: config.redis.prefix ? `${config.redis.prefix}:queue` : 'queue', - limiter: limitPerSec > 0 ? { - max: limitPerSec, - duration: 1000, - } : undefined, - settings: { - backoffStrategies: { - apBackoff, - }, - }, - }); -} - -// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019 -function apBackoff(attemptsMade: number, err: Error) { - const baseDelay = 60 * 1000; // 1min - const maxBackoff = 8 * 60 * 60 * 1000; // 8hours - let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay; - backoff = Math.min(backoff, maxBackoff); - backoff += Math.round(backoff * Math.random() * 0.2); - return backoff; -} - -export type SystemQueue = Bull.Queue>; -export type EndedPollNotificationQueue = Bull.Queue; -export type DeliverQueue = Bull.Queue; -export type InboxQueue = Bull.Queue; -export type DbQueue = Bull.Queue; -export type ObjectStorageQueue = Bull.Queue; -export type WebhookDeliverQueue = Bull.Queue; - -const $system: Provider = { - provide: 'queue:system', - useFactory: (config: Config) => q(config, 'system'), - inject: [DI.config], -}; - -const $endedPollNotification: Provider = { - provide: 'queue:endedPollNotification', - useFactory: (config: Config) => q(config, 'endedPollNotification'), - inject: [DI.config], -}; - -const $deliver: Provider = { - provide: 'queue:deliver', - useFactory: (config: Config) => q(config, 'deliver', config.deliverJobPerSec ?? 128), - inject: [DI.config], -}; - -const $inbox: Provider = { - provide: 'queue:inbox', - useFactory: (config: Config) => q(config, 'inbox', config.inboxJobPerSec ?? 16), - inject: [DI.config], -}; - -const $db: Provider = { - provide: 'queue:db', - useFactory: (config: Config) => q(config, 'db'), - inject: [DI.config], -}; - -const $objectStorage: Provider = { - provide: 'queue:objectStorage', - useFactory: (config: Config) => q(config, 'objectStorage'), - inject: [DI.config], -}; - -const $webhookDeliver: Provider = { - provide: 'queue:webhookDeliver', - useFactory: (config: Config) => q(config, 'webhookDeliver', 64), - inject: [DI.config], -}; - -@Module({ - imports: [ - ], - providers: [ - $system, - $endedPollNotification, - $deliver, - $inbox, - $db, - $objectStorage, - $webhookDeliver, - ], - exports: [ - $system, - $endedPollNotification, - $deliver, - $inbox, - $db, - $objectStorage, - $webhookDeliver, - ], -}) -export class QueueModule {} diff --git a/packages/backend/src/core/remote/RemoteLoggerService.ts b/packages/backend/src/core/remote/RemoteLoggerService.ts deleted file mode 100644 index 68246466c8..0000000000 --- a/packages/backend/src/core/remote/RemoteLoggerService.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import type Logger from '@/logger.js'; -import { LoggerService } from '@/core/LoggerService.js'; - -@Injectable() -export class RemoteLoggerService { - public logger: Logger; - - constructor( - private loggerService: LoggerService, - ) { - this.logger = this.loggerService.getLogger('remote', 'cyan'); - } -} diff --git a/packages/backend/src/core/remote/ResolveUserService.ts b/packages/backend/src/core/remote/ResolveUserService.ts deleted file mode 100644 index 2fd9e7c378..0000000000 --- a/packages/backend/src/core/remote/ResolveUserService.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { URL } from 'node:url'; -import { Inject, Injectable } from '@nestjs/common'; -import chalk from 'chalk'; -import { IsNull } from 'typeorm'; -import { DI } from '@/di-symbols.js'; -import type { UsersRepository } from '@/models/index.js'; -import type { IRemoteUser, User } from '@/models/entities/User.js'; -import type { Config } from '@/config.js'; -import type Logger from '@/logger.js'; -import { UtilityService } from '../UtilityService.js'; -import { WebfingerService } from './WebfingerService.js'; -import { RemoteLoggerService } from './RemoteLoggerService.js'; -import { ApPersonService } from './activitypub/models/ApPersonService.js'; - -@Injectable() -export class ResolveUserService { - private logger: Logger; - - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - private utilityService: UtilityService, - private webfingerService: WebfingerService, - private remoteLoggerService: RemoteLoggerService, - private apPersonService: ApPersonService, - ) { - this.logger = this.remoteLoggerService.logger.createSubLogger('resolve-user'); - } - - public async resolveUser(username: string, host: string | null): Promise { - const usernameLower = username.toLowerCase(); - - if (host == null) { - this.logger.info(`return local user: ${usernameLower}`); - return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => { - if (u == null) { - throw new Error('user not found'); - } else { - return u; - } - }); - } - - host = this.utilityService.toPuny(host); - - if (this.config.host === host) { - this.logger.info(`return local user: ${usernameLower}`); - return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => { - if (u == null) { - throw new Error('user not found'); - } else { - return u; - } - }); - } - - const user = await this.usersRepository.findOneBy({ usernameLower, host }) as IRemoteUser | null; - - const acctLower = `${usernameLower}@${host}`; - - if (user == null) { - const self = await this.resolveSelf(acctLower); - - this.logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`); - return await this.apPersonService.createPerson(self.href); - } - - // ユーザー情報が古い場合は、WebFilgerからやりなおして返す - if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { - // 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する - await this.usersRepository.update(user.id, { - lastFetchedAt: new Date(), - }); - - this.logger.info(`try resync: ${acctLower}`); - const self = await this.resolveSelf(acctLower); - - if (user.uri !== self.href) { - // if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping. - this.logger.info(`uri missmatch: ${acctLower}`); - this.logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`); - - // validate uri - const uri = new URL(self.href); - if (uri.hostname !== host) { - throw new Error('Invalid uri'); - } - - await this.usersRepository.update({ - usernameLower, - host: host, - }, { - uri: self.href, - }); - } else { - this.logger.info(`uri is fine: ${acctLower}`); - } - - await this.apPersonService.updatePerson(self.href); - - this.logger.info(`return resynced remote user: ${acctLower}`); - return await this.usersRepository.findOneBy({ uri: self.href }).then(u => { - if (u == null) { - throw new Error('user not found'); - } else { - return u; - } - }); - } - - this.logger.info(`return existing remote user: ${acctLower}`); - return user; - } - - private async resolveSelf(acctLower: string) { - this.logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); - const finger = await this.webfingerService.webfinger(acctLower).catch(err => { - this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`); - throw new Error(`Failed to WebFinger for ${acctLower}: ${ err.statusCode ?? err.message }`); - }); - const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self'); - if (!self) { - this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`); - throw new Error('self link not found'); - } - return self; - } -} diff --git a/packages/backend/src/core/remote/WebfingerService.ts b/packages/backend/src/core/remote/WebfingerService.ts deleted file mode 100644 index d2a88be583..0000000000 --- a/packages/backend/src/core/remote/WebfingerService.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { URL } from 'node:url'; -import { Inject, Injectable } from '@nestjs/common'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import { query as urlQuery } from '@/misc/prelude/url.js'; -import { HttpRequestService } from '@/core/HttpRequestService.js'; - -type ILink = { - href: string; - rel?: string; -}; - -type IWebFinger = { - links: ILink[]; - subject: string; -}; - -@Injectable() -export class WebfingerService { - constructor( - @Inject(DI.config) - private config: Config, - - private httpRequestService: HttpRequestService, - ) { - } - - public async webfinger(query: string): Promise { - const url = this.genUrl(query); - - return await this.httpRequestService.getJson(url, 'application/jrd+json, application/json') as IWebFinger; - } - - private genUrl(query: string): string { - if (query.match(/^https?:\/\//)) { - const u = new URL(query); - return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query }); - } - - const m = query.match(/^([^@]+)@(.*)/); - if (m) { - const hostname = m[2]; - return `https://${hostname}/.well-known/webfinger?` + urlQuery({ resource: `acct:${query}` }); - } - - throw new Error(`Invalid query (${query})`); - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApAudienceService.ts b/packages/backend/src/core/remote/activitypub/ApAudienceService.ts deleted file mode 100644 index 744017aa3a..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApAudienceService.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { In } from 'typeorm'; -import promiseLimit from 'promise-limit'; -import { DI } from '@/di-symbols.js'; -import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js'; -import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js'; -import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; -import { ApPersonService } from './models/ApPersonService.js'; -import type { ApObject } from './type.js'; -import type { Resolver } from './ApResolverService.js'; - -type Visibility = 'public' | 'home' | 'followers' | 'specified'; - -type AudienceInfo = { - visibility: Visibility, - mentionedUsers: CacheableUser[], - visibleUsers: CacheableUser[], -}; - -@Injectable() -export class ApAudienceService { - constructor( - private apPersonService: ApPersonService, - ) { - } - - public async parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { - const toGroups = this.groupingAudience(getApIds(to), actor); - const ccGroups = this.groupingAudience(getApIds(cc), actor); - - const others = unique(concat([toGroups.other, ccGroups.other])); - - const limit = promiseLimit(2); - const mentionedUsers = (await Promise.all( - others.map(id => limit(() => this.apPersonService.resolvePerson(id, resolver).catch(() => null))), - )).filter((x): x is CacheableUser => x != null); - - if (toGroups.public.length > 0) { - return { - visibility: 'public', - mentionedUsers, - visibleUsers: [], - }; - } - - if (ccGroups.public.length > 0) { - return { - visibility: 'home', - mentionedUsers, - visibleUsers: [], - }; - } - - if (toGroups.followers.length > 0) { - return { - visibility: 'followers', - mentionedUsers, - visibleUsers: [], - }; - } - - return { - visibility: 'specified', - mentionedUsers, - visibleUsers: mentionedUsers, - }; - } - - private groupingAudience(ids: string[], actor: CacheableRemoteUser) { - const groups = { - public: [] as string[], - followers: [] as string[], - other: [] as string[], - }; - - for (const id of ids) { - if (this.isPublic(id)) { - groups.public.push(id); - } else if (this.isFollowers(id, actor)) { - groups.followers.push(id); - } else { - groups.other.push(id); - } - } - - groups.other = unique(groups.other); - - return groups; - } - - private isPublic(id: string) { - return [ - 'https://www.w3.org/ns/activitystreams#Public', - 'as#Public', - 'Public', - ].includes(id); - } - - private isFollowers(id: string, actor: CacheableRemoteUser) { - return ( - id === (actor.followersUri ?? `${actor.uri}/followers`) - ); - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts b/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts deleted file mode 100644 index 77d200c3c8..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApDbResolverService.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import escapeRegexp from 'escape-regexp'; -import { DI } from '@/di-symbols.js'; -import type { MessagingMessagesRepository, NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js'; -import { Cache } from '@/misc/cache.js'; -import type { UserPublickey } from '@/models/entities/UserPublickey.js'; -import { UserCacheService } from '@/core/UserCacheService.js'; -import type { Note } from '@/models/entities/Note.js'; -import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; -import { getApId } from './type.js'; -import { ApPersonService } from './models/ApPersonService.js'; -import type { IObject } from './type.js'; - -export type UriParseResult = { - /** wether the URI was generated by us */ - local: true; - /** id in DB */ - id: string; - /** hint of type, e.g. "notes", "users" */ - type: string; - /** any remaining text after type and id, not including the slash after id. undefined if empty */ - rest?: string; -} | { - /** wether the URI was generated by us */ - local: false; - /** uri in DB */ - uri: string; -}; - -@Injectable() -export class ApDbResolverService { - private publicKeyCache: Cache; - private publicKeyByUserIdCache: Cache; - - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.userPublickeysRepository) - private userPublickeysRepository: UserPublickeysRepository, - - private userCacheService: UserCacheService, - private apPersonService: ApPersonService, - ) { - this.publicKeyCache = new Cache(Infinity); - this.publicKeyByUserIdCache = new Cache(Infinity); - } - - public parseUri(value: string | IObject): UriParseResult { - const uri = getApId(value); - - // the host part of a URL is case insensitive, so use the 'i' flag. - const localRegex = new RegExp('^' + escapeRegexp(this.config.url) + '/(\\w+)/(\\w+)(?:\/(.+))?', 'i'); - const matchLocal = uri.match(localRegex); - - if (matchLocal) { - return { - local: true, - type: matchLocal[1], - id: matchLocal[2], - rest: matchLocal[3], - }; - } else { - return { - local: false, - uri, - }; - } - } - - /** - * AP Note => Misskey Note in DB - */ - public async getNoteFromApId(value: string | IObject): Promise { - const parsed = this.parseUri(value); - - if (parsed.local) { - if (parsed.type !== 'notes') return null; - - return await this.notesRepository.findOneBy({ - id: parsed.id, - }); - } else { - return await this.notesRepository.findOneBy({ - uri: parsed.uri, - }); - } - } - - public async getMessageFromApId(value: string | IObject): Promise { - const parsed = this.parseUri(value); - - if (parsed.local) { - if (parsed.type !== 'notes') return null; - - return await this.messagingMessagesRepository.findOneBy({ - id: parsed.id, - }); - } else { - return await this.messagingMessagesRepository.findOneBy({ - uri: parsed.uri, - }); - } - } - - /** - * AP Person => Misskey User in DB - */ - public async getUserFromApId(value: string | IObject): Promise { - const parsed = this.parseUri(value); - - if (parsed.local) { - if (parsed.type !== 'users') return null; - - return await this.userCacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({ - id: parsed.id, - }).then(x => x ?? undefined)) ?? null; - } else { - return await this.userCacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({ - uri: parsed.uri, - })); - } - } - - /** - * AP KeyId => Misskey User and Key - */ - public async getAuthUserFromKeyId(keyId: string): Promise<{ - user: CacheableRemoteUser; - key: UserPublickey; - } | null> { - const key = await this.publicKeyCache.fetch(keyId, async () => { - const key = await this.userPublickeysRepository.findOneBy({ - keyId, - }); - - if (key == null) return null; - - return key; - }, key => key != null); - - if (key == null) return null; - - return { - user: await this.userCacheService.userByIdCache.fetch(key.userId, () => this.usersRepository.findOneByOrFail({ id: key.userId })) as CacheableRemoteUser, - key, - }; - } - - /** - * AP Actor id => Misskey User and Key - */ - public async getAuthUserFromApId(uri: string): Promise<{ - user: CacheableRemoteUser; - key: UserPublickey | null; - } | null> { - const user = await this.apPersonService.resolvePerson(uri) as CacheableRemoteUser; - - if (user == null) return null; - - const key = await this.publicKeyByUserIdCache.fetch(user.id, () => this.userPublickeysRepository.findOneBy({ userId: user.id }), v => v != null); - - return { - user, - key, - }; - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts deleted file mode 100644 index 6fc75a0397..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApDeliverManagerService.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { IsNull, Not } from 'typeorm'; -import { DI } from '@/di-symbols.js'; -import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; -import { QueueService } from '@/core/QueueService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; - -interface IRecipe { - type: string; -} - -interface IFollowersRecipe extends IRecipe { - type: 'Followers'; -} - -interface IDirectRecipe extends IRecipe { - type: 'Direct'; - to: IRemoteUser; -} - -const isFollowers = (recipe: any): recipe is IFollowersRecipe => - recipe.type === 'Followers'; - -const isDirect = (recipe: any): recipe is IDirectRecipe => - recipe.type === 'Direct'; - -@Injectable() -export class ApDeliverManagerService { - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.followingsRepository) - private followingsRepository: FollowingsRepository, - - private userEntityService: UserEntityService, - private queueService: QueueService, - ) { - } - - /** - * Deliver activity to followers - * @param activity Activity - * @param from Followee - */ - public async deliverToFollowers(actor: { id: ILocalUser['id']; host: null; }, activity: any) { - const manager = new DeliverManager( - this.userEntityService, - this.followingsRepository, - this.queueService, - actor, - activity, - ); - manager.addFollowersRecipe(); - await manager.execute(); - } - - /** - * Deliver activity to user - * @param activity Activity - * @param to Target user - */ - public async deliverToUser(actor: { id: ILocalUser['id']; host: null; }, activity: any, to: IRemoteUser) { - const manager = new DeliverManager( - this.userEntityService, - this.followingsRepository, - this.queueService, - actor, - activity, - ); - manager.addDirectRecipe(to); - await manager.execute(); - } - - public createDeliverManager(actor: { id: User['id']; host: null; }, activity: any) { - return new DeliverManager( - this.userEntityService, - this.followingsRepository, - this.queueService, - - actor, - activity, - ); - } -} - -class DeliverManager { - private actor: { id: User['id']; host: null; }; - private activity: any; - private recipes: IRecipe[] = []; - - /** - * Constructor - * @param actor Actor - * @param activity Activity to deliver - */ - constructor( - private userEntityService: UserEntityService, - private followingsRepository: FollowingsRepository, - private queueService: QueueService, - - actor: { id: User['id']; host: null; }, - activity: any, - ) { - this.actor = actor; - this.activity = activity; - } - - /** - * Add recipe for followers deliver - */ - public addFollowersRecipe() { - const deliver = { - type: 'Followers', - } as IFollowersRecipe; - - this.addRecipe(deliver); - } - - /** - * Add recipe for direct deliver - * @param to To - */ - public addDirectRecipe(to: IRemoteUser) { - const recipe = { - type: 'Direct', - to, - } as IDirectRecipe; - - this.addRecipe(recipe); - } - - /** - * Add recipe - * @param recipe Recipe - */ - public addRecipe(recipe: IRecipe) { - this.recipes.push(recipe); - } - - /** - * Execute delivers - */ - public async execute() { - if (!this.userEntityService.isLocalUser(this.actor)) return; - - const inboxes = new Set(); - - /* - build inbox list - - Process follower recipes first to avoid duplication when processing - direct recipes later. - */ - if (this.recipes.some(r => isFollowers(r))) { - // followers deliver - // TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう - // ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう? - const followers = await this.followingsRepository.find({ - where: { - followeeId: this.actor.id, - followerHost: Not(IsNull()), - }, - select: { - followerSharedInbox: true, - followerInbox: true, - }, - }) as { - followerSharedInbox: string | null; - followerInbox: string; - }[]; - - for (const following of followers) { - const inbox = following.followerSharedInbox ?? following.followerInbox; - inboxes.add(inbox); - } - } - - this.recipes.filter((recipe): recipe is IDirectRecipe => - // followers recipes have already been processed - isDirect(recipe) - // check that shared inbox has not been added yet - && !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox)) - // check that they actually have an inbox - && recipe.to.inbox != null, - ) - .forEach(recipe => inboxes.add(recipe.to.inbox!)); - - // deliver - for (const inbox of inboxes) { - this.queueService.deliver(this.actor, this.activity, inbox); - } - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApInboxService.ts b/packages/backend/src/core/remote/activitypub/ApInboxService.ts deleted file mode 100644 index 3da384ec2d..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApInboxService.ts +++ /dev/null @@ -1,740 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { In } from 'typeorm'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import type { CacheableRemoteUser } from '@/models/entities/User.js'; -import { UserFollowingService } from '@/core/UserFollowingService.js'; -import { ReactionService } from '@/core/ReactionService.js'; -import { RelayService } from '@/core/RelayService.js'; -import { NotePiningService } from '@/core/NotePiningService.js'; -import { UserBlockingService } from '@/core/UserBlockingService.js'; -import { NoteDeleteService } from '@/core/NoteDeleteService.js'; -import { NoteCreateService } from '@/core/NoteCreateService.js'; -import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js'; -import { AppLockService } from '@/core/AppLockService.js'; -import type Logger from '@/logger.js'; -import { MetaService } from '@/core/MetaService.js'; -import { IdService } from '@/core/IdService.js'; -import { StatusError } from '@/misc/status-error.js'; -import { UtilityService } from '@/core/UtilityService.js'; -import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { QueueService } from '@/core/QueueService.js'; -import { MessagingService } from '@/core/MessagingService.js'; -import type { UsersRepository, NotesRepository, FollowingsRepository, MessagingMessagesRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js'; -import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; -import { ApNoteService } from './models/ApNoteService.js'; -import { ApLoggerService } from './ApLoggerService.js'; -import { ApDbResolverService } from './ApDbResolverService.js'; -import { ApResolverService } from './ApResolverService.js'; -import { ApAudienceService } from './ApAudienceService.js'; -import { ApPersonService } from './models/ApPersonService.js'; -import { ApQuestionService } from './models/ApQuestionService.js'; -import type { Resolver } from './ApResolverService.js'; -import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IRead, IReject, IRemove, IUndo, IUpdate } from './type.js'; - -@Injectable() -export class ApInboxService { - private logger: Logger; - - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.followingsRepository) - private followingsRepository: FollowingsRepository, - - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - - @Inject(DI.abuseUserReportsRepository) - private abuseUserReportsRepository: AbuseUserReportsRepository, - - @Inject(DI.followRequestsRepository) - private followRequestsRepository: FollowRequestsRepository, - - private userEntityService: UserEntityService, - private noteEntityService: NoteEntityService, - private utilityService: UtilityService, - private idService: IdService, - private metaService: MetaService, - private userFollowingService: UserFollowingService, - private apAudienceService: ApAudienceService, - private reactionService: ReactionService, - private relayService: RelayService, - private notePiningService: NotePiningService, - private userBlockingService: UserBlockingService, - private noteCreateService: NoteCreateService, - private noteDeleteService: NoteDeleteService, - private appLockService: AppLockService, - private apResolverService: ApResolverService, - private apDbResolverService: ApDbResolverService, - private apLoggerService: ApLoggerService, - private apNoteService: ApNoteService, - private apPersonService: ApPersonService, - private apQuestionService: ApQuestionService, - private queueService: QueueService, - private messagingService: MessagingService, - ) { - this.logger = this.apLoggerService.logger; - } - - public async performActivity(actor: CacheableRemoteUser, activity: IObject) { - if (isCollectionOrOrderedCollection(activity)) { - const resolver = this.apResolverService.createResolver(); - for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) { - const act = await resolver.resolve(item); - try { - await this.performOneActivity(actor, act); - } catch (err) { - if (err instanceof Error || typeof err === 'string') { - this.logger.error(err); - } - } - } - } else { - await this.performOneActivity(actor, activity); - } - - // ついでにリモートユーザーの情報が古かったら更新しておく - if (actor.uri) { - if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { - setImmediate(() => { - this.apPersonService.updatePerson(actor.uri!); - }); - } - } - } - - public async performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise { - if (actor.isSuspended) return; - - if (isCreate(activity)) { - await this.create(actor, activity); - } else if (isDelete(activity)) { - await this.delete(actor, activity); - } else if (isUpdate(activity)) { - await this.update(actor, activity); - } else if (isRead(activity)) { - await this.read(actor, activity); - } else if (isFollow(activity)) { - await this.follow(actor, activity); - } else if (isAccept(activity)) { - await this.accept(actor, activity); - } else if (isReject(activity)) { - await this.reject(actor, activity); - } else if (isAdd(activity)) { - await this.add(actor, activity).catch(err => this.logger.error(err)); - } else if (isRemove(activity)) { - await this.remove(actor, activity).catch(err => this.logger.error(err)); - } else if (isAnnounce(activity)) { - await this.announce(actor, activity); - } else if (isLike(activity)) { - await this.like(actor, activity); - } else if (isUndo(activity)) { - await this.undo(actor, activity); - } else if (isBlock(activity)) { - await this.block(actor, activity); - } else if (isFlag(activity)) { - await this.flag(actor, activity); - } else { - this.logger.warn(`unrecognized activity type: ${(activity as any).type}`); - } - } - - private async follow(actor: CacheableRemoteUser, activity: IFollow): Promise { - const followee = await this.apDbResolverService.getUserFromApId(activity.object); - - if (followee == null) { - return 'skip: followee not found'; - } - - if (followee.host != null) { - return 'skip: フォローしようとしているユーザーはローカルユーザーではありません'; - } - - await this.userFollowingService.follow(actor, followee, activity.id); - return 'ok'; - } - - private async like(actor: CacheableRemoteUser, activity: ILike): Promise { - const targetUri = getApId(activity.object); - - const note = await this.apNoteService.fetchNote(targetUri); - if (!note) return `skip: target note not found ${targetUri}`; - - await this.apNoteService.extractEmojis(activity.tag ?? [], actor.host).catch(() => null); - - return await this.reactionService.create(actor, note, activity._misskey_reaction ?? activity.content ?? activity.name).catch(err => { - if (err.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') { - return 'skip: already reacted'; - } else { - throw err; - } - }).then(() => 'ok'); - } - - private async read(actor: CacheableRemoteUser, activity: IRead): Promise { - const id = await getApId(activity.object); - - if (!this.utilityService.isSelfHost(this.utilityService.extractDbHost(id))) { - return `skip: Read to foreign host (${id})`; - } - - const messageId = id.split('/').pop(); - - const message = await this.messagingMessagesRepository.findOneBy({ id: messageId }); - if (message == null) { - return 'skip: message not found'; - } - - if (actor.id !== message.recipientId) { - return 'skip: actor is not a message recipient'; - } - - await this.messagingService.readUserMessagingMessage(message.recipientId!, message.userId, [message.id]); - return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`; - } - - private async accept(actor: CacheableRemoteUser, activity: IAccept): Promise { - const uri = activity.id ?? activity; - - this.logger.info(`Accept: ${uri}`); - - const resolver = this.apResolverService.createResolver(); - - const object = await resolver.resolve(activity.object).catch(err => { - this.logger.error(`Resolution failed: ${err}`); - throw err; - }); - - if (isFollow(object)) return await this.acceptFollow(actor, object); - - return `skip: Unknown Accept type: ${getApType(object)}`; - } - - private async acceptFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { - // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある - - const follower = await this.apDbResolverService.getUserFromApId(activity.actor); - - if (follower == null) { - return 'skip: follower not found'; - } - - if (follower.host != null) { - return 'skip: follower is not a local user'; - } - - // relay - const match = activity.id?.match(/follow-relay\/(\w+)/); - if (match) { - return await this.relayService.relayAccepted(match[1]); - } - - await this.userFollowingService.acceptFollowRequest(actor, follower); - return 'ok'; - } - - private async add(actor: CacheableRemoteUser, activity: IAdd): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { - throw new Error('invalid actor'); - } - - if (activity.target == null) { - throw new Error('target is null'); - } - - if (activity.target === actor.featured) { - const note = await this.apNoteService.resolveNote(activity.object); - if (note == null) throw new Error('note not found'); - await this.notePiningService.addPinned(actor, note.id); - return; - } - - throw new Error(`unknown target: ${activity.target}`); - } - - private async announce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { - const uri = getApId(activity); - - this.logger.info(`Announce: ${uri}`); - - const targetUri = getApId(activity.object); - - this.announceNote(actor, activity, targetUri); - } - - private async announceNote(actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { - const uri = getApId(activity); - - if (actor.isSuspended) { - return; - } - - // アナウンス先をブロックしてたら中断 - const meta = await this.metaService.fetch(); - if (meta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) return; - - const unlock = await this.appLockService.getApLock(uri); - - try { - // 既に同じURIを持つものが登録されていないかチェック - const exist = await this.apNoteService.fetchNote(uri); - if (exist) { - return; - } - - // Announce対象をresolve - let renote; - try { - renote = await this.apNoteService.resolveNote(targetUri); - if (renote == null) throw new Error('announce target is null'); - } catch (err) { - // 対象が4xxならスキップ - if (err instanceof StatusError) { - if (err.isClientError) { - this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`); - return; - } - - this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode ?? err}`); - } - throw err; - } - - if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) { - this.logger.warn('skip: invalid actor for this activity'); - return; - } - - this.logger.info(`Creating the (Re)Note: ${uri}`); - - const activityAudience = await this.apAudienceService.parseAudience(actor, activity.to, activity.cc); - - await this.noteCreateService.create(actor, { - createdAt: activity.published ? new Date(activity.published) : null, - renote, - visibility: activityAudience.visibility, - visibleUsers: activityAudience.visibleUsers, - uri, - }); - } finally { - unlock(); - } - } - - private async block(actor: CacheableRemoteUser, activity: IBlock): Promise { - // ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず - - const blockee = await this.apDbResolverService.getUserFromApId(activity.object); - - if (blockee == null) { - return 'skip: blockee not found'; - } - - if (blockee.host != null) { - return 'skip: ブロックしようとしているユーザーはローカルユーザーではありません'; - } - - await this.userBlockingService.block(await this.usersRepository.findOneByOrFail({ id: actor.id }), await this.usersRepository.findOneByOrFail({ id: blockee.id })); - return 'ok'; - } - - private async create(actor: CacheableRemoteUser, activity: ICreate): Promise { - const uri = getApId(activity); - - this.logger.info(`Create: ${uri}`); - - // copy audiences between activity <=> object. - if (typeof activity.object === 'object') { - const to = unique(concat([toArray(activity.to), toArray(activity.object.to)])); - const cc = unique(concat([toArray(activity.cc), toArray(activity.object.cc)])); - - activity.to = to; - activity.cc = cc; - activity.object.to = to; - activity.object.cc = cc; - } - - // If there is no attributedTo, use Activity actor. - if (typeof activity.object === 'object' && !activity.object.attributedTo) { - activity.object.attributedTo = activity.actor; - } - - const resolver = this.apResolverService.createResolver(); - - const object = await resolver.resolve(activity.object).catch(e => { - this.logger.error(`Resolution failed: ${e}`); - throw e; - }); - - if (isPost(object)) { - this.createNote(resolver, actor, object, false, activity); - } else { - this.logger.warn(`Unknown type: ${getApType(object)}`); - } - } - - private async createNote(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { - const uri = getApId(note); - - if (typeof note === 'object') { - if (actor.uri !== note.attributedTo) { - return 'skip: actor.uri !== note.attributedTo'; - } - - if (typeof note.id === 'string') { - if (this.utilityService.extractDbHost(actor.uri) !== this.utilityService.extractDbHost(note.id)) { - return 'skip: host in actor.uri !== note.id'; - } - } - } - - const unlock = await this.appLockService.getApLock(uri); - - try { - const exist = await this.apNoteService.fetchNote(note); - if (exist) return 'skip: note exists'; - - await this.apNoteService.createNote(note, resolver, silent); - return 'ok'; - } catch (err) { - if (err instanceof StatusError && err.isClientError) { - return `skip ${err.statusCode}`; - } else { - throw err; - } - } finally { - unlock(); - } - } - - private async delete(actor: CacheableRemoteUser, activity: IDelete): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { - throw new Error('invalid actor'); - } - - // 削除対象objectのtype - let formerType: string | undefined; - - if (typeof activity.object === 'string') { - // typeが不明だけど、どうせ消えてるのでremote resolveしない - formerType = undefined; - } else { - const object = activity.object as IObject; - if (isTombstone(object)) { - formerType = toSingle(object.formerType); - } else { - formerType = toSingle(object.type); - } - } - - const uri = getApId(activity.object); - - // type不明でもactorとobjectが同じならばそれはPersonに違いない - if (!formerType && actor.uri === uri) { - formerType = 'Person'; - } - - // それでもなかったらおそらくNote - if (!formerType) { - formerType = 'Note'; - } - - if (validPost.includes(formerType)) { - return await this.deleteNote(actor, uri); - } else if (validActor.includes(formerType)) { - return await this.deleteActor(actor, uri); - } else { - return `Unknown type ${formerType}`; - } - } - - private async deleteActor(actor: CacheableRemoteUser, uri: string): Promise { - this.logger.info(`Deleting the Actor: ${uri}`); - - if (actor.uri !== uri) { - return `skip: delete actor ${actor.uri} !== ${uri}`; - } - - const user = await this.usersRepository.findOneByOrFail({ id: actor.id }); - if (user.isDeleted) { - this.logger.info('skip: already deleted'); - } - - const job = await this.queueService.createDeleteAccountJob(actor); - - await this.usersRepository.update(actor.id, { - isDeleted: true, - }); - - return `ok: queued ${job.name} ${job.id}`; - } - - private async deleteNote(actor: CacheableRemoteUser, uri: string): Promise { - this.logger.info(`Deleting the Note: ${uri}`); - - const unlock = await this.appLockService.getApLock(uri); - - try { - const note = await this.apDbResolverService.getNoteFromApId(uri); - - if (note == null) { - const message = await this.apDbResolverService.getMessageFromApId(uri); - if (message == null) return 'message not found'; - - if (message.userId !== actor.id) { - return '投稿を削除しようとしているユーザーは投稿の作成者ではありません'; - } - - await this.messagingService.deleteMessage(message); - - return 'ok: message deleted'; - } - - if (note.userId !== actor.id) { - return '投稿を削除しようとしているユーザーは投稿の作成者ではありません'; - } - - await this.noteDeleteService.delete(actor, note); - return 'ok: note deleted'; - } finally { - unlock(); - } - } - - private async flag(actor: CacheableRemoteUser, activity: IFlag): Promise { - // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので - // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する - const uris = getApIds(activity.object); - - const userIds = uris.filter(uri => uri.startsWith(this.config.url + '/users/')).map(uri => uri.split('/').pop()!); - const users = await this.usersRepository.findBy({ - id: In(userIds), - }); - if (users.length < 1) return 'skip'; - - await this.abuseUserReportsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), - targetUserId: users[0].id, - targetUserHost: users[0].host, - reporterId: actor.id, - reporterHost: actor.host, - comment: `${activity.content}\n${JSON.stringify(uris, null, 2)}`, - }); - - return 'ok'; - } - - private async reject(actor: CacheableRemoteUser, activity: IReject): Promise { - const uri = activity.id ?? activity; - - this.logger.info(`Reject: ${uri}`); - - const resolver = this.apResolverService.createResolver(); - - const object = await resolver.resolve(activity.object).catch(e => { - this.logger.error(`Resolution failed: ${e}`); - throw e; - }); - - if (isFollow(object)) return await this.rejectFollow(actor, object); - - return `skip: Unknown Reject type: ${getApType(object)}`; - } - - private async rejectFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { - // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある - - const follower = await this.apDbResolverService.getUserFromApId(activity.actor); - - if (follower == null) { - return 'skip: follower not found'; - } - - if (!this.userEntityService.isLocalUser(follower)) { - return 'skip: follower is not a local user'; - } - - // relay - const match = activity.id?.match(/follow-relay\/(\w+)/); - if (match) { - return await this.relayService.relayRejected(match[1]); - } - - await this.userFollowingService.remoteReject(actor, follower); - return 'ok'; - } - - private async remove(actor: CacheableRemoteUser, activity: IRemove): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { - throw new Error('invalid actor'); - } - - if (activity.target == null) { - throw new Error('target is null'); - } - - if (activity.target === actor.featured) { - const note = await this.apNoteService.resolveNote(activity.object); - if (note == null) throw new Error('note not found'); - await this.notePiningService.removePinned(actor, note.id); - return; - } - - throw new Error(`unknown target: ${activity.target}`); - } - - private async undo(actor: CacheableRemoteUser, activity: IUndo): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { - throw new Error('invalid actor'); - } - - const uri = activity.id ?? activity; - - this.logger.info(`Undo: ${uri}`); - - const resolver = this.apResolverService.createResolver(); - - const object = await resolver.resolve(activity.object).catch(e => { - this.logger.error(`Resolution failed: ${e}`); - throw e; - }); - - if (isFollow(object)) return await this.undoFollow(actor, object); - if (isBlock(object)) return await this.undoBlock(actor, object); - if (isLike(object)) return await this.undoLike(actor, object); - if (isAnnounce(object)) return await this.undoAnnounce(actor, object); - if (isAccept(object)) return await this.undoAccept(actor, object); - - return `skip: unknown object type ${getApType(object)}`; - } - - private async undoAccept(actor: CacheableRemoteUser, activity: IAccept): Promise { - const follower = await this.apDbResolverService.getUserFromApId(activity.object); - if (follower == null) { - return 'skip: follower not found'; - } - - const following = await this.followingsRepository.findOneBy({ - followerId: follower.id, - followeeId: actor.id, - }); - - if (following) { - await this.userFollowingService.unfollow(follower, actor); - return 'ok: unfollowed'; - } - - return 'skip: フォローされていない'; - } - - private async undoAnnounce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { - const uri = getApId(activity); - - const note = await this.notesRepository.findOneBy({ - uri, - userId: actor.id, - }); - - if (!note) return 'skip: no such Announce'; - - await this.noteDeleteService.delete(actor, note); - return 'ok: deleted'; - } - - private async undoBlock(actor: CacheableRemoteUser, activity: IBlock): Promise { - const blockee = await this.apDbResolverService.getUserFromApId(activity.object); - - if (blockee == null) { - return 'skip: blockee not found'; - } - - if (blockee.host != null) { - return 'skip: ブロック解除しようとしているユーザーはローカルユーザーではありません'; - } - - await this.userBlockingService.unblock(await this.usersRepository.findOneByOrFail({ id: actor.id }), blockee); - return 'ok'; - } - - private async undoFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { - const followee = await this.apDbResolverService.getUserFromApId(activity.object); - if (followee == null) { - return 'skip: followee not found'; - } - - if (followee.host != null) { - return 'skip: フォロー解除しようとしているユーザーはローカルユーザーではありません'; - } - - const req = await this.followRequestsRepository.findOneBy({ - followerId: actor.id, - followeeId: followee.id, - }); - - const following = await this.followingsRepository.findOneBy({ - followerId: actor.id, - followeeId: followee.id, - }); - - if (req) { - await this.userFollowingService.cancelFollowRequest(followee, actor); - return 'ok: follow request canceled'; - } - - if (following) { - await this.userFollowingService.unfollow(actor, followee); - return 'ok: unfollowed'; - } - - return 'skip: リクエストもフォローもされていない'; - } - - private async undoLike(actor: CacheableRemoteUser, activity: ILike): Promise { - const targetUri = getApId(activity.object); - - const note = await this.apNoteService.fetchNote(targetUri); - if (!note) return `skip: target note not found ${targetUri}`; - - await this.reactionService.delete(actor, note).catch(e => { - if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') return; - throw e; - }); - - return 'ok'; - } - - private async update(actor: CacheableRemoteUser, activity: IUpdate): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { - return 'skip: invalid actor'; - } - - this.logger.debug('Update'); - - const resolver = this.apResolverService.createResolver(); - - const object = await resolver.resolve(activity.object).catch(e => { - this.logger.error(`Resolution failed: ${e}`); - throw e; - }); - - if (isActor(object)) { - await this.apPersonService.updatePerson(actor.uri!, resolver, object); - return 'ok: Person updated'; - } else if (getApType(object) === 'Question') { - await this.apQuestionService.updateQuestion(object, resolver).catch(err => console.error(err)); - return 'ok: Question updated'; - } else { - return `skip: Unknown type: ${getApType(object)}`; - } - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApLoggerService.ts b/packages/backend/src/core/remote/activitypub/ApLoggerService.ts deleted file mode 100644 index 82fd7c5f18..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApLoggerService.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import type Logger from '@/logger.js'; -import { RemoteLoggerService } from '@/core/remote/RemoteLoggerService.js'; - -@Injectable() -export class ApLoggerService { - public logger: Logger; - - constructor( - private remoteLoggerService: RemoteLoggerService, - ) { - this.logger = this.remoteLoggerService.logger.createSubLogger('ap', 'magenta'); - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApMfmService.ts b/packages/backend/src/core/remote/activitypub/ApMfmService.ts deleted file mode 100644 index 8804fde64a..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApMfmService.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import * as mfm from 'mfm-js'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import { MfmService } from '@/core/MfmService.js'; -import type { Note } from '@/models/entities/Note.js'; -import { extractApHashtagObjects } from './models/tag.js'; -import type { IObject } from './type.js'; - -@Injectable() -export class ApMfmService { - constructor( - @Inject(DI.config) - private config: Config, - - private mfmService: MfmService, - ) { - } - - public htmlToMfm(html: string, tag?: IObject | IObject[]) { - const hashtagNames = extractApHashtagObjects(tag).map(x => x.name).filter((x): x is string => x != null); - - return this.mfmService.fromHtml(html, hashtagNames); - } - - public getNoteHtml(note: Note) { - if (!note.text) return ''; - return this.mfmService.toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApRendererService.ts b/packages/backend/src/core/remote/activitypub/ApRendererService.ts deleted file mode 100644 index 38a92567c3..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApRendererService.ts +++ /dev/null @@ -1,703 +0,0 @@ -import { createPublicKey } from 'node:crypto'; -import { Inject, Injectable } from '@nestjs/common'; -import { In, IsNull } from 'typeorm'; -import { v4 as uuid } from 'uuid'; -import * as mfm from 'mfm-js'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; -import type { IMentionedRemoteUsers, Note } from '@/models/entities/Note.js'; -import type { Blocking } from '@/models/entities/Blocking.js'; -import type { Relay } from '@/models/entities/Relay.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import type { NoteReaction } from '@/models/entities/NoteReaction.js'; -import type { Emoji } from '@/models/entities/Emoji.js'; -import type { Poll } from '@/models/entities/Poll.js'; -import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; -import type { PollVote } from '@/models/entities/PollVote.js'; -import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; -import { MfmService } from '@/core/MfmService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; -import type { UserKeypair } from '@/models/entities/UserKeypair.js'; -import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, EmojisRepository, PollsRepository } from '@/models/index.js'; -import { LdSignatureService } from './LdSignatureService.js'; -import { ApMfmService } from './ApMfmService.js'; -import type { IActivity, IObject } from './type.js'; -import type { IIdentifier } from './models/identifier.js'; - -@Injectable() -export class ApRendererService { - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.userProfilesRepository) - private userProfilesRepository: UserProfilesRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.driveFilesRepository) - private driveFilesRepository: DriveFilesRepository, - - @Inject(DI.emojisRepository) - private emojisRepository: EmojisRepository, - - @Inject(DI.pollsRepository) - private pollsRepository: PollsRepository, - - private userEntityService: UserEntityService, - private driveFileEntityService: DriveFileEntityService, - private ldSignatureService: LdSignatureService, - private userKeypairStoreService: UserKeypairStoreService, - private apMfmService: ApMfmService, - private mfmService: MfmService, - ) { - } - - public renderAccept(object: any, user: { id: User['id']; host: null }) { - return { - type: 'Accept', - actor: `${this.config.url}/users/${user.id}`, - object, - }; - } - - public renderAdd(user: ILocalUser, target: any, object: any) { - return { - type: 'Add', - actor: `${this.config.url}/users/${user.id}`, - target, - object, - }; - } - - public renderAnnounce(object: any, note: Note) { - const attributedTo = `${this.config.url}/users/${note.userId}`; - - let to: string[] = []; - let cc: string[] = []; - - if (note.visibility === 'public') { - to = ['https://www.w3.org/ns/activitystreams#Public']; - cc = [`${attributedTo}/followers`]; - } else if (note.visibility === 'home') { - to = [`${attributedTo}/followers`]; - cc = ['https://www.w3.org/ns/activitystreams#Public']; - } else { - return null; - } - - return { - id: `${this.config.url}/notes/${note.id}/activity`, - actor: `${this.config.url}/users/${note.userId}`, - type: 'Announce', - published: note.createdAt.toISOString(), - to, - cc, - object, - }; - } - - /** - * Renders a block into its ActivityPub representation. - * - * @param block The block to be rendered. The blockee relation must be loaded. - */ - public renderBlock(block: Blocking) { - if (block.blockee?.uri == null) { - throw new Error('renderBlock: missing blockee uri'); - } - - return { - type: 'Block', - id: `${this.config.url}/blocks/${block.id}`, - actor: `${this.config.url}/users/${block.blockerId}`, - object: block.blockee.uri, - }; - } - - public renderCreate(object: any, note: Note) { - const activity = { - id: `${this.config.url}/notes/${note.id}/activity`, - actor: `${this.config.url}/users/${note.userId}`, - type: 'Create', - published: note.createdAt.toISOString(), - object, - } as any; - - if (object.to) activity.to = object.to; - if (object.cc) activity.cc = object.cc; - - return activity; - } - - public renderDelete(object: any, user: { id: User['id']; host: null }) { - return { - type: 'Delete', - actor: `${this.config.url}/users/${user.id}`, - object, - published: new Date().toISOString(), - }; - } - - public renderDocument(file: DriveFile) { - return { - type: 'Document', - mediaType: file.type, - url: this.driveFileEntityService.getPublicUrl(file), - name: file.comment, - }; - } - - public renderEmoji(emoji: Emoji) { - return { - id: `${this.config.url}/emojis/${emoji.name}`, - type: 'Emoji', - name: `:${emoji.name}:`, - updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : new Date().toISOString, - icon: { - type: 'Image', - mediaType: emoji.type ?? 'image/png', - url: emoji.publicUrl ?? emoji.originalUrl, // ?? emoji.originalUrl してるのは後方互換性のため - }, - }; - } - - // to anonymise reporters, the reporting actor must be a system user - // object has to be a uri or array of uris - public renderFlag(user: ILocalUser, object: [string], content: string) { - return { - type: 'Flag', - actor: `${this.config.url}/users/${user.id}`, - content, - object, - }; - } - - public renderFollowRelay(relay: Relay, relayActor: ILocalUser) { - const follow = { - id: `${this.config.url}/activities/follow-relay/${relay.id}`, - type: 'Follow', - actor: `${this.config.url}/users/${relayActor.id}`, - object: 'https://www.w3.org/ns/activitystreams#Public', - }; - - return follow; - } - - /** - * Convert (local|remote)(Follower|Followee)ID to URL - * @param id Follower|Followee ID - */ - public async renderFollowUser(id: User['id']) { - const user = await this.usersRepository.findOneByOrFail({ id: id }); - return this.userEntityService.isLocalUser(user) ? `${this.config.url}/users/${user.id}` : user.uri; - } - - public renderFollow( - follower: { id: User['id']; host: User['host']; uri: User['host'] }, - followee: { id: User['id']; host: User['host']; uri: User['host'] }, - requestId?: string, - ) { - const follow = { - id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`, - type: 'Follow', - actor: this.userEntityService.isLocalUser(follower) ? `${this.config.url}/users/${follower.id}` : follower.uri, - object: this.userEntityService.isLocalUser(followee) ? `${this.config.url}/users/${followee.id}` : followee.uri, - } as any; - - return follow; - } - - public renderHashtag(tag: string) { - return { - type: 'Hashtag', - href: `${this.config.url}/tags/${encodeURIComponent(tag)}`, - name: `#${tag}`, - }; - } - - public renderImage(file: DriveFile) { - return { - type: 'Image', - url: this.driveFileEntityService.getPublicUrl(file), - sensitive: file.isSensitive, - name: file.comment, - }; - } - - public renderKey(user: ILocalUser, key: UserKeypair, postfix?: string) { - return { - id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`, - type: 'Key', - owner: `${this.config.url}/users/${user.id}`, - publicKeyPem: createPublicKey(key.publicKey).export({ - type: 'spki', - format: 'pem', - }), - }; - } - - public async renderLike(noteReaction: NoteReaction, note: { uri: string | null }) { - const reaction = noteReaction.reaction; - - const object = { - type: 'Like', - id: `${this.config.url}/likes/${noteReaction.id}`, - actor: `${this.config.url}/users/${noteReaction.userId}`, - object: note.uri ? note.uri : `${this.config.url}/notes/${noteReaction.noteId}`, - content: reaction, - _misskey_reaction: reaction, - } as any; - - if (reaction.startsWith(':')) { - const name = reaction.replace(/:/g, ''); - const emoji = await this.emojisRepository.findOneBy({ - name, - host: IsNull(), - }); - - if (emoji) object.tag = [this.renderEmoji(emoji)]; - } - - return object; - } - - public renderMention(mention: User) { - return { - type: 'Mention', - href: this.userEntityService.isRemoteUser(mention) ? mention.uri : `${this.config.url}/users/${(mention as ILocalUser).id}`, - name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as ILocalUser).username}`, - }; - } - - public async renderNote(note: Note, dive = true, isTalk = false): Promise { - const getPromisedFiles = async (ids: string[]) => { - if (!ids || ids.length === 0) return []; - const items = await this.driveFilesRepository.findBy({ id: In(ids) }); - return ids.map(id => items.find(item => item.id === id)).filter(item => item != null) as DriveFile[]; - }; - - let inReplyTo; - let inReplyToNote: Note | null; - - if (note.replyId) { - inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId }); - - if (inReplyToNote != null) { - const inReplyToUser = await this.usersRepository.findOneBy({ id: inReplyToNote.userId }); - - if (inReplyToUser != null) { - if (inReplyToNote.uri) { - inReplyTo = inReplyToNote.uri; - } else { - if (dive) { - inReplyTo = await this.renderNote(inReplyToNote, false); - } else { - inReplyTo = `${this.config.url}/notes/${inReplyToNote.id}`; - } - } - } - } - } else { - inReplyTo = null; - } - - let quote; - - if (note.renoteId) { - const renote = await this.notesRepository.findOneBy({ id: note.renoteId }); - - if (renote) { - quote = renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`; - } - } - - const attributedTo = `${this.config.url}/users/${note.userId}`; - - const mentions = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); - - let to: string[] = []; - let cc: string[] = []; - - if (note.visibility === 'public') { - to = ['https://www.w3.org/ns/activitystreams#Public']; - cc = [`${attributedTo}/followers`].concat(mentions); - } else if (note.visibility === 'home') { - to = [`${attributedTo}/followers`]; - cc = ['https://www.w3.org/ns/activitystreams#Public'].concat(mentions); - } else if (note.visibility === 'followers') { - to = [`${attributedTo}/followers`]; - cc = mentions; - } else { - to = mentions; - } - - const mentionedUsers = note.mentions.length > 0 ? await this.usersRepository.findBy({ - id: In(note.mentions), - }) : []; - - const hashtagTags = (note.tags ?? []).map(tag => this.renderHashtag(tag)); - const mentionTags = mentionedUsers.map(u => this.renderMention(u)); - - const files = await getPromisedFiles(note.fileIds); - - const text = note.text ?? ''; - let poll: Poll | null = null; - - if (note.hasPoll) { - poll = await this.pollsRepository.findOneBy({ noteId: note.id }); - } - - let apText = text; - - if (quote) { - apText += `\n\nRE: ${quote}`; - } - - const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw; - - const content = this.apMfmService.getNoteHtml(Object.assign({}, note, { - text: apText, - })); - - const emojis = await this.getEmojis(note.emojis); - const apemojis = emojis.map(emoji => this.renderEmoji(emoji)); - - const tag = [ - ...hashtagTags, - ...mentionTags, - ...apemojis, - ]; - - const asPoll = poll ? { - type: 'Question', - content: this.apMfmService.getNoteHtml(Object.assign({}, note, { - text: text, - })), - [poll.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt, - [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ - type: 'Note', - name: text, - replies: { - type: 'Collection', - totalItems: poll!.votes[i], - }, - })), - } : {}; - - const asTalk = isTalk ? { - _misskey_talk: true, - } : {}; - - return { - id: `${this.config.url}/notes/${note.id}`, - type: 'Note', - attributedTo, - summary: summary ?? undefined, - content: content ?? undefined, - _misskey_content: text, - source: { - content: text, - mediaType: 'text/x.misskeymarkdown', - }, - _misskey_quote: quote, - quoteUrl: quote, - published: note.createdAt.toISOString(), - to, - cc, - inReplyTo, - attachment: files.map(x => this.renderDocument(x)), - sensitive: note.cw != null || files.some(file => file.isSensitive), - tag, - ...asPoll, - ...asTalk, - }; - } - - public async renderPerson(user: ILocalUser) { - const id = `${this.config.url}/users/${user.id}`; - const isSystem = !!user.username.match(/\./); - - const [avatar, banner, profile] = await Promise.all([ - user.avatarId ? this.driveFilesRepository.findOneBy({ id: user.avatarId }) : Promise.resolve(undefined), - user.bannerId ? this.driveFilesRepository.findOneBy({ id: user.bannerId }) : Promise.resolve(undefined), - this.userProfilesRepository.findOneByOrFail({ userId: user.id }), - ]); - - const attachment: { - type: 'PropertyValue', - name: string, - value: string, - identifier?: IIdentifier, - }[] = []; - - if (profile.fields) { - for (const field of profile.fields) { - attachment.push({ - type: 'PropertyValue', - name: field.name, - value: (field.value != null && field.value.match(/^https?:/)) - ? `${new URL(field.value).href}` - : field.value, - }); - } - } - - const emojis = await this.getEmojis(user.emojis); - const apemojis = emojis.map(emoji => this.renderEmoji(emoji)); - - const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag)); - - const tag = [ - ...apemojis, - ...hashtagTags, - ]; - - const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); - - const person = { - type: isSystem ? 'Application' : user.isBot ? 'Service' : 'Person', - id, - inbox: `${id}/inbox`, - outbox: `${id}/outbox`, - followers: `${id}/followers`, - following: `${id}/following`, - featured: `${id}/collections/featured`, - sharedInbox: `${this.config.url}/inbox`, - endpoints: { sharedInbox: `${this.config.url}/inbox` }, - url: `${this.config.url}/@${user.username}`, - preferredUsername: user.username, - name: user.name, - summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null, - icon: avatar ? this.renderImage(avatar) : null, - image: banner ? this.renderImage(banner) : null, - tag, - manuallyApprovesFollowers: user.isLocked, - discoverable: !!user.isExplorable, - publicKey: this.renderKey(user, keypair, '#main-key'), - isCat: user.isCat, - attachment: attachment.length ? attachment : undefined, - } as any; - - if (profile.birthday) { - person['vcard:bday'] = profile.birthday; - } - - if (profile.location) { - person['vcard:Address'] = profile.location; - } - - return person; - } - - public async renderQuestion(user: { id: User['id'] }, note: Note, poll: Poll) { - const question = { - type: 'Question', - id: `${this.config.url}/questions/${note.id}`, - actor: `${this.config.url}/users/${user.id}`, - content: note.text ?? '', - [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ - name: text, - _misskey_votes: poll.votes[i], - replies: { - type: 'Collection', - totalItems: poll.votes[i], - }, - })), - }; - - return question; - } - - public renderRead(user: { id: User['id'] }, message: MessagingMessage) { - return { - type: 'Read', - actor: `${this.config.url}/users/${user.id}`, - object: message.uri, - }; - } - - public renderReject(object: any, user: { id: User['id'] }) { - return { - type: 'Reject', - actor: `${this.config.url}/users/${user.id}`, - object, - }; - } - - public renderRemove(user: { id: User['id'] }, target: any, object: any) { - return { - type: 'Remove', - actor: `${this.config.url}/users/${user.id}`, - target, - object, - }; - } - - public renderTombstone(id: string) { - return { - id, - type: 'Tombstone', - }; - } - - public renderUndo(object: any, user: { id: User['id'] }) { - if (object == null) return null; - const id = typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined; - - return { - type: 'Undo', - ...(id ? { id } : {}), - actor: `${this.config.url}/users/${user.id}`, - object, - published: new Date().toISOString(), - }; - } - - public renderUpdate(object: any, user: { id: User['id'] }) { - const activity = { - id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`, - actor: `${this.config.url}/users/${user.id}`, - type: 'Update', - to: ['https://www.w3.org/ns/activitystreams#Public'], - object, - published: new Date().toISOString(), - } as any; - - return activity; - } - - public renderVote(user: { id: User['id'] }, vote: PollVote, note: Note, poll: Poll, pollOwner: IRemoteUser) { - return { - id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`, - actor: `${this.config.url}/users/${user.id}`, - type: 'Create', - to: [pollOwner.uri], - published: new Date().toISOString(), - object: { - id: `${this.config.url}/users/${user.id}#votes/${vote.id}`, - type: 'Note', - attributedTo: `${this.config.url}/users/${user.id}`, - to: [pollOwner.uri], - inReplyTo: note.uri, - name: poll.choices[vote.choice], - }, - }; - } - - public renderActivity(x: any): IActivity | null { - if (x == null) return null; - - if (typeof x === 'object' && x.id == null) { - x.id = `${this.config.url}/${uuid()}`; - } - - return Object.assign({ - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', - { - // as non-standards - manuallyApprovesFollowers: 'as:manuallyApprovesFollowers', - sensitive: 'as:sensitive', - Hashtag: 'as:Hashtag', - quoteUrl: 'as:quoteUrl', - // Mastodon - toot: 'http://joinmastodon.org/ns#', - Emoji: 'toot:Emoji', - featured: 'toot:featured', - discoverable: 'toot:discoverable', - // schema - schema: 'http://schema.org#', - PropertyValue: 'schema:PropertyValue', - value: 'schema:value', - // Misskey - misskey: 'https://misskey-hub.net/ns#', - '_misskey_content': 'misskey:_misskey_content', - '_misskey_quote': 'misskey:_misskey_quote', - '_misskey_reaction': 'misskey:_misskey_reaction', - '_misskey_votes': 'misskey:_misskey_votes', - '_misskey_talk': 'misskey:_misskey_talk', - 'isCat': 'misskey:isCat', - // vcard - vcard: 'http://www.w3.org/2006/vcard/ns#', - }, - ], - }, x); - } - - public async attachLdSignature(activity: any, user: { id: User['id']; host: null; }): Promise { - const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); - - const ldSignature = this.ldSignatureService.use(); - ldSignature.debug = false; - activity = await ldSignature.signRsaSignature2017(activity, keypair.privateKey, `${this.config.url}/users/${user.id}#main-key`); - - return activity; - } - - /** - * Render OrderedCollectionPage - * @param id URL of self - * @param totalItems Number of total items - * @param orderedItems Items - * @param partOf URL of base - * @param prev URL of prev page (optional) - * @param next URL of next page (optional) - */ - public renderOrderedCollectionPage(id: string, totalItems: any, orderedItems: any, partOf: string, prev?: string, next?: string) { - const page = { - id, - partOf, - type: 'OrderedCollectionPage', - totalItems, - orderedItems, - } as any; - - if (prev) page.prev = prev; - if (next) page.next = next; - - return page; - } - - /** - * Render OrderedCollection - * @param id URL of self - * @param totalItems Total number of items - * @param first URL of first page (optional) - * @param last URL of last page (optional) - * @param orderedItems attached objects (optional) - */ - public renderOrderedCollection(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: IObject[]) { - const page: any = { - id, - type: 'OrderedCollection', - totalItems, - }; - - if (first) page.first = first; - if (last) page.last = last; - if (orderedItems) page.orderedItems = orderedItems; - - return page; - } - - private async getEmojis(names: string[]): Promise { - if (names == null || names.length === 0) return []; - - const emojis = await Promise.all( - names.map(name => this.emojisRepository.findOneBy({ - name, - host: IsNull(), - })), - ); - - return emojis.filter(emoji => emoji != null) as Emoji[]; - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApRequestService.ts b/packages/backend/src/core/remote/activitypub/ApRequestService.ts deleted file mode 100644 index baad46d668..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApRequestService.ts +++ /dev/null @@ -1,182 +0,0 @@ -import * as crypto from 'node:crypto'; -import { URL } from 'node:url'; -import { Inject, Injectable } from '@nestjs/common'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import type { User } from '@/models/entities/User.js'; -import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; -import { HttpRequestService } from '@/core/HttpRequestService.js'; - -type Request = { - url: string; - method: string; - headers: Record; -}; - -type Signed = { - request: Request; - signingString: string; - signature: string; - signatureHeader: string; -}; - -type PrivateKey = { - privateKeyPem: string; - keyId: string; -}; - -@Injectable() -export class ApRequestService { - constructor( - @Inject(DI.config) - private config: Config, - - private userKeypairStoreService: UserKeypairStoreService, - private httpRequestService: HttpRequestService, - ) { - } - - private createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record }): Signed { - const u = new URL(args.url); - const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`; - - const request: Request = { - url: u.href, - method: 'POST', - headers: this.objectAssignWithLcKey({ - 'Date': new Date().toUTCString(), - 'Host': u.hostname, - 'Content-Type': 'application/activity+json', - 'Digest': digestHeader, - }, args.additionalHeaders), - }; - - const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']); - - return { - request, - signingString: result.signingString, - signature: result.signature, - signatureHeader: result.signatureHeader, - }; - } - - private createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record }): Signed { - const u = new URL(args.url); - - const request: Request = { - url: u.href, - method: 'GET', - headers: this.objectAssignWithLcKey({ - 'Accept': 'application/activity+json, application/ld+json', - 'Date': new Date().toUTCString(), - 'Host': new URL(args.url).hostname, - }, args.additionalHeaders), - }; - - const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']); - - return { - request, - signingString: result.signingString, - signature: result.signature, - signatureHeader: result.signatureHeader, - }; - } - - private signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed { - const signingString = this.genSigningString(request, includeHeaders); - const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64'); - const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`; - - request.headers = this.objectAssignWithLcKey(request.headers, { - Signature: signatureHeader, - }); - - return { - request, - signingString, - signature, - signatureHeader, - }; - } - - private genSigningString(request: Request, includeHeaders: string[]): string { - request.headers = this.lcObjectKey(request.headers); - - const results: string[] = []; - - for (const key of includeHeaders.map(x => x.toLowerCase())) { - if (key === '(request-target)') { - results.push(`(request-target): ${request.method.toLowerCase()} ${new URL(request.url).pathname}`); - } else { - results.push(`${key}: ${request.headers[key]}`); - } - } - - return results.join('\n'); - } - - private lcObjectKey(src: Record): Record { - const dst: Record = {}; - for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key]; - return dst; - } - - private objectAssignWithLcKey(a: Record, b: Record): Record { - return Object.assign(this.lcObjectKey(a), this.lcObjectKey(b)); - } - - public async signedPost(user: { id: User['id'] }, url: string, object: any) { - const body = JSON.stringify(object); - - const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); - - const req = this.createSignedPost({ - key: { - privateKeyPem: keypair.privateKey, - keyId: `${this.config.url}/users/${user.id}#main-key`, - }, - url, - body, - additionalHeaders: { - 'User-Agent': this.config.userAgent, - }, - }); - - await this.httpRequestService.getResponse({ - url, - method: req.request.method, - headers: req.request.headers, - body, - }); - } - - /** - * Get AP object with http-signature - * @param user http-signature user - * @param url URL to fetch - */ - public async signedGet(url: string, user: { id: User['id'] }) { - const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); - - const req = this.createSignedGet({ - key: { - privateKeyPem: keypair.privateKey, - keyId: `${this.config.url}/users/${user.id}#main-key`, - }, - url, - additionalHeaders: { - 'User-Agent': this.config.userAgent, - }, - }); - - const res = await this.httpRequestService.getResponse({ - url, - method: req.request.method, - headers: req.request.headers, - }); - - return await res.json(); - } -} diff --git a/packages/backend/src/core/remote/activitypub/ApResolverService.ts b/packages/backend/src/core/remote/activitypub/ApResolverService.ts deleted file mode 100644 index bcdb9383d1..0000000000 --- a/packages/backend/src/core/remote/activitypub/ApResolverService.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import type { ILocalUser } from '@/models/entities/User.js'; -import { InstanceActorService } from '@/core/InstanceActorService.js'; -import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import { MetaService } from '@/core/MetaService.js'; -import { HttpRequestService } from '@/core/HttpRequestService.js'; -import { DI } from '@/di-symbols.js'; -import { UtilityService } from '@/core/UtilityService.js'; -import { isCollectionOrOrderedCollection } from './type.js'; -import { ApDbResolverService } from './ApDbResolverService.js'; -import { ApRendererService } from './ApRendererService.js'; -import { ApRequestService } from './ApRequestService.js'; -import type { IObject, ICollection, IOrderedCollection } from './type.js'; - -@Injectable() -export class ApResolverService { - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.pollsRepository) - private pollsRepository: PollsRepository, - - @Inject(DI.noteReactionsRepository) - private noteReactionsRepository: NoteReactionsRepository, - - private utilityService: UtilityService, - private instanceActorService: InstanceActorService, - private metaService: MetaService, - private apRequestService: ApRequestService, - private httpRequestService: HttpRequestService, - private apRendererService: ApRendererService, - private apDbResolverService: ApDbResolverService, - ) { - } - - public createResolver(): Resolver { - return new Resolver( - this.config, - this.usersRepository, - this.notesRepository, - this.pollsRepository, - this.noteReactionsRepository, - this.utilityService, - this.instanceActorService, - this.metaService, - this.apRequestService, - this.httpRequestService, - this.apRendererService, - this.apDbResolverService, - ); - } -} - -export class Resolver { - private history: Set; - private user?: ILocalUser; - - constructor( - private config: Config, - private usersRepository: UsersRepository, - private notesRepository: NotesRepository, - private pollsRepository: PollsRepository, - private noteReactionsRepository: NoteReactionsRepository, - private utilityService: UtilityService, - private instanceActorService: InstanceActorService, - private metaService: MetaService, - private apRequestService: ApRequestService, - private httpRequestService: HttpRequestService, - private apRendererService: ApRendererService, - private apDbResolverService: ApDbResolverService, - private recursionLimit = 100 - ) { - this.history = new Set(); - } - - public getHistory(): string[] { - return Array.from(this.history); - } - - public async resolveCollection(value: string | IObject): Promise { - const collection = typeof value === 'string' - ? await this.resolve(value) - : value; - - if (isCollectionOrOrderedCollection(collection)) { - return collection; - } else { - throw new Error(`unrecognized collection type: ${collection.type}`); - } - } - - public async resolve(value: string | IObject): Promise { - if (value == null) { - throw new Error('resolvee is null (or undefined)'); - } - - if (typeof value !== 'string') { - return value; - } - - if (value.includes('#')) { - // URLs with fragment parts cannot be resolved correctly because - // the fragment part does not get transmitted over HTTP(S). - // Avoid strange behaviour by not trying to resolve these at all. - throw new Error(`cannot resolve URL with fragment: ${value}`); - } - - if (this.history.has(value)) { - throw new Error('cannot resolve already resolved one'); - } - - if (this.history.size > this.recursionLimit) { - throw new Error(`hit recursion limit: ${this.utilityService.extractDbHost(value)}`); - } - - this.history.add(value); - - const host = this.utilityService.extractDbHost(value); - if (this.utilityService.isSelfHost(host)) { - return await this.resolveLocal(value); - } - - const meta = await this.metaService.fetch(); - if (meta.blockedHosts.includes(host)) { - throw new Error('Instance is blocked'); - } - - if (this.config.signToActivityPubGet && !this.user) { - this.user = await this.instanceActorService.getInstanceActor(); - } - - const object = (this.user - ? await this.apRequestService.signedGet(value, this.user) - : await this.httpRequestService.getJson(value, 'application/activity+json, application/ld+json')) as IObject; - - if (object == null || ( - Array.isArray(object['@context']) ? - !(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') : - object['@context'] !== 'https://www.w3.org/ns/activitystreams' - )) { - throw new Error('invalid response'); - } - - return object; - } - - private resolveLocal(url: string): Promise { - const parsed = this.apDbResolverService.parseUri(url); - if (!parsed.local) throw new Error('resolveLocal: not local'); - - switch (parsed.type) { - case 'notes': - return this.notesRepository.findOneByOrFail({ id: parsed.id }) - .then(note => { - if (parsed.rest === 'activity') { - // this refers to the create activity and not the note itself - return this.apRendererService.renderActivity(this.apRendererService.renderCreate(this.apRendererService.renderNote(note), note)); - } else { - return this.apRendererService.renderNote(note); - } - }); - case 'users': - return this.usersRepository.findOneByOrFail({ id: parsed.id }) - .then(user => this.apRendererService.renderPerson(user as ILocalUser)); - case 'questions': - // Polls are indexed by the note they are attached to. - return Promise.all([ - this.notesRepository.findOneByOrFail({ id: parsed.id }), - this.pollsRepository.findOneByOrFail({ noteId: parsed.id }), - ]) - .then(([note, poll]) => this.apRendererService.renderQuestion({ id: note.userId }, note, poll)); - case 'likes': - return this.noteReactionsRepository.findOneByOrFail({ id: parsed.id }).then(reaction => - this.apRendererService.renderActivity(this.apRendererService.renderLike(reaction, { uri: null }))!); - case 'follows': - // rest should be - if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI'); - - return Promise.all( - [parsed.id, parsed.rest].map(id => this.usersRepository.findOneByOrFail({ id })), - ) - .then(([follower, followee]) => this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee, url))); - default: - throw new Error(`resolveLocal: type ${parsed.type} unhandled`); - } - } -} diff --git a/packages/backend/src/core/remote/activitypub/LdSignatureService.ts b/packages/backend/src/core/remote/activitypub/LdSignatureService.ts deleted file mode 100644 index ea39f15b2b..0000000000 --- a/packages/backend/src/core/remote/activitypub/LdSignatureService.ts +++ /dev/null @@ -1,147 +0,0 @@ -import * as crypto from 'node:crypto'; -import { Inject, Injectable } from '@nestjs/common'; -import fetch from 'node-fetch'; -import { HttpRequestService } from '@/core/HttpRequestService.js'; -import { CONTEXTS } from './misc/contexts.js'; - -// RsaSignature2017 based from https://github.com/transmute-industries/RsaSignature2017 - -@Injectable() -export class LdSignatureService { - constructor( - private httpRequestService: HttpRequestService, - ) { - } - - public use(): LdSignature { - return new LdSignature(this.httpRequestService); - } -} - -class LdSignature { - public debug = false; - public preLoad = true; - public loderTimeout = 10 * 1000; - - constructor( - private httpRequestService: HttpRequestService, - ) { - } - - public async signRsaSignature2017(data: any, privateKey: string, creator: string, domain?: string, created?: Date): Promise { - const options = { - type: 'RsaSignature2017', - creator, - domain, - nonce: crypto.randomBytes(16).toString('hex'), - created: (created ?? new Date()).toISOString(), - } as { - type: string; - creator: string; - domain?: string; - nonce: string; - created: string; - }; - - if (!domain) { - delete options.domain; - } - - const toBeSigned = await this.createVerifyData(data, options); - - const signer = crypto.createSign('sha256'); - signer.update(toBeSigned); - signer.end(); - - const signature = signer.sign(privateKey); - - return { - ...data, - signature: { - ...options, - signatureValue: signature.toString('base64'), - }, - }; - } - - public async verifyRsaSignature2017(data: any, publicKey: string): Promise { - const toBeSigned = await this.createVerifyData(data, data.signature); - const verifier = crypto.createVerify('sha256'); - verifier.update(toBeSigned); - return verifier.verify(publicKey, data.signature.signatureValue, 'base64'); - } - - public async createVerifyData(data: any, options: any) { - const transformedOptions = { - ...options, - '@context': 'https://w3id.org/identity/v1', - }; - delete transformedOptions['type']; - delete transformedOptions['id']; - delete transformedOptions['signatureValue']; - const canonizedOptions = await this.normalize(transformedOptions); - const optionsHash = this.sha256(canonizedOptions.toString()); - const transformedData = { ...data }; - delete transformedData['signature']; - const cannonidedData = await this.normalize(transformedData); - if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`); - const documentHash = this.sha256(cannonidedData.toString()); - const verifyData = `${optionsHash}${documentHash}`; - return verifyData; - } - - public async normalize(data: any) { - const customLoader = this.getLoader(); - return 42; - } - - private getLoader() { - return async (url: string): Promise => { - if (!url.match('^https?\:\/\/')) throw `Invalid URL ${url}`; - - if (this.preLoad) { - if (url in CONTEXTS) { - if (this.debug) console.debug(`HIT: ${url}`); - return { - contextUrl: null, - document: CONTEXTS[url], - documentUrl: url, - }; - } - } - - if (this.debug) console.debug(`MISS: ${url}`); - const document = await this.fetchDocument(url); - return { - contextUrl: null, - document: document, - documentUrl: url, - }; - }; - } - - private async fetchDocument(url: string) { - const json = await fetch(url, { - headers: { - Accept: 'application/ld+json, application/json', - }, - // TODO - //timeout: this.loderTimeout, - agent: u => u.protocol === 'http:' ? this.httpRequestService.httpAgent : this.httpRequestService.httpsAgent, - }).then(res => { - if (!res.ok) { - throw `${res.status} ${res.statusText}`; - } else { - return res.json(); - } - }); - - return json; - } - - public sha256(data: string): string { - const hash = crypto.createHash('sha256'); - hash.update(data); - return hash.digest('hex'); - } -} diff --git a/packages/backend/src/core/remote/activitypub/misc/contexts.ts b/packages/backend/src/core/remote/activitypub/misc/contexts.ts deleted file mode 100644 index aee0d3629c..0000000000 --- a/packages/backend/src/core/remote/activitypub/misc/contexts.ts +++ /dev/null @@ -1,526 +0,0 @@ -/* eslint:disable:quotemark indent */ -const id_v1 = { - '@context': { - 'id': '@id', - 'type': '@type', - - 'cred': 'https://w3id.org/credentials#', - 'dc': 'http://purl.org/dc/terms/', - 'identity': 'https://w3id.org/identity#', - 'perm': 'https://w3id.org/permissions#', - 'ps': 'https://w3id.org/payswarm#', - 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', - 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#', - 'sec': 'https://w3id.org/security#', - 'schema': 'http://schema.org/', - 'xsd': 'http://www.w3.org/2001/XMLSchema#', - - 'Group': 'https://www.w3.org/ns/activitystreams#Group', - - 'claim': { '@id': 'cred:claim', '@type': '@id' }, - 'credential': { '@id': 'cred:credential', '@type': '@id' }, - 'issued': { '@id': 'cred:issued', '@type': 'xsd:dateTime' }, - 'issuer': { '@id': 'cred:issuer', '@type': '@id' }, - 'recipient': { '@id': 'cred:recipient', '@type': '@id' }, - 'Credential': 'cred:Credential', - 'CryptographicKeyCredential': 'cred:CryptographicKeyCredential', - - 'about': { '@id': 'schema:about', '@type': '@id' }, - 'address': { '@id': 'schema:address', '@type': '@id' }, - 'addressCountry': 'schema:addressCountry', - 'addressLocality': 'schema:addressLocality', - 'addressRegion': 'schema:addressRegion', - 'comment': 'rdfs:comment', - 'created': { '@id': 'dc:created', '@type': 'xsd:dateTime' }, - 'creator': { '@id': 'dc:creator', '@type': '@id' }, - 'description': 'schema:description', - 'email': 'schema:email', - 'familyName': 'schema:familyName', - 'givenName': 'schema:givenName', - 'image': { '@id': 'schema:image', '@type': '@id' }, - 'label': 'rdfs:label', - 'name': 'schema:name', - 'postalCode': 'schema:postalCode', - 'streetAddress': 'schema:streetAddress', - 'title': 'dc:title', - 'url': { '@id': 'schema:url', '@type': '@id' }, - 'Person': 'schema:Person', - 'PostalAddress': 'schema:PostalAddress', - 'Organization': 'schema:Organization', - - 'identityService': { '@id': 'identity:identityService', '@type': '@id' }, - 'idp': { '@id': 'identity:idp', '@type': '@id' }, - 'Identity': 'identity:Identity', - - 'paymentProcessor': 'ps:processor', - 'preferences': { '@id': 'ps:preferences', '@type': '@vocab' }, - - 'cipherAlgorithm': 'sec:cipherAlgorithm', - 'cipherData': 'sec:cipherData', - 'cipherKey': 'sec:cipherKey', - 'digestAlgorithm': 'sec:digestAlgorithm', - 'digestValue': 'sec:digestValue', - 'domain': 'sec:domain', - 'expires': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, - 'initializationVector': 'sec:initializationVector', - 'member': { '@id': 'schema:member', '@type': '@id' }, - 'memberOf': { '@id': 'schema:memberOf', '@type': '@id' }, - 'nonce': 'sec:nonce', - 'normalizationAlgorithm': 'sec:normalizationAlgorithm', - 'owner': { '@id': 'sec:owner', '@type': '@id' }, - 'password': 'sec:password', - 'privateKey': { '@id': 'sec:privateKey', '@type': '@id' }, - 'privateKeyPem': 'sec:privateKeyPem', - 'publicKey': { '@id': 'sec:publicKey', '@type': '@id' }, - 'publicKeyPem': 'sec:publicKeyPem', - 'publicKeyService': { '@id': 'sec:publicKeyService', '@type': '@id' }, - 'revoked': { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, - 'signature': 'sec:signature', - 'signatureAlgorithm': 'sec:signatureAlgorithm', - 'signatureValue': 'sec:signatureValue', - 'CryptographicKey': 'sec:Key', - 'EncryptedMessage': 'sec:EncryptedMessage', - 'GraphSignature2012': 'sec:GraphSignature2012', - 'LinkedDataSignature2015': 'sec:LinkedDataSignature2015', - - 'accessControl': { '@id': 'perm:accessControl', '@type': '@id' }, - 'writePermission': { '@id': 'perm:writePermission', '@type': '@id' }, - }, -}; - -const security_v1 = { - '@context': { - 'id': '@id', - 'type': '@type', - - 'dc': 'http://purl.org/dc/terms/', - 'sec': 'https://w3id.org/security#', - 'xsd': 'http://www.w3.org/2001/XMLSchema#', - - 'EcdsaKoblitzSignature2016': 'sec:EcdsaKoblitzSignature2016', - 'Ed25519Signature2018': 'sec:Ed25519Signature2018', - 'EncryptedMessage': 'sec:EncryptedMessage', - 'GraphSignature2012': 'sec:GraphSignature2012', - 'LinkedDataSignature2015': 'sec:LinkedDataSignature2015', - 'LinkedDataSignature2016': 'sec:LinkedDataSignature2016', - 'CryptographicKey': 'sec:Key', - - 'authenticationTag': 'sec:authenticationTag', - 'canonicalizationAlgorithm': 'sec:canonicalizationAlgorithm', - 'cipherAlgorithm': 'sec:cipherAlgorithm', - 'cipherData': 'sec:cipherData', - 'cipherKey': 'sec:cipherKey', - 'created': { '@id': 'dc:created', '@type': 'xsd:dateTime' }, - 'creator': { '@id': 'dc:creator', '@type': '@id' }, - 'digestAlgorithm': 'sec:digestAlgorithm', - 'digestValue': 'sec:digestValue', - 'domain': 'sec:domain', - 'encryptionKey': 'sec:encryptionKey', - 'expiration': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, - 'expires': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, - 'initializationVector': 'sec:initializationVector', - 'iterationCount': 'sec:iterationCount', - 'nonce': 'sec:nonce', - 'normalizationAlgorithm': 'sec:normalizationAlgorithm', - 'owner': { '@id': 'sec:owner', '@type': '@id' }, - 'password': 'sec:password', - 'privateKey': { '@id': 'sec:privateKey', '@type': '@id' }, - 'privateKeyPem': 'sec:privateKeyPem', - 'publicKey': { '@id': 'sec:publicKey', '@type': '@id' }, - 'publicKeyBase58': 'sec:publicKeyBase58', - 'publicKeyPem': 'sec:publicKeyPem', - 'publicKeyWif': 'sec:publicKeyWif', - 'publicKeyService': { '@id': 'sec:publicKeyService', '@type': '@id' }, - 'revoked': { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, - 'salt': 'sec:salt', - 'signature': 'sec:signature', - 'signatureAlgorithm': 'sec:signingAlgorithm', - 'signatureValue': 'sec:signatureValue', - }, -}; - -const activitystreams = { - '@context': { - '@vocab': '_:', - 'xsd': 'http://www.w3.org/2001/XMLSchema#', - 'as': 'https://www.w3.org/ns/activitystreams#', - 'ldp': 'http://www.w3.org/ns/ldp#', - 'vcard': 'http://www.w3.org/2006/vcard/ns#', - 'id': '@id', - 'type': '@type', - 'Accept': 'as:Accept', - 'Activity': 'as:Activity', - 'IntransitiveActivity': 'as:IntransitiveActivity', - 'Add': 'as:Add', - 'Announce': 'as:Announce', - 'Application': 'as:Application', - 'Arrive': 'as:Arrive', - 'Article': 'as:Article', - 'Audio': 'as:Audio', - 'Block': 'as:Block', - 'Collection': 'as:Collection', - 'CollectionPage': 'as:CollectionPage', - 'Relationship': 'as:Relationship', - 'Create': 'as:Create', - 'Delete': 'as:Delete', - 'Dislike': 'as:Dislike', - 'Document': 'as:Document', - 'Event': 'as:Event', - 'Follow': 'as:Follow', - 'Flag': 'as:Flag', - 'Group': 'as:Group', - 'Ignore': 'as:Ignore', - 'Image': 'as:Image', - 'Invite': 'as:Invite', - 'Join': 'as:Join', - 'Leave': 'as:Leave', - 'Like': 'as:Like', - 'Link': 'as:Link', - 'Mention': 'as:Mention', - 'Note': 'as:Note', - 'Object': 'as:Object', - 'Offer': 'as:Offer', - 'OrderedCollection': 'as:OrderedCollection', - 'OrderedCollectionPage': 'as:OrderedCollectionPage', - 'Organization': 'as:Organization', - 'Page': 'as:Page', - 'Person': 'as:Person', - 'Place': 'as:Place', - 'Profile': 'as:Profile', - 'Question': 'as:Question', - 'Reject': 'as:Reject', - 'Remove': 'as:Remove', - 'Service': 'as:Service', - 'TentativeAccept': 'as:TentativeAccept', - 'TentativeReject': 'as:TentativeReject', - 'Tombstone': 'as:Tombstone', - 'Undo': 'as:Undo', - 'Update': 'as:Update', - 'Video': 'as:Video', - 'View': 'as:View', - 'Listen': 'as:Listen', - 'Read': 'as:Read', - 'Move': 'as:Move', - 'Travel': 'as:Travel', - 'IsFollowing': 'as:IsFollowing', - 'IsFollowedBy': 'as:IsFollowedBy', - 'IsContact': 'as:IsContact', - 'IsMember': 'as:IsMember', - 'subject': { - '@id': 'as:subject', - '@type': '@id', - }, - 'relationship': { - '@id': 'as:relationship', - '@type': '@id', - }, - 'actor': { - '@id': 'as:actor', - '@type': '@id', - }, - 'attributedTo': { - '@id': 'as:attributedTo', - '@type': '@id', - }, - 'attachment': { - '@id': 'as:attachment', - '@type': '@id', - }, - 'bcc': { - '@id': 'as:bcc', - '@type': '@id', - }, - 'bto': { - '@id': 'as:bto', - '@type': '@id', - }, - 'cc': { - '@id': 'as:cc', - '@type': '@id', - }, - 'context': { - '@id': 'as:context', - '@type': '@id', - }, - 'current': { - '@id': 'as:current', - '@type': '@id', - }, - 'first': { - '@id': 'as:first', - '@type': '@id', - }, - 'generator': { - '@id': 'as:generator', - '@type': '@id', - }, - 'icon': { - '@id': 'as:icon', - '@type': '@id', - }, - 'image': { - '@id': 'as:image', - '@type': '@id', - }, - 'inReplyTo': { - '@id': 'as:inReplyTo', - '@type': '@id', - }, - 'items': { - '@id': 'as:items', - '@type': '@id', - }, - 'instrument': { - '@id': 'as:instrument', - '@type': '@id', - }, - 'orderedItems': { - '@id': 'as:items', - '@type': '@id', - '@container': '@list', - }, - 'last': { - '@id': 'as:last', - '@type': '@id', - }, - 'location': { - '@id': 'as:location', - '@type': '@id', - }, - 'next': { - '@id': 'as:next', - '@type': '@id', - }, - 'object': { - '@id': 'as:object', - '@type': '@id', - }, - 'oneOf': { - '@id': 'as:oneOf', - '@type': '@id', - }, - 'anyOf': { - '@id': 'as:anyOf', - '@type': '@id', - }, - 'closed': { - '@id': 'as:closed', - '@type': 'xsd:dateTime', - }, - 'origin': { - '@id': 'as:origin', - '@type': '@id', - }, - 'accuracy': { - '@id': 'as:accuracy', - '@type': 'xsd:float', - }, - 'prev': { - '@id': 'as:prev', - '@type': '@id', - }, - 'preview': { - '@id': 'as:preview', - '@type': '@id', - }, - 'replies': { - '@id': 'as:replies', - '@type': '@id', - }, - 'result': { - '@id': 'as:result', - '@type': '@id', - }, - 'audience': { - '@id': 'as:audience', - '@type': '@id', - }, - 'partOf': { - '@id': 'as:partOf', - '@type': '@id', - }, - 'tag': { - '@id': 'as:tag', - '@type': '@id', - }, - 'target': { - '@id': 'as:target', - '@type': '@id', - }, - 'to': { - '@id': 'as:to', - '@type': '@id', - }, - 'url': { - '@id': 'as:url', - '@type': '@id', - }, - 'altitude': { - '@id': 'as:altitude', - '@type': 'xsd:float', - }, - 'content': 'as:content', - 'contentMap': { - '@id': 'as:content', - '@container': '@language', - }, - 'name': 'as:name', - 'nameMap': { - '@id': 'as:name', - '@container': '@language', - }, - 'duration': { - '@id': 'as:duration', - '@type': 'xsd:duration', - }, - 'endTime': { - '@id': 'as:endTime', - '@type': 'xsd:dateTime', - }, - 'height': { - '@id': 'as:height', - '@type': 'xsd:nonNegativeInteger', - }, - 'href': { - '@id': 'as:href', - '@type': '@id', - }, - 'hreflang': 'as:hreflang', - 'latitude': { - '@id': 'as:latitude', - '@type': 'xsd:float', - }, - 'longitude': { - '@id': 'as:longitude', - '@type': 'xsd:float', - }, - 'mediaType': 'as:mediaType', - 'published': { - '@id': 'as:published', - '@type': 'xsd:dateTime', - }, - 'radius': { - '@id': 'as:radius', - '@type': 'xsd:float', - }, - 'rel': 'as:rel', - 'startIndex': { - '@id': 'as:startIndex', - '@type': 'xsd:nonNegativeInteger', - }, - 'startTime': { - '@id': 'as:startTime', - '@type': 'xsd:dateTime', - }, - 'summary': 'as:summary', - 'summaryMap': { - '@id': 'as:summary', - '@container': '@language', - }, - 'totalItems': { - '@id': 'as:totalItems', - '@type': 'xsd:nonNegativeInteger', - }, - 'units': 'as:units', - 'updated': { - '@id': 'as:updated', - '@type': 'xsd:dateTime', - }, - 'width': { - '@id': 'as:width', - '@type': 'xsd:nonNegativeInteger', - }, - 'describes': { - '@id': 'as:describes', - '@type': '@id', - }, - 'formerType': { - '@id': 'as:formerType', - '@type': '@id', - }, - 'deleted': { - '@id': 'as:deleted', - '@type': 'xsd:dateTime', - }, - 'inbox': { - '@id': 'ldp:inbox', - '@type': '@id', - }, - 'outbox': { - '@id': 'as:outbox', - '@type': '@id', - }, - 'following': { - '@id': 'as:following', - '@type': '@id', - }, - 'followers': { - '@id': 'as:followers', - '@type': '@id', - }, - 'streams': { - '@id': 'as:streams', - '@type': '@id', - }, - 'preferredUsername': 'as:preferredUsername', - 'endpoints': { - '@id': 'as:endpoints', - '@type': '@id', - }, - 'uploadMedia': { - '@id': 'as:uploadMedia', - '@type': '@id', - }, - 'proxyUrl': { - '@id': 'as:proxyUrl', - '@type': '@id', - }, - 'liked': { - '@id': 'as:liked', - '@type': '@id', - }, - 'oauthAuthorizationEndpoint': { - '@id': 'as:oauthAuthorizationEndpoint', - '@type': '@id', - }, - 'oauthTokenEndpoint': { - '@id': 'as:oauthTokenEndpoint', - '@type': '@id', - }, - 'provideClientKey': { - '@id': 'as:provideClientKey', - '@type': '@id', - }, - 'signClientKey': { - '@id': 'as:signClientKey', - '@type': '@id', - }, - 'sharedInbox': { - '@id': 'as:sharedInbox', - '@type': '@id', - }, - 'Public': { - '@id': 'as:Public', - '@type': '@id', - }, - 'source': 'as:source', - 'likes': { - '@id': 'as:likes', - '@type': '@id', - }, - 'shares': { - '@id': 'as:shares', - '@type': '@id', - }, - 'alsoKnownAs': { - '@id': 'as:alsoKnownAs', - '@type': '@id', - }, - }, -}; - -export const CONTEXTS: Record = { - 'https://w3id.org/identity/v1': id_v1, - 'https://w3id.org/security/v1': security_v1, - 'https://www.w3.org/ns/activitystreams': activitystreams, -}; diff --git a/packages/backend/src/core/remote/activitypub/models/ApImageService.ts b/packages/backend/src/core/remote/activitypub/models/ApImageService.ts deleted file mode 100644 index 9bf87f19d4..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/ApImageService.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import type { CacheableRemoteUser } from '@/models/entities/User.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import { MetaService } from '@/core/MetaService.js'; -import { truncate } from '@/misc/truncate.js'; -import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; -import { DriveService } from '@/core/DriveService.js'; -import type Logger from '@/logger.js'; -import { ApResolverService } from '../ApResolverService.js'; -import { ApLoggerService } from '../ApLoggerService.js'; - -@Injectable() -export class ApImageService { - private logger: Logger; - - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.driveFilesRepository) - private driveFilesRepository: DriveFilesRepository, - - private metaService: MetaService, - private apResolverService: ApResolverService, - private driveService: DriveService, - private apLoggerService: ApLoggerService, - ) { - this.logger = this.apLoggerService.logger; - } - - /** - * Imageを作成します。 - */ - public async createImage(actor: CacheableRemoteUser, value: any): Promise { - // 投稿者が凍結されていたらスキップ - if (actor.isSuspended) { - throw new Error('actor has been suspended'); - } - - const image = await this.apResolverService.createResolver().resolve(value) as any; - - if (image.url == null) { - throw new Error('invalid image: url not privided'); - } - - this.logger.info(`Creating the Image: ${image.url}`); - - const instance = await this.metaService.fetch(); - - let file = await this.driveService.uploadFromUrl({ - url: image.url, - user: actor, - uri: image.url, - sensitive: image.sensitive, - isLink: !instance.cacheRemoteFiles, - comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH), - }); - - if (file.isLink) { - // URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、 - // URLを更新する - if (file.url !== image.url) { - await this.driveFilesRepository.update({ id: file.id }, { - url: image.url, - uri: image.url, - }); - - file = await this.driveFilesRepository.findOneByOrFail({ id: file.id }); - } - } - - return file; - } - - /** - * Imageを解決します。 - * - * Misskeyに対象のImageが登録されていればそれを返し、そうでなければ - * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 - */ - public async resolveImage(actor: CacheableRemoteUser, value: any): Promise { - // TODO - - // リモートサーバーからフェッチしてきて登録 - return await this.createImage(actor, value); - } -} diff --git a/packages/backend/src/core/remote/activitypub/models/ApMentionService.ts b/packages/backend/src/core/remote/activitypub/models/ApMentionService.ts deleted file mode 100644 index 1275e24c62..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/ApMentionService.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import promiseLimit from 'promise-limit'; -import { DI } from '@/di-symbols.js'; -import type { UsersRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import { toArray, unique } from '@/misc/prelude/array.js'; -import type { CacheableUser } from '@/models/entities/User.js'; -import { isMention } from '../type.js'; -import { ApResolverService, Resolver } from '../ApResolverService.js'; -import { ApPersonService } from './ApPersonService.js'; -import type { IObject, IApMention } from '../type.js'; - -@Injectable() -export class ApMentionService { - constructor( - @Inject(DI.config) - private config: Config, - - private apResolverService: ApResolverService, - private apPersonService: ApPersonService, - ) { - } - - public async extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) { - const hrefs = unique(this.extractApMentionObjects(tags).map(x => x.href as string)); - - const limit = promiseLimit(2); - const mentionedUsers = (await Promise.all( - hrefs.map(x => limit(() => this.apPersonService.resolvePerson(x, resolver).catch(() => null))), - )).filter((x): x is CacheableUser => x != null); - - return mentionedUsers; - } - - public extractApMentionObjects(tags: IObject | IObject[] | null | undefined): IApMention[] { - if (tags == null) return []; - return toArray(tags).filter(isMention); - } -} diff --git a/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts b/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts deleted file mode 100644 index 7cf6725a38..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/ApNoteService.ts +++ /dev/null @@ -1,403 +0,0 @@ -import { forwardRef, Inject, Injectable } from '@nestjs/common'; -import promiseLimit from 'promise-limit'; -import { DI } from '@/di-symbols.js'; -import type { MessagingMessagesRepository, PollsRepository, EmojisRepository } from '@/models/index.js'; -import type { UsersRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import type { CacheableRemoteUser } from '@/models/entities/User.js'; -import type { Note } from '@/models/entities/Note.js'; -import { toArray, toSingle, unique } from '@/misc/prelude/array.js'; -import type { Emoji } from '@/models/entities/Emoji.js'; -import { MetaService } from '@/core/MetaService.js'; -import { AppLockService } from '@/core/AppLockService.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import { NoteCreateService } from '@/core/NoteCreateService.js'; -import type Logger from '@/logger.js'; -import { IdService } from '@/core/IdService.js'; -import { PollService } from '@/core/PollService.js'; -import { StatusError } from '@/misc/status-error.js'; -import { UtilityService } from '@/core/UtilityService.js'; -import { MessagingService } from '@/core/MessagingService.js'; -import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js'; -// eslint-disable-next-line @typescript-eslint/consistent-type-imports -import { ApLoggerService } from '../ApLoggerService.js'; -import { ApMfmService } from '../ApMfmService.js'; -import { ApDbResolverService } from '../ApDbResolverService.js'; -import { ApResolverService } from '../ApResolverService.js'; -import { ApAudienceService } from '../ApAudienceService.js'; -import { ApPersonService } from './ApPersonService.js'; -import { extractApHashtags } from './tag.js'; -import { ApMentionService } from './ApMentionService.js'; -import { ApQuestionService } from './ApQuestionService.js'; -import { ApImageService } from './ApImageService.js'; -import type { Resolver } from '../ApResolverService.js'; -import type { IObject, IPost } from '../type.js'; - -@Injectable() -export class ApNoteService { - private logger: Logger; - - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.pollsRepository) - private pollsRepository: PollsRepository, - - @Inject(DI.emojisRepository) - private emojisRepository: EmojisRepository, - - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - - private idService: IdService, - private apMfmService: ApMfmService, - private apResolverService: ApResolverService, - - // 循環参照のため / for circular dependency - @Inject(forwardRef(() => ApPersonService)) - private apPersonService: ApPersonService, - - private utilityService: UtilityService, - private apAudienceService: ApAudienceService, - private apMentionService: ApMentionService, - private apImageService: ApImageService, - private apQuestionService: ApQuestionService, - private metaService: MetaService, - private messagingService: MessagingService, - private appLockService: AppLockService, - private pollService: PollService, - private noteCreateService: NoteCreateService, - private apDbResolverService: ApDbResolverService, - private apLoggerService: ApLoggerService, - ) { - this.logger = this.apLoggerService.logger; - } - - public validateNote(object: any, uri: string) { - const expectHost = this.utilityService.extractDbHost(uri); - - if (object == null) { - return new Error('invalid Note: object is null'); - } - - if (!validPost.includes(getApType(object))) { - return new Error(`invalid Note: invalid object type ${getApType(object)}`); - } - - if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) { - return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`); - } - - if (object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo)) !== expectHost) { - return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.attributedTo)}`); - } - - return null; - } - - /** - * Noteをフェッチします。 - * - * Misskeyに対象のNoteが登録されていればそれを返します。 - */ - public async fetchNote(object: string | IObject): Promise { - return await this.apDbResolverService.getNoteFromApId(object); - } - - /** - * Noteを作成します。 - */ - public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise { - if (resolver == null) resolver = this.apResolverService.createResolver(); - - const object: any = await resolver.resolve(value); - - const entryUri = getApId(value); - const err = this.validateNote(object, entryUri); - if (err) { - this.logger.error(`${err.message}`, { - resolver: { - history: resolver.getHistory(), - }, - value: value, - object: object, - }); - throw new Error('invalid note'); - } - - const note: IPost = object; - - this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); - - this.logger.info(`Creating the Note: ${note.id}`); - - // 投稿者をフェッチ - const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser; - - // 投稿者が凍結されていたらスキップ - if (actor.isSuspended) { - throw new Error('actor has been suspended'); - } - - const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver); - let visibility = noteAudience.visibility; - const visibleUsers = noteAudience.visibleUsers; - - // Audience (to, cc) が指定されてなかった場合 - if (visibility === 'specified' && visibleUsers.length === 0) { - if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している - // こちらから匿名GET出来たものならばpublic - visibility = 'public'; - } - } - - let isMessaging = note._misskey_talk && visibility === 'specified'; - - const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver); - const apHashtags = await extractApHashtags(note.tag); - - // 添付ファイル - // TODO: attachmentは必ずしもImageではない - // TODO: attachmentは必ずしも配列ではない - // Noteがsensitiveなら添付もsensitiveにする - const limit = promiseLimit(2); - - note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : []; - const files = note.attachment - .map(attach => attach.sensitive = note.sensitive) - ? (await Promise.all(note.attachment.map(x => limit(() => this.apImageService.resolveImage(actor, x)) as Promise))) - .filter(image => image != null) - : []; - - // リプライ - const reply: Note | null = note.inReplyTo - ? await this.resolveNote(note.inReplyTo, resolver).then(x => { - if (x == null) { - this.logger.warn('Specified inReplyTo, but nout found'); - throw new Error('inReplyTo not found'); - } else { - return x; - } - }).catch(async err => { - // トークだったらinReplyToのエラーは無視 - const uri = getApId(note.inReplyTo); - if (uri.startsWith(this.config.url + '/')) { - const id = uri.split('/').pop(); - const talk = await this.messagingMessagesRepository.findOneBy({ id }); - if (talk) { - isMessaging = true; - return null; - } - } - - this.logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`); - throw err; - }) - : null; - - // 引用 - let quote: Note | undefined | null; - - if (note._misskey_quote || note.quoteUrl) { - const tryResolveNote = async (uri: string): Promise<{ - status: 'ok'; - res: Note | null; - } | { - status: 'permerror' | 'temperror'; - }> => { - if (typeof uri !== 'string' || !uri.match(/^https?:/)) return { status: 'permerror' }; - try { - const res = await this.resolveNote(uri); - if (res) { - return { - status: 'ok', - res, - }; - } else { - return { - status: 'permerror', - }; - } - } catch (e) { - return { - status: (e instanceof StatusError && e.isClientError) ? 'permerror' : 'temperror', - }; - } - }; - - const uris = unique([note._misskey_quote, note.quoteUrl].filter((x): x is string => typeof x === 'string')); - const results = await Promise.all(uris.map(uri => tryResolveNote(uri))); - - quote = results.filter((x): x is { status: 'ok', res: Note | null } => x.status === 'ok').map(x => x.res).find(x => x); - if (!quote) { - if (results.some(x => x.status === 'temperror')) { - throw 'quote resolve failed'; - } - } - } - - const cw = note.summary === '' ? null : note.summary; - - // テキストのパース - let text: string | null = null; - if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') { - text = note.source.content; - } else if (typeof note._misskey_content !== 'undefined') { - text = note._misskey_content; - } else if (typeof note.content === 'string') { - text = this.apMfmService.htmlToMfm(note.content, note.tag); - } - - // vote - if (reply && reply.hasPoll) { - const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); - - const tryCreateVote = async (name: string, index: number): Promise => { - if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { - this.logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); - } else if (index >= 0) { - this.logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); - await this.pollService.vote(actor, reply, index); - - // リモートフォロワーにUpdate配信 - this.pollService.deliverQuestionUpdate(reply.id); - } - return null; - }; - - if (note.name) { - return await tryCreateVote(note.name, poll.choices.findIndex(x => x === note.name)); - } - } - - const emojis = await this.extractEmojis(note.tag ?? [], actor.host).catch(e => { - this.logger.info(`extractEmojis: ${e}`); - return [] as Emoji[]; - }); - - const apEmojis = emojis.map(emoji => emoji.name); - - const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined); - - if (isMessaging) { - for (const recipient of visibleUsers) { - await this.messagingService.createMessage(actor, recipient, undefined, text ?? undefined, (files && files.length > 0) ? files[0] : null, object.id); - return null; - } - } - - return await this.noteCreateService.create(actor, { - createdAt: note.published ? new Date(note.published) : null, - files, - reply, - renote: quote, - name: note.name, - cw, - text, - localOnly: false, - visibility, - visibleUsers, - apMentions, - apHashtags, - apEmojis, - poll, - uri: note.id, - url: getOneApHrefNullable(note.url), - }, silent); - } - - /** - * Noteを解決します。 - * - * Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ - * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 - */ - public async resolveNote(value: string | IObject, resolver?: Resolver): Promise { - const uri = typeof value === 'string' ? value : value.id; - if (uri == null) throw new Error('missing uri'); - - // ブロックしてたら中断 - const meta = await this.metaService.fetch(); - if (meta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) throw { statusCode: 451 }; - - const unlock = await this.appLockService.getApLock(uri); - - try { - //#region このサーバーに既に登録されていたらそれを返す - const exist = await this.fetchNote(uri); - - if (exist) { - return exist; - } - //#endregion - - if (uri.startsWith(this.config.url)) { - throw new StatusError('cannot resolve local note', 400, 'cannot resolve local note'); - } - - // リモートサーバーからフェッチしてきて登録 - // ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが - // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 - return await this.createNote(uri, resolver, true); - } finally { - unlock(); - } - } - - public async extractEmojis(tags: IObject | IObject[], host: string): Promise { - host = this.utilityService.toPuny(host); - - if (!tags) return []; - - const eomjiTags = toArray(tags).filter(isEmoji); - - return await Promise.all(eomjiTags.map(async tag => { - const name = tag.name!.replace(/^:/, '').replace(/:$/, ''); - tag.icon = toSingle(tag.icon); - - const exists = await this.emojisRepository.findOneBy({ - host, - name, - }); - - if (exists) { - if ((tag.updated != null && exists.updatedAt == null) - || (tag.id != null && exists.uri == null) - || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt) - || (tag.icon!.url !== exists.originalUrl) - ) { - await this.emojisRepository.update({ - host, - name, - }, { - uri: tag.id, - originalUrl: tag.icon!.url, - publicUrl: tag.icon!.url, - updatedAt: new Date(), - }); - - return await this.emojisRepository.findOneBy({ - host, - name, - }) as Emoji; - } - - return exists; - } - - this.logger.info(`register emoji host=${host}, name=${name}`); - - return await this.emojisRepository.insert({ - id: this.idService.genId(), - host, - name, - uri: tag.id, - originalUrl: tag.icon!.url, - publicUrl: tag.icon!.url, - updatedAt: new Date(), - aliases: [], - } as Partial).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0])); - })); - } -} diff --git a/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts b/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts deleted file mode 100644 index f9d6f42ef6..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/ApPersonService.ts +++ /dev/null @@ -1,594 +0,0 @@ -import { forwardRef, Inject, Injectable } from '@nestjs/common'; -import promiseLimit from 'promise-limit'; -import { DataSource } from 'typeorm'; -import { ModuleRef } from '@nestjs/core'; -import { DI } from '@/di-symbols.js'; -import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import type { CacheableUser, IRemoteUser } from '@/models/entities/User.js'; -import { User } from '@/models/entities/User.js'; -import { truncate } from '@/misc/truncate.js'; -import type { UserCacheService } from '@/core/UserCacheService.js'; -import { normalizeForSearch } from '@/misc/normalize-for-search.js'; -import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; -import type Logger from '@/logger.js'; -import type { Note } from '@/models/entities/Note.js'; -import type { IdService } from '@/core/IdService.js'; -import type { MfmService } from '@/core/MfmService.js'; -import type { Emoji } from '@/models/entities/Emoji.js'; -import { toArray } from '@/misc/prelude/array.js'; -import type { GlobalEventService } from '@/core/GlobalEventService.js'; -import type { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; -import type { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; -import { UserProfile } from '@/models/entities/UserProfile.js'; -import { UserPublickey } from '@/models/entities/UserPublickey.js'; -import type UsersChart from '@/core/chart/charts/users.js'; -import type InstanceChart from '@/core/chart/charts/instance.js'; -import type { HashtagService } from '@/core/HashtagService.js'; -import { UserNotePining } from '@/models/entities/UserNotePining.js'; -import { StatusError } from '@/misc/status-error.js'; -import type { UtilityService } from '@/core/UtilityService.js'; -import type { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js'; -import { extractApHashtags } from './tag.js'; -import type { OnModuleInit } from '@nestjs/common'; -import type { ApNoteService } from './ApNoteService.js'; -import type { ApMfmService } from '../ApMfmService.js'; -import type { ApResolverService, Resolver } from '../ApResolverService.js'; -import type { ApLoggerService } from '../ApLoggerService.js'; -// eslint-disable-next-line @typescript-eslint/consistent-type-imports -import type { ApImageService } from './ApImageService.js'; -import type { IActor, IObject, IApPropertyValue } from '../type.js'; - -const nameLength = 128; -const summaryLength = 2048; - -const services: { - [x: string]: (id: string, username: string) => any -} = { - 'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }), - 'misskey:authentication:github': (id, login) => ({ id, login }), - 'misskey:authentication:discord': (id, name) => $discord(id, name), -}; - -const $discord = (id: string, name: string) => { - if (typeof name !== 'string') { - name = 'unknown#0000'; - } - const [username, discriminator] = name.split('#'); - return { id, username, discriminator }; -}; - -function addService(target: { [x: string]: any }, source: IApPropertyValue) { - const service = services[source.name]; - - if (typeof source.value !== 'string') { - source.value = 'unknown'; - } - - const [id, username] = source.value.split('@'); - - if (service) { - target[source.name.split(':')[2]] = service(id, username); - } -} - -@Injectable() -export class ApPersonService implements OnModuleInit { - private utilityService: UtilityService; - private userEntityService: UserEntityService; - private idService: IdService; - private globalEventService: GlobalEventService; - private federatedInstanceService: FederatedInstanceService; - private fetchInstanceMetadataService: FetchInstanceMetadataService; - private userCacheService: UserCacheService; - private apResolverService: ApResolverService; - private apNoteService: ApNoteService; - private apImageService: ApImageService; - private apMfmService: ApMfmService; - private mfmService: MfmService; - private hashtagService: HashtagService; - private usersChart: UsersChart; - private instanceChart: InstanceChart; - private apLoggerService: ApLoggerService; - private logger: Logger; - - constructor( - private moduleRef: ModuleRef, - - @Inject(DI.config) - private config: Config, - - @Inject(DI.db) - private db: DataSource, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.userProfilesRepository) - private userProfilesRepository: UserProfilesRepository, - - @Inject(DI.userPublickeysRepository) - private userPublickeysRepository: UserPublickeysRepository, - - @Inject(DI.instancesRepository) - private instancesRepository: InstancesRepository, - - @Inject(DI.followingsRepository) - private followingsRepository: FollowingsRepository, - - //private utilityService: UtilityService, - //private userEntityService: UserEntityService, - //private idService: IdService, - //private globalEventService: GlobalEventService, - //private federatedInstanceService: FederatedInstanceService, - //private fetchInstanceMetadataService: FetchInstanceMetadataService, - //private userCacheService: UserCacheService, - //private apResolverService: ApResolverService, - //private apNoteService: ApNoteService, - //private apImageService: ApImageService, - //private apMfmService: ApMfmService, - //private mfmService: MfmService, - //private hashtagService: HashtagService, - //private usersChart: UsersChart, - //private instanceChart: InstanceChart, - //private apLoggerService: ApLoggerService, - ) { - } - - onModuleInit() { - this.utilityService = this.moduleRef.get('UtilityService'); - this.userEntityService = this.moduleRef.get('UserEntityService'); - this.idService = this.moduleRef.get('IdService'); - this.globalEventService = this.moduleRef.get('GlobalEventService'); - this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService'); - this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService'); - this.userCacheService = this.moduleRef.get('UserCacheService'); - this.apResolverService = this.moduleRef.get('ApResolverService'); - this.apNoteService = this.moduleRef.get('ApNoteService'); - this.apImageService = this.moduleRef.get('ApImageService'); - this.apMfmService = this.moduleRef.get('ApMfmService'); - this.mfmService = this.moduleRef.get('MfmService'); - this.hashtagService = this.moduleRef.get('HashtagService'); - this.usersChart = this.moduleRef.get('UsersChart'); - this.instanceChart = this.moduleRef.get('InstanceChart'); - this.apLoggerService = this.moduleRef.get('ApLoggerService'); - this.logger = this.apLoggerService.logger; - } - - /** - * Validate and convert to actor object - * @param x Fetched object - * @param uri Fetch target URI - */ - private validateActor(x: IObject, uri: string): IActor { - const expectHost = this.utilityService.toPuny(new URL(uri).hostname); - - if (x == null) { - throw new Error('invalid Actor: object is null'); - } - - if (!isActor(x)) { - throw new Error(`invalid Actor type '${x.type}'`); - } - - if (!(typeof x.id === 'string' && x.id.length > 0)) { - throw new Error('invalid Actor: wrong id'); - } - - if (!(typeof x.inbox === 'string' && x.inbox.length > 0)) { - throw new Error('invalid Actor: wrong inbox'); - } - - if (!(typeof x.preferredUsername === 'string' && x.preferredUsername.length > 0 && x.preferredUsername.length <= 128 && /^\w([\w-.]*\w)?$/.test(x.preferredUsername))) { - throw new Error('invalid Actor: wrong username'); - } - - // These fields are only informational, and some AP software allows these - // fields to be very long. If they are too long, we cut them off. This way - // we can at least see these users and their activities. - if (x.name) { - if (!(typeof x.name === 'string' && x.name.length > 0)) { - throw new Error('invalid Actor: wrong name'); - } - x.name = truncate(x.name, nameLength); - } - if (x.summary) { - if (!(typeof x.summary === 'string' && x.summary.length > 0)) { - throw new Error('invalid Actor: wrong summary'); - } - x.summary = truncate(x.summary, summaryLength); - } - - const idHost = this.utilityService.toPuny(new URL(x.id!).hostname); - if (idHost !== expectHost) { - throw new Error('invalid Actor: id has different host'); - } - - if (x.publicKey) { - if (typeof x.publicKey.id !== 'string') { - throw new Error('invalid Actor: publicKey.id is not a string'); - } - - const publicKeyIdHost = this.utilityService.toPuny(new URL(x.publicKey.id).hostname); - if (publicKeyIdHost !== expectHost) { - throw new Error('invalid Actor: publicKey.id has different host'); - } - } - - return x; - } - - /** - * Personをフェッチします。 - * - * Misskeyに対象のPersonが登録されていればそれを返します。 - */ - public async fetchPerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw new Error('uri is not string'); - - const cached = this.userCacheService.uriPersonCache.get(uri); - if (cached) return cached; - - // URIがこのサーバーを指しているならデータベースからフェッチ - if (uri.startsWith(this.config.url + '/')) { - const id = uri.split('/').pop(); - const u = await this.usersRepository.findOneBy({ id }); - if (u) this.userCacheService.uriPersonCache.set(uri, u); - return u; - } - - //#region このサーバーに既に登録されていたらそれを返す - const exist = await this.usersRepository.findOneBy({ uri }); - - if (exist) { - this.userCacheService.uriPersonCache.set(uri, exist); - return exist; - } - //#endregion - - return null; - } - - /** - * Personを作成します。 - */ - public async createPerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw new Error('uri is not string'); - - if (uri.startsWith(this.config.url)) { - throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user'); - } - - if (resolver == null) resolver = this.apResolverService.createResolver(); - - const object = await resolver.resolve(uri) as any; - - const person = this.validateActor(object, uri); - - this.logger.info(`Creating the Person: ${person.id}`); - - const host = this.utilityService.toPuny(new URL(object.id).hostname); - - const { fields } = this.analyzeAttachments(person.attachment ?? []); - - const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32); - - const isBot = getApType(object) === 'Service'; - - const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); - - // Create user - let user: IRemoteUser; - try { - // Start transaction - await this.db.transaction(async transactionalEntityManager => { - user = await transactionalEntityManager.save(new User({ - id: this.idService.genId(), - avatarId: null, - bannerId: null, - createdAt: new Date(), - lastFetchedAt: new Date(), - name: truncate(person.name, nameLength), - isLocked: !!person.manuallyApprovesFollowers, - isExplorable: !!person.discoverable, - username: person.preferredUsername, - usernameLower: person.preferredUsername!.toLowerCase(), - host, - inbox: person.inbox, - sharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), - followersUri: person.followers ? getApId(person.followers) : undefined, - featured: person.featured ? getApId(person.featured) : undefined, - uri: person.id, - tags, - isBot, - isCat: (person as any).isCat === true, - showTimelineReplies: false, - })) as IRemoteUser; - - await transactionalEntityManager.save(new UserProfile({ - userId: user.id, - description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, - url: getOneApHrefNullable(person.url), - fields, - birthday: bday ? bday[0] : null, - location: person['vcard:Address'] ?? null, - userHost: host, - })); - - if (person.publicKey) { - await transactionalEntityManager.save(new UserPublickey({ - userId: user.id, - keyId: person.publicKey.id, - keyPem: person.publicKey.publicKeyPem, - })); - } - }); - } catch (e) { - // duplicate key error - if (isDuplicateKeyValueError(e)) { - // /users/@a => /users/:id のように入力がaliasなときにエラーになることがあるのを対応 - const u = await this.usersRepository.findOneBy({ - uri: person.id, - }); - - if (u) { - user = u as IRemoteUser; - } else { - throw new Error('already registered'); - } - } else { - this.logger.error(e instanceof Error ? e : new Error(e as string)); - throw e; - } - } - - // Register host - this.federatedInstanceService.registerOrFetchInstanceDoc(host).then(i => { - this.instancesRepository.increment({ id: i.id }, 'usersCount', 1); - this.instanceChart.newUser(i.host); - this.fetchInstanceMetadataService.fetchInstanceMetadata(i); - }); - - this.usersChart.update(user!, true); - - // ハッシュタグ更新 - this.hashtagService.updateUsertags(user!, tags); - - //#region アバターとヘッダー画像をフェッチ - const [avatar, banner] = await Promise.all([ - person.icon, - person.image, - ].map(img => - img == null - ? Promise.resolve(null) - : this.apImageService.resolveImage(user!, img).catch(() => null), - )); - - const avatarId = avatar ? avatar.id : null; - const bannerId = banner ? banner.id : null; - - await this.usersRepository.update(user!.id, { - avatarId, - bannerId, - }); - - user!.avatarId = avatarId; - user!.bannerId = bannerId; - //#endregion - - //#region カスタム絵文字取得 - const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host).catch(err => { - this.logger.info(`extractEmojis: ${err}`); - return [] as Emoji[]; - }); - - const emojiNames = emojis.map(emoji => emoji.name); - - await this.usersRepository.update(user!.id, { - emojis: emojiNames, - }); - //#endregion - - await this.updateFeatured(user!.id, resolver).catch(err => this.logger.error(err)); - - return user!; - } - - /** - * Personの情報を更新します。 - * Misskeyに対象のPersonが登録されていなければ無視します。 - * @param uri URI of Person - * @param resolver Resolver - * @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します) - */ - public async updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise { - if (typeof uri !== 'string') throw new Error('uri is not string'); - - // URIがこのサーバーを指しているならスキップ - if (uri.startsWith(this.config.url + '/')) { - return; - } - - //#region このサーバーに既に登録されているか - const exist = await this.usersRepository.findOneBy({ uri }) as IRemoteUser; - - if (exist == null) { - return; - } - //#endregion - - if (resolver == null) resolver = this.apResolverService.createResolver(); - - const object = hint ?? await resolver.resolve(uri); - - const person = this.validateActor(object, uri); - - this.logger.info(`Updating the Person: ${person.id}`); - - // アバターとヘッダー画像をフェッチ - const [avatar, banner] = await Promise.all([ - person.icon, - person.image, - ].map(img => - img == null - ? Promise.resolve(null) - : this.apImageService.resolveImage(exist, img).catch(() => null), - )); - - // カスタム絵文字取得 - const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => { - this.logger.info(`extractEmojis: ${e}`); - return [] as Emoji[]; - }); - - const emojiNames = emojis.map(emoji => emoji.name); - - const { fields } = this.analyzeAttachments(person.attachment ?? []); - - const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32); - - const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); - - const updates = { - lastFetchedAt: new Date(), - inbox: person.inbox, - sharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), - followersUri: person.followers ? getApId(person.followers) : undefined, - featured: person.featured, - emojis: emojiNames, - name: truncate(person.name, nameLength), - tags, - isBot: getApType(object) === 'Service', - isCat: (person as any).isCat === true, - isLocked: !!person.manuallyApprovesFollowers, - isExplorable: !!person.discoverable, - } as Partial; - - if (avatar) { - updates.avatarId = avatar.id; - } - - if (banner) { - updates.bannerId = banner.id; - } - - // Update user - await this.usersRepository.update(exist.id, updates); - - if (person.publicKey) { - await this.userPublickeysRepository.update({ userId: exist.id }, { - keyId: person.publicKey.id, - keyPem: person.publicKey.publicKeyPem, - }); - } - - await this.userProfilesRepository.update({ userId: exist.id }, { - url: getOneApHrefNullable(person.url), - fields, - description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, - birthday: bday ? bday[0] : null, - location: person['vcard:Address'] ?? null, - }); - - this.globalEventService.publishInternalEvent('remoteUserUpdated', { id: exist.id }); - - // ハッシュタグ更新 - this.hashtagService.updateUsertags(exist, tags); - - // 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする - await this.followingsRepository.update({ - followerId: exist.id, - }, { - followerSharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), - }); - - await this.updateFeatured(exist.id, resolver).catch(err => this.logger.error(err)); - } - - /** - * Personを解決します。 - * - * Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ - * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 - */ - public async resolvePerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw new Error('uri is not string'); - - //#region このサーバーに既に登録されていたらそれを返す - const exist = await this.fetchPerson(uri); - - if (exist) { - return exist; - } - //#endregion - - // リモートサーバーからフェッチしてきて登録 - if (resolver == null) resolver = this.apResolverService.createResolver(); - return await this.createPerson(uri, resolver); - } - - public analyzeAttachments(attachments: IObject | IObject[] | undefined) { - const fields: { - name: string, - value: string - }[] = []; - const services: { [x: string]: any } = {}; - - if (Array.isArray(attachments)) { - for (const attachment of attachments.filter(isPropertyValue)) { - if (isPropertyValue(attachment.identifier)) { - addService(services, attachment.identifier); - } else { - fields.push({ - name: attachment.name, - value: this.mfmService.fromHtml(attachment.value), - }); - } - } - } - - return { fields, services }; - } - - public async updateFeatured(userId: User['id'], resolver?: Resolver) { - const user = await this.usersRepository.findOneByOrFail({ id: userId }); - if (!this.userEntityService.isRemoteUser(user)) return; - if (!user.featured) return; - - this.logger.info(`Updating the featured: ${user.uri}`); - - if (resolver == null) resolver = this.apResolverService.createResolver(); - - // Resolve to (Ordered)Collection Object - const collection = await resolver.resolveCollection(user.featured); - if (!isCollectionOrOrderedCollection(collection)) throw new Error('Object is not Collection or OrderedCollection'); - - // Resolve to Object(may be Note) arrays - const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems; - const items = await Promise.all(toArray(unresolvedItems).map(x => resolver.resolve(x))); - - // Resolve and regist Notes - const limit = promiseLimit(2); - const featuredNotes = await Promise.all(items - .filter(item => getApType(item) === 'Note') // TODO: Noteでなくてもいいかも - .slice(0, 5) - .map(item => limit(() => this.apNoteService.resolveNote(item, resolver)))); - - await this.db.transaction(async transactionalEntityManager => { - await transactionalEntityManager.delete(UserNotePining, { userId: user.id }); - - // とりあえずidを別の時間で生成して順番を維持 - let td = 0; - for (const note of featuredNotes.filter(note => note != null)) { - td -= 1000; - transactionalEntityManager.insert(UserNotePining, { - id: this.idService.genId(new Date(Date.now() + td)), - createdAt: new Date(), - userId: user.id, - noteId: note!.id, - }); - } - }); - } -} diff --git a/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts deleted file mode 100644 index 5793b98353..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/ApQuestionService.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { DI } from '@/di-symbols.js'; -import type { NotesRepository, PollsRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; -import type { IPoll } from '@/models/entities/Poll.js'; -import type Logger from '@/logger.js'; -import { isQuestion } from '../type.js'; -import { ApLoggerService } from '../ApLoggerService.js'; -import { ApResolverService } from '../ApResolverService.js'; -import type { Resolver } from '../ApResolverService.js'; -import type { IObject, IQuestion } from '../type.js'; - -@Injectable() -export class ApQuestionService { - private logger: Logger; - - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.pollsRepository) - private pollsRepository: PollsRepository, - - private apResolverService: ApResolverService, - private apLoggerService: ApLoggerService, - ) { - this.logger = this.apLoggerService.logger; - } - - public async extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise { - if (resolver == null) resolver = this.apResolverService.createResolver(); - - const question = await resolver.resolve(source); - - if (!isQuestion(question)) { - throw new Error('invalid type'); - } - - const multiple = !question.oneOf; - const expiresAt = question.endTime ? new Date(question.endTime) : question.closed ? new Date(question.closed) : null; - - if (multiple && !question.anyOf) { - throw new Error('invalid question'); - } - - const choices = question[multiple ? 'anyOf' : 'oneOf']! - .map((x, i) => x.name!); - - const votes = question[multiple ? 'anyOf' : 'oneOf']! - .map((x, i) => x.replies && x.replies.totalItems || x._misskey_votes || 0); - - return { - choices, - votes, - multiple, - expiresAt, - }; - } - - /** - * Update votes of Question - * @param uri URI of AP Question object - * @returns true if updated - */ - public async updateQuestion(value: any, resolver?: Resolver) { - const uri = typeof value === 'string' ? value : value.id; - - // URIがこのサーバーを指しているならスキップ - if (uri.startsWith(this.config.url + '/')) throw new Error('uri points local'); - - //#region このサーバーに既に登録されているか - const note = await this.notesRepository.findOneBy({ uri }); - if (note == null) throw new Error('Question is not registed'); - - const poll = await this.pollsRepository.findOneBy({ noteId: note.id }); - if (poll == null) throw new Error('Question is not registed'); - //#endregion - - // resolve new Question object - if (resolver == null) resolver = this.apResolverService.createResolver(); - const question = await resolver.resolve(value) as IQuestion; - this.logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); - - if (question.type !== 'Question') throw new Error('object is not a Question'); - - const apChoices = question.oneOf ?? question.anyOf; - - let changed = false; - - for (const choice of poll.choices) { - const oldCount = poll.votes[poll.choices.indexOf(choice)]; - const newCount = apChoices!.filter(ap => ap.name === choice)[0].replies!.totalItems; - - if (oldCount !== newCount) { - changed = true; - poll.votes[poll.choices.indexOf(choice)] = newCount; - } - } - - await this.pollsRepository.update({ noteId: note.id }, { - votes: poll.votes, - }); - - return changed; - } -} diff --git a/packages/backend/src/core/remote/activitypub/models/icon.ts b/packages/backend/src/core/remote/activitypub/models/icon.ts deleted file mode 100644 index 50794a937d..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/icon.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type IIcon = { - type: string; - mediaType?: string; - url?: string; -}; diff --git a/packages/backend/src/core/remote/activitypub/models/identifier.ts b/packages/backend/src/core/remote/activitypub/models/identifier.ts deleted file mode 100644 index f6c3bb8c88..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/identifier.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type IIdentifier = { - type: string; - name: string; - value: string; -}; diff --git a/packages/backend/src/core/remote/activitypub/models/tag.ts b/packages/backend/src/core/remote/activitypub/models/tag.ts deleted file mode 100644 index 803846a0b0..0000000000 --- a/packages/backend/src/core/remote/activitypub/models/tag.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { toArray } from '@/misc/prelude/array.js'; -import { isHashtag } from '../type.js'; -import type { IObject, IApHashtag } from '../type.js'; - -export function extractApHashtags(tags: IObject | IObject[] | null | undefined) { - if (tags == null) return []; - - const hashtags = extractApHashtagObjects(tags); - - return hashtags.map(tag => { - const m = tag.name.match(/^#(.+)/); - return m ? m[1] : null; - }).filter((x): x is string => x != null); -} - -export function extractApHashtagObjects(tags: IObject | IObject[] | null | undefined): IApHashtag[] { - if (tags == null) return []; - return toArray(tags).filter(isHashtag); -} diff --git a/packages/backend/src/core/remote/activitypub/type.ts b/packages/backend/src/core/remote/activitypub/type.ts deleted file mode 100644 index dcc5110aa5..0000000000 --- a/packages/backend/src/core/remote/activitypub/type.ts +++ /dev/null @@ -1,296 +0,0 @@ -export type obj = { [x: string]: any }; -export type ApObject = IObject | string | (IObject | string)[]; - -export interface IObject { - '@context': string | string[] | obj | obj[]; - type: string | string[]; - id?: string; - summary?: string; - published?: string; - cc?: ApObject; - to?: ApObject; - attributedTo: ApObject; - attachment?: any[]; - inReplyTo?: any; - replies?: ICollection; - content?: string; - name?: string; - startTime?: Date; - endTime?: Date; - icon?: any; - image?: any; - url?: ApObject; - href?: string; - tag?: IObject | IObject[]; - sensitive?: boolean; -} - -/** - * Get array of ActivityStreams Objects id - */ -export function getApIds(value: ApObject | undefined): string[] { - if (value == null) return []; - const array = Array.isArray(value) ? value : [value]; - return array.map(x => getApId(x)); -} - -/** - * Get first ActivityStreams Object id - */ -export function getOneApId(value: ApObject): string { - const firstOne = Array.isArray(value) ? value[0] : value; - return getApId(firstOne); -} - -/** - * Get ActivityStreams Object id - */ -export function getApId(value: string | IObject): string { - if (typeof value === 'string') return value; - if (typeof value.id === 'string') return value.id; - throw new Error('cannot detemine id'); -} - -/** - * Get ActivityStreams Object type - */ -export function getApType(value: IObject): string { - if (typeof value.type === 'string') return value.type; - if (Array.isArray(value.type) && typeof value.type[0] === 'string') return value.type[0]; - throw new Error('cannot detect type'); -} - -export function getOneApHrefNullable(value: ApObject | undefined): string | undefined { - const firstOne = Array.isArray(value) ? value[0] : value; - return getApHrefNullable(firstOne); -} - -export function getApHrefNullable(value: string | IObject | undefined): string | undefined { - if (typeof value === 'string') return value; - if (typeof value?.href === 'string') return value.href; - return undefined; -} - -export interface IActivity extends IObject { - //type: 'Activity'; - actor: IObject | string; - object: IObject | string; - target?: IObject | string; - /** LD-Signature */ - signature?: { - type: string; - created: Date; - creator: string; - domain?: string; - nonce?: string; - signatureValue: string; - }; -} - -export interface ICollection extends IObject { - type: 'Collection'; - totalItems: number; - items: ApObject; -} - -export interface IOrderedCollection extends IObject { - type: 'OrderedCollection'; - totalItems: number; - orderedItems: ApObject; -} - -export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video', 'Event']; - -export const isPost = (object: IObject): object is IPost => - validPost.includes(getApType(object)); - -export interface IPost extends IObject { - type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video' | 'Event'; - source?: { - content: string; - mediaType: string; - }; - _misskey_quote?: string; - _misskey_content?: string; - quoteUrl?: string; - _misskey_talk?: boolean; -} - -export interface IQuestion extends IObject { - type: 'Note' | 'Question'; - source?: { - content: string; - mediaType: string; - }; - _misskey_quote?: string; - quoteUrl?: string; - oneOf?: IQuestionChoice[]; - anyOf?: IQuestionChoice[]; - endTime?: Date; - closed?: Date; -} - -export const isQuestion = (object: IObject): object is IQuestion => - getApType(object) === 'Note' || getApType(object) === 'Question'; - -interface IQuestionChoice { - name?: string; - replies?: ICollection; - _misskey_votes?: number; -} -export interface ITombstone extends IObject { - type: 'Tombstone'; - formerType?: string; - deleted?: Date; -} - -export const isTombstone = (object: IObject): object is ITombstone => - getApType(object) === 'Tombstone'; - -export const validActor = ['Person', 'Service', 'Group', 'Organization', 'Application']; - -export const isActor = (object: IObject): object is IActor => - validActor.includes(getApType(object)); - -export interface IActor extends IObject { - type: 'Person' | 'Service' | 'Organization' | 'Group' | 'Application'; - name?: string; - preferredUsername?: string; - manuallyApprovesFollowers?: boolean; - discoverable?: boolean; - inbox: string; - sharedInbox?: string; // 後方互換性のため - publicKey?: { - id: string; - publicKeyPem: string; - }; - followers?: string | ICollection | IOrderedCollection; - following?: string | ICollection | IOrderedCollection; - featured?: string | IOrderedCollection; - outbox: string | IOrderedCollection; - endpoints?: { - sharedInbox?: string; - }; - 'vcard:bday'?: string; - 'vcard:Address'?: string; -} - -export const isCollection = (object: IObject): object is ICollection => - getApType(object) === 'Collection'; - -export const isOrderedCollection = (object: IObject): object is IOrderedCollection => - getApType(object) === 'OrderedCollection'; - -export const isCollectionOrOrderedCollection = (object: IObject): object is ICollection | IOrderedCollection => - isCollection(object) || isOrderedCollection(object); - -export interface IApPropertyValue extends IObject { - type: 'PropertyValue'; - identifier: IApPropertyValue; - name: string; - value: string; -} - -export const isPropertyValue = (object: IObject): object is IApPropertyValue => - object && - getApType(object) === 'PropertyValue' && - typeof object.name === 'string' && - typeof (object as any).value === 'string'; - -export interface IApMention extends IObject { - type: 'Mention'; - href: string; -} - -export const isMention = (object: IObject): object is IApMention => - getApType(object) === 'Mention' && - typeof object.href === 'string'; - -export interface IApHashtag extends IObject { - type: 'Hashtag'; - name: string; -} - -export const isHashtag = (object: IObject): object is IApHashtag => - getApType(object) === 'Hashtag' && - typeof object.name === 'string'; - -export interface IApEmoji extends IObject { - type: 'Emoji'; - updated: Date; -} - -export const isEmoji = (object: IObject): object is IApEmoji => - getApType(object) === 'Emoji' && !Array.isArray(object.icon) && object.icon.url != null; - -export interface ICreate extends IActivity { - type: 'Create'; -} - -export interface IDelete extends IActivity { - type: 'Delete'; -} - -export interface IUpdate extends IActivity { - type: 'Update'; -} - -export interface IRead extends IActivity { - type: 'Read'; -} - -export interface IUndo extends IActivity { - type: 'Undo'; -} - -export interface IFollow extends IActivity { - type: 'Follow'; -} - -export interface IAccept extends IActivity { - type: 'Accept'; -} - -export interface IReject extends IActivity { - type: 'Reject'; -} - -export interface IAdd extends IActivity { - type: 'Add'; -} - -export interface IRemove extends IActivity { - type: 'Remove'; -} - -export interface ILike extends IActivity { - type: 'Like' | 'EmojiReaction' | 'EmojiReact'; - _misskey_reaction?: string; -} - -export interface IAnnounce extends IActivity { - type: 'Announce'; -} - -export interface IBlock extends IActivity { - type: 'Block'; -} - -export interface IFlag extends IActivity { - type: 'Flag'; -} - -export const isCreate = (object: IObject): object is ICreate => getApType(object) === 'Create'; -export const isDelete = (object: IObject): object is IDelete => getApType(object) === 'Delete'; -export const isUpdate = (object: IObject): object is IUpdate => getApType(object) === 'Update'; -export const isRead = (object: IObject): object is IRead => getApType(object) === 'Read'; -export const isUndo = (object: IObject): object is IUndo => getApType(object) === 'Undo'; -export const isFollow = (object: IObject): object is IFollow => getApType(object) === 'Follow'; -export const isAccept = (object: IObject): object is IAccept => getApType(object) === 'Accept'; -export const isReject = (object: IObject): object is IReject => getApType(object) === 'Reject'; -export const isAdd = (object: IObject): object is IAdd => getApType(object) === 'Add'; -export const isRemove = (object: IObject): object is IRemove => getApType(object) === 'Remove'; -export const isLike = (object: IObject): object is ILike => getApType(object) === 'Like' || getApType(object) === 'EmojiReaction' || getApType(object) === 'EmojiReact'; -export const isAnnounce = (object: IObject): object is IAnnounce => getApType(object) === 'Announce'; -export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block'; -export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag'; diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 9042a21d2c..2a4b201a7d 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -5,7 +5,7 @@ import type { DriveFilesRepository, InstancesRepository } from '@/models/index.j import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { MetaService } from '@/core/MetaService.js'; -import { ApRequestService } from '@/core/remote/activitypub/ApRequestService.js'; +import { ApRequestService } from '@/core/activitypub/ApRequestService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; import { Cache } from '@/misc/cache.js'; diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index 6c6789cff4..9442a60d8d 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -5,7 +5,7 @@ import type { UsersRepository, BlockingsRepository, DriveFilesRepository } from import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; -import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; +import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -32,7 +32,7 @@ export class ImportBlockingProcessorService { private utilityService: UtilityService, private userBlockingService: UserBlockingService, - private resolveUserService: ResolveUserService, + private remoteUserResolveService: RemoteUserResolveService, private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { @@ -78,7 +78,7 @@ export class ImportBlockingProcessorService { if (host == null && target == null) continue; if (target == null) { - target = await this.resolveUserService.resolveUser(username, host); + target = await this.remoteUserResolveService.resolveUser(username, host); } if (target == null) { diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index 9f2e5e801a..667f7279fb 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -5,7 +5,7 @@ import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; -import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; +import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -29,7 +29,7 @@ export class ImportFollowingProcessorService { private utilityService: UtilityService, private userFollowingService: UserFollowingService, - private resolveUserService: ResolveUserService, + private remoteUserResolveService: RemoteUserResolveService, private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { @@ -75,7 +75,7 @@ export class ImportFollowingProcessorService { if (host == null && target == null) continue; if (target == null) { - target = await this.resolveUserService.resolveUser(username, host); + target = await this.remoteUserResolveService.resolveUser(username, host); } if (target == null) { diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index 62ad3b5c88..f3c16e73d5 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -5,7 +5,7 @@ import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; -import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; +import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -29,7 +29,7 @@ export class ImportMutingProcessorService { private utilityService: UtilityService, private userMutingService: UserMutingService, - private resolveUserService: ResolveUserService, + private remoteUserResolveService: RemoteUserResolveService, private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { @@ -75,7 +75,7 @@ export class ImportMutingProcessorService { if (host == null && target == null) continue; if (target == null) { - target = await this.resolveUserService.resolveUser(username, host); + target = await this.remoteUserResolveService.resolveUser(username, host); } if (target == null) { diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index f214d59e1c..1519877c5f 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -5,7 +5,7 @@ import type { UsersRepository, DriveFilesRepository, UserListJoiningsRepository, import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; -import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; +import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UserListService } from '@/core/UserListService.js'; import { IdService } from '@/core/IdService.js'; @@ -37,7 +37,7 @@ export class ImportUserListsProcessorService { private utilityService: UtilityService, private idService: IdService, private userListService: UserListService, - private resolveUserService: ResolveUserService, + private remoteUserResolveService: RemoteUserResolveService, private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, ) { @@ -95,7 +95,7 @@ export class ImportUserListsProcessorService { }); if (target == null) { - target = await this.resolveUserService.resolveUser(username, host); + target = await this.remoteUserResolveService.resolveUser(username, host); } if (await this.userListJoiningsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue; diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index e8cd7dcaca..8f1c474020 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -7,7 +7,7 @@ import type { InstancesRepository, DriveFilesRepository } from '@/models/index.j import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { MetaService } from '@/core/MetaService.js'; -import { ApRequestService } from '@/core/remote/activitypub/ApRequestService.js'; +import { ApRequestService } from '@/core/activitypub/ApRequestService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; import { Cache } from '@/misc/cache.js'; @@ -15,15 +15,15 @@ import type { Instance } from '@/models/entities/Instance.js'; import InstanceChart from '@/core/chart/charts/instance.js'; import ApRequestChart from '@/core/chart/charts/ap-request.js'; import FederationChart from '@/core/chart/charts/federation.js'; -import { getApId } from '@/core/remote/activitypub/type.js'; +import { getApId } from '@/core/activitypub/type.js'; import type { CacheableRemoteUser } from '@/models/entities/User.js'; import type { UserPublickey } from '@/models/entities/UserPublickey.js'; -import { ApDbResolverService } from '@/core/remote/activitypub/ApDbResolverService.js'; +import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js'; import { StatusError } from '@/misc/status-error.js'; import { UtilityService } from '@/core/UtilityService.js'; -import { ApPersonService } from '@/core/remote/activitypub/models/ApPersonService.js'; -import { LdSignatureService } from '@/core/remote/activitypub/LdSignatureService.js'; -import { ApInboxService } from '@/core/remote/activitypub/ApInboxService.js'; +import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; +import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js'; +import { ApInboxService } from '@/core/activitypub/ApInboxService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DeliverJobData, InboxJobData } from '../types.js'; diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index 18ec997a1b..1214c9eb95 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -2,7 +2,7 @@ import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Note } from '@/models/entities/Note.js'; import type { User } from '@/models/entities/User.js'; import type { Webhook } from '@/models/entities/Webhook.js'; -import type { IActivity } from '@/core/remote/activitypub/type.js'; +import type { IActivity } from '@/core/activitypub/type.js'; import type httpSignature from '@peertube/http-signature'; export type DeliverJobData = { diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 9689a623fd..0ce2c9cd97 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js'; import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; import * as url from '@/misc/prelude/url.js'; import type { Config } from '@/config.js'; -import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { QueueService } from '@/core/QueueService.js'; import type { ILocalUser, User } from '@/models/entities/User.js'; import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts index 526efa9f9d..9442bda5eb 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts @@ -1,7 +1,7 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DeliverQueue } from '@/core/queue/QueueModule.js'; +import type { DeliverQueue } from '@/core/QueueModule.js'; export const meta = { tags: ['admin'], diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts index b8934428c3..55a3410d49 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts @@ -1,7 +1,7 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { InboxQueue } from '@/core/queue/QueueModule.js'; +import type { InboxQueue } from '@/core/QueueModule.js'; export const meta = { tags: ['admin'], diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts index 605ea3d042..7f3732c970 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/queue/QueueModule.js'; +import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/QueueModule.js'; export const meta = { tags: ['admin'], diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index a6e59276fb..cdaec13a3f 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -3,7 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; import { QueueService } from '@/core/QueueService.js'; -import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { DI } from '@/di-symbols.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts index 3d4c85e50b..8bafb3b122 100644 --- a/packages/backend/src/server/api/endpoints/ap/get.ts +++ b/packages/backend/src/server/api/endpoints/ap/get.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ApResolverService } from '@/core/remote/activitypub/ApResolverService.js'; +import { ApResolverService } from '@/core/activitypub/ApResolverService.js'; import { ApiError } from '../../error.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 0a5fc31751..c218ec4642 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -4,13 +4,13 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, NotesRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import type { CacheableLocalUser, User } from '@/models/entities/User.js'; -import { isActor, isPost, getApId } from '@/core/remote/activitypub/type.js'; +import { isActor, isPost, getApId } from '@/core/activitypub/type.js'; import type { SchemaType } from '@/misc/schema.js'; -import { ApResolverService } from '@/core/remote/activitypub/ApResolverService.js'; -import { ApDbResolverService } from '@/core/remote/activitypub/ApDbResolverService.js'; +import { ApResolverService } from '@/core/activitypub/ApResolverService.js'; +import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js'; import { MetaService } from '@/core/MetaService.js'; -import { ApPersonService } from '@/core/remote/activitypub/models/ApPersonService.js'; -import { ApNoteService } from '@/core/remote/activitypub/models/ApNoteService.js'; +import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; +import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts index 30e77aab45..c19252f198 100644 --- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts +++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ApPersonService } from '@/core/remote/activitypub/models/ApPersonService.js'; +import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { GetterService } from '@/server/api/GetterService.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 6b3b062c15..793d7c5408 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -7,7 +7,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; import { QueueService } from '@/core/QueueService.js'; import { PollService } from '@/core/PollService.js'; -import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 1e15025bf4..7932d5cd12 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -4,7 +4,7 @@ import type { UsersRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { ResolveUserService } from '@/core/remote/ResolveUserService.js'; +import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; import { ApiLoggerService } from '../../ApiLoggerService.js'; @@ -89,7 +89,7 @@ export default class extends Endpoint { private usersRepository: UsersRepository, private userEntityService: UserEntityService, - private resolveUserService: ResolveUserService, + private remoteUserResolveService: RemoteUserResolveService, private apiLoggerService: ApiLoggerService, ) { super(meta, paramDef, async (ps, me) => { @@ -121,7 +121,7 @@ export default class extends Endpoint { } else { // Lookup user if (typeof ps.host === 'string' && typeof ps.username === 'string') { - user = await this.resolveUserService.resolveUser(ps.username, ps.host).catch(err => { + user = await this.remoteUserResolveService.resolveUser(ps.username, ps.host).catch(err => { this.apiLoggerService.logger.warn(`failed to resolve remote user: ${err}`); throw new ApiError(meta.errors.failedToResolveRemoteUser); }); diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 4c3f2bfd36..3fcf8b7c0d 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -14,7 +14,7 @@ import { getNoteSummary } from '@/misc/get-note-summary.js'; import { DI } from '@/di-symbols.js'; import * as Acct from '@/misc/acct.js'; import { MetaService } from '@/core/MetaService.js'; -import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/queue/QueueModule.js'; +import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/QueueModule.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts index ba89ac329a..cad4d8af6c 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.ts @@ -1,5 +1,5 @@ -import Resolver from '../../src/remote/activitypub/resolver.js'; -import { IObject } from '../../src/remote/activitypub/type.js'; +import Resolver from '../../src/activitypub/resolver.js'; +import { IObject } from '../../src/activitypub/type.js'; type MockResponse = { type: string; diff --git a/packages/backend/test/tests/activitypub.ts b/packages/backend/test/tests/activitypub.ts index 6f549ca9c2..08ec0a59ea 100644 --- a/packages/backend/test/tests/activitypub.ts +++ b/packages/backend/test/tests/activitypub.ts @@ -29,7 +29,7 @@ describe('ActivityPub', () => { it('Minimum Actor', async () => { const { MockResolver } = await import('../misc/mock-resolver.js'); - const { createPerson } = await import('../../src/remote/activitypub/models/person.js'); + const { createPerson } = await import('../../src/activitypub/models/person.js'); const resolver = new MockResolver(); resolver._register(actor.id, actor); @@ -43,7 +43,7 @@ describe('ActivityPub', () => { it('Minimum Note', async () => { const { MockResolver } = await import('../misc/mock-resolver.js'); - const { createNote } = await import('../../src/remote/activitypub/models/note.js'); + const { createNote } = await import('../../src/activitypub/models/note.js'); const resolver = new MockResolver(); resolver._register(actor.id, actor); @@ -76,7 +76,7 @@ describe('ActivityPub', () => { it('Actor', async () => { const { MockResolver } = await import('../misc/mock-resolver.js'); - const { createPerson } = await import('../../src/remote/activitypub/models/person.js'); + const { createPerson } = await import('../../src/activitypub/models/person.js'); const resolver = new MockResolver(); resolver._register(actor.id, actor); diff --git a/packages/backend/test/tests/ap-request.ts b/packages/backend/test/tests/ap-request.ts index 299df9be32..d628f03f44 100644 --- a/packages/backend/test/tests/ap-request.ts +++ b/packages/backend/test/tests/ap-request.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import httpSignature from '@peertube/http-signature'; import { genRsaKeyPair } from '../../src/misc/gen-key-pair.js'; -import { createSignedPost, createSignedGet } from '../../src/remote/activitypub/ap-request.js'; +import { createSignedPost, createSignedGet } from '../../src/activitypub/ap-request.js'; export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => { return { diff --git a/packages/backend/test/unit/RelayService.ts b/packages/backend/test/unit/RelayService.ts index bb555648e9..5f87fea7aa 100644 --- a/packages/backend/test/unit/RelayService.ts +++ b/packages/backend/test/unit/RelayService.ts @@ -5,7 +5,7 @@ import { ModuleMocker } from 'jest-mock'; import { Test } from '@nestjs/testing'; import { GlobalModule } from '@/GlobalModule.js'; import { RelayService } from '@/core/RelayService.js'; -import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; import { QueueService } from '@/core/QueueService.js'; import { IdService } from '@/core/IdService.js'; -- cgit v1.2.3-freya From bbb49457f9fb5d46402e913c92ebf77722cad6ff Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 4 Dec 2022 15:03:09 +0900 Subject: refactor: introduce bindThis decorator to bind this automaticaly --- packages/backend/src/core/AccountUpdateService.ts | 2 + packages/backend/src/core/AiService.ts | 3 + packages/backend/src/core/AntennaService.ts | 6 ++ packages/backend/src/core/AppLockService.ts | 4 + packages/backend/src/core/CaptchaService.ts | 5 ++ .../backend/src/core/CreateNotificationService.ts | 4 + .../backend/src/core/CreateSystemUserService.ts | 2 + packages/backend/src/core/CustomEmojiService.ts | 8 ++ packages/backend/src/core/DeleteAccountService.ts | 2 + packages/backend/src/core/DownloadService.ts | 4 + packages/backend/src/core/DriveService.ts | 11 +++ packages/backend/src/core/EmailService.ts | 3 + .../backend/src/core/FederatedInstanceService.ts | 2 + .../src/core/FetchInstanceMetadataService.ts | 10 +++ packages/backend/src/core/FileInfoService.ts | 11 +++ packages/backend/src/core/GlobalEventService.ts | 3 + packages/backend/src/core/HashtagService.ts | 4 + packages/backend/src/core/HttpRequestService.ts | 5 ++ packages/backend/src/core/IdService.ts | 2 + .../backend/src/core/ImageProcessingService.ts | 7 ++ packages/backend/src/core/InstanceActorService.ts | 2 + .../backend/src/core/InternalStorageService.ts | 6 ++ packages/backend/src/core/LoggerService.ts | 2 + packages/backend/src/core/MessagingService.ts | 7 ++ packages/backend/src/core/MetaService.ts | 7 +- packages/backend/src/core/MfmService.ts | 3 + packages/backend/src/core/ModerationLogService.ts | 2 + packages/backend/src/core/NoteCreateService.ts | 13 +++ packages/backend/src/core/NoteDeleteService.ts | 4 + packages/backend/src/core/NotePiningService.ts | 4 + packages/backend/src/core/NoteReadService.ts | 3 + packages/backend/src/core/NotificationService.ts | 5 ++ packages/backend/src/core/PollService.ts | 3 + packages/backend/src/core/ProxyAccountService.ts | 2 + .../backend/src/core/PushNotificationService.ts | 1 + packages/backend/src/core/QueryService.ts | 10 +++ packages/backend/src/core/QueueService.ts | 20 +++++ packages/backend/src/core/ReactionService.ts | 8 ++ packages/backend/src/core/RelayService.ts | 8 ++ packages/backend/src/core/RemoteLoggerService.ts | 1 + .../backend/src/core/RemoteUserResolveService.ts | 3 + packages/backend/src/core/S3Service.ts | 2 + packages/backend/src/core/SignupService.ts | 2 + .../src/core/TwoFactorAuthenticationService.ts | 4 + packages/backend/src/core/UserBlockingService.ts | 6 ++ packages/backend/src/core/UserCacheService.ts | 5 +- packages/backend/src/core/UserFollowingService.ts | 16 ++++ .../backend/src/core/UserKeypairStoreService.ts | 2 + packages/backend/src/core/UserListService.ts | 2 + packages/backend/src/core/UserMutingService.ts | 2 + packages/backend/src/core/UserSuspendService.ts | 3 + packages/backend/src/core/UtilityService.ts | 6 ++ .../backend/src/core/VideoProcessingService.ts | 2 + packages/backend/src/core/WebfingerService.ts | 3 + packages/backend/src/core/WebhookService.ts | 6 +- .../src/core/activitypub/ApAudienceService.ts | 5 ++ .../src/core/activitypub/ApDbResolverService.ts | 7 ++ .../core/activitypub/ApDeliverManagerService.ts | 8 ++ .../backend/src/core/activitypub/ApInboxService.ts | 28 ++++++ .../src/core/activitypub/ApLoggerService.ts | 1 + .../backend/src/core/activitypub/ApMfmService.ts | 3 + .../src/core/activitypub/ApRendererService.ts | 33 +++++++ .../src/core/activitypub/ApRequestService.ts | 9 ++ .../src/core/activitypub/ApResolverService.ts | 100 +++++++++++---------- .../src/core/activitypub/LdSignatureService.ts | 33 ++++--- .../src/core/activitypub/models/ApImageService.ts | 3 + .../core/activitypub/models/ApMentionService.ts | 3 + .../src/core/activitypub/models/ApNoteService.ts | 6 ++ .../src/core/activitypub/models/ApPersonService.ts | 8 ++ .../core/activitypub/models/ApQuestionService.ts | 3 + .../backend/src/core/chart/ChartLoggerService.ts | 1 + .../src/core/chart/ChartManagementService.ts | 2 + .../backend/src/core/chart/charts/active-users.ts | 3 + .../backend/src/core/chart/charts/ap-request.ts | 4 + packages/backend/src/core/chart/charts/drive.ts | 2 + .../backend/src/core/chart/charts/federation.ts | 3 + packages/backend/src/core/chart/charts/hashtag.ts | 2 + packages/backend/src/core/chart/charts/instance.ts | 8 ++ packages/backend/src/core/chart/charts/notes.ts | 2 + .../src/core/chart/charts/per-user-drive.ts | 2 + .../src/core/chart/charts/per-user-following.ts | 2 + .../src/core/chart/charts/per-user-notes.ts | 2 + .../src/core/chart/charts/per-user-reactions.ts | 2 + .../backend/src/core/chart/charts/test-grouped.ts | 2 + .../src/core/chart/charts/test-intersection.ts | 3 + .../backend/src/core/chart/charts/test-unique.ts | 2 + packages/backend/src/core/chart/charts/test.ts | 3 + packages/backend/src/core/chart/charts/users.ts | 2 + packages/backend/src/core/chart/core.ts | 11 +++ .../core/entities/AbuseUserReportEntityService.ts | 3 + .../src/core/entities/AntennaEntityService.ts | 2 + .../backend/src/core/entities/AppEntityService.ts | 2 + .../src/core/entities/AuthSessionEntityService.ts | 2 + .../src/core/entities/BlockingEntityService.ts | 3 + .../src/core/entities/ChannelEntityService.ts | 2 + .../backend/src/core/entities/ClipEntityService.ts | 3 + .../src/core/entities/DriveFileEntityService.ts | 11 +++ .../src/core/entities/DriveFolderEntityService.ts | 2 + .../src/core/entities/EmojiEntityService.ts | 3 + .../core/entities/FollowRequestEntityService.ts | 2 + .../src/core/entities/FollowingEntityService.ts | 7 ++ .../src/core/entities/GalleryLikeEntityService.ts | 3 + .../src/core/entities/GalleryPostEntityService.ts | 3 + .../src/core/entities/HashtagEntityService.ts | 3 + .../src/core/entities/InstanceEntityService.ts | 3 + .../core/entities/MessagingMessageEntityService.ts | 2 + .../core/entities/ModerationLogEntityService.ts | 3 + .../src/core/entities/MutingEntityService.ts | 3 + .../backend/src/core/entities/NoteEntityService.ts | 8 ++ .../src/core/entities/NoteFavoriteEntityService.ts | 3 + .../src/core/entities/NoteReactionEntityService.ts | 2 + .../src/core/entities/NotificationEntityService.ts | 3 + .../backend/src/core/entities/PageEntityService.ts | 3 + .../src/core/entities/PageLikeEntityService.ts | 3 + .../src/core/entities/SigninEntityService.ts | 2 + .../backend/src/core/entities/UserEntityService.ts | 12 +++ .../src/core/entities/UserGroupEntityService.ts | 2 + .../entities/UserGroupInvitationEntityService.ts | 3 + .../src/core/entities/UserListEntityService.ts | 2 + packages/backend/src/daemons/JanitorService.ts | 3 + packages/backend/src/daemons/QueueStatsService.ts | 3 + packages/backend/src/daemons/ServerStatsService.ts | 3 + packages/backend/src/decorators.ts | 41 +++++++++ packages/backend/src/logger.ts | 8 ++ packages/backend/src/misc/cache.ts | 7 ++ packages/backend/src/misc/i18n.ts | 3 +- packages/backend/src/postgre.ts | 8 ++ .../backend/src/queue/DbQueueProcessorsService.ts | 2 + .../queue/ObjectStorageQueueProcessorsService.ts | 2 + packages/backend/src/queue/QueueLoggerService.ts | 1 + .../backend/src/queue/QueueProcessorService.ts | 2 + .../src/queue/SystemQueueProcessorsService.ts | 2 + .../CheckExpiredMutingsProcessorService.ts | 2 + .../processors/CleanChartsProcessorService.ts | 2 + .../src/queue/processors/CleanProcessorService.ts | 2 + .../processors/CleanRemoteFilesProcessorService.ts | 2 + .../processors/DeleteAccountProcessorService.ts | 2 + .../processors/DeleteDriveFilesProcessorService.ts | 2 + .../queue/processors/DeleteFileProcessorService.ts | 2 + .../queue/processors/DeliverProcessorService.ts | 2 + .../EndedPollNotificationProcessorService.ts | 2 + .../processors/ExportBlockingProcessorService.ts | 2 + .../ExportCustomEmojisProcessorService.ts | 2 + .../processors/ExportFollowingProcessorService.ts | 2 + .../processors/ExportMutingProcessorService.ts | 2 + .../processors/ExportNotesProcessorService.ts | 2 + .../processors/ExportUserListsProcessorService.ts | 2 + .../processors/ImportBlockingProcessorService.ts | 2 + .../ImportCustomEmojisProcessorService.ts | 2 + .../processors/ImportFollowingProcessorService.ts | 2 + .../processors/ImportMutingProcessorService.ts | 2 + .../processors/ImportUserListsProcessorService.ts | 2 + .../src/queue/processors/InboxProcessorService.ts | 2 + .../processors/ResyncChartsProcessorService.ts | 2 + .../queue/processors/TickChartsProcessorService.ts | 2 + .../processors/WebhookDeliverProcessorService.ts | 2 + .../backend/src/server/ActivityPubServerService.ts | 12 ++- packages/backend/src/server/FileServerService.ts | 6 +- .../backend/src/server/MediaProxyServerService.ts | 5 +- .../backend/src/server/NodeinfoServerService.ts | 5 +- packages/backend/src/server/ServerService.ts | 2 + .../backend/src/server/WellKnownServerService.ts | 4 +- packages/backend/src/server/api/ApiCallService.ts | 7 ++ .../backend/src/server/api/ApiLoggerService.ts | 1 + .../backend/src/server/api/ApiServerService.ts | 4 +- .../backend/src/server/api/AuthenticateService.ts | 2 + packages/backend/src/server/api/GetterService.ts | 5 ++ .../backend/src/server/api/RateLimiterService.ts | 2 + .../backend/src/server/api/SigninApiService.ts | 2 + packages/backend/src/server/api/SigninService.ts | 2 + .../backend/src/server/api/SignupApiService.ts | 3 + .../src/server/api/StreamingApiServerService.ts | 2 + .../src/server/api/endpoints/admin/suspend-user.ts | 3 + .../backend/src/server/api/endpoints/ap/show.ts | 3 + .../server/api/integration/DiscordServerService.ts | 6 +- .../server/api/integration/GithubServerService.ts | 6 +- .../server/api/integration/TwitterServerService.ts | 6 +- .../src/server/api/stream/ChannelsService.ts | 2 + packages/backend/src/server/api/stream/channel.ts | 2 + .../src/server/api/stream/channels/admin.ts | 3 + .../src/server/api/stream/channels/antenna.ts | 7 +- .../src/server/api/stream/channels/channel.ts | 11 ++- .../src/server/api/stream/channels/drive.ts | 3 + .../server/api/stream/channels/global-timeline.ts | 7 +- .../src/server/api/stream/channels/hashtag.ts | 7 +- .../server/api/stream/channels/home-timeline.ts | 7 +- .../server/api/stream/channels/hybrid-timeline.ts | 7 +- .../server/api/stream/channels/local-timeline.ts | 7 +- .../backend/src/server/api/stream/channels/main.ts | 3 + .../server/api/stream/channels/messaging-index.ts | 3 + .../src/server/api/stream/channels/messaging.ts | 13 ++- .../src/server/api/stream/channels/queue-stats.ts | 10 ++- .../src/server/api/stream/channels/server-stats.ts | 10 ++- .../src/server/api/stream/channels/user-list.ts | 13 ++- packages/backend/src/server/api/stream/index.ts | 32 ++++++- .../backend/src/server/web/ClientServerService.ts | 5 +- packages/backend/src/server/web/FeedService.ts | 2 + .../backend/src/server/web/UrlPreviewService.ts | 3 + packages/backend/test/misc/mock-resolver.ts | 1 + 199 files changed, 969 insertions(+), 96 deletions(-) create mode 100644 packages/backend/src/decorators.ts (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/core/AccountUpdateService.ts b/packages/backend/src/core/AccountUpdateService.ts index a5ab4fdfce..5f6dfca0ca 100644 --- a/packages/backend/src/core/AccountUpdateService.ts +++ b/packages/backend/src/core/AccountUpdateService.ts @@ -7,6 +7,7 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { RelayService } from '@/core/RelayService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class AccountUpdateService { @@ -24,6 +25,7 @@ export class AccountUpdateService { ) { } + @bindThis public async publishToFollowers(userId: User['id']) { const user = await this.usersRepository.findOneBy({ id: userId }); if (user == null) throw new Error('user not found'); diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index 15084b8ff1..aee9504c85 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -12,6 +12,7 @@ const _dirname = dirname(_filename); const REQUIRED_CPU_FLAGS = ['avx2', 'fma']; let isSupportedCpu: undefined | boolean = undefined; +import { bindThis } from '@/decorators.js'; @Injectable() export class AiService { @@ -23,6 +24,7 @@ export class AiService { ) { } + @bindThis public async detectSensitive(path: string): Promise { try { if (isSupportedCpu === undefined) { @@ -53,6 +55,7 @@ export class AiService { } } + @bindThis private async getCpuFlags(): Promise { const str = await si.cpuFlags(); return str.split(/\s+/); diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 8046ba5311..9c120c993d 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -13,6 +13,7 @@ import { DI } from '@/di-symbols.js'; import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; import { UtilityService } from '@/core/UtilityService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; @Injectable() export class AntennaService implements OnApplicationShutdown { @@ -56,10 +57,12 @@ export class AntennaService implements OnApplicationShutdown { this.redisSubscriber.on('message', this.onRedisMessage); } + @bindThis public onApplicationShutdown(signal?: string | undefined) { this.redisSubscriber.off('message', this.onRedisMessage); } + @bindThis private async onRedisMessage(_: string, data: string): Promise { const obj = JSON.parse(data); @@ -81,6 +84,7 @@ export class AntennaService implements OnApplicationShutdown { } } + @bindThis public async addNoteToAntenna(antenna: Antenna, note: Note, noteUser: { id: User['id']; }): Promise { // 通知しない設定になっているか、自分自身の投稿なら既読にする const read = !antenna.notify || (antenna.userId === noteUser.id); @@ -133,6 +137,7 @@ export class AntennaService implements OnApplicationShutdown { /** * noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい */ + @bindThis public async checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise { if (note.visibility === 'specified') return false; @@ -217,6 +222,7 @@ export class AntennaService implements OnApplicationShutdown { return true; } + @bindThis public async getAntennas() { if (!this.antennasFetched) { this.antennas = await this.antennasRepository.find(); diff --git a/packages/backend/src/core/AppLockService.ts b/packages/backend/src/core/AppLockService.ts index 04b3d8b112..1f512b5790 100644 --- a/packages/backend/src/core/AppLockService.ts +++ b/packages/backend/src/core/AppLockService.ts @@ -8,6 +8,7 @@ import { DI } from '@/di-symbols.js'; * Retry delay (ms) for lock acquisition */ const retryDelay = 100; +import { bindThis } from '@/decorators.js'; @Injectable() export class AppLockService { @@ -26,14 +27,17 @@ export class AppLockService { * @param timeout Lock timeout (ms), The timeout releases previous lock. * @returns Unlock function */ + @bindThis public getApLock(uri: string, timeout = 30 * 1000): Promise<() => void> { return this.lock(`ap-object:${uri}`, timeout); } + @bindThis public getFetchInstanceMetadataLock(host: string, timeout = 30 * 1000): Promise<() => void> { return this.lock(`instance:${host}`, timeout); } + @bindThis public getChartInsertLock(lockKey: string, timeout = 30 * 1000): Promise<() => void> { return this.lock(`chart-insert:${lockKey}`, timeout); } diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index b60271812c..0207cf58a0 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -3,6 +3,7 @@ import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { bindThis } from '@/decorators.js'; type CaptchaResponse = { success: boolean; @@ -19,6 +20,7 @@ export class CaptchaService { ) { } + @bindThis private async getCaptchaResponse(url: string, secret: string, response: string): Promise { const params = new URLSearchParams({ secret, @@ -45,6 +47,7 @@ export class CaptchaService { return await res.json() as CaptchaResponse; } + @bindThis public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise { if (response == null) { throw 'recaptcha-failed: no response provided'; @@ -60,6 +63,7 @@ export class CaptchaService { } } + @bindThis public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise { if (response == null) { throw 'hcaptcha-failed: no response provided'; @@ -75,6 +79,7 @@ export class CaptchaService { } } + @bindThis public async verifyTurnstile(secret: string, response: string | null | undefined): Promise { if (response == null) { throw 'turnstile-failed: no response provided'; diff --git a/packages/backend/src/core/CreateNotificationService.ts b/packages/backend/src/core/CreateNotificationService.ts index 504661c3bd..f376b7b9cf 100644 --- a/packages/backend/src/core/CreateNotificationService.ts +++ b/packages/backend/src/core/CreateNotificationService.ts @@ -7,6 +7,7 @@ import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js'; import { PushNotificationService } from '@/core/PushNotificationService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class CreateNotificationService { @@ -30,6 +31,7 @@ export class CreateNotificationService { ) { } + @bindThis public async createNotification( notifieeId: User['id'], type: Notification['type'], @@ -90,6 +92,7 @@ export class CreateNotificationService { // TODO: locale ファイルをクライアント用とサーバー用で分けたい + @bindThis private async emailNotificationFollow(userId: User['id'], follower: User) { /* const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); @@ -101,6 +104,7 @@ export class CreateNotificationService { */ } + @bindThis private async emailNotificationReceiveFollowRequest(userId: User['id'], follower: User) { /* const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); diff --git a/packages/backend/src/core/CreateSystemUserService.ts b/packages/backend/src/core/CreateSystemUserService.ts index 71f50d7cb3..1e753f65cc 100644 --- a/packages/backend/src/core/CreateSystemUserService.ts +++ b/packages/backend/src/core/CreateSystemUserService.ts @@ -10,6 +10,7 @@ import { UserKeypair } from '@/models/entities/UserKeypair.js'; import { UsedUsername } from '@/models/entities/UsedUsername.js'; import { DI } from '@/di-symbols.js'; import generateNativeUserToken from '@/misc/generate-native-user-token.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class CreateSystemUserService { @@ -21,6 +22,7 @@ export class CreateSystemUserService { ) { } + @bindThis public async createSystemUser(username: string): Promise { const password = uuid(); diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 3319f3efa8..36f88fd743 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -20,6 +20,7 @@ type PopulatedEmoji = { name: string; url: string; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class CustomEmojiService { @@ -43,6 +44,7 @@ export class CustomEmojiService { this.cache = new Cache(1000 * 60 * 60 * 12); } + @bindThis public async add(data: { driveFile: DriveFile; name: string; @@ -67,6 +69,7 @@ export class CustomEmojiService { return emoji; } + @bindThis private normalizeHost(src: string | undefined, noteUserHost: string | null): string | null { // クエリに使うホスト let host = src === '.' ? null // .はローカルホスト (ここがマッチするのはリアクションのみ) @@ -79,6 +82,7 @@ export class CustomEmojiService { return host; } + @bindThis private parseEmojiStr(emojiName: string, noteUserHost: string | null) { const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/); if (!match) return { name: null, host: null }; @@ -97,6 +101,7 @@ export class CustomEmojiService { * @param noteUserHost ノートやユーザープロフィールの所有者のホスト * @returns 絵文字情報, nullは未マッチを意味する */ + @bindThis public async populateEmoji(emojiName: string, noteUserHost: string | null): Promise { const { name, host } = this.parseEmojiStr(emojiName, noteUserHost); if (name == null) return null; @@ -123,11 +128,13 @@ export class CustomEmojiService { /** * 複数の添付用絵文字情報を解決する (キャシュ付き, 存在しないものは結果から除外される) */ + @bindThis public async populateEmojis(emojiNames: string[], noteUserHost: string | null): Promise { const emojis = await Promise.all(emojiNames.map(x => this.populateEmoji(x, noteUserHost))); return emojis.filter((x): x is PopulatedEmoji => x != null); } + @bindThis public aggregateNoteEmojis(notes: Note[]) { let emojis: { name: string | null; host: string | null; }[] = []; for (const note of notes) { @@ -154,6 +161,7 @@ export class CustomEmojiService { /** * 与えられた絵文字のリストをデータベースから取得し、キャッシュに追加します */ + @bindThis public async prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise { const notCachedEmojis = emojis.filter(emoji => this.cache.get(`${emoji.name} ${emoji.host}`) == null); const emojisQuery: any[] = []; diff --git a/packages/backend/src/core/DeleteAccountService.ts b/packages/backend/src/core/DeleteAccountService.ts index 53d48c450b..e42c738707 100644 --- a/packages/backend/src/core/DeleteAccountService.ts +++ b/packages/backend/src/core/DeleteAccountService.ts @@ -4,6 +4,7 @@ import { QueueService } from '@/core/QueueService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DeleteAccountService { @@ -17,6 +18,7 @@ export class DeleteAccountService { ) { } + @bindThis public async deleteAccount(user: { id: string; host: string | null; diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts index 25965b7ac4..9097bb08e0 100644 --- a/packages/backend/src/core/DownloadService.ts +++ b/packages/backend/src/core/DownloadService.ts @@ -15,6 +15,7 @@ import { LoggerService } from '@/core/LoggerService.js'; import type Logger from '@/logger.js'; const pipeline = util.promisify(stream.pipeline); +import { bindThis } from '@/decorators.js'; @Injectable() export class DownloadService { @@ -30,6 +31,7 @@ export class DownloadService { this.logger = this.loggerService.getLogger('download'); } + @bindThis public async downloadUrl(url: string, path: string): Promise { this.logger.info(`Downloading ${chalk.cyan(url)} ...`); @@ -94,6 +96,7 @@ export class DownloadService { this.logger.succ(`Download finished: ${chalk.cyan(url)}`); } + @bindThis public async downloadTextFile(url: string): Promise { // Create temp file const [path, cleanup] = await createTemp(); @@ -112,6 +115,7 @@ export class DownloadService { } } + @bindThis private isPrivateIp(ip: string): boolean { for (const net of this.config.allowedPrivateNetworks ?? []) { const cidr = new IPCIDR(net); diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 1d2ba5df8c..b83047dbc2 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -71,6 +71,7 @@ type UploadFromUrlArgs = { requestIp?: string | null; requestHeaders?: Record | null; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class DriveService { @@ -122,6 +123,7 @@ export class DriveService { * @param hash Hash for original * @param size Size for original */ + @bindThis private async save(file: DriveFile, path: string, name: string, type: string, hash: string, size: number): Promise { // thunbnail, webpublic を必要なら生成 const alts = await this.generateAlts(path, type, !file.uri); @@ -242,6 +244,7 @@ export class DriveService { * @param type Content-Type for original * @param generateWeb Generate webpublic or not */ + @bindThis public async generateAlts(path: string, type: string, generateWeb: boolean) { if (type.startsWith('video/')) { try { @@ -345,6 +348,7 @@ export class DriveService { /** * Upload to ObjectStorage */ + @bindThis private async upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { if (type === 'image/apng') type = 'image/png'; if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream'; @@ -372,6 +376,7 @@ export class DriveService { if (result) this.registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`); } + @bindThis private async deleteOldFile(user: IRemoteUser) { const q = this.driveFilesRepository.createQueryBuilder('file') .where('file.userId = :userId', { userId: user.id }) @@ -398,6 +403,7 @@ export class DriveService { * Add file to drive * */ + @bindThis public async addFile({ user, path, @@ -601,6 +607,7 @@ export class DriveService { return file; } + @bindThis public async deleteFile(file: DriveFile, isExpired = false) { if (file.storedInternal) { this.internalStorageService.del(file.accessKey!); @@ -627,6 +634,7 @@ export class DriveService { this.deletePostProcess(file, isExpired); } + @bindThis public async deleteFileSync(file: DriveFile, isExpired = false) { if (file.storedInternal) { this.internalStorageService.del(file.accessKey!); @@ -657,6 +665,7 @@ export class DriveService { this.deletePostProcess(file, isExpired); } + @bindThis private async deletePostProcess(file: DriveFile, isExpired = false) { // リモートファイル期限切れ削除後は直リンクにする if (isExpired && file.userHost !== null && file.uri != null) { @@ -683,6 +692,7 @@ export class DriveService { } } + @bindThis public async deleteObjectStorageFile(key: string) { const meta = await this.metaService.fetch(); @@ -694,6 +704,7 @@ export class DriveService { }).promise(); } + @bindThis public async uploadFromUrl({ url, user, diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 019b9087cd..59932a5b88 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -7,6 +7,7 @@ import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import type { UserProfilesRepository } from '@/models/index.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class EmailService { @@ -25,6 +26,7 @@ export class EmailService { this.logger = this.loggerService.getLogger('email'); } + @bindThis public async sendEmail(to: string, subject: string, html: string, text: string) { const meta = await this.metaService.fetch(true); @@ -141,6 +143,7 @@ export class EmailService { } } + @bindThis public async validateEmailForAccount(emailAddress: string): Promise<{ available: boolean; reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp'; diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index a05c95a2ae..97745a1168 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -5,6 +5,7 @@ import { Cache } from '@/misc/cache.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class FederatedInstanceService { @@ -20,6 +21,7 @@ export class FederatedInstanceService { this.cache = new Cache(1000 * 60 * 60); } + @bindThis public async registerOrFetchInstanceDoc(host: string): Promise { host = this.utilityService.toPuny(host); diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index b92ebe6059..4d4945ca7f 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -30,6 +30,7 @@ type NodeInfo = { themeColor?: unknown; }; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class FetchInstanceMetadataService { @@ -46,6 +47,7 @@ export class FetchInstanceMetadataService { this.logger = this.loggerService.getLogger('metadata', 'cyan'); } + @bindThis public async fetchInstanceMetadata(instance: Instance, force = false): Promise { const unlock = await this.appLockService.getFetchInstanceMetadataLock(instance.host); @@ -105,6 +107,7 @@ export class FetchInstanceMetadataService { } } + @bindThis private async fetchNodeinfo(instance: Instance): Promise { this.logger.info(`Fetching nodeinfo of ${instance.host} ...`); @@ -148,6 +151,7 @@ export class FetchInstanceMetadataService { } } + @bindThis private async fetchDom(instance: Instance): Promise { this.logger.info(`Fetching HTML of ${instance.host} ...`); @@ -161,6 +165,7 @@ export class FetchInstanceMetadataService { return doc; } + @bindThis private async fetchManifest(instance: Instance): Promise | null> { const url = 'https://' + instance.host; @@ -171,6 +176,7 @@ export class FetchInstanceMetadataService { return manifest; } + @bindThis private async fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] | null): Promise { const url = 'https://' + instance.host; @@ -198,6 +204,7 @@ export class FetchInstanceMetadataService { return null; } + @bindThis private async fetchIconUrl(instance: Instance, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) { const url = 'https://' + instance.host; @@ -226,6 +233,7 @@ export class FetchInstanceMetadataService { return null; } + @bindThis private async getThemeColor(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { const themeColor = info?.metadata?.themeColor ?? doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') ?? manifest?.theme_color; @@ -237,6 +245,7 @@ export class FetchInstanceMetadataService { return null; } + @bindThis private async getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (info && info.metadata) { if (typeof info.metadata.nodeName === 'string') { @@ -261,6 +270,7 @@ export class FetchInstanceMetadataService { return null; } + @bindThis private async getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (info && info.metadata) { if (typeof info.metadata.nodeDescription === 'string') { diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts index fd8a4fdd3a..bea1b3402e 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -14,6 +14,7 @@ import sharp from 'sharp'; import { encode } from 'blurhash'; import { createTempDir } from '@/misc/create-temp.js'; import { AiService } from '@/core/AiService.js'; +import { bindThis } from '@/decorators.js'; const pipeline = util.promisify(stream.pipeline); @@ -42,6 +43,7 @@ const TYPE_SVG = { mime: 'image/svg+xml', ext: 'svg', }; + @Injectable() export class FileInfoService { constructor( @@ -52,6 +54,7 @@ export class FileInfoService { /** * Get file information */ + @bindThis public async getFileInfo(path: string, opts: { skipSensitiveDetection: boolean; sensitiveThreshold?: number; @@ -135,6 +138,7 @@ export class FileInfoService { }; } + @bindThis private async detectSensitivity(source: string, mime: string, sensitiveThreshold: number, sensitiveThresholdForPorn: number, analyzeVideo: boolean): Promise<[sensitive: boolean, porn: boolean]> { let sensitive = false; let porn = false; @@ -269,6 +273,7 @@ export class FileInfoService { } } + @bindThis private exists(path: string): Promise { return fs.promises.access(path).then(() => true, () => false); } @@ -276,6 +281,7 @@ export class FileInfoService { /** * Detect MIME Type and extension */ + @bindThis public async detectType(path: string): Promise<{ mime: string; ext: string | null; @@ -312,6 +318,7 @@ export class FileInfoService { /** * Check the file is SVG or not */ + @bindThis public async checkSvg(path: string) { try { const size = await this.getFileSize(path); @@ -325,6 +332,7 @@ export class FileInfoService { /** * Get file size */ + @bindThis public async getFileSize(path: string): Promise { const getStat = util.promisify(fs.stat); return (await getStat(path)).size; @@ -333,6 +341,7 @@ export class FileInfoService { /** * Calculate MD5 hash */ + @bindThis private async calcHash(path: string): Promise { const hash = crypto.createHash('md5').setEncoding('hex'); await pipeline(fs.createReadStream(path), hash); @@ -342,6 +351,7 @@ export class FileInfoService { /** * Detect dimensions of image */ + @bindThis private async detectImageSize(path: string): Promise<{ width: number; height: number; @@ -358,6 +368,7 @@ export class FileInfoService { /** * Calculate average color of image */ + @bindThis private getBlurhash(path: string): Promise { return new Promise((resolve, reject) => { sharp(path) diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index df0c9b5ccb..90f911027e 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -25,6 +25,7 @@ import type { import type { Packed } from '@/misc/schema.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class GlobalEventService { @@ -37,6 +38,7 @@ export class GlobalEventService { ) { } + @bindThis private publish(channel: StreamChannels, type: string | null, value?: any): void { const message = type == null ? value : value == null ? { type: type, body: null } : @@ -99,6 +101,7 @@ export class GlobalEventService { this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value); } + @bindThis public publishNotesStream(note: Packed<'Note'>): void { this.publish('notesStream', null, note); } diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts index 5ca058e9a4..309cfe8c3f 100644 --- a/packages/backend/src/core/HashtagService.ts +++ b/packages/backend/src/core/HashtagService.ts @@ -7,6 +7,7 @@ import type { Hashtag } from '@/models/entities/Hashtag.js'; import HashtagChart from '@/core/chart/charts/hashtag.js'; import type { HashtagsRepository, UsersRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class HashtagService { @@ -23,12 +24,14 @@ export class HashtagService { ) { } + @bindThis public async updateHashtags(user: { id: User['id']; host: User['host']; }, tags: string[]) { for (const tag of tags) { await this.updateHashtag(user, tag); } } + @bindThis public async updateUsertags(user: User, tags: string[]) { for (const tag of tags) { await this.updateHashtag(user, tag, true, true); @@ -39,6 +42,7 @@ export class HashtagService { } } + @bindThis public async updateHashtag(user: { id: User['id']; host: User['host']; }, tag: string, isUserAttached = false, inc = true) { tag = normalizeForSearch(tag); diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index 396fefad1c..d998307973 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { StatusError } from '@/misc/status-error.js'; +import { bindThis } from '@/decorators.js'; import type { Response } from 'node-fetch'; import type { URL } from 'node:url'; @@ -84,6 +85,7 @@ export class HttpRequestService { * @param url URL * @param bypassProxy Allways bypass proxy */ + @bindThis public getAgentByUrl(url: URL, bypassProxy = false): http.Agent | https.Agent { if (bypassProxy || (this.config.proxyBypassHosts || []).includes(url.hostname)) { return url.protocol === 'http:' ? this.http : this.https; @@ -92,6 +94,7 @@ export class HttpRequestService { } } + @bindThis public async getJson(url: string, accept = 'application/json, */*', timeout = 10000, headers?: Record): Promise { const res = await this.getResponse({ url, @@ -106,6 +109,7 @@ export class HttpRequestService { return await res.json(); } + @bindThis public async getHtml(url: string, accept = 'text/html, */*', timeout = 10000, headers?: Record): Promise { const res = await this.getResponse({ url, @@ -120,6 +124,7 @@ export class HttpRequestService { return await res.text(); } + @bindThis public async getResponse(args: { url: string, method: string, diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts index 997be17937..0e8a7b13ad 100644 --- a/packages/backend/src/core/IdService.ts +++ b/packages/backend/src/core/IdService.ts @@ -6,6 +6,7 @@ import { genAid } from '@/misc/id/aid.js'; import { genMeid } from '@/misc/id/meid.js'; import { genMeidg } from '@/misc/id/meidg.js'; import { genObjectId } from '@/misc/id/object-id.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class IdService { @@ -18,6 +19,7 @@ export class IdService { this.metohd = config.id.toLowerCase(); } + @bindThis public genId(date?: Date): string { if (!date || (date > new Date())) date = new Date(); diff --git a/packages/backend/src/core/ImageProcessingService.ts b/packages/backend/src/core/ImageProcessingService.ts index 3a50361a42..3a61873044 100644 --- a/packages/backend/src/core/ImageProcessingService.ts +++ b/packages/backend/src/core/ImageProcessingService.ts @@ -8,6 +8,7 @@ export type IImage = { ext: string | null; type: string; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class ImageProcessingService { @@ -21,10 +22,12 @@ export class ImageProcessingService { * Convert to JPEG * with resize, remove metadata, resolve orientation, stop animation */ + @bindThis public async convertToJpeg(path: string, width: number, height: number): Promise { return this.convertSharpToJpeg(await sharp(path), width, height); } + @bindThis public async convertSharpToJpeg(sharp: sharp.Sharp, width: number, height: number): Promise { const data = await sharp .resize(width, height, { @@ -49,10 +52,12 @@ export class ImageProcessingService { * Convert to WebP * with resize, remove metadata, resolve orientation, stop animation */ + @bindThis public async convertToWebp(path: string, width: number, height: number, quality = 85): Promise { return this.convertSharpToWebp(await sharp(path), width, height, quality); } + @bindThis public async convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number, quality = 85): Promise { const data = await sharp .resize(width, height, { @@ -76,10 +81,12 @@ export class ImageProcessingService { * Convert to PNG * with resize, remove metadata, resolve orientation, stop animation */ + @bindThis public async convertToPng(path: string, width: number, height: number): Promise { return this.convertSharpToPng(await sharp(path), width, height); } + @bindThis public async convertSharpToPng(sharp: sharp.Sharp, width: number, height: number): Promise { const data = await sharp .resize(width, height, { diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts index f35a28147d..abd6685d61 100644 --- a/packages/backend/src/core/InstanceActorService.ts +++ b/packages/backend/src/core/InstanceActorService.ts @@ -7,6 +7,7 @@ import { DI } from '@/di-symbols.js'; import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; const ACTOR_USERNAME = 'instance.actor' as const; +import { bindThis } from '@/decorators.js'; @Injectable() export class InstanceActorService { @@ -21,6 +22,7 @@ export class InstanceActorService { this.cache = new Cache(Infinity); } + @bindThis public async getInstanceActor(): Promise { const cached = this.cache.get(null); if (cached) return cached; diff --git a/packages/backend/src/core/InternalStorageService.ts b/packages/backend/src/core/InternalStorageService.ts index 6d2a9b2db6..e32cbf645c 100644 --- a/packages/backend/src/core/InternalStorageService.ts +++ b/packages/backend/src/core/InternalStorageService.ts @@ -10,6 +10,7 @@ const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); const path = Path.resolve(_dirname, '../../../../files'); +import { bindThis } from '@/decorators.js'; @Injectable() export class InternalStorageService { @@ -19,26 +20,31 @@ export class InternalStorageService { ) { } + @bindThis public resolvePath(key: string) { return Path.resolve(path, key); } + @bindThis public read(key: string) { return fs.createReadStream(this.resolvePath(key)); } + @bindThis public saveFromPath(key: string, srcPath: string) { fs.mkdirSync(path, { recursive: true }); fs.copyFileSync(srcPath, this.resolvePath(key)); return `${this.config.url}/files/${key}`; } + @bindThis public saveFromBuffer(key: string, data: Buffer) { fs.mkdirSync(path, { recursive: true }); fs.writeFileSync(this.resolvePath(key), data); return `${this.config.url}/files/${key}`; } + @bindThis public del(key: string) { fs.unlink(this.resolvePath(key), () => {}); } diff --git a/packages/backend/src/core/LoggerService.ts b/packages/backend/src/core/LoggerService.ts index a3192c0262..4303f3ae22 100644 --- a/packages/backend/src/core/LoggerService.ts +++ b/packages/backend/src/core/LoggerService.ts @@ -3,6 +3,7 @@ import * as SyslogPro from 'syslog-pro'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class LoggerService { @@ -27,6 +28,7 @@ export class LoggerService { } } + @bindThis public getLogger(domain: string, color?: string | undefined, store?: boolean) { return new Logger(domain, color, store, this.syslogClient); } diff --git a/packages/backend/src/core/MessagingService.ts b/packages/backend/src/core/MessagingService.ts index 9de28ad8db..f4a1090658 100644 --- a/packages/backend/src/core/MessagingService.ts +++ b/packages/backend/src/core/MessagingService.ts @@ -17,6 +17,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; import { PushNotificationService } from '@/core/PushNotificationService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class MessagingService { @@ -46,6 +47,7 @@ export class MessagingService { ) { } + @bindThis public async createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: CacheableUser | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) { const message = { id: this.idService.genId(), @@ -140,11 +142,13 @@ export class MessagingService { return messageObj; } + @bindThis public async deleteMessage(message: MessagingMessage) { await this.messagingMessagesRepository.delete(message.id); this.postDeleteMessage(message); } + @bindThis private async postDeleteMessage(message: MessagingMessage) { if (message.recipientId) { const user = await this.usersRepository.findOneByOrFail({ id: message.userId }); @@ -165,6 +169,7 @@ export class MessagingService { /** * Mark messages as read */ + @bindThis public async readUserMessagingMessage( userId: User['id'], otherpartyId: User['id'], @@ -220,6 +225,7 @@ export class MessagingService { /** * Mark messages as read */ + @bindThis public async readGroupMessagingMessage( userId: User['id'], groupId: UserGroup['id'], @@ -284,6 +290,7 @@ export class MessagingService { } } + @bindThis public async deliverReadActivity(user: { id: User['id']; host: null; }, recipient: IRemoteUser, messages: MessagingMessage | MessagingMessage[]) { messages = toArray(messages).filter(x => x.uri); const contents = messages.map(x => this.apRendererService.renderRead(user, x)); diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index c3d41bfccb..ff05779aee 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -5,6 +5,7 @@ import { DI } from '@/di-symbols.js'; import { Meta } from '@/models/entities/Meta.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; @Injectable() export class MetaService implements OnApplicationShutdown { @@ -20,7 +21,7 @@ export class MetaService implements OnApplicationShutdown { private globalEventService: GlobalEventService, ) { - this.onMessage = this.onMessage.bind(this); + //this.onMessage = this.onMessage.bind(this); if (process.env.NODE_ENV !== 'test') { this.intervalId = setInterval(() => { @@ -34,6 +35,7 @@ export class MetaService implements OnApplicationShutdown { this.redisSubscriber.on('message', this.onMessage); } + @bindThis private async onMessage(_: string, data: string): Promise { const obj = JSON.parse(data); @@ -50,6 +52,7 @@ export class MetaService implements OnApplicationShutdown { } } + @bindThis public async fetch(noCache = false): Promise { if (!noCache && this.cache) return this.cache; @@ -84,6 +87,7 @@ export class MetaService implements OnApplicationShutdown { }); } + @bindThis public async update(data: Partial): Promise { const updated = await this.db.transaction(async transactionalEntityManager => { const metas = await transactionalEntityManager.find(Meta, { @@ -114,6 +118,7 @@ export class MetaService implements OnApplicationShutdown { return updated; } + @bindThis public onApplicationShutdown(signal?: string | undefined) { clearInterval(this.intervalId); this.redisSubscriber.off('message', this.onMessage); diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index 2e03bf3cc0..d53623baaa 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -14,6 +14,7 @@ const treeAdapter = TreeAdapter.defaultTreeAdapter; const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/; const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/; +import { bindThis } from '@/decorators.js'; @Injectable() export class MfmService { @@ -23,6 +24,7 @@ export class MfmService { ) { } + @bindThis public fromHtml(html: string, hashtagNames?: string[]): string { // some AP servers like Pixelfed use br tags as well as newlines html = html.replace(/\r?\n/gi, '\n'); @@ -228,6 +230,7 @@ export class MfmService { } } + @bindThis public toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = []) { if (nodes == null) { return null; diff --git a/packages/backend/src/core/ModerationLogService.ts b/packages/backend/src/core/ModerationLogService.ts index 81ae322b95..80e8cb9e52 100644 --- a/packages/backend/src/core/ModerationLogService.ts +++ b/packages/backend/src/core/ModerationLogService.ts @@ -3,6 +3,7 @@ import { DI } from '@/di-symbols.js'; import type { ModerationLogsRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { IdService } from '@/core/IdService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ModerationLogService { @@ -14,6 +15,7 @@ export class ModerationLogService { ) { } + @bindThis public async insertModerationLog(moderator: { id: User['id'] }, type: string, info?: Record) { await this.moderationLogsRepository.insert({ id: this.idService.genId(), diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index cf1566a5e8..a41df28050 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -64,6 +64,7 @@ class NotificationManager { this.queue = []; } + @bindThis public push(notifiee: ILocalUser['id'], reason: NotificationType) { // 自分自身へは通知しない if (this.notifier.id === notifiee) return; @@ -83,6 +84,7 @@ class NotificationManager { } } + @bindThis public async deliver() { for (const x of this.queue) { // ミュート情報を取得 @@ -130,6 +132,7 @@ type Option = { url?: string | null; app?: App | null; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class NoteCreateService { @@ -188,6 +191,7 @@ export class NoteCreateService { private instanceChart: InstanceChart, ) {} + @bindThis public async create(user: { id: User['id']; username: User['username']; @@ -307,6 +311,7 @@ export class NoteCreateService { return note; } + @bindThis private async insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) { const insert = new Note({ id: this.idService.genId(data.createdAt!), @@ -403,6 +408,7 @@ export class NoteCreateService { } } + @bindThis private async postNoteCreated(note: Note, user: { id: User['id']; username: User['username']; @@ -644,6 +650,7 @@ export class NoteCreateService { this.index(note); } + @bindThis private incRenoteCount(renote: Note) { this.notesRepository.createQueryBuilder().update() .set({ @@ -654,6 +661,7 @@ export class NoteCreateService { .execute(); } + @bindThis private async createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) { for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) { const threadMuted = await this.noteThreadMutingsRepository.findOneBy({ @@ -683,10 +691,12 @@ export class NoteCreateService { } } + @bindThis private saveReply(reply: Note, note: Note) { this.notesRepository.increment({ id: reply.id }, 'repliesCount', 1); } + @bindThis private async renderNoteOrRenoteActivity(data: Option, note: Note) { if (data.localOnly) return null; @@ -697,6 +707,7 @@ export class NoteCreateService { return this.apRendererService.renderActivity(content); } + @bindThis private index(note: Note) { if (note.text == null || this.config.elasticsearch == null) return; /* @@ -711,6 +722,7 @@ export class NoteCreateService { });*/ } + @bindThis private incNotesCountOfUser(user: { id: User['id']; }) { this.usersRepository.createQueryBuilder().update() .set({ @@ -721,6 +733,7 @@ export class NoteCreateService { .execute(); } + @bindThis private async extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.MfmNode[]): Promise { if (tokens == null) return []; diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index ce6e755a7e..7331d03552 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -15,6 +15,7 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NoteDeleteService { @@ -112,6 +113,7 @@ export class NoteDeleteService { }); } + @bindThis private async findCascadingNotes(note: Note) { const cascadingNotes: Note[] = []; @@ -134,6 +136,7 @@ export class NoteDeleteService { return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users } + @bindThis private async getMentionedRemoteUsers(note: Note) { const where = [] as any[]; @@ -159,6 +162,7 @@ export class NoteDeleteService { }) as IRemoteUser[]; } + @bindThis private async deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) { this.apDeliverManagerService.deliverToFollowers(user, content); this.relayService.deliverToRelays(user, content); diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts index a04b52fe4c..f8997574a7 100644 --- a/packages/backend/src/core/NotePiningService.ts +++ b/packages/backend/src/core/NotePiningService.ts @@ -11,6 +11,7 @@ import type { Config } from '@/config.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NotePiningService { @@ -40,6 +41,7 @@ export class NotePiningService { * @param user * @param noteId */ + @bindThis public async addPinned(user: { id: User['id']; host: User['host']; }, noteId: Note['id']) { // Fetch pinee const note = await this.notesRepository.findOneBy({ @@ -79,6 +81,7 @@ export class NotePiningService { * @param user * @param noteId */ + @bindThis public async removePinned(user: { id: User['id']; host: User['host']; }, noteId: Note['id']) { // Fetch unpinee const note = await this.notesRepository.findOneBy({ @@ -101,6 +104,7 @@ export class NotePiningService { } } + @bindThis public async deliverPinnedChange(userId: User['id'], noteId: Note['id'], isAddition: boolean) { const user = await this.usersRepository.findOneBy({ id: userId }); if (user == null) throw new Error('user not found'); diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index e0feaa957d..f70495ff30 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -11,6 +11,7 @@ import type { UsersRepository, NoteUnreadsRepository, MutingsRepository, NoteThr import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NotificationService } from './NotificationService.js'; import { AntennaService } from './AntennaService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NoteReadService { @@ -44,6 +45,7 @@ export class NoteReadService { ) { } + @bindThis public async insertNoteUnread(userId: User['id'], note: Note, params: { // NOTE: isSpecifiedがtrueならisMentionedは必ずfalse isSpecified: boolean; @@ -94,6 +96,7 @@ export class NoteReadService { }, 2000); } + @bindThis public async read( userId: User['id'], notes: (Note | Packed<'Note'>)[], diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index 8bbc95b02d..9fef36dd2c 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -8,6 +8,7 @@ import type { Notification } from '@/models/entities/Notification.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalEventService } from './GlobalEventService.js'; import { PushNotificationService } from './PushNotificationService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NotificationService { @@ -21,6 +22,7 @@ export class NotificationService { ) { } + @bindThis public async readNotification( userId: User['id'], notificationIds: Notification['id'][], @@ -42,6 +44,7 @@ export class NotificationService { else return this.postReadNotifications(userId, notificationIds); } + @bindThis public async readNotificationByQuery( userId: User['id'], query: Record, @@ -55,11 +58,13 @@ export class NotificationService { return this.readNotification(userId, notificationIds); } + @bindThis private postReadAllNotifications(userId: User['id']) { this.globalEventService.publishMainStream(userId, 'readAllNotifications'); return this.pushNotificationService.pushNotification(userId, 'readAllNotifications', undefined); } + @bindThis private postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) { this.globalEventService.publishMainStream(userId, 'readNotifications', notificationIds); return this.pushNotificationService.pushNotification(userId, 'readNotifications', { notificationIds }); diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index 287ce8ada4..3cc9b0cc9b 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -11,6 +11,7 @@ import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class PollService { @@ -40,6 +41,7 @@ export class PollService { ) { } + @bindThis public async vote(user: CacheableUser, note: Note, choice: number) { const poll = await this.pollsRepository.findOneBy({ noteId: note.id }); @@ -99,6 +101,7 @@ export class PollService { }); } + @bindThis public async deliverQuestionUpdate(noteId: Note['id']) { const note = await this.notesRepository.findOneBy({ id: noteId }); if (note == null) throw new Error('note not found'); diff --git a/packages/backend/src/core/ProxyAccountService.ts b/packages/backend/src/core/ProxyAccountService.ts index 4cbdadd029..55b70bfc94 100644 --- a/packages/backend/src/core/ProxyAccountService.ts +++ b/packages/backend/src/core/ProxyAccountService.ts @@ -3,6 +3,7 @@ import type { UsersRepository } from '@/models/index.js'; import type { ILocalUser, User } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ProxyAccountService { @@ -14,6 +15,7 @@ export class ProxyAccountService { ) { } + @bindThis public async fetch(): Promise { const meta = await this.metaService.fetch(); if (meta.proxyAccountId == null) return null; diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index 98e0841799..bffb24cf4c 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -37,6 +37,7 @@ function truncateNotification(notification: Packed<'Notification'>): any { return notification; } +import { bindThis } from '@/decorators.js'; @Injectable() export class PushNotificationService { diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index 771adeaed5..4cc844ccea 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -4,6 +4,7 @@ import { DI } from '@/di-symbols.js'; import type { User } from '@/models/entities/User.js'; import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository } from '@/models/index.js'; import type { SelectQueryBuilder } from 'typeorm'; +import { bindThis } from '@/decorators.js'; @Injectable() export class QueryService { @@ -59,6 +60,7 @@ export class QueryService { } // ここでいうBlockedは被Blockedの意 + @bindThis public generateBlockedUserQuery(q: SelectQueryBuilder, me: { id: User['id'] }): void { const blockingQuery = this.blockingsRepository.createQueryBuilder('blocking') .select('blocking.blockerId') @@ -81,6 +83,7 @@ export class QueryService { q.setParameters(blockingQuery.getParameters()); } + @bindThis public generateBlockQueryForUsers(q: SelectQueryBuilder, me: { id: User['id'] }): void { const blockingQuery = this.blockingsRepository.createQueryBuilder('blocking') .select('blocking.blockeeId') @@ -97,6 +100,7 @@ export class QueryService { q.setParameters(blockedQuery.getParameters()); } + @bindThis public generateChannelQuery(q: SelectQueryBuilder, me?: { id: User['id'] } | null): void { if (me == null) { q.andWhere('note.channelId IS NULL'); @@ -118,6 +122,7 @@ export class QueryService { } } + @bindThis public generateMutedNoteQuery(q: SelectQueryBuilder, me: { id: User['id'] }): void { const mutedQuery = this.mutedNotesRepository.createQueryBuilder('muted') .select('muted.noteId') @@ -128,6 +133,7 @@ export class QueryService { q.setParameters(mutedQuery.getParameters()); } + @bindThis public generateMutedNoteThreadQuery(q: SelectQueryBuilder, me: { id: User['id'] }): void { const mutedQuery = this.noteThreadMutingsRepository.createQueryBuilder('threadMuted') .select('threadMuted.threadId') @@ -142,6 +148,7 @@ export class QueryService { q.setParameters(mutedQuery.getParameters()); } + @bindThis public generateMutedUserQuery(q: SelectQueryBuilder, me: { id: User['id'] }, exclude?: User): void { const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') .select('muting.muteeId') @@ -186,6 +193,7 @@ export class QueryService { q.setParameters(mutingInstanceQuery.getParameters()); } + @bindThis public generateMutedUserQueryForUsers(q: SelectQueryBuilder, me: { id: User['id'] }): void { const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') .select('muting.muteeId') @@ -196,6 +204,7 @@ export class QueryService { q.setParameters(mutingQuery.getParameters()); } + @bindThis public generateRepliesQuery(q: SelectQueryBuilder, me?: Pick | null): void { if (me == null) { q.andWhere(new Brackets(qb => { qb @@ -221,6 +230,7 @@ export class QueryService { } } + @bindThis public generateVisibilityQuery(q: SelectQueryBuilder, me?: { id: User['id'] } | null): void { // This code must always be synchronized with the checks in Notes.isVisibleForMe. if (me == null) { diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index a27d68ee19..7956a3a8f9 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -8,6 +8,7 @@ import { DI } from '@/di-symbols.js'; import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js'; import type { ThinUser } from '../queue/types.js'; import type httpSignature from '@peertube/http-signature'; +import { bindThis } from '@/decorators.js'; @Injectable() export class QueueService { @@ -24,6 +25,7 @@ export class QueueService { @Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue, ) {} + @bindThis public deliver(user: ThinUser, content: IActivity | null, to: string | null) { if (content == null) return null; if (to == null) return null; @@ -47,6 +49,7 @@ export class QueueService { }); } + @bindThis public inbox(activity: IActivity, signature: httpSignature.IParsedSignature) { const data = { activity: activity, @@ -64,6 +67,7 @@ export class QueueService { }); } + @bindThis public createDeleteDriveFilesJob(user: ThinUser) { return this.dbQueue.add('deleteDriveFiles', { user: user, @@ -73,6 +77,7 @@ export class QueueService { }); } + @bindThis public createExportCustomEmojisJob(user: ThinUser) { return this.dbQueue.add('exportCustomEmojis', { user: user, @@ -82,6 +87,7 @@ export class QueueService { }); } + @bindThis public createExportNotesJob(user: ThinUser) { return this.dbQueue.add('exportNotes', { user: user, @@ -91,6 +97,7 @@ export class QueueService { }); } + @bindThis public createExportFollowingJob(user: ThinUser, excludeMuting = false, excludeInactive = false) { return this.dbQueue.add('exportFollowing', { user: user, @@ -102,6 +109,7 @@ export class QueueService { }); } + @bindThis public createExportMuteJob(user: ThinUser) { return this.dbQueue.add('exportMuting', { user: user, @@ -111,6 +119,7 @@ export class QueueService { }); } + @bindThis public createExportBlockingJob(user: ThinUser) { return this.dbQueue.add('exportBlocking', { user: user, @@ -120,6 +129,7 @@ export class QueueService { }); } + @bindThis public createExportUserListsJob(user: ThinUser) { return this.dbQueue.add('exportUserLists', { user: user, @@ -129,6 +139,7 @@ export class QueueService { }); } + @bindThis public createImportFollowingJob(user: ThinUser, fileId: DriveFile['id']) { return this.dbQueue.add('importFollowing', { user: user, @@ -139,6 +150,7 @@ export class QueueService { }); } + @bindThis public createImportMutingJob(user: ThinUser, fileId: DriveFile['id']) { return this.dbQueue.add('importMuting', { user: user, @@ -149,6 +161,7 @@ export class QueueService { }); } + @bindThis public createImportBlockingJob(user: ThinUser, fileId: DriveFile['id']) { return this.dbQueue.add('importBlocking', { user: user, @@ -159,6 +172,7 @@ export class QueueService { }); } + @bindThis public createImportUserListsJob(user: ThinUser, fileId: DriveFile['id']) { return this.dbQueue.add('importUserLists', { user: user, @@ -169,6 +183,7 @@ export class QueueService { }); } + @bindThis public createImportCustomEmojisJob(user: ThinUser, fileId: DriveFile['id']) { return this.dbQueue.add('importCustomEmojis', { user: user, @@ -179,6 +194,7 @@ export class QueueService { }); } + @bindThis public createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean; } = {}) { return this.dbQueue.add('deleteAccount', { user: user, @@ -189,6 +205,7 @@ export class QueueService { }); } + @bindThis public createDeleteObjectStorageFileJob(key: string) { return this.objectStorageQueue.add('deleteFile', { key: key, @@ -198,6 +215,7 @@ export class QueueService { }); } + @bindThis public createCleanRemoteFilesJob() { return this.objectStorageQueue.add('cleanRemoteFiles', {}, { removeOnComplete: true, @@ -205,6 +223,7 @@ export class QueueService { }); } + @bindThis public webhookDeliver(webhook: Webhook, type: typeof webhookEventTypes[number], content: unknown) { const data = { type, @@ -228,6 +247,7 @@ export class QueueService { }); } + @bindThis public destroy() { this.deliverQueue.once('cleaned', (jobs, status) => { //deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`); diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 7a9724e7dd..b02c990566 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -49,6 +49,7 @@ type DecodedReaction = { */ host?: string | null; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class ReactionService { @@ -81,6 +82,7 @@ export class ReactionService { ) { } + @bindThis public async create(user: { id: User['id']; host: User['host']; }, note: Note, reaction?: string) { // Check blocking if (note.userId !== user.id) { @@ -196,6 +198,7 @@ export class ReactionService { //#endregion } + @bindThis public async delete(user: { id: User['id']; host: User['host']; }, note: Note) { // if already unreacted const exist = await this.noteReactionsRepository.findOneBy({ @@ -244,11 +247,13 @@ export class ReactionService { //#endregion } + @bindThis public async getFallbackReaction(): Promise { const meta = await this.metaService.fetch(); return meta.useStarForReactionFallback ? '⭐' : '👍'; } + @bindThis public convertLegacyReactions(reactions: Record) { const _reactions = {} as Record; @@ -279,6 +284,7 @@ export class ReactionService { return _reactions2; } + @bindThis public async toDbReaction(reaction?: string | null, reacterHost?: string | null): Promise { if (reaction == null) return await this.getFallbackReaction(); @@ -311,6 +317,7 @@ export class ReactionService { return await this.getFallbackReaction(); } + @bindThis public decodeReaction(str: string): DecodedReaction { const custom = str.match(/^:([\w+-]+)(?:@([\w.-]+))?:$/); @@ -332,6 +339,7 @@ export class ReactionService { }; } + @bindThis public convertLegacyReaction(reaction: string): string { reaction = this.decodeReaction(reaction).reaction; if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts index 7951edddcb..5fb853f25a 100644 --- a/packages/backend/src/core/RelayService.ts +++ b/packages/backend/src/core/RelayService.ts @@ -12,6 +12,7 @@ import { DI } from '@/di-symbols.js'; import { deepClone } from '@/misc/clone.js'; const ACTOR_USERNAME = 'relay.actor' as const; +import { bindThis } from '@/decorators.js'; @Injectable() export class RelayService { @@ -32,6 +33,7 @@ export class RelayService { this.relaysCache = new Cache(1000 * 60 * 10); } + @bindThis private async getRelayActor(): Promise { const user = await this.usersRepository.findOneBy({ host: IsNull(), @@ -44,6 +46,7 @@ export class RelayService { return created as ILocalUser; } + @bindThis public async addRelay(inbox: string): Promise { const relay = await this.relaysRepository.insert({ id: this.idService.genId(), @@ -59,6 +62,7 @@ export class RelayService { return relay; } + @bindThis public async removeRelay(inbox: string): Promise { const relay = await this.relaysRepository.findOneBy({ inbox, @@ -77,11 +81,13 @@ export class RelayService { await this.relaysRepository.delete(relay.id); } + @bindThis public async listRelay(): Promise { const relays = await this.relaysRepository.find(); return relays; } + @bindThis public async relayAccepted(id: string): Promise { const result = await this.relaysRepository.update(id, { status: 'accepted', @@ -90,6 +96,7 @@ export class RelayService { return JSON.stringify(result); } + @bindThis public async relayRejected(id: string): Promise { const result = await this.relaysRepository.update(id, { status: 'rejected', @@ -98,6 +105,7 @@ export class RelayService { return JSON.stringify(result); } + @bindThis public async deliverToRelays(user: { id: User['id']; host: null; }, activity: any): Promise { if (activity == null) return; diff --git a/packages/backend/src/core/RemoteLoggerService.ts b/packages/backend/src/core/RemoteLoggerService.ts index 68246466c8..0ea5d7b42f 100644 --- a/packages/backend/src/core/RemoteLoggerService.ts +++ b/packages/backend/src/core/RemoteLoggerService.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class RemoteLoggerService { diff --git a/packages/backend/src/core/RemoteUserResolveService.ts b/packages/backend/src/core/RemoteUserResolveService.ts index 809b50f6e9..dde4098624 100644 --- a/packages/backend/src/core/RemoteUserResolveService.ts +++ b/packages/backend/src/core/RemoteUserResolveService.ts @@ -11,6 +11,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { WebfingerService } from '@/core/WebfingerService.js'; import { RemoteLoggerService } from '@/core/RemoteLoggerService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class RemoteUserResolveService { @@ -31,6 +32,7 @@ export class RemoteUserResolveService { this.logger = this.remoteLoggerService.logger.createSubLogger('resolve-user'); } + @bindThis public async resolveUser(username: string, host: string | null): Promise { const usernameLower = username.toLowerCase(); @@ -116,6 +118,7 @@ export class RemoteUserResolveService { return user; } + @bindThis private async resolveSelf(acctLower: string) { this.logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); const finger = await this.webfingerService.webfinger(acctLower).catch(err => { diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts index 1374ee06c8..0ce69aaa74 100644 --- a/packages/backend/src/core/S3Service.ts +++ b/packages/backend/src/core/S3Service.ts @@ -5,6 +5,7 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import type { Meta } from '@/models/entities/Meta.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class S3Service { @@ -16,6 +17,7 @@ export class S3Service { ) { } + @bindThis public getS3(meta: Meta) { const u = meta.objectStorageEndpoint != null ? `${meta.objectStorageUseSSL ? 'https://' : 'http://'}${meta.objectStorageEndpoint}` diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 1e34d9e4f8..9cf203566d 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -14,6 +14,7 @@ import generateUserToken from '@/misc/generate-native-user-token.js'; import UsersChart from './chart/charts/users.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UtilityService } from './UtilityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class SignupService { @@ -37,6 +38,7 @@ export class SignupService { ) { } + @bindThis public async signup(opts: { username: User['username']; password?: string | null; diff --git a/packages/backend/src/core/TwoFactorAuthenticationService.ts b/packages/backend/src/core/TwoFactorAuthenticationService.ts index 54e87ec36f..150047fd22 100644 --- a/packages/backend/src/core/TwoFactorAuthenticationService.ts +++ b/packages/backend/src/core/TwoFactorAuthenticationService.ts @@ -103,6 +103,7 @@ function PEMString(pemBuffer: Buffer, type = 'CERTIFICATE') { `\n-----END ${type}-----\n` ); } +import { bindThis } from '@/decorators.js'; @Injectable() export class TwoFactorAuthenticationService { @@ -115,6 +116,7 @@ export class TwoFactorAuthenticationService { ) { } + @bindThis public hash(data: Buffer) { return crypto .createHash('sha256') @@ -122,6 +124,7 @@ export class TwoFactorAuthenticationService { .digest(); } + @bindThis public verifySignin({ publicKey, authenticatorData, @@ -159,6 +162,7 @@ export class TwoFactorAuthenticationService { .verify(PEMString(publicKey), signature); } + @bindThis public getProcedures() { return { none: { diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 3399bb510f..d411768dc7 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -14,6 +14,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { WebhookService } from '@/core/WebhookService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserBlockingService { @@ -50,6 +51,7 @@ export class UserBlockingService { this.logger = this.loggerService.getLogger('user-block'); } + @bindThis public async block(blocker: User, blockee: User) { await Promise.all([ this.cancelRequest(blocker, blockee), @@ -76,6 +78,7 @@ export class UserBlockingService { } } + @bindThis private async cancelRequest(follower: User, followee: User) { const request = await this.followRequestsRepository.findOneBy({ followeeId: followee.id, @@ -126,6 +129,7 @@ export class UserBlockingService { } } + @bindThis private async unFollow(follower: User, followee: User) { const following = await this.followingsRepository.findOneBy({ followerId: follower.id, @@ -167,6 +171,7 @@ export class UserBlockingService { } } + @bindThis private async removeFromList(listOwner: User, user: User) { const userLists = await this.userListsRepository.findBy({ userId: listOwner.id, @@ -180,6 +185,7 @@ export class UserBlockingService { } } + @bindThis public async unblock(blocker: CacheableUser, blockee: CacheableUser) { const blocking = await this.blockingsRepository.findOneBy({ blockerId: blocker.id, diff --git a/packages/backend/src/core/UserCacheService.ts b/packages/backend/src/core/UserCacheService.ts index 25a600a8da..423c8993e3 100644 --- a/packages/backend/src/core/UserCacheService.ts +++ b/packages/backend/src/core/UserCacheService.ts @@ -6,6 +6,7 @@ import type { CacheableLocalUser, CacheableUser, ILocalUser } from '@/models/ent import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserCacheService implements OnApplicationShutdown { @@ -23,7 +24,7 @@ export class UserCacheService implements OnApplicationShutdown { private userEntityService: UserEntityService, ) { - this.onMessage = this.onMessage.bind(this); + //this.onMessage = this.onMessage.bind(this); this.userByIdCache = new Cache(Infinity); this.localUserByNativeTokenCache = new Cache(Infinity); @@ -33,6 +34,7 @@ export class UserCacheService implements OnApplicationShutdown { this.redisSubscriber.on('message', this.onMessage); } + @bindThis private async onMessage(_: string, data: string): Promise { const obj = JSON.parse(data); @@ -68,6 +70,7 @@ export class UserCacheService implements OnApplicationShutdown { } } + @bindThis public onApplicationShutdown(signal?: string | undefined) { this.redisSubscriber.off('message', this.onMessage); } diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 2f51e2a9df..a6cb042c7d 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -31,6 +31,7 @@ type Remote = IRemoteUser | { inbox: IRemoteUser['inbox']; }; type Both = Local | Remote; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserFollowingService { @@ -66,6 +67,7 @@ export class UserFollowingService { ) { } + @bindThis public async follow(_follower: { id: User['id'] }, _followee: { id: User['id'] }, requestId?: string): Promise { const [follower, followee] = await Promise.all([ this.usersRepository.findOneByOrFail({ id: _follower.id }), @@ -140,6 +142,7 @@ export class UserFollowingService { } } + @bindThis private async insertFollowingDoc( followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox'] @@ -253,6 +256,7 @@ export class UserFollowingService { } } + @bindThis public async unfollow( follower: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; @@ -305,6 +309,7 @@ export class UserFollowingService { } } + @bindThis private async decrementFollowing( follower: {id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }, @@ -333,6 +338,7 @@ export class UserFollowingService { this.perUserFollowingChart.update(follower, followee, false); } + @bindThis public async createFollowRequest( follower: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; @@ -396,6 +402,7 @@ export class UserFollowingService { } } + @bindThis public async cancelFollowRequest( followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox'] @@ -431,6 +438,7 @@ export class UserFollowingService { }).then(packed => this.globalEventServie.publishMainStream(followee.id, 'meUpdated', packed)); } + @bindThis public async acceptFollowRequest( followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; @@ -458,6 +466,7 @@ export class UserFollowingService { }).then(packed => this.globalEventServie.publishMainStream(followee.id, 'meUpdated', packed)); } + @bindThis public async acceptAllFollowRequests( user: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; @@ -476,6 +485,7 @@ export class UserFollowingService { /** * API following/request/reject */ + @bindThis public async rejectFollowRequest(user: Local, follower: Both): Promise { if (this.userEntityService.isRemoteUser(follower)) { this.deliverReject(user, follower); @@ -491,6 +501,7 @@ export class UserFollowingService { /** * API following/reject */ + @bindThis public async rejectFollow(user: Local, follower: Both): Promise { if (this.userEntityService.isRemoteUser(follower)) { this.deliverReject(user, follower); @@ -506,6 +517,7 @@ export class UserFollowingService { /** * AP Reject/Follow */ + @bindThis public async remoteReject(actor: Remote, follower: Local): Promise { await this.removeFollowRequest(actor, follower); await this.removeFollow(actor, follower); @@ -515,6 +527,7 @@ export class UserFollowingService { /** * Remove follow request record */ + @bindThis private async removeFollowRequest(followee: Both, follower: Both): Promise { const request = await this.followRequestsRepository.findOneBy({ followeeId: followee.id, @@ -529,6 +542,7 @@ export class UserFollowingService { /** * Remove follow record */ + @bindThis private async removeFollow(followee: Both, follower: Both): Promise { const following = await this.followingsRepository.findOneBy({ followeeId: followee.id, @@ -544,6 +558,7 @@ export class UserFollowingService { /** * Deliver Reject to remote */ + @bindThis private async deliverReject(followee: Local, follower: Remote): Promise { const request = await this.followRequestsRepository.findOneBy({ followeeId: followee.id, @@ -557,6 +572,7 @@ export class UserFollowingService { /** * Publish unfollow to local */ + @bindThis private async publishUnfollow(followee: Both, follower: Local): Promise { const packedFollowee = await this.userEntityService.pack(followee.id, follower, { detail: true, diff --git a/packages/backend/src/core/UserKeypairStoreService.ts b/packages/backend/src/core/UserKeypairStoreService.ts index 8eca03a10b..1d3cc87c8d 100644 --- a/packages/backend/src/core/UserKeypairStoreService.ts +++ b/packages/backend/src/core/UserKeypairStoreService.ts @@ -4,6 +4,7 @@ import type { UserKeypairsRepository } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; import type { UserKeypair } from '@/models/entities/UserKeypair.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserKeypairStoreService { @@ -16,6 +17,7 @@ export class UserKeypairStoreService { this.cache = new Cache(Infinity); } + @bindThis public async getUserKeypair(userId: User['id']): Promise { return await this.cache.fetch(userId, () => this.userKeypairsRepository.findOneByOrFail({ userId: userId })); } diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 1d1ead5a1f..054387ff8e 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -9,6 +9,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ProxyAccountService } from '@/core/ProxyAccountService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserListService { @@ -27,6 +28,7 @@ export class UserListService { ) { } + @bindThis public async push(target: User, list: UserList) { await this.userListJoiningsRepository.insert({ id: this.idService.genId(), diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts index 4c09e450c8..3029d02c00 100644 --- a/packages/backend/src/core/UserMutingService.ts +++ b/packages/backend/src/core/UserMutingService.ts @@ -5,6 +5,7 @@ import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { User } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserMutingService { @@ -21,6 +22,7 @@ export class UserMutingService { ) { } + @bindThis public async mute(user: User, target: User): Promise { await this.mutingsRepository.insert({ id: this.idService.genId(), diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index 02f686bab6..df1664942f 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -8,6 +8,7 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserSuspendService { @@ -28,6 +29,7 @@ export class UserSuspendService { ) { } + @bindThis public async doPostSuspend(user: { id: User['id']; host: User['host'] }): Promise { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); @@ -57,6 +59,7 @@ export class UserSuspendService { } } + @bindThis public async doPostUnsuspend(user: User): Promise { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false }); diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index 15dd684286..1412e6e9aa 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -3,6 +3,7 @@ import { toASCII } from 'punycode'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UtilityService { @@ -12,24 +13,29 @@ export class UtilityService { ) { } + @bindThis public getFullApAccount(username: string, host: string | null): string { return host ? `${username}@${this.toPuny(host)}` : `${username}@${this.toPuny(this.config.host)}`; } + @bindThis public isSelfHost(host: string | null): boolean { if (host == null) return true; return this.toPuny(this.config.host) === this.toPuny(host); } + @bindThis public extractDbHost(uri: string): string { const url = new URL(uri); return this.toPuny(url.hostname); } + @bindThis public toPuny(host: string): string { return toASCII(host.toLowerCase()); } + @bindThis public toPunyNullable(host: string | null | undefined): string | null { if (host == null) return null; return toASCII(host.toLowerCase()); diff --git a/packages/backend/src/core/VideoProcessingService.ts b/packages/backend/src/core/VideoProcessingService.ts index af4036a291..2807960cb7 100644 --- a/packages/backend/src/core/VideoProcessingService.ts +++ b/packages/backend/src/core/VideoProcessingService.ts @@ -5,6 +5,7 @@ import type { Config } from '@/config.js'; import { ImageProcessingService } from '@/core/ImageProcessingService.js'; import type { IImage } from '@/core/ImageProcessingService.js'; import { createTempDir } from '@/misc/create-temp.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class VideoProcessingService { @@ -16,6 +17,7 @@ export class VideoProcessingService { ) { } + @bindThis public async generateVideoThumbnail(source: string): Promise { const [dir, cleanup] = await createTempDir(); diff --git a/packages/backend/src/core/WebfingerService.ts b/packages/backend/src/core/WebfingerService.ts index d2a88be583..2d7afe5c88 100644 --- a/packages/backend/src/core/WebfingerService.ts +++ b/packages/backend/src/core/WebfingerService.ts @@ -14,6 +14,7 @@ type IWebFinger = { links: ILink[]; subject: string; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class WebfingerService { @@ -25,12 +26,14 @@ export class WebfingerService { ) { } + @bindThis public async webfinger(query: string): Promise { const url = this.genUrl(query); return await this.httpRequestService.getJson(url, 'application/jrd+json, application/json') as IWebFinger; } + @bindThis private genUrl(query: string): string { if (query.match(/^https?:\/\//)) { const u = new URL(query); diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index 1a690314f8..91a39f1359 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -4,6 +4,7 @@ import type { WebhooksRepository } from '@/models/index.js'; import type { Webhook } from '@/models/entities/Webhook.js'; import { DI } from '@/di-symbols.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; @Injectable() export class WebhookService implements OnApplicationShutdown { @@ -17,10 +18,11 @@ export class WebhookService implements OnApplicationShutdown { @Inject(DI.webhooksRepository) private webhooksRepository: WebhooksRepository, ) { - this.onMessage = this.onMessage.bind(this); + //this.onMessage = this.onMessage.bind(this); this.redisSubscriber.on('message', this.onMessage); } + @bindThis public async getActiveWebhooks() { if (!this.webhooksFetched) { this.webhooks = await this.webhooksRepository.findBy({ @@ -32,6 +34,7 @@ export class WebhookService implements OnApplicationShutdown { return this.webhooks; } + @bindThis private async onMessage(_: string, data: string): Promise { const obj = JSON.parse(data); @@ -64,6 +67,7 @@ export class WebhookService implements OnApplicationShutdown { } } + @bindThis public onApplicationShutdown(signal?: string | undefined) { this.redisSubscriber.off('message', this.onMessage); } diff --git a/packages/backend/src/core/activitypub/ApAudienceService.ts b/packages/backend/src/core/activitypub/ApAudienceService.ts index 744017aa3a..64f01644a7 100644 --- a/packages/backend/src/core/activitypub/ApAudienceService.ts +++ b/packages/backend/src/core/activitypub/ApAudienceService.ts @@ -4,6 +4,7 @@ import promiseLimit from 'promise-limit'; import { DI } from '@/di-symbols.js'; import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js'; import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js'; +import { bindThis } from '@/decorators.js'; import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; import { ApPersonService } from './models/ApPersonService.js'; import type { ApObject } from './type.js'; @@ -24,6 +25,7 @@ export class ApAudienceService { ) { } + @bindThis public async parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { const toGroups = this.groupingAudience(getApIds(to), actor); const ccGroups = this.groupingAudience(getApIds(cc), actor); @@ -66,6 +68,7 @@ export class ApAudienceService { }; } + @bindThis private groupingAudience(ids: string[], actor: CacheableRemoteUser) { const groups = { public: [] as string[], @@ -88,6 +91,7 @@ export class ApAudienceService { return groups; } + @bindThis private isPublic(id: string) { return [ 'https://www.w3.org/ns/activitystreams#Public', @@ -96,6 +100,7 @@ export class ApAudienceService { ].includes(id); } + @bindThis private isFollowers(id: string, actor: CacheableRemoteUser) { return ( id === (actor.followersUri ?? `${actor.uri}/followers`) diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index 77d200c3c8..1f28fb3a07 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -9,6 +9,7 @@ import type { UserPublickey } from '@/models/entities/UserPublickey.js'; import { UserCacheService } from '@/core/UserCacheService.js'; import type { Note } from '@/models/entities/Note.js'; import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; +import { bindThis } from '@/decorators.js'; import { getApId } from './type.js'; import { ApPersonService } from './models/ApPersonService.js'; import type { IObject } from './type.js'; @@ -57,6 +58,7 @@ export class ApDbResolverService { this.publicKeyByUserIdCache = new Cache(Infinity); } + @bindThis public parseUri(value: string | IObject): UriParseResult { const uri = getApId(value); @@ -82,6 +84,7 @@ export class ApDbResolverService { /** * AP Note => Misskey Note in DB */ + @bindThis public async getNoteFromApId(value: string | IObject): Promise { const parsed = this.parseUri(value); @@ -98,6 +101,7 @@ export class ApDbResolverService { } } + @bindThis public async getMessageFromApId(value: string | IObject): Promise { const parsed = this.parseUri(value); @@ -117,6 +121,7 @@ export class ApDbResolverService { /** * AP Person => Misskey User in DB */ + @bindThis public async getUserFromApId(value: string | IObject): Promise { const parsed = this.parseUri(value); @@ -136,6 +141,7 @@ export class ApDbResolverService { /** * AP KeyId => Misskey User and Key */ + @bindThis public async getAuthUserFromKeyId(keyId: string): Promise<{ user: CacheableRemoteUser; key: UserPublickey; @@ -161,6 +167,7 @@ export class ApDbResolverService { /** * AP Actor id => Misskey User and Key */ + @bindThis public async getAuthUserFromApId(uri: string): Promise<{ user: CacheableRemoteUser; key: UserPublickey | null; diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts index 6fc75a0397..256cf12651 100644 --- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts +++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts @@ -6,6 +6,7 @@ import type { Config } from '@/config.js'; import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; import { QueueService } from '@/core/QueueService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; interface IRecipe { type: string; @@ -48,6 +49,7 @@ export class ApDeliverManagerService { * @param activity Activity * @param from Followee */ + @bindThis public async deliverToFollowers(actor: { id: ILocalUser['id']; host: null; }, activity: any) { const manager = new DeliverManager( this.userEntityService, @@ -65,6 +67,7 @@ export class ApDeliverManagerService { * @param activity Activity * @param to Target user */ + @bindThis public async deliverToUser(actor: { id: ILocalUser['id']; host: null; }, activity: any, to: IRemoteUser) { const manager = new DeliverManager( this.userEntityService, @@ -77,6 +80,7 @@ export class ApDeliverManagerService { await manager.execute(); } + @bindThis public createDeliverManager(actor: { id: User['id']; host: null; }, activity: any) { return new DeliverManager( this.userEntityService, @@ -114,6 +118,7 @@ class DeliverManager { /** * Add recipe for followers deliver */ + @bindThis public addFollowersRecipe() { const deliver = { type: 'Followers', @@ -126,6 +131,7 @@ class DeliverManager { * Add recipe for direct deliver * @param to To */ + @bindThis public addDirectRecipe(to: IRemoteUser) { const recipe = { type: 'Direct', @@ -139,6 +145,7 @@ class DeliverManager { * Add recipe * @param recipe Recipe */ + @bindThis public addRecipe(recipe: IRecipe) { this.recipes.push(recipe); } @@ -146,6 +153,7 @@ class DeliverManager { /** * Execute delivers */ + @bindThis public async execute() { if (!this.userEntityService.isLocalUser(this.actor)) return; diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 3da384ec2d..79a917426a 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -32,6 +32,7 @@ import { ApPersonService } from './models/ApPersonService.js'; import { ApQuestionService } from './models/ApQuestionService.js'; import type { Resolver } from './ApResolverService.js'; import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IRead, IReject, IRemove, IUndo, IUpdate } from './type.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApInboxService { @@ -85,6 +86,7 @@ export class ApInboxService { this.logger = this.apLoggerService.logger; } + @bindThis public async performActivity(actor: CacheableRemoteUser, activity: IObject) { if (isCollectionOrOrderedCollection(activity)) { const resolver = this.apResolverService.createResolver(); @@ -112,6 +114,7 @@ export class ApInboxService { } } + @bindThis public async performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise { if (actor.isSuspended) return; @@ -148,6 +151,7 @@ export class ApInboxService { } } + @bindThis private async follow(actor: CacheableRemoteUser, activity: IFollow): Promise { const followee = await this.apDbResolverService.getUserFromApId(activity.object); @@ -163,6 +167,7 @@ export class ApInboxService { return 'ok'; } + @bindThis private async like(actor: CacheableRemoteUser, activity: ILike): Promise { const targetUri = getApId(activity.object); @@ -180,6 +185,7 @@ export class ApInboxService { }).then(() => 'ok'); } + @bindThis private async read(actor: CacheableRemoteUser, activity: IRead): Promise { const id = await getApId(activity.object); @@ -202,6 +208,7 @@ export class ApInboxService { return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`; } + @bindThis private async accept(actor: CacheableRemoteUser, activity: IAccept): Promise { const uri = activity.id ?? activity; @@ -219,6 +226,7 @@ export class ApInboxService { return `skip: Unknown Accept type: ${getApType(object)}`; } + @bindThis private async acceptFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある @@ -242,6 +250,7 @@ export class ApInboxService { return 'ok'; } + @bindThis private async add(actor: CacheableRemoteUser, activity: IAdd): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); @@ -261,6 +270,7 @@ export class ApInboxService { throw new Error(`unknown target: ${activity.target}`); } + @bindThis private async announce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { const uri = getApId(activity); @@ -271,6 +281,7 @@ export class ApInboxService { this.announceNote(actor, activity, targetUri); } + @bindThis private async announceNote(actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { const uri = getApId(activity); @@ -330,6 +341,7 @@ export class ApInboxService { } } + @bindThis private async block(actor: CacheableRemoteUser, activity: IBlock): Promise { // ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず @@ -347,6 +359,7 @@ export class ApInboxService { return 'ok'; } + @bindThis private async create(actor: CacheableRemoteUser, activity: ICreate): Promise { const uri = getApId(activity); @@ -382,6 +395,7 @@ export class ApInboxService { } } + @bindThis private async createNote(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { const uri = getApId(note); @@ -416,6 +430,7 @@ export class ApInboxService { } } + @bindThis private async delete(actor: CacheableRemoteUser, activity: IDelete): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); @@ -457,6 +472,7 @@ export class ApInboxService { } } + @bindThis private async deleteActor(actor: CacheableRemoteUser, uri: string): Promise { this.logger.info(`Deleting the Actor: ${uri}`); @@ -478,6 +494,7 @@ export class ApInboxService { return `ok: queued ${job.name} ${job.id}`; } + @bindThis private async deleteNote(actor: CacheableRemoteUser, uri: string): Promise { this.logger.info(`Deleting the Note: ${uri}`); @@ -510,6 +527,7 @@ export class ApInboxService { } } + @bindThis private async flag(actor: CacheableRemoteUser, activity: IFlag): Promise { // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する @@ -534,6 +552,7 @@ export class ApInboxService { return 'ok'; } + @bindThis private async reject(actor: CacheableRemoteUser, activity: IReject): Promise { const uri = activity.id ?? activity; @@ -551,6 +570,7 @@ export class ApInboxService { return `skip: Unknown Reject type: ${getApType(object)}`; } + @bindThis private async rejectFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある @@ -574,6 +594,7 @@ export class ApInboxService { return 'ok'; } + @bindThis private async remove(actor: CacheableRemoteUser, activity: IRemove): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); @@ -593,6 +614,7 @@ export class ApInboxService { throw new Error(`unknown target: ${activity.target}`); } + @bindThis private async undo(actor: CacheableRemoteUser, activity: IUndo): Promise { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); @@ -618,6 +640,7 @@ export class ApInboxService { return `skip: unknown object type ${getApType(object)}`; } + @bindThis private async undoAccept(actor: CacheableRemoteUser, activity: IAccept): Promise { const follower = await this.apDbResolverService.getUserFromApId(activity.object); if (follower == null) { @@ -637,6 +660,7 @@ export class ApInboxService { return 'skip: フォローされていない'; } + @bindThis private async undoAnnounce(actor: CacheableRemoteUser, activity: IAnnounce): Promise { const uri = getApId(activity); @@ -651,6 +675,7 @@ export class ApInboxService { return 'ok: deleted'; } + @bindThis private async undoBlock(actor: CacheableRemoteUser, activity: IBlock): Promise { const blockee = await this.apDbResolverService.getUserFromApId(activity.object); @@ -666,6 +691,7 @@ export class ApInboxService { return 'ok'; } + @bindThis private async undoFollow(actor: CacheableRemoteUser, activity: IFollow): Promise { const followee = await this.apDbResolverService.getUserFromApId(activity.object); if (followee == null) { @@ -699,6 +725,7 @@ export class ApInboxService { return 'skip: リクエストもフォローもされていない'; } + @bindThis private async undoLike(actor: CacheableRemoteUser, activity: ILike): Promise { const targetUri = getApId(activity.object); @@ -713,6 +740,7 @@ export class ApInboxService { return 'ok'; } + @bindThis private async update(actor: CacheableRemoteUser, activity: IUpdate): Promise { if ('actor' in activity && actor.uri !== activity.actor) { return 'skip: invalid actor'; diff --git a/packages/backend/src/core/activitypub/ApLoggerService.ts b/packages/backend/src/core/activitypub/ApLoggerService.ts index a742cc42da..b9bf1e4054 100644 --- a/packages/backend/src/core/activitypub/ApLoggerService.ts +++ b/packages/backend/src/core/activitypub/ApLoggerService.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type Logger from '@/logger.js'; import { RemoteLoggerService } from '@/core/RemoteLoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApLoggerService { diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts index 8804fde64a..6116822f7a 100644 --- a/packages/backend/src/core/activitypub/ApMfmService.ts +++ b/packages/backend/src/core/activitypub/ApMfmService.ts @@ -6,6 +6,7 @@ import { MfmService } from '@/core/MfmService.js'; import type { Note } from '@/models/entities/Note.js'; import { extractApHashtagObjects } from './models/tag.js'; import type { IObject } from './type.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApMfmService { @@ -17,12 +18,14 @@ export class ApMfmService { ) { } + @bindThis public htmlToMfm(html: string, tag?: IObject | IObject[]) { const hashtagNames = extractApHashtagObjects(tag).map(x => x.name).filter((x): x is string => x != null); return this.mfmService.fromHtml(html, hashtagNames); } + @bindThis public getNoteHtml(note: Note) { if (!note.text) return ''; return this.mfmService.toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 38a92567c3..1800840ee9 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -25,6 +25,7 @@ import { LdSignatureService } from './LdSignatureService.js'; import { ApMfmService } from './ApMfmService.js'; import type { IActivity, IObject } from './type.js'; import type { IIdentifier } from './models/identifier.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApRendererService { @@ -59,6 +60,7 @@ export class ApRendererService { ) { } + @bindThis public renderAccept(object: any, user: { id: User['id']; host: null }) { return { type: 'Accept', @@ -67,6 +69,7 @@ export class ApRendererService { }; } + @bindThis public renderAdd(user: ILocalUser, target: any, object: any) { return { type: 'Add', @@ -76,6 +79,7 @@ export class ApRendererService { }; } + @bindThis public renderAnnounce(object: any, note: Note) { const attributedTo = `${this.config.url}/users/${note.userId}`; @@ -108,6 +112,7 @@ export class ApRendererService { * * @param block The block to be rendered. The blockee relation must be loaded. */ + @bindThis public renderBlock(block: Blocking) { if (block.blockee?.uri == null) { throw new Error('renderBlock: missing blockee uri'); @@ -121,6 +126,7 @@ export class ApRendererService { }; } + @bindThis public renderCreate(object: any, note: Note) { const activity = { id: `${this.config.url}/notes/${note.id}/activity`, @@ -136,6 +142,7 @@ export class ApRendererService { return activity; } + @bindThis public renderDelete(object: any, user: { id: User['id']; host: null }) { return { type: 'Delete', @@ -145,6 +152,7 @@ export class ApRendererService { }; } + @bindThis public renderDocument(file: DriveFile) { return { type: 'Document', @@ -154,6 +162,7 @@ export class ApRendererService { }; } + @bindThis public renderEmoji(emoji: Emoji) { return { id: `${this.config.url}/emojis/${emoji.name}`, @@ -170,6 +179,7 @@ export class ApRendererService { // to anonymise reporters, the reporting actor must be a system user // object has to be a uri or array of uris + @bindThis public renderFlag(user: ILocalUser, object: [string], content: string) { return { type: 'Flag', @@ -179,6 +189,7 @@ export class ApRendererService { }; } + @bindThis public renderFollowRelay(relay: Relay, relayActor: ILocalUser) { const follow = { id: `${this.config.url}/activities/follow-relay/${relay.id}`, @@ -194,11 +205,13 @@ export class ApRendererService { * Convert (local|remote)(Follower|Followee)ID to URL * @param id Follower|Followee ID */ + @bindThis public async renderFollowUser(id: User['id']) { const user = await this.usersRepository.findOneByOrFail({ id: id }); return this.userEntityService.isLocalUser(user) ? `${this.config.url}/users/${user.id}` : user.uri; } + @bindThis public renderFollow( follower: { id: User['id']; host: User['host']; uri: User['host'] }, followee: { id: User['id']; host: User['host']; uri: User['host'] }, @@ -214,6 +227,7 @@ export class ApRendererService { return follow; } + @bindThis public renderHashtag(tag: string) { return { type: 'Hashtag', @@ -222,6 +236,7 @@ export class ApRendererService { }; } + @bindThis public renderImage(file: DriveFile) { return { type: 'Image', @@ -231,6 +246,7 @@ export class ApRendererService { }; } + @bindThis public renderKey(user: ILocalUser, key: UserKeypair, postfix?: string) { return { id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`, @@ -243,6 +259,7 @@ export class ApRendererService { }; } + @bindThis public async renderLike(noteReaction: NoteReaction, note: { uri: string | null }) { const reaction = noteReaction.reaction; @@ -268,6 +285,7 @@ export class ApRendererService { return object; } + @bindThis public renderMention(mention: User) { return { type: 'Mention', @@ -276,6 +294,7 @@ export class ApRendererService { }; } + @bindThis public async renderNote(note: Note, dive = true, isTalk = false): Promise { const getPromisedFiles = async (ids: string[]) => { if (!ids || ids.length === 0) return []; @@ -420,6 +439,7 @@ export class ApRendererService { }; } + @bindThis public async renderPerson(user: ILocalUser) { const id = `${this.config.url}/users/${user.id}`; const isSystem = !!user.username.match(/\./); @@ -496,6 +516,7 @@ export class ApRendererService { return person; } + @bindThis public async renderQuestion(user: { id: User['id'] }, note: Note, poll: Poll) { const question = { type: 'Question', @@ -515,6 +536,7 @@ export class ApRendererService { return question; } + @bindThis public renderRead(user: { id: User['id'] }, message: MessagingMessage) { return { type: 'Read', @@ -523,6 +545,7 @@ export class ApRendererService { }; } + @bindThis public renderReject(object: any, user: { id: User['id'] }) { return { type: 'Reject', @@ -531,6 +554,7 @@ export class ApRendererService { }; } + @bindThis public renderRemove(user: { id: User['id'] }, target: any, object: any) { return { type: 'Remove', @@ -540,6 +564,7 @@ export class ApRendererService { }; } + @bindThis public renderTombstone(id: string) { return { id, @@ -547,6 +572,7 @@ export class ApRendererService { }; } + @bindThis public renderUndo(object: any, user: { id: User['id'] }) { if (object == null) return null; const id = typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined; @@ -560,6 +586,7 @@ export class ApRendererService { }; } + @bindThis public renderUpdate(object: any, user: { id: User['id'] }) { const activity = { id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`, @@ -573,6 +600,7 @@ export class ApRendererService { return activity; } + @bindThis public renderVote(user: { id: User['id'] }, vote: PollVote, note: Note, poll: Poll, pollOwner: IRemoteUser) { return { id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`, @@ -591,6 +619,7 @@ export class ApRendererService { }; } + @bindThis public renderActivity(x: any): IActivity | null { if (x == null) return null; @@ -632,6 +661,7 @@ export class ApRendererService { }, x); } + @bindThis public async attachLdSignature(activity: any, user: { id: User['id']; host: null; }): Promise { const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); @@ -651,6 +681,7 @@ export class ApRendererService { * @param prev URL of prev page (optional) * @param next URL of next page (optional) */ + @bindThis public renderOrderedCollectionPage(id: string, totalItems: any, orderedItems: any, partOf: string, prev?: string, next?: string) { const page = { id, @@ -674,6 +705,7 @@ export class ApRendererService { * @param last URL of last page (optional) * @param orderedItems attached objects (optional) */ + @bindThis public renderOrderedCollection(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: IObject[]) { const page: any = { id, @@ -688,6 +720,7 @@ export class ApRendererService { return page; } + @bindThis private async getEmojis(names: string[]): Promise { if (names == null || names.length === 0) return []; diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index baad46d668..d1edd579fa 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -6,6 +6,7 @@ import type { Config } from '@/config.js'; import type { User } from '@/models/entities/User.js'; import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { bindThis } from '@/decorators.js'; type Request = { url: string; @@ -36,6 +37,7 @@ export class ApRequestService { ) { } + @bindThis private createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record }): Signed { const u = new URL(args.url); const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`; @@ -61,6 +63,7 @@ export class ApRequestService { }; } + @bindThis private createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record }): Signed { const u = new URL(args.url); @@ -84,6 +87,7 @@ export class ApRequestService { }; } + @bindThis private signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed { const signingString = this.genSigningString(request, includeHeaders); const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64'); @@ -101,6 +105,7 @@ export class ApRequestService { }; } + @bindThis private genSigningString(request: Request, includeHeaders: string[]): string { request.headers = this.lcObjectKey(request.headers); @@ -117,16 +122,19 @@ export class ApRequestService { return results.join('\n'); } + @bindThis private lcObjectKey(src: Record): Record { const dst: Record = {}; for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key]; return dst; } + @bindThis private objectAssignWithLcKey(a: Record, b: Record): Record { return Object.assign(this.lcObjectKey(a), this.lcObjectKey(b)); } + @bindThis public async signedPost(user: { id: User['id'] }, url: string, object: any) { const body = JSON.stringify(object); @@ -157,6 +165,7 @@ export class ApRequestService { * @param user http-signature user * @param url URL to fetch */ + @bindThis public async signedGet(url: string, user: { id: User['id'] }) { const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index bcdb9383d1..e96c84f148 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -7,58 +7,13 @@ import { MetaService } from '@/core/MetaService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { DI } from '@/di-symbols.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { bindThis } from '@/decorators.js'; import { isCollectionOrOrderedCollection } from './type.js'; import { ApDbResolverService } from './ApDbResolverService.js'; import { ApRendererService } from './ApRendererService.js'; import { ApRequestService } from './ApRequestService.js'; import type { IObject, ICollection, IOrderedCollection } from './type.js'; -@Injectable() -export class ApResolverService { - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.pollsRepository) - private pollsRepository: PollsRepository, - - @Inject(DI.noteReactionsRepository) - private noteReactionsRepository: NoteReactionsRepository, - - private utilityService: UtilityService, - private instanceActorService: InstanceActorService, - private metaService: MetaService, - private apRequestService: ApRequestService, - private httpRequestService: HttpRequestService, - private apRendererService: ApRendererService, - private apDbResolverService: ApDbResolverService, - ) { - } - - public createResolver(): Resolver { - return new Resolver( - this.config, - this.usersRepository, - this.notesRepository, - this.pollsRepository, - this.noteReactionsRepository, - this.utilityService, - this.instanceActorService, - this.metaService, - this.apRequestService, - this.httpRequestService, - this.apRendererService, - this.apDbResolverService, - ); - } -} - export class Resolver { private history: Set; private user?: ILocalUser; @@ -76,15 +31,17 @@ export class Resolver { private httpRequestService: HttpRequestService, private apRendererService: ApRendererService, private apDbResolverService: ApDbResolverService, - private recursionLimit = 100 + private recursionLimit = 100, ) { this.history = new Set(); } + @bindThis public getHistory(): string[] { return Array.from(this.history); } + @bindThis public async resolveCollection(value: string | IObject): Promise { const collection = typeof value === 'string' ? await this.resolve(value) @@ -97,6 +54,7 @@ export class Resolver { } } + @bindThis public async resolve(value: string | IObject): Promise { if (value == null) { throw new Error('resolvee is null (or undefined)'); @@ -152,6 +110,7 @@ export class Resolver { return object; } + @bindThis private resolveLocal(url: string): Promise { const parsed = this.apDbResolverService.parseUri(url); if (!parsed.local) throw new Error('resolveLocal: not local'); @@ -193,3 +152,50 @@ export class Resolver { } } } + +@Injectable() +export class ApResolverService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + @Inject(DI.noteReactionsRepository) + private noteReactionsRepository: NoteReactionsRepository, + + private utilityService: UtilityService, + private instanceActorService: InstanceActorService, + private metaService: MetaService, + private apRequestService: ApRequestService, + private httpRequestService: HttpRequestService, + private apRendererService: ApRendererService, + private apDbResolverService: ApDbResolverService, + ) { + } + + @bindThis + public createResolver(): Resolver { + return new Resolver( + this.config, + this.usersRepository, + this.notesRepository, + this.pollsRepository, + this.noteReactionsRepository, + this.utilityService, + this.instanceActorService, + this.metaService, + this.apRequestService, + this.httpRequestService, + this.apRendererService, + this.apDbResolverService, + ); + } +} diff --git a/packages/backend/src/core/activitypub/LdSignatureService.ts b/packages/backend/src/core/activitypub/LdSignatureService.ts index ea39f15b2b..b71320ed0b 100644 --- a/packages/backend/src/core/activitypub/LdSignatureService.ts +++ b/packages/backend/src/core/activitypub/LdSignatureService.ts @@ -2,22 +2,11 @@ import * as crypto from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import fetch from 'node-fetch'; import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { bindThis } from '@/decorators.js'; import { CONTEXTS } from './misc/contexts.js'; // RsaSignature2017 based from https://github.com/transmute-industries/RsaSignature2017 -@Injectable() -export class LdSignatureService { - constructor( - private httpRequestService: HttpRequestService, - ) { - } - - public use(): LdSignature { - return new LdSignature(this.httpRequestService); - } -} - class LdSignature { public debug = false; public preLoad = true; @@ -28,6 +17,7 @@ class LdSignature { ) { } + @bindThis public async signRsaSignature2017(data: any, privateKey: string, creator: string, domain?: string, created?: Date): Promise { const options = { type: 'RsaSignature2017', @@ -64,6 +54,7 @@ class LdSignature { }; } + @bindThis public async verifyRsaSignature2017(data: any, publicKey: string): Promise { const toBeSigned = await this.createVerifyData(data, data.signature); const verifier = crypto.createVerify('sha256'); @@ -71,6 +62,7 @@ class LdSignature { return verifier.verify(publicKey, data.signature.signatureValue, 'base64'); } + @bindThis public async createVerifyData(data: any, options: any) { const transformedOptions = { ...options, @@ -90,11 +82,13 @@ class LdSignature { return verifyData; } + @bindThis public async normalize(data: any) { const customLoader = this.getLoader(); return 42; } + @bindThis private getLoader() { return async (url: string): Promise => { if (!url.match('^https?\:\/\/')) throw `Invalid URL ${url}`; @@ -120,6 +114,7 @@ class LdSignature { }; } + @bindThis private async fetchDocument(url: string) { const json = await fetch(url, { headers: { @@ -139,9 +134,23 @@ class LdSignature { return json; } + @bindThis public sha256(data: string): string { const hash = crypto.createHash('sha256'); hash.update(data); return hash.digest('hex'); } } + +@Injectable() +export class LdSignatureService { + constructor( + private httpRequestService: HttpRequestService, + ) { + } + + @bindThis + public use(): LdSignature { + return new LdSignature(this.httpRequestService); + } +} diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index 9bf87f19d4..58fcc8cb53 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -11,6 +11,7 @@ import { DriveService } from '@/core/DriveService.js'; import type Logger from '@/logger.js'; import { ApResolverService } from '../ApResolverService.js'; import { ApLoggerService } from '../ApLoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApImageService { @@ -34,6 +35,7 @@ export class ApImageService { /** * Imageを作成します。 */ + @bindThis public async createImage(actor: CacheableRemoteUser, value: any): Promise { // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { @@ -81,6 +83,7 @@ export class ApImageService { * Misskeyに対象のImageが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ + @bindThis public async resolveImage(actor: CacheableRemoteUser, value: any): Promise { // TODO diff --git a/packages/backend/src/core/activitypub/models/ApMentionService.ts b/packages/backend/src/core/activitypub/models/ApMentionService.ts index 1275e24c62..41e6c6b14f 100644 --- a/packages/backend/src/core/activitypub/models/ApMentionService.ts +++ b/packages/backend/src/core/activitypub/models/ApMentionService.ts @@ -9,6 +9,7 @@ import { isMention } from '../type.js'; import { ApResolverService, Resolver } from '../ApResolverService.js'; import { ApPersonService } from './ApPersonService.js'; import type { IObject, IApMention } from '../type.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApMentionService { @@ -21,6 +22,7 @@ export class ApMentionService { ) { } + @bindThis public async extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) { const hrefs = unique(this.extractApMentionObjects(tags).map(x => x.href as string)); @@ -32,6 +34,7 @@ export class ApMentionService { return mentionedUsers; } + @bindThis public extractApMentionObjects(tags: IObject | IObject[] | null | undefined): IApMention[] { if (tags == null) return []; return toArray(tags).filter(isMention); diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 7cf6725a38..e1d93a08b0 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -32,6 +32,7 @@ import { ApQuestionService } from './ApQuestionService.js'; import { ApImageService } from './ApImageService.js'; import type { Resolver } from '../ApResolverService.js'; import type { IObject, IPost } from '../type.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApNoteService { @@ -74,6 +75,7 @@ export class ApNoteService { this.logger = this.apLoggerService.logger; } + @bindThis public validateNote(object: any, uri: string) { const expectHost = this.utilityService.extractDbHost(uri); @@ -101,6 +103,7 @@ export class ApNoteService { * * Misskeyに対象のNoteが登録されていればそれを返します。 */ + @bindThis public async fetchNote(object: string | IObject): Promise { return await this.apDbResolverService.getNoteFromApId(object); } @@ -108,6 +111,7 @@ export class ApNoteService { /** * Noteを作成します。 */ + @bindThis public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise { if (resolver == null) resolver = this.apResolverService.createResolver(); @@ -313,6 +317,7 @@ export class ApNoteService { * Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ + @bindThis public async resolveNote(value: string | IObject, resolver?: Resolver): Promise { const uri = typeof value === 'string' ? value : value.id; if (uri == null) throw new Error('missing uri'); @@ -345,6 +350,7 @@ export class ApNoteService { } } + @bindThis public async extractEmojis(tags: IObject | IObject[], host: string): Promise { host = this.utilityService.toPuny(host); diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index f9d6f42ef6..d5faf37df2 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -72,6 +72,7 @@ function addService(target: { [x: string]: any }, source: IApPropertyValue) { target[source.name.split(':')[2]] = service(id, username); } } +import { bindThis } from '@/decorators.js'; @Injectable() export class ApPersonService implements OnModuleInit { @@ -161,6 +162,7 @@ export class ApPersonService implements OnModuleInit { * @param x Fetched object * @param uri Fetch target URI */ + @bindThis private validateActor(x: IObject, uri: string): IActor { const expectHost = this.utilityService.toPuny(new URL(uri).hostname); @@ -224,6 +226,7 @@ export class ApPersonService implements OnModuleInit { * * Misskeyに対象のPersonが登録されていればそれを返します。 */ + @bindThis public async fetchPerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); @@ -253,6 +256,7 @@ export class ApPersonService implements OnModuleInit { /** * Personを作成します。 */ + @bindThis public async createPerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); @@ -402,6 +406,7 @@ export class ApPersonService implements OnModuleInit { * @param resolver Resolver * @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します) */ + @bindThis public async updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); @@ -512,6 +517,7 @@ export class ApPersonService implements OnModuleInit { * Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ + @bindThis public async resolvePerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); @@ -528,6 +534,7 @@ export class ApPersonService implements OnModuleInit { return await this.createPerson(uri, resolver); } + @bindThis public analyzeAttachments(attachments: IObject | IObject[] | undefined) { const fields: { name: string, @@ -551,6 +558,7 @@ export class ApPersonService implements OnModuleInit { return { fields, services }; } + @bindThis public async updateFeatured(userId: User['id'], resolver?: Resolver) { const user = await this.usersRepository.findOneByOrFail({ id: userId }); if (!this.userEntityService.isRemoteUser(user)) return; diff --git a/packages/backend/src/core/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/activitypub/models/ApQuestionService.ts index 5793b98353..13a2f0fa5c 100644 --- a/packages/backend/src/core/activitypub/models/ApQuestionService.ts +++ b/packages/backend/src/core/activitypub/models/ApQuestionService.ts @@ -9,6 +9,7 @@ import { ApLoggerService } from '../ApLoggerService.js'; import { ApResolverService } from '../ApResolverService.js'; import type { Resolver } from '../ApResolverService.js'; import type { IObject, IQuestion } from '../type.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApQuestionService { @@ -30,6 +31,7 @@ export class ApQuestionService { this.logger = this.apLoggerService.logger; } + @bindThis public async extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise { if (resolver == null) resolver = this.apResolverService.createResolver(); @@ -65,6 +67,7 @@ export class ApQuestionService { * @param uri URI of AP Question object * @returns true if updated */ + @bindThis public async updateQuestion(value: any, resolver?: Resolver) { const uri = typeof value === 'string' ? value : value.id; diff --git a/packages/backend/src/core/chart/ChartLoggerService.ts b/packages/backend/src/core/chart/ChartLoggerService.ts index 544a006ac9..d392c6d595 100644 --- a/packages/backend/src/core/chart/ChartLoggerService.ts +++ b/packages/backend/src/core/chart/ChartLoggerService.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ChartLoggerService { diff --git a/packages/backend/src/core/chart/ChartManagementService.ts b/packages/backend/src/core/chart/ChartManagementService.ts index 6476cd6843..13ee06c6c5 100644 --- a/packages/backend/src/core/chart/ChartManagementService.ts +++ b/packages/backend/src/core/chart/ChartManagementService.ts @@ -13,6 +13,7 @@ import PerUserFollowingChart from './charts/per-user-following.js'; import PerUserDriveChart from './charts/per-user-drive.js'; import ApRequestChart from './charts/ap-request.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ChartManagementService implements OnApplicationShutdown { @@ -49,6 +50,7 @@ export class ChartManagementService implements OnApplicationShutdown { ]; } + @bindThis public async run() { // 20分おきにメモリ情報をDBに書き込み this.saveIntervalId = setInterval(() => { diff --git a/packages/backend/src/core/chart/charts/active-users.ts b/packages/backend/src/core/chart/charts/active-users.ts index 40c60910ea..bc0ba25cbb 100644 --- a/packages/backend/src/core/chart/charts/active-users.ts +++ b/packages/backend/src/core/chart/charts/active-users.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import type { User } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/active-users.js'; @@ -36,6 +37,7 @@ export default class ActiveUsersChart extends Chart { return {}; } + @bindThis public async read(user: { id: User['id'], host: null, createdAt: User['createdAt'] }): Promise { await this.commit({ 'read': [user.id], @@ -48,6 +50,7 @@ export default class ActiveUsersChart extends Chart { }); } + @bindThis public async write(user: { id: User['id'], host: null, createdAt: User['createdAt'] }): Promise { await this.commit({ 'write': [user.id], diff --git a/packages/backend/src/core/chart/charts/ap-request.ts b/packages/backend/src/core/chart/charts/ap-request.ts index 4b91fbbf18..ce377460c8 100644 --- a/packages/backend/src/core/chart/charts/ap-request.ts +++ b/packages/backend/src/core/chart/charts/ap-request.ts @@ -2,6 +2,7 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/ap-request.js'; @@ -31,18 +32,21 @@ export default class ApRequestChart extends Chart { return {}; } + @bindThis public async deliverSucc(): Promise { await this.commit({ 'deliverSucceeded': 1, }); } + @bindThis public async deliverFail(): Promise { await this.commit({ 'deliverFailed': 1, }); } + @bindThis public async inbox(): Promise { await this.commit({ 'inboxReceived': 1, diff --git a/packages/backend/src/core/chart/charts/drive.ts b/packages/backend/src/core/chart/charts/drive.ts index 494dfbbe57..da36b944f5 100644 --- a/packages/backend/src/core/chart/charts/drive.ts +++ b/packages/backend/src/core/chart/charts/drive.ts @@ -3,6 +3,7 @@ import { Not, IsNull, DataSource } from 'typeorm'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/drive.js'; @@ -32,6 +33,7 @@ export default class DriveChart extends Chart { return {}; } + @bindThis public async update(file: DriveFile, isAdditional: boolean): Promise { const fileSizeKb = file.size / 1000; await this.commit(file.userHost === null ? { diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts index 21e4cedea3..d9234e8028 100644 --- a/packages/backend/src/core/chart/charts/federation.ts +++ b/packages/backend/src/core/chart/charts/federation.ts @@ -4,6 +4,7 @@ import type { FollowingsRepository, InstancesRepository } from '@/models/index.j import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/federation.js'; @@ -107,6 +108,7 @@ export default class FederationChart extends Chart { }; } + @bindThis public async deliverd(host: string, succeeded: boolean): Promise { await this.commit(succeeded ? { 'deliveredInstances': [host], @@ -115,6 +117,7 @@ export default class FederationChart extends Chart { }); } + @bindThis public async inbox(host: string): Promise { await this.commit({ 'inboxInstances': [host], diff --git a/packages/backend/src/core/chart/charts/hashtag.ts b/packages/backend/src/core/chart/charts/hashtag.ts index 8b8c795cfd..3899b41363 100644 --- a/packages/backend/src/core/chart/charts/hashtag.ts +++ b/packages/backend/src/core/chart/charts/hashtag.ts @@ -4,6 +4,7 @@ import type { User } from '@/models/entities/User.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/hashtag.js'; @@ -34,6 +35,7 @@ export default class HashtagChart extends Chart { return {}; } + @bindThis public async update(hashtag: string, user: { id: User['id'], host: User['host'] }): Promise { await this.commit({ 'local.users': this.userEntityService.isLocalUser(user) ? [user.id] : [], diff --git a/packages/backend/src/core/chart/charts/instance.ts b/packages/backend/src/core/chart/charts/instance.ts index 2e0f4c7126..8ca88d80e3 100644 --- a/packages/backend/src/core/chart/charts/instance.ts +++ b/packages/backend/src/core/chart/charts/instance.ts @@ -6,6 +6,7 @@ import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/instance.js'; @@ -68,12 +69,14 @@ export default class InstanceChart extends Chart { return {}; } + @bindThis public async requestReceived(host: string): Promise { await this.commit({ 'requests.received': 1, }, this.utilityService.toPuny(host)); } + @bindThis public async requestSent(host: string, isSucceeded: boolean): Promise { await this.commit({ 'requests.succeeded': isSucceeded ? 1 : 0, @@ -81,6 +84,7 @@ export default class InstanceChart extends Chart { }, this.utilityService.toPuny(host)); } + @bindThis public async newUser(host: string): Promise { await this.commit({ 'users.total': 1, @@ -88,6 +92,7 @@ export default class InstanceChart extends Chart { }, this.utilityService.toPuny(host)); } + @bindThis public async updateNote(host: string, note: Note, isAdditional: boolean): Promise { await this.commit({ 'notes.total': isAdditional ? 1 : -1, @@ -100,6 +105,7 @@ export default class InstanceChart extends Chart { }, this.utilityService.toPuny(host)); } + @bindThis public async updateFollowing(host: string, isAdditional: boolean): Promise { await this.commit({ 'following.total': isAdditional ? 1 : -1, @@ -108,6 +114,7 @@ export default class InstanceChart extends Chart { }, this.utilityService.toPuny(host)); } + @bindThis public async updateFollowers(host: string, isAdditional: boolean): Promise { await this.commit({ 'followers.total': isAdditional ? 1 : -1, @@ -116,6 +123,7 @@ export default class InstanceChart extends Chart { }, this.utilityService.toPuny(host)); } + @bindThis public async updateDrive(file: DriveFile, isAdditional: boolean): Promise { const fileSizeKb = file.size / 1000; await this.commit({ diff --git a/packages/backend/src/core/chart/charts/notes.ts b/packages/backend/src/core/chart/charts/notes.ts index 2153cfe4b4..23dc248fec 100644 --- a/packages/backend/src/core/chart/charts/notes.ts +++ b/packages/backend/src/core/chart/charts/notes.ts @@ -4,6 +4,7 @@ import type { NotesRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/notes.js'; @@ -44,6 +45,7 @@ export default class NotesChart extends Chart { return {}; } + @bindThis public async update(note: Note, isAdditional: boolean): Promise { const prefix = note.userHost === null ? 'local' : 'remote'; diff --git a/packages/backend/src/core/chart/charts/per-user-drive.ts b/packages/backend/src/core/chart/charts/per-user-drive.ts index a44460bb4e..ffba04b041 100644 --- a/packages/backend/src/core/chart/charts/per-user-drive.ts +++ b/packages/backend/src/core/chart/charts/per-user-drive.ts @@ -5,6 +5,7 @@ import type { DriveFile } from '@/models/entities/DriveFile.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-drive.js'; @@ -46,6 +47,7 @@ export default class PerUserDriveChart extends Chart { return {}; } + @bindThis public async update(file: DriveFile, isAdditional: boolean): Promise { const fileSizeKb = file.size / 1000; await this.commit({ diff --git a/packages/backend/src/core/chart/charts/per-user-following.ts b/packages/backend/src/core/chart/charts/per-user-following.ts index 5ea08a0872..aea6d44a9a 100644 --- a/packages/backend/src/core/chart/charts/per-user-following.ts +++ b/packages/backend/src/core/chart/charts/per-user-following.ts @@ -5,6 +5,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { FollowingsRepository } from '@/models/index.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-following.js'; @@ -55,6 +56,7 @@ export default class PerUserFollowingChart extends Chart { return {}; } + @bindThis public async update(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }, isFollow: boolean): Promise { const prefixFollower = this.userEntityService.isLocalUser(follower) ? 'local' : 'remote'; const prefixFollowee = this.userEntityService.isLocalUser(followee) ? 'local' : 'remote'; diff --git a/packages/backend/src/core/chart/charts/per-user-notes.ts b/packages/backend/src/core/chart/charts/per-user-notes.ts index 5c14309d89..1e2a579dfa 100644 --- a/packages/backend/src/core/chart/charts/per-user-notes.ts +++ b/packages/backend/src/core/chart/charts/per-user-notes.ts @@ -5,6 +5,7 @@ import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import type { NotesRepository } from '@/models/index.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-notes.js'; @@ -43,6 +44,7 @@ export default class PerUserNotesChart extends Chart { return {}; } + @bindThis public async update(user: { id: User['id'] }, note: Note, isAdditional: boolean): Promise { await this.commit({ 'total': isAdditional ? 1 : -1, diff --git a/packages/backend/src/core/chart/charts/per-user-reactions.ts b/packages/backend/src/core/chart/charts/per-user-reactions.ts index 4160219720..7bc6d4b521 100644 --- a/packages/backend/src/core/chart/charts/per-user-reactions.ts +++ b/packages/backend/src/core/chart/charts/per-user-reactions.ts @@ -5,6 +5,7 @@ import type { Note } from '@/models/entities/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/per-user-reactions.js'; @@ -35,6 +36,7 @@ export default class PerUserReactionsChart extends Chart { return {}; } + @bindThis public async update(user: { id: User['id'], host: User['host'] }, note: Note): Promise { const prefix = this.userEntityService.isLocalUser(user) ? 'local' : 'remote'; this.commit({ diff --git a/packages/backend/src/core/chart/charts/test-grouped.ts b/packages/backend/src/core/chart/charts/test-grouped.ts index bc215f3942..128967bc65 100644 --- a/packages/backend/src/core/chart/charts/test-grouped.ts +++ b/packages/backend/src/core/chart/charts/test-grouped.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { name, schema } from './entities/test-grouped.js'; import type { KVs } from '../core.js'; @@ -35,6 +36,7 @@ export default class TestGroupedChart extends Chart { return {}; } + @bindThis public async increment(group: string): Promise { if (this.total[group] == null) this.total[group] = 0; diff --git a/packages/backend/src/core/chart/charts/test-intersection.ts b/packages/backend/src/core/chart/charts/test-intersection.ts index a074a7dded..6b4eed9062 100644 --- a/packages/backend/src/core/chart/charts/test-intersection.ts +++ b/packages/backend/src/core/chart/charts/test-intersection.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { name, schema } from './entities/test-intersection.js'; import type { KVs } from '../core.js'; @@ -31,12 +32,14 @@ export default class TestIntersectionChart extends Chart { return {}; } + @bindThis public async addA(key: string): Promise { await this.commit({ a: [key], }); } + @bindThis public async addB(key: string): Promise { await this.commit({ b: [key], diff --git a/packages/backend/src/core/chart/charts/test-unique.ts b/packages/backend/src/core/chart/charts/test-unique.ts index 4d3e2f2403..5d2b3f8ab1 100644 --- a/packages/backend/src/core/chart/charts/test-unique.ts +++ b/packages/backend/src/core/chart/charts/test-unique.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { name, schema } from './entities/test-unique.js'; import type { KVs } from '../core.js'; @@ -31,6 +32,7 @@ export default class TestUniqueChart extends Chart { return {}; } + @bindThis public async uniqueIncrement(key: string): Promise { await this.commit({ foo: [key], diff --git a/packages/backend/src/core/chart/charts/test.ts b/packages/backend/src/core/chart/charts/test.ts index 72caf79e0f..238351d8b3 100644 --- a/packages/backend/src/core/chart/charts/test.ts +++ b/packages/backend/src/core/chart/charts/test.ts @@ -3,6 +3,7 @@ import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { name, schema } from './entities/test.js'; import type { KVs } from '../core.js'; @@ -35,6 +36,7 @@ export default class TestChart extends Chart { return {}; } + @bindThis public async increment(): Promise { this.total++; @@ -44,6 +46,7 @@ export default class TestChart extends Chart { }); } + @bindThis public async decrement(): Promise { this.total--; diff --git a/packages/backend/src/core/chart/charts/users.ts b/packages/backend/src/core/chart/charts/users.ts index f0359968eb..7bc3602439 100644 --- a/packages/backend/src/core/chart/charts/users.ts +++ b/packages/backend/src/core/chart/charts/users.ts @@ -5,6 +5,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { UsersRepository } from '@/models/index.js'; +import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/users.js'; @@ -46,6 +47,7 @@ export default class UsersChart extends Chart { return {}; } + @bindThis public async update(user: { id: User['id'], host: User['host'] }, isAdditional: boolean): Promise { const prefix = this.userEntityService.isLocalUser(user) ? 'local' : 'remote'; diff --git a/packages/backend/src/core/chart/core.ts b/packages/backend/src/core/chart/core.ts index cf5aa48884..2092b13b7e 100644 --- a/packages/backend/src/core/chart/core.ts +++ b/packages/backend/src/core/chart/core.ts @@ -8,6 +8,7 @@ import * as nestedProperty from 'nested-property'; import { EntitySchema, LessThan, Between } from 'typeorm'; import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '@/misc/prelude/time.js'; import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; import type { Repository, DataSource } from 'typeorm'; const columnPrefix = '___' as const; @@ -249,6 +250,7 @@ export default abstract class Chart { this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day); } + @bindThis private convertRawRecord(x: RawRecord): KVs { const kvs = {} as Record; for (const k of Object.keys(x).filter((k) => k.startsWith(columnPrefix)) as (keyof Columns)[]) { @@ -257,6 +259,7 @@ export default abstract class Chart { return kvs as KVs; } + @bindThis private getNewLog(latest: KVs | null): KVs { const log = {} as Record; for (const [k, v] of Object.entries(this.schema) as ([keyof typeof this['schema'], this['schema'][string]])[]) { @@ -269,6 +272,7 @@ export default abstract class Chart { return log as KVs; } + @bindThis private getLatestLog(group: string | null, span: 'hour' | 'day'): Promise | null> { const repository = span === 'hour' ? this.repositoryForHour : @@ -288,6 +292,7 @@ export default abstract class Chart { /** * 現在(=今のHour or Day)のログをデータベースから探して、あればそれを返し、なければ作成して返します。 */ + @bindThis private async claimCurrentLog(group: string | null, span: 'hour' | 'day'): Promise> { const [y, m, d, h] = Chart.getCurrentDate(); @@ -380,6 +385,7 @@ export default abstract class Chart { }); } + @bindThis public async save(): Promise { if (this.buffer.length === 0) { this.logger.info(`${this.name}: Write skipped`); @@ -498,6 +504,7 @@ export default abstract class Chart { update(logHour, logDay)))); } + @bindThis public async tick(major: boolean, group: string | null = null): Promise { const data = major ? await this.tickMajor(group) : await this.tickMinor(group); @@ -533,10 +540,12 @@ export default abstract class Chart { update(logHour, logDay)); } + @bindThis public resync(group: string | null = null): Promise { return this.tick(true, group); } + @bindThis public async clean(): Promise { const current = dateUTC(Chart.getCurrentDate()); @@ -572,6 +581,7 @@ export default abstract class Chart { ]); } + @bindThis public async getChartRaw(span: 'hour' | 'day', amount: number, cursor: Date | null, group: string | null = null): Promise> { const [y, m, d, h, _m, _s, _ms] = cursor ? Chart.parseDate(subtractTime(addTime(cursor, 1, span), 1)) : Chart.getCurrentDate(); const [y2, m2, d2, h2] = cursor ? Chart.parseDate(addTime(cursor, 1, span)) : [] as never; @@ -676,6 +686,7 @@ export default abstract class Chart { return res; } + @bindThis public async getChart(span: 'hour' | 'day', amount: number, cursor: Date | null, group: string | null = null): Promise>> { const result = await this.getChartRaw(span, amount, cursor, group); const object = {}; diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts index 1660894571..7f8240b8b2 100644 --- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts +++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts @@ -4,6 +4,7 @@ import type { AbuseUserReportsRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { AbuseUserReport } from '@/models/entities/AbuseUserReport.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class AbuseUserReportEntityService { @@ -15,6 +16,7 @@ export class AbuseUserReportEntityService { ) { } + @bindThis public async pack( src: AbuseUserReport['id'] | AbuseUserReport, ) { @@ -41,6 +43,7 @@ export class AbuseUserReportEntityService { }); } + @bindThis public packMany( reports: any[], ) { diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts index 44110e7364..bc79ce26aa 100644 --- a/packages/backend/src/core/entities/AntennaEntityService.ts +++ b/packages/backend/src/core/entities/AntennaEntityService.ts @@ -4,6 +4,7 @@ import type { AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepos import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/schema.js'; import type { Antenna } from '@/models/entities/Antenna.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class AntennaEntityService { @@ -19,6 +20,7 @@ export class AntennaEntityService { ) { } + @bindThis public async pack( src: Antenna['id'] | Antenna, ): Promise> { diff --git a/packages/backend/src/core/entities/AppEntityService.ts b/packages/backend/src/core/entities/AppEntityService.ts index 1cc7ca11dc..781cbdcc6b 100644 --- a/packages/backend/src/core/entities/AppEntityService.ts +++ b/packages/backend/src/core/entities/AppEntityService.ts @@ -6,6 +6,7 @@ import type { Packed } from '@/misc/schema.js'; import type { App } from '@/models/entities/App.js'; import type { User } from '@/models/entities/User.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class AppEntityService { @@ -18,6 +19,7 @@ export class AppEntityService { ) { } + @bindThis public async pack( src: App['id'] | App, me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/AuthSessionEntityService.ts b/packages/backend/src/core/entities/AuthSessionEntityService.ts index bf8efa5f78..4a74f9c2f6 100644 --- a/packages/backend/src/core/entities/AuthSessionEntityService.ts +++ b/packages/backend/src/core/entities/AuthSessionEntityService.ts @@ -7,6 +7,7 @@ import type { AuthSession } from '@/models/entities/AuthSession.js'; import type { User } from '@/models/entities/User.js'; import { UserEntityService } from './UserEntityService.js'; import { AppEntityService } from './AppEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class AuthSessionEntityService { @@ -18,6 +19,7 @@ export class AuthSessionEntityService { ) { } + @bindThis public async pack( src: AuthSession['id'] | AuthSession, me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/BlockingEntityService.ts b/packages/backend/src/core/entities/BlockingEntityService.ts index 49a96037ca..c9e15207b9 100644 --- a/packages/backend/src/core/entities/BlockingEntityService.ts +++ b/packages/backend/src/core/entities/BlockingEntityService.ts @@ -6,6 +6,7 @@ import type { Packed } from '@/misc/schema.js'; import type { Blocking } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class BlockingEntityService { @@ -17,6 +18,7 @@ export class BlockingEntityService { ) { } + @bindThis public async pack( src: Blocking['id'] | Blocking, me?: { id: User['id'] } | null | undefined, @@ -33,6 +35,7 @@ export class BlockingEntityService { }); } + @bindThis public packMany( blockings: any[], me: { id: User['id'] }, diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts index 860967443e..5e2f019a12 100644 --- a/packages/backend/src/core/entities/ChannelEntityService.ts +++ b/packages/backend/src/core/entities/ChannelEntityService.ts @@ -8,6 +8,7 @@ import type { User } from '@/models/entities/User.js'; import type { Channel } from '@/models/entities/Channel.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ChannelEntityService { @@ -29,6 +30,7 @@ export class ChannelEntityService { ) { } + @bindThis public async pack( src: Channel['id'] | Channel, me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts index 7a5d2f7f0a..1e794391e9 100644 --- a/packages/backend/src/core/entities/ClipEntityService.ts +++ b/packages/backend/src/core/entities/ClipEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { Clip } from '@/models/entities/Clip.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ClipEntityService { @@ -18,6 +19,7 @@ export class ClipEntityService { ) { } + @bindThis public async pack( src: Clip['id'] | Clip, ): Promise> { @@ -34,6 +36,7 @@ export class ClipEntityService { }); } + @bindThis public packMany( clips: Clip[], ) { diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index e0aeb70dfc..706c8c1186 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -19,6 +19,7 @@ type PackOptions = { self?: boolean, withUser?: boolean, }; +import { bindThis } from '@/decorators.js'; @Injectable() export class DriveFileEntityService { @@ -44,6 +45,7 @@ export class DriveFileEntityService { ) { } + @bindThis public validateFileName(name: string): boolean { return ( (name.trim().length > 0) && @@ -54,6 +56,7 @@ export class DriveFileEntityService { ); } + @bindThis public getPublicProperties(file: DriveFile): DriveFile['properties'] { if (file.properties.orientation != null) { const properties = deepClone(file.properties); @@ -67,6 +70,7 @@ export class DriveFileEntityService { return file.properties; } + @bindThis public getPublicUrl(file: DriveFile, thumbnail = false): string | null { // リモートかつメディアプロキシ if (file.uri != null && file.userHost != null && this.config.mediaProxy != null) { @@ -90,6 +94,7 @@ export class DriveFileEntityService { return thumbnail ? (file.thumbnailUrl ?? (isImage ? (file.webpublicUrl ?? file.url) : null)) : (file.webpublicUrl ?? file.url); } + @bindThis public async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise { const id = typeof user === 'object' ? user.id : user; @@ -103,6 +108,7 @@ export class DriveFileEntityService { return parseInt(sum, 10) ?? 0; } + @bindThis public async calcDriveUsageOfHost(host: string): Promise { const { sum } = await this.driveFilesRepository .createQueryBuilder('file') @@ -114,6 +120,7 @@ export class DriveFileEntityService { return parseInt(sum, 10) ?? 0; } + @bindThis public async calcDriveUsageOfLocal(): Promise { const { sum } = await this.driveFilesRepository .createQueryBuilder('file') @@ -125,6 +132,7 @@ export class DriveFileEntityService { return parseInt(sum, 10) ?? 0; } + @bindThis public async calcDriveUsageOfRemote(): Promise { const { sum } = await this.driveFilesRepository .createQueryBuilder('file') @@ -136,6 +144,7 @@ export class DriveFileEntityService { return parseInt(sum, 10) ?? 0; } + @bindThis public async pack( src: DriveFile['id'] | DriveFile, options?: PackOptions, @@ -169,6 +178,7 @@ export class DriveFileEntityService { }); } + @bindThis public async packNullable( src: DriveFile['id'] | DriveFile, options?: PackOptions, @@ -203,6 +213,7 @@ export class DriveFileEntityService { }); } + @bindThis public async packMany( files: (DriveFile['id'] | DriveFile)[], options?: PackOptions, diff --git a/packages/backend/src/core/entities/DriveFolderEntityService.ts b/packages/backend/src/core/entities/DriveFolderEntityService.ts index 5761fa37bc..0bb0f1754e 100644 --- a/packages/backend/src/core/entities/DriveFolderEntityService.ts +++ b/packages/backend/src/core/entities/DriveFolderEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { DriveFolder } from '@/models/entities/DriveFolder.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DriveFolderEntityService { @@ -19,6 +20,7 @@ export class DriveFolderEntityService { ) { } + @bindThis public async pack( src: DriveFolder['id'] | DriveFolder, options?: { diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts index fc09b5a2c7..08d83a2753 100644 --- a/packages/backend/src/core/entities/EmojiEntityService.ts +++ b/packages/backend/src/core/entities/EmojiEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { Emoji } from '@/models/entities/Emoji.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class EmojiEntityService { @@ -18,6 +19,7 @@ export class EmojiEntityService { ) { } + @bindThis public async pack( src: Emoji['id'] | Emoji, ): Promise> { @@ -34,6 +36,7 @@ export class EmojiEntityService { }; } + @bindThis public packMany( emojis: any[], ) { diff --git a/packages/backend/src/core/entities/FollowRequestEntityService.ts b/packages/backend/src/core/entities/FollowRequestEntityService.ts index 4a60c1263f..88c91d0f21 100644 --- a/packages/backend/src/core/entities/FollowRequestEntityService.ts +++ b/packages/backend/src/core/entities/FollowRequestEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { FollowRequest } from '@/models/entities/FollowRequest.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class FollowRequestEntityService { @@ -18,6 +19,7 @@ export class FollowRequestEntityService { ) { } + @bindThis public async pack( src: FollowRequest['id'] | FollowRequest, me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/FollowingEntityService.ts b/packages/backend/src/core/entities/FollowingEntityService.ts index c7e040a57b..a833ae719b 100644 --- a/packages/backend/src/core/entities/FollowingEntityService.ts +++ b/packages/backend/src/core/entities/FollowingEntityService.ts @@ -31,6 +31,7 @@ type RemoteFolloweeFollowing = Following & { followeeInbox: string; followeeSharedInbox: string; }; +import { bindThis } from '@/decorators.js'; @Injectable() export class FollowingEntityService { @@ -42,22 +43,27 @@ export class FollowingEntityService { ) { } + @bindThis public isLocalFollower(following: Following): following is LocalFollowerFollowing { return following.followerHost == null; } + @bindThis public isRemoteFollower(following: Following): following is RemoteFollowerFollowing { return following.followerHost != null; } + @bindThis public isLocalFollowee(following: Following): following is LocalFolloweeFollowing { return following.followeeHost == null; } + @bindThis public isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing { return following.followeeHost != null; } + @bindThis public async pack( src: Following['id'] | Following, me?: { id: User['id'] } | null | undefined, @@ -84,6 +90,7 @@ export class FollowingEntityService { }); } + @bindThis public packMany( followings: any[], me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/GalleryLikeEntityService.ts b/packages/backend/src/core/entities/GalleryLikeEntityService.ts index 7e599113cc..8b15ffc2bb 100644 --- a/packages/backend/src/core/entities/GalleryLikeEntityService.ts +++ b/packages/backend/src/core/entities/GalleryLikeEntityService.ts @@ -8,6 +8,7 @@ import type { User } from '@/models/entities/User.js'; import type { GalleryLike } from '@/models/entities/GalleryLike.js'; import { UserEntityService } from './UserEntityService.js'; import { GalleryPostEntityService } from './GalleryPostEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class GalleryLikeEntityService { @@ -19,6 +20,7 @@ export class GalleryLikeEntityService { ) { } + @bindThis public async pack( src: GalleryLike['id'] | GalleryLike, me?: any, @@ -31,6 +33,7 @@ export class GalleryLikeEntityService { }; } + @bindThis public packMany( likes: any[], me: any, diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts index ca98687d7b..ab29e7dba1 100644 --- a/packages/backend/src/core/entities/GalleryPostEntityService.ts +++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts @@ -8,6 +8,7 @@ import type { User } from '@/models/entities/User.js'; import type { GalleryPost } from '@/models/entities/GalleryPost.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class GalleryPostEntityService { @@ -23,6 +24,7 @@ export class GalleryPostEntityService { ) { } + @bindThis public async pack( src: GalleryPost['id'] | GalleryPost, me?: { id: User['id'] } | null | undefined, @@ -47,6 +49,7 @@ export class GalleryPostEntityService { }); } + @bindThis public packMany( posts: GalleryPost[], me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/HashtagEntityService.ts b/packages/backend/src/core/entities/HashtagEntityService.ts index 511992c44f..f79b821222 100644 --- a/packages/backend/src/core/entities/HashtagEntityService.ts +++ b/packages/backend/src/core/entities/HashtagEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { Hashtag } from '@/models/entities/Hashtag.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class HashtagEntityService { @@ -18,6 +19,7 @@ export class HashtagEntityService { ) { } + @bindThis public async pack( src: Hashtag, ): Promise> { @@ -32,6 +34,7 @@ export class HashtagEntityService { }; } + @bindThis public packMany( hashtags: Hashtag[], ) { diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index f4fe9a17d3..5a7ceb89a3 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -8,6 +8,7 @@ import type { User } from '@/models/entities/User.js'; import type { Instance } from '@/models/entities/Instance.js'; import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class InstanceEntityService { @@ -19,6 +20,7 @@ export class InstanceEntityService { ) { } + @bindThis public async pack( instance: Instance, ): Promise> { @@ -50,6 +52,7 @@ export class InstanceEntityService { }; } + @bindThis public packMany( instances: Instance[], ) { diff --git a/packages/backend/src/core/entities/MessagingMessageEntityService.ts b/packages/backend/src/core/entities/MessagingMessageEntityService.ts index b7c42a5760..cdb752dd81 100644 --- a/packages/backend/src/core/entities/MessagingMessageEntityService.ts +++ b/packages/backend/src/core/entities/MessagingMessageEntityService.ts @@ -9,6 +9,7 @@ import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; import { UserGroupEntityService } from './UserGroupEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class MessagingMessageEntityService { @@ -22,6 +23,7 @@ export class MessagingMessageEntityService { ) { } + @bindThis public async pack( src: MessagingMessage['id'] | MessagingMessage, me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/ModerationLogEntityService.ts b/packages/backend/src/core/entities/ModerationLogEntityService.ts index 2f508710b8..ab61797910 100644 --- a/packages/backend/src/core/entities/ModerationLogEntityService.ts +++ b/packages/backend/src/core/entities/ModerationLogEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { ModerationLog } from '@/models/entities/ModerationLog.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ModerationLogEntityService { @@ -18,6 +19,7 @@ export class ModerationLogEntityService { ) { } + @bindThis public async pack( src: ModerationLog['id'] | ModerationLog, ) { @@ -35,6 +37,7 @@ export class ModerationLogEntityService { }); } + @bindThis public packMany( reports: any[], ) { diff --git a/packages/backend/src/core/entities/MutingEntityService.ts b/packages/backend/src/core/entities/MutingEntityService.ts index 862be009da..4f02ef4087 100644 --- a/packages/backend/src/core/entities/MutingEntityService.ts +++ b/packages/backend/src/core/entities/MutingEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { Muting } from '@/models/entities/Muting.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class MutingEntityService { @@ -18,6 +19,7 @@ export class MutingEntityService { ) { } + @bindThis public async pack( src: Muting['id'] | Muting, me?: { id: User['id'] } | null | undefined, @@ -35,6 +37,7 @@ export class MutingEntityService { }); } + @bindThis public packMany( mutings: any[], me: { id: User['id'] }, diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 5605cf8ce6..73d3184957 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -16,6 +16,7 @@ import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { ReactionService } from '../ReactionService.js'; import type { UserEntityService } from './UserEntityService.js'; import type { DriveFileEntityService } from './DriveFileEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NoteEntityService implements OnModuleInit { @@ -68,6 +69,7 @@ export class NoteEntityService implements OnModuleInit { this.reactionService = this.moduleRef.get('ReactionService'); } + @bindThis private async hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) { // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) let hide = false; @@ -128,6 +130,7 @@ export class NoteEntityService implements OnModuleInit { } } + @bindThis private async populatePoll(note: Note, meId: User['id'] | null) { const poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id }); const choices = poll.choices.map(c => ({ @@ -166,6 +169,7 @@ export class NoteEntityService implements OnModuleInit { }; } + @bindThis private async populateMyReaction(note: Note, meId: User['id'], _hint_?: { myReactions: Map; }) { @@ -191,6 +195,7 @@ export class NoteEntityService implements OnModuleInit { return undefined; } + @bindThis public async isVisibleForMe(note: Note, meId: User['id'] | null): Promise { // This code must always be synchronized with the checks in generateVisibilityQuery. // visibility が specified かつ自分が指定されていなかったら非表示 @@ -244,6 +249,7 @@ export class NoteEntityService implements OnModuleInit { return true; } + @bindThis public async pack( src: Note['id'] | Note, me?: { id: User['id'] } | null | undefined, @@ -353,6 +359,7 @@ export class NoteEntityService implements OnModuleInit { return packed; } + @bindThis public async packMany( notes: Note[], me?: { id: User['id'] } | null | undefined, @@ -388,6 +395,7 @@ export class NoteEntityService implements OnModuleInit { }))); } + @bindThis public async countSameRenotes(userId: string, renoteId: string, excludeNoteId: string | undefined): Promise { // 指定したユーザーの指定したノートのリノートがいくつあるか数える const query = this.notesRepository.createQueryBuilder('note') diff --git a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts index 1a68a5c628..aa5c354b6d 100644 --- a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts +++ b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts @@ -8,6 +8,7 @@ import type { User } from '@/models/entities/User.js'; import type { NoteFavorite } from '@/models/entities/NoteFavorite.js'; import { UserEntityService } from './UserEntityService.js'; import { NoteEntityService } from './NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NoteFavoriteEntityService { @@ -19,6 +20,7 @@ export class NoteFavoriteEntityService { ) { } + @bindThis public async pack( src: NoteFavorite['id'] | NoteFavorite, me?: { id: User['id'] } | null | undefined, @@ -33,6 +35,7 @@ export class NoteFavoriteEntityService { }; } + @bindThis public packMany( favorites: any[], me: { id: User['id'] }, diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index 47008ee08e..eba6f9d908 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -11,6 +11,7 @@ import type { ReactionService } from '../ReactionService.js'; import type { UserEntityService } from './UserEntityService.js'; import type { NoteEntityService } from './NoteEntityService.js'; import { ModuleRef } from '@nestjs/core'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NoteReactionEntityService implements OnModuleInit { @@ -36,6 +37,7 @@ export class NoteReactionEntityService implements OnModuleInit { this.reactionService = this.moduleRef.get('ReactionService'); } + @bindThis public async pack( src: NoteReaction['id'] | NoteReaction, me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index c415599fea..346faae6b0 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -13,6 +13,7 @@ import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { UserEntityService } from './UserEntityService.js'; import type { NoteEntityService } from './NoteEntityService.js'; import type { UserGroupInvitationEntityService } from './UserGroupInvitationEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class NotificationEntityService implements OnModuleInit { @@ -47,6 +48,7 @@ export class NotificationEntityService implements OnModuleInit { this.customEmojiService = this.moduleRef.get('CustomEmojiService'); } + @bindThis public async pack( src: Notification['id'] | Notification, options: { @@ -120,6 +122,7 @@ export class NotificationEntityService implements OnModuleInit { }); } + @bindThis public async packMany( notifications: Notification[], meId: User['id'], diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts index 004443759b..48e45dd019 100644 --- a/packages/backend/src/core/entities/PageEntityService.ts +++ b/packages/backend/src/core/entities/PageEntityService.ts @@ -9,6 +9,7 @@ import type { Page } from '@/models/entities/Page.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class PageEntityService { @@ -27,6 +28,7 @@ export class PageEntityService { ) { } + @bindThis public async pack( src: Page['id'] | Page, me?: { id: User['id'] } | null | undefined, @@ -99,6 +101,7 @@ export class PageEntityService { }); } + @bindThis public packMany( pages: Page[], me?: { id: User['id'] } | null | undefined, diff --git a/packages/backend/src/core/entities/PageLikeEntityService.ts b/packages/backend/src/core/entities/PageLikeEntityService.ts index 62d9c82ca6..d3e45783dd 100644 --- a/packages/backend/src/core/entities/PageLikeEntityService.ts +++ b/packages/backend/src/core/entities/PageLikeEntityService.ts @@ -8,6 +8,7 @@ import type { User } from '@/models/entities/User.js'; import type { PageLike } from '@/models/entities/PageLike.js'; import { UserEntityService } from './UserEntityService.js'; import { PageEntityService } from './PageEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class PageLikeEntityService { @@ -19,6 +20,7 @@ export class PageLikeEntityService { ) { } + @bindThis public async pack( src: PageLike['id'] | PageLike, me?: { id: User['id'] } | null | undefined, @@ -31,6 +33,7 @@ export class PageLikeEntityService { }; } + @bindThis public packMany( likes: any[], me: { id: User['id'] }, diff --git a/packages/backend/src/core/entities/SigninEntityService.ts b/packages/backend/src/core/entities/SigninEntityService.ts index fd89662f7d..c402644742 100644 --- a/packages/backend/src/core/entities/SigninEntityService.ts +++ b/packages/backend/src/core/entities/SigninEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { Signin } from '@/models/entities/Signin.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class SigninEntityService { @@ -18,6 +19,7 @@ export class SigninEntityService { ) { } + @bindThis public async pack( src: Signin, ) { diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index c691eaebdf..4a027d1de6 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -41,6 +41,7 @@ function isRemoteUser(user: T): user is T & { function isRemoteUser(user: User | { host: User['host'] }): boolean { return !isLocalUser(user); } +import { bindThis } from '@/decorators.js'; @Injectable() export class UserEntityService implements OnModuleInit { @@ -143,6 +144,7 @@ export class UserEntityService implements OnModuleInit { public isLocalUser = isLocalUser; public isRemoteUser = isRemoteUser; + @bindThis public async getRelation(me: User['id'], target: User['id']) { return awaitAll({ id: target, @@ -198,6 +200,7 @@ export class UserEntityService implements OnModuleInit { }); } + @bindThis public async getHasUnreadMessagingMessage(userId: User['id']): Promise { const mute = await this.mutingsRepository.findBy({ muterId: userId, @@ -227,6 +230,7 @@ export class UserEntityService implements OnModuleInit { return withUser || withGroups.some(x => x); } + @bindThis public async getHasUnreadAnnouncement(userId: User['id']): Promise { const reads = await this.announcementReadsRepository.findBy({ userId: userId, @@ -239,6 +243,7 @@ export class UserEntityService implements OnModuleInit { return count > 0; } + @bindThis public async getHasUnreadAntenna(userId: User['id']): Promise { const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId); @@ -250,6 +255,7 @@ export class UserEntityService implements OnModuleInit { return unread != null; } + @bindThis public async getHasUnreadChannel(userId: User['id']): Promise { const channels = await this.channelFollowingsRepository.findBy({ followerId: userId }); @@ -261,6 +267,7 @@ export class UserEntityService implements OnModuleInit { return unread != null; } + @bindThis public async getHasUnreadNotification(userId: User['id']): Promise { const mute = await this.mutingsRepository.findBy({ muterId: userId, @@ -279,6 +286,7 @@ export class UserEntityService implements OnModuleInit { return count > 0; } + @bindThis public async getHasPendingReceivedFollowRequest(userId: User['id']): Promise { const count = await this.followRequestsRepository.countBy({ followeeId: userId, @@ -287,6 +295,7 @@ export class UserEntityService implements OnModuleInit { return count > 0; } + @bindThis public getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' { if (user.hideOnlineStatus) return 'unknown'; if (user.lastActiveDate == null) return 'unknown'; @@ -298,6 +307,7 @@ export class UserEntityService implements OnModuleInit { ); } + @bindThis public async getAvatarUrl(user: User): Promise { if (user.avatar) { return this.driveFileEntityService.getPublicUrl(user.avatar, true) ?? this.getIdenticonUrl(user.id); @@ -309,6 +319,7 @@ export class UserEntityService implements OnModuleInit { } } + @bindThis public getAvatarUrlSync(user: User): string { if (user.avatar) { return this.driveFileEntityService.getPublicUrl(user.avatar, true) ?? this.getIdenticonUrl(user.id); @@ -317,6 +328,7 @@ export class UserEntityService implements OnModuleInit { } } + @bindThis public getIdenticonUrl(userId: User['id']): string { return `${this.config.url}/identicon/${userId}`; } diff --git a/packages/backend/src/core/entities/UserGroupEntityService.ts b/packages/backend/src/core/entities/UserGroupEntityService.ts index e399197618..0674a76723 100644 --- a/packages/backend/src/core/entities/UserGroupEntityService.ts +++ b/packages/backend/src/core/entities/UserGroupEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { UserGroup } from '@/models/entities/UserGroup.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserGroupEntityService { @@ -21,6 +22,7 @@ export class UserGroupEntityService { ) { } + @bindThis public async pack( src: UserGroup['id'] | UserGroup, ): Promise> { diff --git a/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts b/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts index f5c9be3475..0fba1426f4 100644 --- a/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts +++ b/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts @@ -8,6 +8,7 @@ import type { User } from '@/models/entities/User.js'; import type { UserGroupInvitation } from '@/models/entities/UserGroupInvitation.js'; import { UserEntityService } from './UserEntityService.js'; import { UserGroupEntityService } from './UserGroupEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserGroupInvitationEntityService { @@ -19,6 +20,7 @@ export class UserGroupInvitationEntityService { ) { } + @bindThis public async pack( src: UserGroupInvitation['id'] | UserGroupInvitation, ) { @@ -30,6 +32,7 @@ export class UserGroupInvitationEntityService { }; } + @bindThis public packMany( invitations: any[], ) { diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts index e2b0814914..f2e0426928 100644 --- a/packages/backend/src/core/entities/UserListEntityService.ts +++ b/packages/backend/src/core/entities/UserListEntityService.ts @@ -7,6 +7,7 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { UserList } from '@/models/entities/UserList.js'; import { UserEntityService } from './UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UserListEntityService { @@ -21,6 +22,7 @@ export class UserListEntityService { ) { } + @bindThis public async pack( src: UserList['id'] | UserList, ): Promise> { diff --git a/packages/backend/src/daemons/JanitorService.ts b/packages/backend/src/daemons/JanitorService.ts index dbad576abe..8cdfb703f1 100644 --- a/packages/backend/src/daemons/JanitorService.ts +++ b/packages/backend/src/daemons/JanitorService.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { LessThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { AttestationChallengesRepository } from '@/models/index.js'; +import { bindThis } from '@/decorators.js'; import type { OnApplicationShutdown } from '@nestjs/common'; const interval = 30 * 60 * 1000; @@ -19,6 +20,7 @@ export class JanitorService implements OnApplicationShutdown { /** * Clean up database occasionally */ + @bindThis public start(): void { const tick = async () => { await this.attestationChallengesRepository.delete({ @@ -31,6 +33,7 @@ export class JanitorService implements OnApplicationShutdown { this.intervalId = setInterval(tick, interval); } + @bindThis public onApplicationShutdown(signal?: string | undefined) { clearInterval(this.intervalId); } diff --git a/packages/backend/src/daemons/QueueStatsService.ts b/packages/backend/src/daemons/QueueStatsService.ts index 931de19067..7b47d78a17 100644 --- a/packages/backend/src/daemons/QueueStatsService.ts +++ b/packages/backend/src/daemons/QueueStatsService.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import Xev from 'xev'; import { DI } from '@/di-symbols.js'; import { QueueService } from '@/core/QueueService.js'; +import { bindThis } from '@/decorators.js'; import type { OnApplicationShutdown } from '@nestjs/common'; const ev = new Xev(); @@ -20,6 +21,7 @@ export class QueueStatsService implements OnApplicationShutdown { /** * Report queue stats regularly */ + @bindThis public start(): void { const log = [] as any[]; @@ -71,6 +73,7 @@ export class QueueStatsService implements OnApplicationShutdown { this.intervalId = setInterval(tick, interval); } + @bindThis public onApplicationShutdown(signal?: string | undefined) { clearInterval(this.intervalId); } diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts index e40912442d..7971f9e810 100644 --- a/packages/backend/src/daemons/ServerStatsService.ts +++ b/packages/backend/src/daemons/ServerStatsService.ts @@ -3,6 +3,7 @@ import si from 'systeminformation'; import Xev from 'xev'; import * as osUtils from 'os-utils'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import type { OnApplicationShutdown } from '@nestjs/common'; const ev = new Xev(); @@ -23,6 +24,7 @@ export class ServerStatsService implements OnApplicationShutdown { /** * Report server stats regularly */ + @bindThis public start(): void { const log = [] as any[]; @@ -61,6 +63,7 @@ export class ServerStatsService implements OnApplicationShutdown { this.intervalId = setInterval(tick, interval); } + @bindThis public onApplicationShutdown(signal?: string | undefined) { clearInterval(this.intervalId); } diff --git a/packages/backend/src/decorators.ts b/packages/backend/src/decorators.ts new file mode 100644 index 0000000000..94b1c4be8c --- /dev/null +++ b/packages/backend/src/decorators.ts @@ -0,0 +1,41 @@ +// https://github.com/andreypopp/autobind-decorator + +/** + * Return a descriptor removing the value and returning a getter + * The getter will return a .bind version of the function + * and memoize the result against a symbol on the instance + */ +export function bindThis(target, key, descriptor) { + let fn = descriptor.value; + + if (typeof fn !== 'function') { + throw new TypeError(`@bindThis decorator can only be applied to methods not: ${typeof fn}`); + } + + return { + configurable: true, + get() { + // eslint-disable-next-line no-prototype-builtins + if (this === target.prototype || this.hasOwnProperty(key) || + typeof fn !== 'function') { + return fn; + } + + const boundFn = fn.bind(this); + Object.defineProperty(this, key, { + configurable: true, + get() { + return boundFn; + }, + set(value) { + fn = value; + delete this[key]; + }, + }); + return boundFn; + }, + set(value) { + fn = value; + }, + }; +} diff --git a/packages/backend/src/logger.ts b/packages/backend/src/logger.ts index 6722220681..d09b479c42 100644 --- a/packages/backend/src/logger.ts +++ b/packages/backend/src/logger.ts @@ -2,6 +2,7 @@ import cluster from 'node:cluster'; import chalk from 'chalk'; import { default as convertColor } from 'color-convert'; import { format as dateFormat } from 'date-fns'; +import { bindThis } from '@/decorators.js'; import { envOption } from './env.js'; type Domain = { @@ -26,12 +27,14 @@ export default class Logger { this.syslogClient = syslogClient; } + @bindThis public createSubLogger(domain: string, color?: string, store = true): Logger { const logger = new Logger(domain, color, store); logger.parentLogger = this; return logger; } + @bindThis private log(level: Level, message: string, data?: Record | null, important = false, subDomains: Domain[] = [], store = true): void { if (envOption.quiet) return; if (!this.store) store = false; @@ -80,6 +83,7 @@ export default class Logger { } } + @bindThis public error(x: string | Error, data?: Record | null, important = false): void { // 実行を継続できない状況で使う if (x instanceof Error) { data = data ?? {}; @@ -92,20 +96,24 @@ export default class Logger { } } + @bindThis public warn(message: string, data?: Record | null, important = false): void { // 実行を継続できるが改善すべき状況で使う this.log('warning', message, data, important); } + @bindThis public succ(message: string, data?: Record | null, important = false): void { // 何かに成功した状況で使う this.log('success', message, data, important); } + @bindThis public debug(message: string, data?: Record | null, important = false): void { // デバッグ用に使う(開発者に必要だが利用者に不要な情報) if (process.env.NODE_ENV !== 'production' || envOption.verbose) { this.log('debug', message, data, important); } } + @bindThis public info(message: string, data?: Record | null, important = false): void { // それ以外 this.log('info', message, data, important); } diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index e5b911ed32..69512498f8 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -1,3 +1,5 @@ +import { bindThis } from '@/decorators.js'; + export class Cache { public cache: Map; private lifetime: number; @@ -7,6 +9,7 @@ export class Cache { this.lifetime = lifetime; } + @bindThis public set(key: string | null, value: T): void { this.cache.set(key, { date: Date.now(), @@ -14,6 +17,7 @@ export class Cache { }); } + @bindThis public get(key: string | null): T | undefined { const cached = this.cache.get(key); if (cached == null) return undefined; @@ -24,6 +28,7 @@ export class Cache { return cached.value; } + @bindThis public delete(key: string | null) { this.cache.delete(key); } @@ -32,6 +37,7 @@ export class Cache { * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします */ + @bindThis public async fetch(key: string | null, fetcher: () => Promise, validator?: (cachedValue: T) => boolean): Promise { const cachedValue = this.get(key); if (cachedValue !== undefined) { @@ -56,6 +62,7 @@ export class Cache { * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします */ + @bindThis public async fetchMaybe(key: string | null, fetcher: () => Promise, validator?: (cachedValue: T) => boolean): Promise { const cachedValue = this.get(key); if (cachedValue !== undefined) { diff --git a/packages/backend/src/misc/i18n.ts b/packages/backend/src/misc/i18n.ts index 4fa398763a..e304a8adac 100644 --- a/packages/backend/src/misc/i18n.ts +++ b/packages/backend/src/misc/i18n.ts @@ -5,12 +5,13 @@ export class I18n> { this.locale = locale; //#region BIND - this.t = this.t.bind(this); + //this.t = this.t.bind(this); //#endregion } // string にしているのは、ドット区切りでのパス指定を許可するため // なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも + @bindThis public t(key: string, args?: Record): string { try { let str = key.split('.').reduce((o, i) => o[i], this.locale) as string; diff --git a/packages/backend/src/postgre.ts b/packages/backend/src/postgre.ts index 2beb31e24e..0f9f7a6a96 100644 --- a/packages/backend/src/postgre.ts +++ b/packages/backend/src/postgre.ts @@ -72,6 +72,7 @@ import { Channel } from '@/models/entities/Channel.js'; import { Config } from '@/config.js'; import MisskeyLogger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; import { envOption } from './env.js'; export const dbLogger = new MisskeyLogger('db'); @@ -79,32 +80,39 @@ export const dbLogger = new MisskeyLogger('db'); const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false); class MyCustomLogger implements Logger { + @bindThis private highlight(sql: string) { return highlight.highlight(sql, { language: 'sql', ignoreIllegals: true, }); } + @bindThis public logQuery(query: string, parameters?: any[]) { sqlLogger.info(this.highlight(query).substring(0, 100)); } + @bindThis public logQueryError(error: string, query: string, parameters?: any[]) { sqlLogger.error(this.highlight(query)); } + @bindThis public logQuerySlow(time: number, query: string, parameters?: any[]) { sqlLogger.warn(this.highlight(query)); } + @bindThis public logSchemaBuild(message: string) { sqlLogger.info(message); } + @bindThis public log(message: string) { sqlLogger.info(message); } + @bindThis public logMigration(message: string) { sqlLogger.info(message); } diff --git a/packages/backend/src/queue/DbQueueProcessorsService.ts b/packages/backend/src/queue/DbQueueProcessorsService.ts index 58384c4d1b..e5568ab9bf 100644 --- a/packages/backend/src/queue/DbQueueProcessorsService.ts +++ b/packages/backend/src/queue/DbQueueProcessorsService.ts @@ -16,6 +16,7 @@ import { ImportUserListsProcessorService } from './processors/ImportUserListsPro import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js'; import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DbQueueProcessorsService { @@ -39,6 +40,7 @@ export class DbQueueProcessorsService { ) { } + @bindThis public start(q: Bull.Queue): void { q.process('deleteDriveFiles', (job, done) => this.deleteDriveFilesProcessorService.process(job, done)); q.process('exportCustomEmojis', (job, done) => this.exportCustomEmojisProcessorService.process(job, done)); diff --git a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts index 3ff3dd090c..c95e1c1ba6 100644 --- a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts +++ b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts @@ -5,6 +5,7 @@ import type { Config } from '@/config.js'; import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js'; import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ObjectStorageQueueProcessorsService { @@ -17,6 +18,7 @@ export class ObjectStorageQueueProcessorsService { ) { } + @bindThis public start(q: Bull.Queue): void { q.process('deleteFile', 16, (job) => this.deleteFileProcessorService.process(job)); q.process('cleanRemoteFiles', 16, (job, done) => this.cleanRemoteFilesProcessorService.process(job, done)); diff --git a/packages/backend/src/queue/QueueLoggerService.ts b/packages/backend/src/queue/QueueLoggerService.ts index a311470cc9..3a8a734f10 100644 --- a/packages/backend/src/queue/QueueLoggerService.ts +++ b/packages/backend/src/queue/QueueLoggerService.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class QueueLoggerService { diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 8c300d479c..1d2feb5ef8 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -13,6 +13,7 @@ import { EndedPollNotificationProcessorService } from './processors/EndedPollNot import { DeliverProcessorService } from './processors/DeliverProcessorService.js'; import { InboxProcessorService } from './processors/InboxProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class QueueProcessorService { @@ -35,6 +36,7 @@ export class QueueProcessorService { this.logger = this.queueLoggerService.logger; } + @bindThis public start() { function renderError(e: Error): any { if (e) { // 何故かeがundefinedで来ることがある diff --git a/packages/backend/src/queue/SystemQueueProcessorsService.ts b/packages/backend/src/queue/SystemQueueProcessorsService.ts index a8af92b9ba..1ce4152b2c 100644 --- a/packages/backend/src/queue/SystemQueueProcessorsService.ts +++ b/packages/backend/src/queue/SystemQueueProcessorsService.ts @@ -7,6 +7,7 @@ import { CleanChartsProcessorService } from './processors/CleanChartsProcessorSe import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class SystemQueueProcessorsService { @@ -22,6 +23,7 @@ export class SystemQueueProcessorsService { ) { } + @bindThis public start(q: Bull.Queue): void { q.process('tickCharts', (job, done) => this.tickChartsProcessorService.process(job, done)); q.process('resyncCharts', (job, done) => this.resyncChartsProcessorService.process(job, done)); diff --git a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts index e91cba9d10..7a1e3e71be 100644 --- a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts +++ b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts @@ -7,6 +7,7 @@ import type Logger from '@/logger.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class CheckExpiredMutingsProcessorService { @@ -25,6 +26,7 @@ export class CheckExpiredMutingsProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('check-expired-mutings'); } + @bindThis public async process(job: Bull.Job>, done: () => void): Promise { this.logger.info('Checking expired mutings...'); diff --git a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts index e8e90f1422..c57086240a 100644 --- a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts @@ -17,6 +17,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js'; import ApRequestChart from '@/core/chart/charts/ap-request.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class CleanChartsProcessorService { @@ -44,6 +45,7 @@ export class CleanChartsProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('clean-charts'); } + @bindThis public async process(job: Bull.Job>, done: () => void): Promise { this.logger.info('Clean charts...'); diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts index 6eb457ce9f..8ca39a9677 100644 --- a/packages/backend/src/queue/processors/CleanProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanProcessorService.ts @@ -6,6 +6,7 @@ import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class CleanProcessorService { @@ -23,6 +24,7 @@ export class CleanProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('clean'); } + @bindThis public async process(job: Bull.Job>, done: () => void): Promise { this.logger.info('Cleaning...'); diff --git a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts index a4fd8c502d..5a33c27188 100644 --- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts @@ -7,6 +7,7 @@ import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class CleanRemoteFilesProcessorService { @@ -25,6 +26,7 @@ export class CleanRemoteFilesProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('clean-remote-files'); } + @bindThis public async process(job: Bull.Job>, done: () => void): Promise { this.logger.info('Deleting cached remote files...'); diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index 5e4c8bdd69..e36a78de6a 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -11,6 +11,7 @@ import { EmailService } from '@/core/EmailService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserDeleteJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DeleteAccountProcessorService { @@ -39,6 +40,7 @@ export class DeleteAccountProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('delete-account'); } + @bindThis public async process(job: Bull.Job): Promise { this.logger.info(`Deleting account of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts index 682382b2db..fa0c1733f6 100644 --- a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts @@ -8,6 +8,7 @@ import { DriveService } from '@/core/DriveService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DeleteDriveFilesProcessorService { @@ -29,6 +30,7 @@ export class DeleteDriveFilesProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('delete-drive-files'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Deleting drive files of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts index 6740643fe2..2fb2f56f8d 100644 --- a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts @@ -6,6 +6,7 @@ import { DriveService } from '@/core/DriveService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { ObjectStorageFileJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DeleteFileProcessorService { @@ -21,6 +22,7 @@ export class DeleteFileProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('delete-file'); } + @bindThis public async process(job: Bull.Job): Promise { const key: string = job.data.key; diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 2a4b201a7d..58969d550e 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -18,6 +18,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DeliverJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DeliverProcessorService { @@ -50,6 +51,7 @@ export class DeliverProcessorService { this.latest = null; } + @bindThis public async process(job: Bull.Job): Promise { const { host } = new URL(job.data.to); diff --git a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts index 2fc7fe219e..21d2dc9efc 100644 --- a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts +++ b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts @@ -8,6 +8,7 @@ import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { EndedPollNotificationJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class EndedPollNotificationProcessorService { @@ -29,6 +30,7 @@ export class EndedPollNotificationProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('ended-poll-notification'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { const note = await this.notesRepository.findOneBy({ id: job.data.noteId }); if (note == null || !note.hasPoll) { diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index db149b68cf..5b3c1a415b 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -12,6 +12,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ExportBlockingProcessorService { @@ -34,6 +35,7 @@ export class ExportBlockingProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('export-blocking'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Exporting blocking of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index f8f261b478..87b23f1891 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -14,6 +14,7 @@ import { createTemp, createTempDir } from '@/misc/create-temp.js'; import { DownloadService } from '@/core/DownloadService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ExportCustomEmojisProcessorService { @@ -36,6 +37,7 @@ export class ExportCustomEmojisProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info('Exporting custom emojis ...'); diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index 1e3fba06b5..064b126e44 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -13,6 +13,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ExportFollowingProcessorService { @@ -38,6 +39,7 @@ export class ExportFollowingProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('export-following'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Exporting following of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index e263c245fd..94c7ea8a46 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -12,6 +12,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ExportMutingProcessorService { @@ -37,6 +38,7 @@ export class ExportMutingProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('export-muting'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Exporting muting of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index 533d4bd7c6..8431829e91 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -13,6 +13,7 @@ import type { Note } from '@/models/entities/Note.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ExportNotesProcessorService { @@ -37,6 +38,7 @@ export class ExportNotesProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('export-notes'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Exporting notes of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index 8c3e3dbe17..a8daa5e5ee 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -12,6 +12,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ExportUserListsProcessorService { @@ -37,6 +38,7 @@ export class ExportUserListsProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('export-user-lists'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Exporting user lists of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index 9442a60d8d..2eed420e96 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -12,6 +12,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserImportJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ImportBlockingProcessorService { @@ -39,6 +40,7 @@ export class ImportBlockingProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('import-blocking'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Importing blocking of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index 492f17f9ff..0061c2a8f7 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -10,6 +10,7 @@ import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { createTempDir } from '@/misc/create-temp.js'; import { DriveService } from '@/core/DriveService.js'; import { DownloadService } from '@/core/DownloadService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserImportJobData } from '../types.js'; @@ -43,6 +44,7 @@ export class ImportCustomEmojisProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('import-custom-emojis'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info('Importing custom emojis ...'); diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index 667f7279fb..b61846d747 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -12,6 +12,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserImportJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ImportFollowingProcessorService { @@ -36,6 +37,7 @@ export class ImportFollowingProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('import-following'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Importing following of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index f3c16e73d5..21236da2ef 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -12,6 +12,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserImportJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ImportMutingProcessorService { @@ -36,6 +37,7 @@ export class ImportMutingProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('import-muting'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Importing muting of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index 1519877c5f..1bec77b837 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -13,6 +13,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserImportJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ImportUserListsProcessorService { @@ -44,6 +45,7 @@ export class ImportUserListsProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('import-user-lists'); } + @bindThis public async process(job: Bull.Job, done: () => void): Promise { this.logger.info(`Importing user lists of ${job.data.user.id} ...`); diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 8f1c474020..c032122caf 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -24,6 +24,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js'; import { ApInboxService } from '@/core/activitypub/ApInboxService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DeliverJobData, InboxJobData } from '../types.js'; @@ -60,6 +61,7 @@ export class InboxProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('inbox'); } + @bindThis public async process(job: Bull.Job): Promise { const signature = job.data.signature; // HTTP-signature const activity = job.data.activity; diff --git a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts index bf2fdeb7a0..1a8fe65a4f 100644 --- a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts @@ -17,6 +17,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js'; import ApRequestChart from '@/core/chart/charts/ap-request.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ResyncChartsProcessorService { @@ -44,6 +45,7 @@ export class ResyncChartsProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('resync-charts'); } + @bindThis public async process(job: Bull.Job>, done: () => void): Promise { this.logger.info('Resync charts...'); diff --git a/packages/backend/src/queue/processors/TickChartsProcessorService.ts b/packages/backend/src/queue/processors/TickChartsProcessorService.ts index 96607e1d68..323e5227cd 100644 --- a/packages/backend/src/queue/processors/TickChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/TickChartsProcessorService.ts @@ -17,6 +17,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js'; import ApRequestChart from '@/core/chart/charts/ap-request.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; +import { bindThis } from '@/decorators.js'; @Injectable() export class TickChartsProcessorService { @@ -44,6 +45,7 @@ export class TickChartsProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('tick-charts'); } + @bindThis public async process(job: Bull.Job>, done: () => void): Promise { this.logger.info('Tick charts...'); diff --git a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts index 43e3f37201..183ef07477 100644 --- a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts @@ -9,6 +9,7 @@ import { StatusError } from '@/misc/status-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { WebhookDeliverJobData } from '../types.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class WebhookDeliverProcessorService { @@ -27,6 +28,7 @@ export class WebhookDeliverProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('webhook'); } + @bindThis public async process(job: Bull.Job): Promise { try { this.logger.debug(`delivering ${job.data.webhookId}`); diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 015c8f2b4c..94a277f4a4 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -18,6 +18,7 @@ import type { Note } from '@/models/entities/Note.js'; import { QueryService } from '@/core/QueryService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; import type { FindOptionsWhere } from 'typeorm'; const ACTIVITY_JSON = 'application/activity+json; charset=utf-8'; @@ -57,9 +58,10 @@ export class ActivityPubServerService { private userKeypairStoreService: UserKeypairStoreService, private queryService: QueryService, ) { - this.createServer = this.createServer.bind(this); + //this.createServer = this.createServer.bind(this); } + @bindThis private setResponseType(request: FastifyRequest, reply: FastifyReply): void { const accept = request.accepts().type([ACTIVITY_JSON, LD_JSON]); if (accept === LD_JSON) { @@ -73,6 +75,7 @@ export class ActivityPubServerService { * Pack Create or Announce Activity * @param note Note */ + @bindThis private async packActivity(note: Note): Promise { if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { const renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId }); @@ -82,6 +85,7 @@ export class ActivityPubServerService { return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note); } + @bindThis private inbox(request: FastifyRequest, reply: FastifyReply) { let signature; @@ -97,6 +101,7 @@ export class ActivityPubServerService { reply.code(202); } + @bindThis private async followers( request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>, reply: FastifyReply, @@ -184,6 +189,7 @@ export class ActivityPubServerService { } } + @bindThis private async following( request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>, reply: FastifyReply, @@ -271,6 +277,7 @@ export class ActivityPubServerService { } } + @bindThis private async featured(request: FastifyRequest<{ Params: { user: string; }; }>, reply: FastifyReply) { const userId = request.params.user; @@ -304,6 +311,7 @@ export class ActivityPubServerService { return (this.apRendererService.renderActivity(rendered)); } + @bindThis private async outbox( request: FastifyRequest<{ Params: { user: string; }; @@ -390,6 +398,7 @@ export class ActivityPubServerService { } } + @bindThis private async userInfo(request: FastifyRequest, reply: FastifyReply, user: User | null) { if (user == null) { reply.code(404); @@ -401,6 +410,7 @@ export class ActivityPubServerService { return (this.apRendererService.renderActivity(await this.apRendererService.renderPerson(user as ILocalUser))); } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.addConstraintStrategy({ name: 'apOrHtml', diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 088e780d69..b7ab549611 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -19,6 +19,7 @@ import { InternalStorageService } from '@/core/InternalStorageService.js'; import { contentDisposition } from '@/misc/content-disposition.js'; import { FileInfoService } from '@/core/FileInfoService.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -45,9 +46,10 @@ export class FileServerService { ) { this.logger = this.loggerService.getLogger('server', 'gray', false); - this.createServer = this.createServer.bind(this); + //this.createServer = this.createServer.bind(this); } + @bindThis public commonReadableHandlerGenerator(reply: FastifyReply) { return (err: Error): void => { this.logger.error(err); @@ -56,6 +58,7 @@ export class FileServerService { }; } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.addHook('onRequest', (request, reply, done) => { reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); @@ -80,6 +83,7 @@ export class FileServerService { done(); } + @bindThis private async sendDriveFile(request: FastifyRequest<{ Params: { key: string; } }>, reply: FastifyReply) { const key = request.params.key; diff --git a/packages/backend/src/server/MediaProxyServerService.ts b/packages/backend/src/server/MediaProxyServerService.ts index 4d7bbdf599..733a7feeb5 100644 --- a/packages/backend/src/server/MediaProxyServerService.ts +++ b/packages/backend/src/server/MediaProxyServerService.ts @@ -14,6 +14,7 @@ import { StatusError } from '@/misc/status-error.js'; import type Logger from '@/logger.js'; import { FileInfoService } from '@/core/FileInfoService.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class MediaProxyServerService { @@ -30,9 +31,10 @@ export class MediaProxyServerService { ) { this.logger = this.loggerService.getLogger('server', 'gray', false); - this.createServer = this.createServer.bind(this); + //this.createServer = this.createServer.bind(this); } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.addHook('onRequest', (request, reply, done) => { reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); @@ -47,6 +49,7 @@ export class MediaProxyServerService { done(); } + @bindThis private async handler(request: FastifyRequest<{ Params: { url: string; }; Querystring: { url?: string; }; }>, reply: FastifyReply) { const url = 'url' in request.query ? request.query.url : 'https://' + request.params.url; diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index b85925f53e..0f3cc36dae 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -8,6 +8,7 @@ import { MetaService } from '@/core/MetaService.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Cache } from '@/misc/cache.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; const nodeinfo2_1path = '/nodeinfo/2.1'; const nodeinfo2_0path = '/nodeinfo/2.0'; @@ -27,9 +28,10 @@ export class NodeinfoServerService { private userEntityService: UserEntityService, private metaService: MetaService, ) { - this.createServer = this.createServer.bind(this); + //this.createServer = this.createServer.bind(this); } + @bindThis public getLinks() { return [/* (awaiting release) { rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1', @@ -40,6 +42,7 @@ export class NodeinfoServerService { }]; } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { const nodeinfo2 = async () => { const now = Date.now(); diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 96159cfc53..075b9cdff7 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -23,6 +23,7 @@ import { WellKnownServerService } from './WellKnownServerService.js'; import { MediaProxyServerService } from './MediaProxyServerService.js'; import { FileServerService } from './FileServerService.js'; import { ClientServerService } from './web/ClientServerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ServerService { @@ -53,6 +54,7 @@ export class ServerService { this.logger = this.loggerService.getLogger('server', 'gray', false); } + @bindThis public launch() { const fastify = Fastify({ trustProxy: true, diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts index 412c608313..ea34ad5b0f 100644 --- a/packages/backend/src/server/WellKnownServerService.ts +++ b/packages/backend/src/server/WellKnownServerService.ts @@ -10,6 +10,7 @@ import type { User } from '@/models/entities/User.js'; import * as Acct from '@/misc/acct.js'; import { NodeinfoServerService } from './NodeinfoServerService.js'; import type { FindOptionsWhere } from 'typeorm'; +import { bindThis } from '@/decorators.js'; @Injectable() export class WellKnownServerService { @@ -22,9 +23,10 @@ export class WellKnownServerService { private nodeinfoServerService: NodeinfoServerService, ) { - this.createServer = this.createServer.bind(this); + //this.createServer = this.createServer.bind(this); } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { const XRD = (...x: { element: string, value?: string, attributes?: Record }[]) => `${x.map(({ element, value, attributes }) => diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index 2e72cdf9f8..fb1e17790c 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -12,6 +12,7 @@ import type Logger from '@/logger.js'; import type { UserIpsRepository } from '@/models/index.js'; import { MetaService } from '@/core/MetaService.js'; import { createTemp } from '@/misc/create-temp.js'; +import { bindThis } from '@/decorators.js'; import { ApiError } from './error.js'; import { RateLimiterService } from './RateLimiterService.js'; import { ApiLoggerService } from './ApiLoggerService.js'; @@ -50,6 +51,7 @@ export class ApiCallService implements OnApplicationShutdown { }, 1000 * 60 * 60); } + @bindThis public handleRequest( endpoint: IEndpoint & { exec: any }, request: FastifyRequest<{ Body: Record, Querystring: Record }>, @@ -90,6 +92,7 @@ export class ApiCallService implements OnApplicationShutdown { }); } + @bindThis public async handleMultipartRequest( endpoint: IEndpoint & { exec: any }, request: FastifyRequest<{ Body: Record, Querystring: Record }>, @@ -140,6 +143,7 @@ export class ApiCallService implements OnApplicationShutdown { }); } + @bindThis private send(reply: FastifyReply, x?: any, y?: ApiError) { if (x == null) { reply.code(204); @@ -160,6 +164,7 @@ export class ApiCallService implements OnApplicationShutdown { } } + @bindThis private async logIp(request: FastifyRequest, user: ILocalUser) { const meta = await this.metaService.fetch(); if (!meta.enableIpLogging) return; @@ -183,6 +188,7 @@ export class ApiCallService implements OnApplicationShutdown { } } + @bindThis private async call( ep: IEndpoint & { exec: any }, user: CacheableLocalUser | null | undefined, @@ -315,6 +321,7 @@ export class ApiCallService implements OnApplicationShutdown { }); } + @bindThis public onApplicationShutdown(signal?: string | undefined) { clearInterval(this.userIpHistoriesClearIntervalId); } diff --git a/packages/backend/src/server/api/ApiLoggerService.ts b/packages/backend/src/server/api/ApiLoggerService.ts index c4fb25036e..cabd65fd3e 100644 --- a/packages/backend/src/server/api/ApiLoggerService.ts +++ b/packages/backend/src/server/api/ApiLoggerService.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApiLoggerService { diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index cf3f2deebf..b17456d0e2 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -14,6 +14,7 @@ import { SigninApiService } from './SigninApiService.js'; import { GithubServerService } from './integration/GithubServerService.js'; import { DiscordServerService } from './integration/DiscordServerService.js'; import { TwitterServerService } from './integration/TwitterServerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ApiServerService { @@ -40,9 +41,10 @@ export class ApiServerService { private discordServerService: DiscordServerService, private twitterServerService: TwitterServerService, ) { - this.createServer = this.createServer.bind(this); + //this.createServer = this.createServer.bind(this); } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.register(cors, { origin: '*', diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts index ad387c4732..8b39f6c924 100644 --- a/packages/backend/src/server/api/AuthenticateService.ts +++ b/packages/backend/src/server/api/AuthenticateService.ts @@ -7,6 +7,7 @@ import { Cache } from '@/misc/cache.js'; import type { App } from '@/models/entities/App.js'; import { UserCacheService } from '@/core/UserCacheService.js'; import isNativeToken from '@/misc/is-native-token.js'; +import { bindThis } from '@/decorators.js'; export class AuthenticationError extends Error { constructor(message: string) { @@ -34,6 +35,7 @@ export class AuthenticateService { this.appCache = new Cache(Infinity); } + @bindThis public async authenticate(token: string | null | undefined): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> { if (token == null) { return [null, null]; diff --git a/packages/backend/src/server/api/GetterService.ts b/packages/backend/src/server/api/GetterService.ts index 70ab46ec35..c7f9916f97 100644 --- a/packages/backend/src/server/api/GetterService.ts +++ b/packages/backend/src/server/api/GetterService.ts @@ -5,6 +5,7 @@ import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class GetterService { @@ -22,6 +23,7 @@ export class GetterService { /** * Get note for API processing */ + @bindThis public async getNote(noteId: Note['id']) { const note = await this.notesRepository.findOneBy({ id: noteId }); @@ -35,6 +37,7 @@ export class GetterService { /** * Get user for API processing */ + @bindThis public async getUser(userId: User['id']) { const user = await this.usersRepository.findOneBy({ id: userId }); @@ -48,6 +51,7 @@ export class GetterService { /** * Get remote user for API processing */ + @bindThis public async getRemoteUser(userId: User['id']) { const user = await this.getUser(userId); @@ -61,6 +65,7 @@ export class GetterService { /** * Get local user for API processing */ + @bindThis public async getLocalUser(userId: User['id']) { const user = await this.getUser(userId); diff --git a/packages/backend/src/server/api/RateLimiterService.ts b/packages/backend/src/server/api/RateLimiterService.ts index 35f28bfd63..94a15f94bb 100644 --- a/packages/backend/src/server/api/RateLimiterService.ts +++ b/packages/backend/src/server/api/RateLimiterService.ts @@ -5,6 +5,7 @@ import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; import type { IEndpointMeta } from './endpoints.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class RateLimiterService { @@ -19,6 +20,7 @@ export class RateLimiterService { this.logger = this.loggerService.getLogger('limiter'); } + @bindThis public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable }, actor: string) { return new Promise((ok, reject) => { if (process.env.NODE_ENV === 'test') ok(); diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 8b3d86e5a6..b633c2888f 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -13,6 +13,7 @@ import { IdService } from '@/core/IdService.js'; import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; import { RateLimiterService } from './RateLimiterService.js'; import { SigninService } from './SigninService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class SigninApiService { @@ -42,6 +43,7 @@ export class SigninApiService { ) { } + @bindThis public async signin( request: FastifyRequest<{ Body: { diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts index 18a1d6c088..96a89956f9 100644 --- a/packages/backend/src/server/api/SigninService.ts +++ b/packages/backend/src/server/api/SigninService.ts @@ -7,6 +7,7 @@ import { IdService } from '@/core/IdService.js'; import type { ILocalUser } from '@/models/entities/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { SigninEntityService } from '@/core/entities/SigninEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class SigninService { @@ -23,6 +24,7 @@ export class SigninService { ) { } + @bindThis public signin(request: FastifyRequest, reply: FastifyReply, user: ILocalUser, redirect = false) { setImmediate(async () => { // Append signin history diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 771858d091..59676426af 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -14,6 +14,7 @@ import { EmailService } from '@/core/EmailService.js'; import { ILocalUser } from '@/models/entities/User.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { SigninService } from './SigninService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class SignupApiService { @@ -43,6 +44,7 @@ export class SignupApiService { ) { } + @bindThis public async signup( request: FastifyRequest<{ Body: { @@ -165,6 +167,7 @@ export class SignupApiService { } } + @bindThis public async signupPending(request: FastifyRequest<{ Body: { code: string; } }>, reply: FastifyReply) { const body = request.body; diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index 46eaf8566e..487eef2d50 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -13,6 +13,7 @@ import MainStreamConnection from './stream/index.js'; import { ChannelsService } from './stream/ChannelsService.js'; import type { ParsedUrlQuery } from 'querystring'; import type * as http from 'node:http'; +import { bindThis } from '@/decorators.js'; @Injectable() export class StreamingApiServerService { @@ -49,6 +50,7 @@ export class StreamingApiServerService { ) { } + @bindThis public attachStreamingApi(server: http.Server) { // Init websocket server const ws = new websocket.server({ diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index 53de8d9495..9fc1391570 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -8,6 +8,7 @@ import { UserSuspendService } from '@/core/UserSuspendService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; export const meta = { tags: ['admin'], @@ -79,6 +80,7 @@ export default class extends Endpoint { }); } + @bindThis private async unFollowAll(follower: User) { const followings = await this.followingsRepository.findBy({ followerId: follower.id, @@ -97,6 +99,7 @@ export default class extends Endpoint { } } + @bindThis private async readAllNotify(notifier: User) { await this.notificationsRepository.update({ notifierId: notifier.id, diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index c218ec4642..1068a2eec7 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -15,6 +15,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -112,6 +113,7 @@ export default class extends Endpoint { /*** * URIからUserかNoteを解決する */ + @bindThis private async fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise | null> { // ブロックしてたら中断 const fetchedMeta = await this.metaService.fetch(); @@ -144,6 +146,7 @@ export default class extends Endpoint { ); } + @bindThis private async mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise | null> { if (user != null) { return { diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index 93c22a6c0b..a7f39a78d0 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -14,6 +14,7 @@ import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { SigninService } from '../SigninService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class DiscordServerService { @@ -36,9 +37,10 @@ export class DiscordServerService { private metaService: MetaService, private signinService: SigninService, ) { - this.create = this.create.bind(this); + //this.create = this.create.bind(this); } + @bindThis public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.get('/disconnect/discord', async (request, reply) => { if (!this.compareOrigin(request)) { @@ -288,10 +290,12 @@ export class DiscordServerService { done(); } + @bindThis private getUserToken(request: FastifyRequest): string | null { return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } + @bindThis private compareOrigin(request: FastifyRequest): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index 2fd20bf831..3aa04f72ee 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -14,6 +14,7 @@ import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { SigninService } from '../SigninService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class GithubServerService { @@ -36,9 +37,10 @@ export class GithubServerService { private metaService: MetaService, private signinService: SigninService, ) { - this.create = this.create.bind(this); + //this.create = this.create.bind(this); } + @bindThis public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.get('/disconnect/github', async (request, reply) => { if (!this.compareOrigin(request)) { @@ -260,10 +262,12 @@ export class GithubServerService { done(); } + @bindThis private getUserToken(request: FastifyRequest): string | null { return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } + @bindThis private compareOrigin(request: FastifyRequest): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; diff --git a/packages/backend/src/server/api/integration/TwitterServerService.ts b/packages/backend/src/server/api/integration/TwitterServerService.ts index a8447f9d49..7a127fa29a 100644 --- a/packages/backend/src/server/api/integration/TwitterServerService.ts +++ b/packages/backend/src/server/api/integration/TwitterServerService.ts @@ -14,6 +14,7 @@ import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { SigninService } from '../SigninService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class TwitterServerService { @@ -36,9 +37,10 @@ export class TwitterServerService { private metaService: MetaService, private signinService: SigninService, ) { - this.create = this.create.bind(this); + //this.create = this.create.bind(this); } + @bindThis public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.get('/disconnect/twitter', async (request, reply) => { if (!this.compareOrigin(request)) { @@ -205,10 +207,12 @@ export class TwitterServerService { done(); } + @bindThis private getUserToken(request: FastifyRequest): string | null { return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1]; } + @bindThis private compareOrigin(request: FastifyRequest): boolean { function normalizeUrl(url?: string): string { return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; diff --git a/packages/backend/src/server/api/stream/ChannelsService.ts b/packages/backend/src/server/api/stream/ChannelsService.ts index d6005b1ee8..198fc190d4 100644 --- a/packages/backend/src/server/api/stream/ChannelsService.ts +++ b/packages/backend/src/server/api/stream/ChannelsService.ts @@ -15,6 +15,7 @@ import { MessagingChannelService } from './channels/messaging.js'; import { MessagingIndexChannelService } from './channels/messaging-index.js'; import { DriveChannelService } from './channels/drive.js'; import { HashtagChannelService } from './channels/hashtag.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class ChannelsService { @@ -37,6 +38,7 @@ export class ChannelsService { ) { } + @bindThis public getChannelService(name: string) { switch (name) { case 'main': return this.mainChannelService; diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 5480c12c09..3e67880b45 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -1,3 +1,4 @@ +import { bindThis } from '@/decorators.js'; import type Connection from '.'; /** @@ -43,6 +44,7 @@ export default abstract class Channel { this.connection = connection; } + @bindThis public send(typeOrPayload: any, payload?: any) { const type = payload === undefined ? typeOrPayload.type : typeOrPayload; const body = payload === undefined ? typeOrPayload.body : payload; diff --git a/packages/backend/src/server/api/stream/channels/admin.ts b/packages/backend/src/server/api/stream/channels/admin.ts index 8c3c0d2adf..210e016a7e 100644 --- a/packages/backend/src/server/api/stream/channels/admin.ts +++ b/packages/backend/src/server/api/stream/channels/admin.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class AdminChannel extends Channel { @@ -6,6 +7,7 @@ class AdminChannel extends Channel { public static shouldShare = true; public static requireCredential = true; + @bindThis public async init(params: any) { // Subscribe admin stream this.subscriber.on(`adminStream:${this.user!.id}`, data => { @@ -23,6 +25,7 @@ export class AdminChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): AdminChannel { return new AdminChannel( id, diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts index 7c34aef495..44beef2da2 100644 --- a/packages/backend/src/server/api/stream/channels/antenna.ts +++ b/packages/backend/src/server/api/stream/channels/antenna.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { NotesRepository } from '@/models/index.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; import type { StreamMessages } from '../types.js'; @@ -18,9 +19,10 @@ class AntennaChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onEvent = this.onEvent.bind(this); + //this.onEvent = this.onEvent.bind(this); } + @bindThis public async init(params: any) { this.antennaId = params.antennaId as string; @@ -28,6 +30,7 @@ class AntennaChannel extends Channel { this.subscriber.on(`antennaStream:${this.antennaId}`, this.onEvent); } + @bindThis private async onEvent(data: StreamMessages['antenna']['payload']) { if (data.type === 'note') { const note = await this.noteEntityService.pack(data.body.id, this.user, { detail: true }); @@ -45,6 +48,7 @@ class AntennaChannel extends Channel { } } + @bindThis public dispose() { // Unsubscribe events this.subscriber.off(`antennaStream:${this.antennaId}`, this.onEvent); @@ -61,6 +65,7 @@ export class AntennaChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): AntennaChannel { return new AntennaChannel( this.noteEntityService, diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 2ef70e62e9..5ba84e43c4 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -5,6 +5,7 @@ import type { User } from '@/models/entities/User.js'; import type { Packed } from '@/misc/schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; import type { StreamMessages } from '../types.js'; @@ -24,10 +25,11 @@ class ChannelChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onNote = this.onNote.bind(this); - this.emitTypers = this.emitTypers.bind(this); + //this.onNote = this.onNote.bind(this); + //this.emitTypers = this.emitTypers.bind(this); } + @bindThis public async init(params: any) { this.channelId = params.channelId as string; @@ -37,6 +39,7 @@ class ChannelChannel extends Channel { this.emitTypersIntervalId = setInterval(this.emitTypers, 5000); } + @bindThis private async onNote(note: Packed<'Note'>) { if (note.channelId !== this.channelId) return; @@ -63,6 +66,7 @@ class ChannelChannel extends Channel { this.send('note', note); } + @bindThis private onEvent(data: StreamMessages['channel']['payload']) { if (data.type === 'typing') { const id = data.body; @@ -74,6 +78,7 @@ class ChannelChannel extends Channel { } } + @bindThis private async emitTypers() { const now = new Date(); @@ -90,6 +95,7 @@ class ChannelChannel extends Channel { }); } + @bindThis public dispose() { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); @@ -110,6 +116,7 @@ export class ChannelChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): ChannelChannel { return new ChannelChannel( this.noteEntityService, diff --git a/packages/backend/src/server/api/stream/channels/drive.ts b/packages/backend/src/server/api/stream/channels/drive.ts index 80d83cd690..cfcb125b6b 100644 --- a/packages/backend/src/server/api/stream/channels/drive.ts +++ b/packages/backend/src/server/api/stream/channels/drive.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class DriveChannel extends Channel { @@ -6,6 +7,7 @@ class DriveChannel extends Channel { public static shouldShare = true; public static requireCredential = true; + @bindThis public async init(params: any) { // Subscribe drive stream this.subscriber.on(`driveStream:${this.user!.id}`, data => { @@ -23,6 +25,7 @@ export class DriveChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): DriveChannel { return new DriveChannel( id, diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index a8617582dc..34f782e580 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -6,6 +6,7 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/schema.js'; import { MetaService } from '@/core/MetaService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class GlobalTimelineChannel extends Channel { @@ -21,9 +22,10 @@ class GlobalTimelineChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onNote = this.onNote.bind(this); + //this.onNote = this.onNote.bind(this); } + @bindThis public async init(params: any) { const meta = await this.metaService.fetch(); if (meta.disableGlobalTimeline) { @@ -34,6 +36,7 @@ class GlobalTimelineChannel extends Channel { this.subscriber.on('notesStream', this.onNote); } + @bindThis private async onNote(note: Packed<'Note'>) { if (note.visibility !== 'public') return; if (note.channelId != null) return; @@ -78,6 +81,7 @@ class GlobalTimelineChannel extends Channel { this.send('note', note); } + @bindThis public dispose() { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); @@ -95,6 +99,7 @@ export class GlobalTimelineChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): GlobalTimelineChannel { return new GlobalTimelineChannel( this.metaService, diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index 0f6c081c12..073b737079 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -4,6 +4,7 @@ import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class HashtagChannel extends Channel { @@ -19,9 +20,10 @@ class HashtagChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onNote = this.onNote.bind(this); + //this.onNote = this.onNote.bind(this); } + @bindThis public async init(params: any) { this.q = params.q; @@ -31,6 +33,7 @@ class HashtagChannel extends Channel { this.subscriber.on('notesStream', this.onNote); } + @bindThis private async onNote(note: Packed<'Note'>) { const noteTags = note.tags ? note.tags.map((t: string) => t.toLowerCase()) : []; const matched = this.q.some(tags => tags.every(tag => noteTags.includes(normalizeForSearch(tag)))); @@ -53,6 +56,7 @@ class HashtagChannel extends Channel { this.send('note', note); } + @bindThis public dispose() { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); @@ -69,6 +73,7 @@ export class HashtagChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): HashtagChannel { return new HashtagChannel( this.noteEntityService, diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 16e0cebc72..5707ddd821 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -5,6 +5,7 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import { isInstanceMuted } from '@/misc/is-instance-muted.js'; import type { Packed } from '@/misc/schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class HomeTimelineChannel extends Channel { @@ -19,14 +20,16 @@ class HomeTimelineChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onNote = this.onNote.bind(this); + //this.onNote = this.onNote.bind(this); } + @bindThis public async init(params: any) { // Subscribe events this.subscriber.on('notesStream', this.onNote); } + @bindThis private async onNote(note: Packed<'Note'>) { if (note.channelId) { if (!this.followingChannels.has(note.channelId)) return; @@ -85,6 +88,7 @@ class HomeTimelineChannel extends Channel { this.send('note', note); } + @bindThis public dispose() { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); @@ -101,6 +105,7 @@ export class HomeTimelineChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): HomeTimelineChannel { return new HomeTimelineChannel( this.noteEntityService, diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index f1ce822583..6c6afb12bf 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -7,6 +7,7 @@ import type { Packed } from '@/misc/schema.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class HybridTimelineChannel extends Channel { @@ -22,9 +23,10 @@ class HybridTimelineChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onNote = this.onNote.bind(this); + //this.onNote = this.onNote.bind(this); } + @bindThis public async init(params: any): Promise { const meta = await this.metaService.fetch(); if (meta.disableLocalTimeline && !this.user!.isAdmin && !this.user!.isModerator) return; @@ -33,6 +35,7 @@ class HybridTimelineChannel extends Channel { this.subscriber.on('notesStream', this.onNote); } + @bindThis private async onNote(note: Packed<'Note'>) { // チャンネルの投稿ではなく、自分自身の投稿 または // チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または @@ -95,6 +98,7 @@ class HybridTimelineChannel extends Channel { this.send('note', note); } + @bindThis public dispose(): void { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); @@ -112,6 +116,7 @@ export class HybridTimelineChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): HybridTimelineChannel { return new HybridTimelineChannel( this.metaService, diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index 5a5a43f845..54388787ef 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -5,6 +5,7 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/schema.js'; import { MetaService } from '@/core/MetaService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class LocalTimelineChannel extends Channel { @@ -20,9 +21,10 @@ class LocalTimelineChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onNote = this.onNote.bind(this); + //this.onNote = this.onNote.bind(this); } + @bindThis public async init(params: any) { const meta = await this.metaService.fetch(); if (meta.disableLocalTimeline) { @@ -33,6 +35,7 @@ class LocalTimelineChannel extends Channel { this.subscriber.on('notesStream', this.onNote); } + @bindThis private async onNote(note: Packed<'Note'>) { if (note.user.host !== null) return; if (note.visibility !== 'public') return; @@ -75,6 +78,7 @@ class LocalTimelineChannel extends Channel { this.send('note', note); } + @bindThis public dispose() { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); @@ -92,6 +96,7 @@ export class LocalTimelineChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): LocalTimelineChannel { return new LocalTimelineChannel( this.metaService, diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts index 12908e07b4..42f255b8fe 100644 --- a/packages/backend/src/server/api/stream/channels/main.ts +++ b/packages/backend/src/server/api/stream/channels/main.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { NotesRepository } from '@/models/index.js'; import { isInstanceMuted, isUserFromMutedInstance } from '@/misc/is-instance-muted.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class MainChannel extends Channel { @@ -18,6 +19,7 @@ class MainChannel extends Channel { super(id, connection); } + @bindThis public async init(params: any) { // Subscribe main stream channel this.subscriber.on(`mainStream:${this.user!.id}`, async data => { @@ -66,6 +68,7 @@ export class MainChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): MainChannel { return new MainChannel( this.noteEntityService, diff --git a/packages/backend/src/server/api/stream/channels/messaging-index.ts b/packages/backend/src/server/api/stream/channels/messaging-index.ts index bebc07f4ad..66cb79f7a7 100644 --- a/packages/backend/src/server/api/stream/channels/messaging-index.ts +++ b/packages/backend/src/server/api/stream/channels/messaging-index.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class MessagingIndexChannel extends Channel { @@ -6,6 +7,7 @@ class MessagingIndexChannel extends Channel { public static shouldShare = true; public static requireCredential = true; + @bindThis public async init(params: any) { // Subscribe messaging index stream this.subscriber.on(`messagingIndexStream:${this.user!.id}`, data => { @@ -23,6 +25,7 @@ export class MessagingIndexChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): MessagingIndexChannel { return new MessagingIndexChannel( id, diff --git a/packages/backend/src/server/api/stream/channels/messaging.ts b/packages/backend/src/server/api/stream/channels/messaging.ts index b6ce6c217e..92af6b591c 100644 --- a/packages/backend/src/server/api/stream/channels/messaging.ts +++ b/packages/backend/src/server/api/stream/channels/messaging.ts @@ -5,6 +5,7 @@ import type { UserGroup } from '@/models/entities/UserGroup.js'; import { MessagingService } from '@/core/MessagingService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; import type { StreamMessages } from '../types.js'; @@ -31,11 +32,12 @@ class MessagingChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.onEvent = this.onEvent.bind(this); - this.onMessage = this.onMessage.bind(this); - this.emitTypers = this.emitTypers.bind(this); + //this.onEvent = this.onEvent.bind(this); + //this.onMessage = this.onMessage.bind(this); + //this.emitTypers = this.emitTypers.bind(this); } + @bindThis public async init(params: any) { this.otherpartyId = params.otherparty; this.otherparty = this.otherpartyId ? await this.usersRepository.findOneByOrFail({ id: this.otherpartyId }) : null; @@ -63,6 +65,7 @@ class MessagingChannel extends Channel { this.subscriber.on(this.subCh, this.onEvent); } + @bindThis private onEvent(data: StreamMessages['messaging']['payload'] | StreamMessages['groupMessaging']['payload']) { if (data.type === 'typing') { const id = data.body; @@ -76,6 +79,7 @@ class MessagingChannel extends Channel { } } + @bindThis public onMessage(type: string, body: any) { switch (type) { case 'read': @@ -95,6 +99,7 @@ class MessagingChannel extends Channel { } } + @bindThis private async emitTypers() { const now = new Date(); @@ -111,6 +116,7 @@ class MessagingChannel extends Channel { }); } + @bindThis public dispose() { this.subscriber.off(this.subCh, this.onEvent); @@ -138,6 +144,7 @@ export class MessagingChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): MessagingChannel { return new MessagingChannel( this.usersRepository, diff --git a/packages/backend/src/server/api/stream/channels/queue-stats.ts b/packages/backend/src/server/api/stream/channels/queue-stats.ts index 1802c6723b..c773916103 100644 --- a/packages/backend/src/server/api/stream/channels/queue-stats.ts +++ b/packages/backend/src/server/api/stream/channels/queue-stats.ts @@ -1,5 +1,6 @@ import Xev from 'xev'; import { Inject, Injectable } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; const ev = new Xev(); @@ -11,18 +12,21 @@ class QueueStatsChannel extends Channel { constructor(id: string, connection: Channel['connection']) { super(id, connection); - this.onStats = this.onStats.bind(this); - this.onMessage = this.onMessage.bind(this); + //this.onStats = this.onStats.bind(this); + //this.onMessage = this.onMessage.bind(this); } + @bindThis public async init(params: any) { ev.addListener('queueStats', this.onStats); } + @bindThis private onStats(stats: any) { this.send('stats', stats); } + @bindThis public onMessage(type: string, body: any) { switch (type) { case 'requestLog': @@ -37,6 +41,7 @@ class QueueStatsChannel extends Channel { } } + @bindThis public dispose() { ev.removeListener('queueStats', this.onStats); } @@ -51,6 +56,7 @@ export class QueueStatsChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): QueueStatsChannel { return new QueueStatsChannel( id, diff --git a/packages/backend/src/server/api/stream/channels/server-stats.ts b/packages/backend/src/server/api/stream/channels/server-stats.ts index e2b00de25f..492912dbe6 100644 --- a/packages/backend/src/server/api/stream/channels/server-stats.ts +++ b/packages/backend/src/server/api/stream/channels/server-stats.ts @@ -1,5 +1,6 @@ import Xev from 'xev'; import { Inject, Injectable } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; const ev = new Xev(); @@ -11,18 +12,21 @@ class ServerStatsChannel extends Channel { constructor(id: string, connection: Channel['connection']) { super(id, connection); - this.onStats = this.onStats.bind(this); - this.onMessage = this.onMessage.bind(this); + //this.onStats = this.onStats.bind(this); + //this.onMessage = this.onMessage.bind(this); } + @bindThis public async init(params: any) { ev.addListener('serverStats', this.onStats); } + @bindThis private onStats(stats: any) { this.send('stats', stats); } + @bindThis public onMessage(type: string, body: any) { switch (type) { case 'requestLog': @@ -37,6 +41,7 @@ class ServerStatsChannel extends Channel { } } + @bindThis public dispose() { ev.removeListener('serverStats', this.onStats); } @@ -51,6 +56,7 @@ export class ServerStatsChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): ServerStatsChannel { return new ServerStatsChannel( id, diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index f9f0d02558..16af32868c 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -1,11 +1,11 @@ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; -import type { NotesRepository } from '@/models/index.js'; +import type { UserListJoiningsRepository, UserListsRepository, NotesRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; class UserListChannel extends Channel { @@ -25,10 +25,11 @@ class UserListChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - this.updateListUsers = this.updateListUsers.bind(this); - this.onNote = this.onNote.bind(this); + //this.updateListUsers = this.updateListUsers.bind(this); + //this.onNote = this.onNote.bind(this); } + @bindThis public async init(params: any) { this.listId = params.listId as string; @@ -48,6 +49,7 @@ class UserListChannel extends Channel { this.listUsersClock = setInterval(this.updateListUsers, 5000); } + @bindThis private async updateListUsers() { const users = await this.userListJoiningsRepository.find({ where: { @@ -59,6 +61,7 @@ class UserListChannel extends Channel { this.listUsers = users.map(x => x.userId); } + @bindThis private async onNote(note: Packed<'Note'>) { if (!this.listUsers.includes(note.userId)) return; @@ -93,6 +96,7 @@ class UserListChannel extends Channel { this.send('note', note); } + @bindThis public dispose() { // Unsubscribe events this.subscriber.off(`userListStream:${this.listId}`, this.send); @@ -118,6 +122,7 @@ export class UserListChannelService { ) { } + @bindThis public create(id: string, connection: Channel['connection']): UserListChannel { return new UserListChannel( this.userListsRepository, diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index 0c5066b736..6763953f9d 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -8,6 +8,7 @@ import type { Packed } from '@/misc/schema.js'; import type { GlobalEventService } from '@/core/GlobalEventService.js'; import type { NoteReadService } from '@/core/NoteReadService.js'; import type { NotificationService } from '@/core/NotificationService.js'; +import { bindThis } from '@/decorators.js'; import type { ChannelsService } from './ChannelsService.js'; import type * as websocket from 'websocket'; import type { EventEmitter } from 'events'; @@ -52,10 +53,10 @@ export default class Connection { if (user) this.user = user; if (token) this.token = token; - this.onWsConnectionMessage = this.onWsConnectionMessage.bind(this); - this.onUserEvent = this.onUserEvent.bind(this); - this.onNoteStreamMessage = this.onNoteStreamMessage.bind(this); - this.onBroadcastMessage = this.onBroadcastMessage.bind(this); + //this.onWsConnectionMessage = this.onWsConnectionMessage.bind(this); + //this.onUserEvent = this.onUserEvent.bind(this); + //this.onNoteStreamMessage = this.onNoteStreamMessage.bind(this); + //this.onBroadcastMessage = this.onBroadcastMessage.bind(this); this.wsConnection.on('message', this.onWsConnectionMessage); @@ -74,6 +75,7 @@ export default class Connection { } } + @bindThis private onUserEvent(data: StreamMessages['user']['payload']) { // { type, body }と展開するとそれぞれ型が分離してしまう switch (data.type) { case 'follow': @@ -119,6 +121,7 @@ export default class Connection { /** * クライアントからメッセージ受信時 */ + @bindThis private async onWsConnectionMessage(data: websocket.Message) { if (data.type !== 'utf8') return; if (data.utf8Data == null) return; @@ -153,10 +156,12 @@ export default class Connection { } } + @bindThis private onBroadcastMessage(data: StreamMessages['broadcast']['payload']) { this.sendMessageToWs(data.type, data.body); } + @bindThis public cacheNote(note: Packed<'Note'>) { const add = (note: Packed<'Note'>) => { const existIndex = this.cachedNotes.findIndex(n => n.id === note.id); @@ -176,6 +181,7 @@ export default class Connection { if (note.renote) add(note.renote); } + @bindThis private readNote(body: any) { const id = body.id; @@ -190,6 +196,7 @@ export default class Connection { } } + @bindThis private onReadNotification(payload: any) { if (!payload.id) return; this.notificationService.readNotification(this.user!.id, [payload.id]); @@ -198,6 +205,7 @@ export default class Connection { /** * 投稿購読要求時 */ + @bindThis private onSubscribeNote(payload: any) { if (!payload.id) return; @@ -215,6 +223,7 @@ export default class Connection { /** * 投稿購読解除要求時 */ + @bindThis private onUnsubscribeNote(payload: any) { if (!payload.id) return; @@ -225,6 +234,7 @@ export default class Connection { } } + @bindThis private async onNoteStreamMessage(data: StreamMessages['note']['payload']) { this.sendMessageToWs('noteUpdated', { id: data.body.id, @@ -236,6 +246,7 @@ export default class Connection { /** * チャンネル接続要求時 */ + @bindThis private onChannelConnectRequested(payload: any) { const { channel, id, params, pong } = payload; this.connectChannel(id, params, channel, pong); @@ -244,6 +255,7 @@ export default class Connection { /** * チャンネル切断要求時 */ + @bindThis private onChannelDisconnectRequested(payload: any) { const { id } = payload; this.disconnectChannel(id); @@ -252,6 +264,7 @@ export default class Connection { /** * クライアントにメッセージ送信 */ + @bindThis public sendMessageToWs(type: string, payload: any) { this.wsConnection.send(JSON.stringify({ type: type, @@ -262,6 +275,7 @@ export default class Connection { /** * チャンネルに接続 */ + @bindThis public connectChannel(id: string, params: any, channel: string, pong = false) { const channelService = this.channelsService.getChannelService(channel); @@ -289,6 +303,7 @@ export default class Connection { * チャンネルから切断 * @param id チャンネルコネクションID */ + @bindThis public disconnectChannel(id: string) { const channel = this.channels.find(c => c.id === id); @@ -302,6 +317,7 @@ export default class Connection { * チャンネルへメッセージ送信要求時 * @param data メッセージ */ + @bindThis private onChannelMessageRequested(data: any) { const channel = this.channels.find(c => c.id === data.id); if (channel != null && channel.onMessage != null) { @@ -309,12 +325,14 @@ export default class Connection { } } + @bindThis private typingOnChannel(channel: ChannelModel['id']) { if (this.user) { this.globalEventService.publishChannelStream(channel, 'typing', this.user.id); } } + @bindThis private typingOnMessaging(param: { partner?: User['id']; group?: UserGroup['id']; }) { if (this.user) { if (param.partner) { @@ -325,6 +343,7 @@ export default class Connection { } } + @bindThis private async updateFollowing() { const followings = await this.followingsRepository.find({ where: { @@ -336,6 +355,7 @@ export default class Connection { this.following = new Set(followings.map(x => x.followeeId)); } + @bindThis private async updateMuting() { const mutings = await this.mutingsRepository.find({ where: { @@ -347,6 +367,7 @@ export default class Connection { this.muting = new Set(mutings.map(x => x.muteeId)); } + @bindThis private async updateBlocking() { // ここでいうBlockingは被Blockingの意 const blockings = await this.blockingsRepository.find({ where: { @@ -358,6 +379,7 @@ export default class Connection { this.blocking = new Set(blockings.map(x => x.blockerId)); } + @bindThis private async updateFollowingChannels() { const followings = await this.channelFollowingsRepository.find({ where: { @@ -369,6 +391,7 @@ export default class Connection { this.followingChannels = new Set(followings.map(x => x.followeeId)); } + @bindThis private async updateUserProfile() { this.userProfile = await this.userProfilesRepository.findOneBy({ userId: this.user!.id, @@ -378,6 +401,7 @@ export default class Connection { /** * ストリームが切れたとき */ + @bindThis public dispose() { for (const c of this.channels.filter(c => c.dispose)) { if (c.dispose) c.dispose(); diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 3fcf8b7c0d..727cf92831 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -23,6 +23,7 @@ import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import type { ChannelsRepository, ClipsRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { deepClone } from '@/misc/clone.js'; +import { bindThis } from '@/decorators.js'; import manifest from './manifest.json' assert { type: 'json' }; import { FeedService } from './FeedService.js'; import { UrlPreviewService } from './UrlPreviewService.js'; @@ -80,9 +81,10 @@ export class ClientServerService { @Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue, @Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue, ) { - this.createServer = this.createServer.bind(this); + //this.createServer = this.createServer.bind(this); } + @bindThis private async manifestHandler(reply: FastifyReply) { const res = deepClone(manifest); @@ -96,6 +98,7 @@ export class ClientServerService { return (res); } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { /* TODO //#region Bull Dashboard diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts index 1d7d49961d..a14609adf9 100644 --- a/packages/backend/src/server/web/FeedService.ts +++ b/packages/backend/src/server/web/FeedService.ts @@ -7,6 +7,7 @@ import type { Config } from '@/config.js'; import type { User } from '@/models/entities/User.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class FeedService { @@ -31,6 +32,7 @@ export class FeedService { ) { } + @bindThis public async packFeed(user: User) { const author = { link: `${this.config.url}/@${user.username}`, diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 69f52cc2f2..69bb232d4a 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -9,6 +9,7 @@ import { HttpRequestService } from '@/core/HttpRequestService.js'; import type Logger from '@/logger.js'; import { query } from '@/misc/prelude/url.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { bindThis } from '@/decorators.js'; @Injectable() export class UrlPreviewService { @@ -28,6 +29,7 @@ export class UrlPreviewService { this.logger = this.loggerService.getLogger('url-preview'); } + @bindThis private wrap(url?: string): string | null { return url != null ? url.match(/^https?:\/\//) @@ -39,6 +41,7 @@ export class UrlPreviewService { : null; } + @bindThis public async handle( request: FastifyRequest<{ Querystring: { url: string; lang: string; } }>, reply: FastifyReply, diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts index cad4d8af6c..9efed267ea 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.ts @@ -15,6 +15,7 @@ export class MockResolver extends Resolver { }); } + @bindThis public async resolve(value: string | IObject): Promise { if (typeof value !== 'string') return value; -- cgit v1.2.3-freya From 4ecc42744c3c8b68e38f58bfe03919bf437f137a Mon Sep 17 00:00:00 2001 From: tamaina Date: Sun, 18 Dec 2022 01:59:59 +0900 Subject: enhance: Implement the toggle to (or not to) close push notifications when notifications or messages are read (#9219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * create file * wip * fix * wip * tabun dekita * :v: * implement subscribe push notification button to tutorial * check-exists→show-registration * add column sendReadMessage * fix migration file * sw api * change PushNotificationService * wip * :v: * fix tutorial footer flex --- locales/ja-JP.yml | 11 ++ ...138716634-whetherPushNotifyToSendReadMessage.js | 11 ++ .../backend/src/core/PushNotificationService.ts | 8 + .../backend/src/models/entities/SwSubscription.ts | 5 + packages/backend/src/server/api/EndpointsModule.ts | 6 + packages/backend/src/server/api/endpoints.ts | 4 + .../src/server/api/endpoints/sw/register.ts | 20 +++ .../server/api/endpoints/sw/show-registration.ts | 66 ++++++++ .../src/server/api/endpoints/sw/unregister.ts | 4 +- .../server/api/endpoints/sw/update-registration.ts | 82 ++++++++++ .../components/MkPushNotificationAllowButton.vue | 167 +++++++++++++++++++++ .../client/src/pages/settings/notifications.vue | 30 ++++ packages/client/src/pages/timeline.tutorial.vue | 116 ++++++++------ packages/client/src/scripts/initialize-sw.ts | 55 ------- 14 files changed, 481 insertions(+), 104 deletions(-) create mode 100644 packages/backend/migration/1669138716634-whetherPushNotifyToSendReadMessage.js create mode 100644 packages/backend/src/server/api/endpoints/sw/show-registration.ts create mode 100644 packages/backend/src/server/api/endpoints/sw/update-registration.ts create mode 100644 packages/client/src/components/MkPushNotificationAllowButton.vue (limited to 'packages/backend/src/server/api/endpoints') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 3fba36a754..3a53f470e3 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -13,6 +13,7 @@ fetchingAsApObject: "連合に照会中" ok: "OK" gotIt: "わかった" cancel: "キャンセル" +noThankYou: "やめておく" enterUsername: "ユーザー名を入力" renotedBy: "{user}がRenote" noNotes: "ノートはありません" @@ -898,6 +899,13 @@ navbar: "ナビゲーションバー" shuffle: "シャッフル" account: "アカウント" move: "移動" +pushNotification: "プッシュ通知" +subscribePushNotification: "プッシュ通知を有効化" +unsubscribePushNotification: "プッシュ通知を停止する" +pushNotificationAlreadySubscribed: "プッシュ通知は有効です" +pushNotificationNotSupported: "ブラウザかインスタンスがプッシュ通知に非対応" +sendPushNotificationReadMessage: "通知やメッセージが既読になったらプッシュ通知を削除する" +sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」という通知が一瞬表示されるようになります。端末の電池消費量が増加する可能性があります。" _sensitiveMediaDetection: description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" @@ -1235,6 +1243,9 @@ _tutorial: step7_1: "これで、Misskeyの基本的な使い方の説明は終わりました。お疲れ様でした。" step7_2: "もっとMisskeyについて知りたいときは、{help}を見てみてください。" step7_3: "では、Misskeyをお楽しみください🚀" + step8_1: "最後に、プッシュ通知を有効化してみませんか?" + step8_2: "プッシュ通知を受け取ることで、Misskeyを開いていない時にもリアクションやフォロー、メンションなどに気づけます。" + step8_3: "通知の設定は後から変更できます。" _2fa: alreadyRegistered: "既に設定は完了しています。" diff --git a/packages/backend/migration/1669138716634-whetherPushNotifyToSendReadMessage.js b/packages/backend/migration/1669138716634-whetherPushNotifyToSendReadMessage.js new file mode 100644 index 0000000000..2265b00617 --- /dev/null +++ b/packages/backend/migration/1669138716634-whetherPushNotifyToSendReadMessage.js @@ -0,0 +1,11 @@ +export class whetherPushNotifyToSendReadMessage1669138716634 { + name = 'whetherPushNotifyToSendReadMessage1669138716634' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "sw_subscription" ADD "sendReadMessage" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "sw_subscription" DROP COLUMN "sendReadMessage"`); + } +} diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index df5284de4b..842cd1a9f8 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -69,6 +69,14 @@ export class PushNotificationService { }); for (const subscription of subscriptions) { + // Continue if sendReadMessage is false + if ([ + 'readNotifications', + 'readAllNotifications', + 'readAllMessagingMessages', + 'readAllMessagingMessagesOfARoom', + ].includes(type) && !subscription.sendReadMessage) continue; + const pushSubscription = { endpoint: subscription.endpoint, keys: { diff --git a/packages/backend/src/models/entities/SwSubscription.ts b/packages/backend/src/models/entities/SwSubscription.ts index 51b9786e96..0658294983 100644 --- a/packages/backend/src/models/entities/SwSubscription.ts +++ b/packages/backend/src/models/entities/SwSubscription.ts @@ -34,4 +34,9 @@ export class SwSubscription { length: 128, }) public publickey: string; + + @Column('boolean', { + default: false, + }) + public sendReadMessage: boolean; } diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index e41ed388b4..647f60317a 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -272,6 +272,8 @@ import * as ep___resetDb from './endpoints/reset-db.js'; import * as ep___resetPassword from './endpoints/reset-password.js'; import * as ep___serverInfo from './endpoints/server-info.js'; import * as ep___stats from './endpoints/stats.js'; +import * as ep___sw_show_registration from './endpoints/sw/show-registration.js'; +import * as ep___sw_update_registration from './endpoints/sw/update-registration.js'; import * as ep___sw_register from './endpoints/sw/register.js'; import * as ep___sw_unregister from './endpoints/sw/unregister.js'; import * as ep___test from './endpoints/test.js'; @@ -588,6 +590,8 @@ const $resetDb: Provider = { provide: 'ep:reset-db', useClass: ep___resetDb.defa const $resetPassword: Provider = { provide: 'ep:reset-password', useClass: ep___resetPassword.default }; const $serverInfo: Provider = { provide: 'ep:server-info', useClass: ep___serverInfo.default }; const $stats: Provider = { provide: 'ep:stats', useClass: ep___stats.default }; +const $sw_show_registration: Provider = { provide: 'ep:sw/show-registration', useClass: ep___sw_show_registration.default }; +const $sw_update_registration: Provider = { provide: 'ep:sw/update-registration', useClass: ep___sw_update_registration.default }; const $sw_register: Provider = { provide: 'ep:sw/register', useClass: ep___sw_register.default }; const $sw_unregister: Provider = { provide: 'ep:sw/unregister', useClass: ep___sw_unregister.default }; const $test: Provider = { provide: 'ep:test', useClass: ep___test.default }; @@ -908,6 +912,8 @@ const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.d $resetPassword, $serverInfo, $stats, + $sw_show_registration, + $sw_update_registration, $sw_register, $sw_unregister, $test, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index b2ab36e070..6d10cb8f35 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -271,6 +271,8 @@ import * as ep___resetDb from './endpoints/reset-db.js'; import * as ep___resetPassword from './endpoints/reset-password.js'; import * as ep___serverInfo from './endpoints/server-info.js'; import * as ep___stats from './endpoints/stats.js'; +import * as ep___sw_show_registration from './endpoints/sw/show-registration.js'; +import * as ep___sw_update_registration from './endpoints/sw/update-registration.js'; import * as ep___sw_register from './endpoints/sw/register.js'; import * as ep___sw_unregister from './endpoints/sw/unregister.js'; import * as ep___test from './endpoints/test.js'; @@ -585,6 +587,8 @@ const eps = [ ['reset-password', ep___resetPassword], ['server-info', ep___serverInfo], ['stats', ep___stats], + ['sw/show-registration', ep___sw_show_registration], + ['sw/update-registration', ep___sw_update_registration], ['sw/register', ep___sw_register], ['sw/unregister', ep___sw_unregister], ['test', ep___test], diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index ddec877dd4..bfd5de7b00 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -25,6 +25,18 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + userId: { + type: 'string', + optional: false, nullable: false, + }, + endpoint: { + type: 'string', + optional: false, nullable: false, + }, + sendReadMessage: { + type: 'boolean', + optional: false, nullable: false, + }, }, }, } as const; @@ -35,6 +47,7 @@ export const paramDef = { endpoint: { type: 'string' }, auth: { type: 'string' }, publickey: { type: 'string' }, + sendReadMessage: { type: 'boolean', default: false }, }, required: ['endpoint', 'auth', 'publickey'], } as const; @@ -64,6 +77,9 @@ export default class extends Endpoint { return { state: 'already-subscribed' as const, key: instance.swPublicKey, + userId: me.id, + endpoint: exist.endpoint, + sendReadMessage: exist.sendReadMessage, }; } @@ -74,11 +90,15 @@ export default class extends Endpoint { endpoint: ps.endpoint, auth: ps.auth, publickey: ps.publickey, + sendReadMessage: ps.sendReadMessage, }); return { state: 'subscribed' as const, key: instance.swPublicKey, + userId: me.id, + endpoint: ps.endpoint, + sendReadMessage: ps.sendReadMessage, }; }); } diff --git a/packages/backend/src/server/api/endpoints/sw/show-registration.ts b/packages/backend/src/server/api/endpoints/sw/show-registration.ts new file mode 100644 index 0000000000..bede10be5c --- /dev/null +++ b/packages/backend/src/server/api/endpoints/sw/show-registration.ts @@ -0,0 +1,66 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type { SwSubscriptionsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; + +export const meta = { + tags: ['account'], + + requireCredential: true, + + description: 'Check push notification registration exists.', + + res: { + type: 'object', + optional: false, nullable: true, + properties: { + userId: { + type: 'string', + optional: false, nullable: false, + }, + endpoint: { + type: 'string', + optional: false, nullable: false, + }, + sendReadMessage: { + type: 'boolean', + optional: false, nullable: false, + }, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + endpoint: { type: 'string' }, + }, + required: ['endpoint'], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.swSubscriptionsRepository) + private swSubscriptionsRepository: SwSubscriptionsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + // if already subscribed + const exist = await this.swSubscriptionsRepository.findOneBy({ + userId: me.id, + endpoint: ps.endpoint, + }); + + if (exist != null) { + return { + userId: exist.userId, + endpoint: exist.endpoint, + sendReadMessage: exist.sendReadMessage, + }; + } + + return null; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index 5772eeee26..f12b98617d 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -6,7 +6,7 @@ import { DI } from '@/di-symbols.js'; export const meta = { tags: ['account'], - requireCredential: true, + requireCredential: false, description: 'Unregister from receiving push notifications.', } as const; @@ -28,7 +28,7 @@ export default class extends Endpoint { ) { super(meta, paramDef, async (ps, me) => { await this.swSubscriptionsRepository.delete({ - userId: me.id, + ...(me ? { userId: me.id } : {}), endpoint: ps.endpoint, }); }); diff --git a/packages/backend/src/server/api/endpoints/sw/update-registration.ts b/packages/backend/src/server/api/endpoints/sw/update-registration.ts new file mode 100644 index 0000000000..9f08c8148d --- /dev/null +++ b/packages/backend/src/server/api/endpoints/sw/update-registration.ts @@ -0,0 +1,82 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type { SwSubscriptionsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; + +export const meta = { + tags: ['account'], + + requireCredential: true, + + description: 'Update push notification registration.', + + res: { + type: 'object', + optional: false, nullable: false, + properties: { + userId: { + type: 'string', + optional: false, nullable: false, + }, + endpoint: { + type: 'string', + optional: false, nullable: false, + }, + sendReadMessage: { + type: 'boolean', + optional: false, nullable: false, + }, + }, + }, + errors: { + noSuchRegistration: { + message: 'No such registration.', + code: 'NO_SUCH_REGISTRATION', + id: ' b09d8066-8064-5613-efb6-0e963b21d012', + }, + } +} as const; + +export const paramDef = { + type: 'object', + properties: { + endpoint: { type: 'string' }, + sendReadMessage: { type: 'boolean' }, + }, + required: ['endpoint'], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.swSubscriptionsRepository) + private swSubscriptionsRepository: SwSubscriptionsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const swSubscription = await this.swSubscriptionsRepository.findOneBy({ + userId: me.id, + endpoint: ps.endpoint, + }); + + if (swSubscription === null) { + throw new ApiError(meta.errors.noSuchRegistration); + } + + if (ps.sendReadMessage !== undefined) { + swSubscription.sendReadMessage = ps.sendReadMessage; + } + + await this.swSubscriptionsRepository.update(swSubscription.id, { + sendReadMessage: swSubscription.sendReadMessage, + }); + + return { + userId: swSubscription.userId, + endpoint: swSubscription.endpoint, + sendReadMessage: swSubscription.sendReadMessage, + }; + }); + } +} diff --git a/packages/client/src/components/MkPushNotificationAllowButton.vue b/packages/client/src/components/MkPushNotificationAllowButton.vue new file mode 100644 index 0000000000..a762914e64 --- /dev/null +++ b/packages/client/src/components/MkPushNotificationAllowButton.vue @@ -0,0 +1,167 @@ + + + diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue index 5703e0c6b6..77ec567da4 100644 --- a/packages/client/src/pages/settings/notifications.vue +++ b/packages/client/src/pages/settings/notifications.vue @@ -6,6 +6,18 @@ {{ i18n.ts.markAsReadAllUnreadNotes }} {{ i18n.ts.markAsReadAllTalkMessages }} + + + + + + + + @@ -15,10 +27,16 @@ import { notificationTypes } from 'misskey-js'; import FormButton from '@/components/MkButton.vue'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; +import FormSwitch from '@/components/form/switch.vue'; import * as os from '@/os'; import { $i } from '@/account'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue'; + +let allowButton = $ref>(); +let pushRegistrationInServer = $computed(() => allowButton?.pushRegistrationInServer); +let sendReadMessage = $computed(() => pushRegistrationInServer?.sendReadMessage || false); async function readAllUnreadNotes() { await os.api('i/read-all-unread-notes'); @@ -49,6 +67,18 @@ function configure() { }, 'closed'); } +function onChangeSendReadMessage(v: boolean) { + if (!pushRegistrationInServer) return; + + os.apiWithDialog('sw/update-registration', { + endpoint: pushRegistrationInServer.endpoint, + sendReadMessage: v, + }).then(res => { + if (!allowButton) return; + allowButton.pushRegistrationInServer = res; + }); +} + const headerActions = $computed(() => []); const headerTabs = $computed(() => []); diff --git a/packages/client/src/pages/timeline.tutorial.vue b/packages/client/src/pages/timeline.tutorial.vue index 7f08ccc2a1..9683cc22a5 100644 --- a/packages/client/src/pages/timeline.tutorial.vue +++ b/packages/client/src/pages/timeline.tutorial.vue @@ -1,6 +1,17 @@
{{ i18n.ts._tutorial.step5_3 }}
- {{ i18n.ts._tutorial.step5_4 }} + {{ i18n.ts._tutorial.step5_4 }}
{{ i18n.ts._tutorial.step6_1 }}
@@ -48,19 +59,20 @@
{{ i18n.ts._tutorial.step7_3 }}
+
+
{{ i18n.ts._tutorial.step8_1 }}
+
{{ i18n.ts._tutorial.step8_2 }}
+ {{ i18n.ts._tutorial.step8_3 }} +
- @@ -68,53 +80,63 @@ - diff --git a/packages/client/src/scripts/initialize-sw.ts b/packages/client/src/scripts/initialize-sw.ts index 7bacfbdf00..de52f30523 100644 --- a/packages/client/src/scripts/initialize-sw.ts +++ b/packages/client/src/scripts/initialize-sw.ts @@ -1,6 +1,3 @@ -import { instance } from '@/instance'; -import { $i } from '@/account'; -import { api } from '@/os'; import { lang } from '@/config'; export async function initializeSw() { @@ -12,57 +9,5 @@ export async function initializeSw() { msg: 'initialize', lang, }); - - if (instance.swPublickey && ('PushManager' in window) && $i && $i.token) { - // SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters - registration.pushManager.subscribe({ - userVisibleOnly: true, - applicationServerKey: urlBase64ToUint8Array(instance.swPublickey) - }) - .then(subscription => { - function encode(buffer: ArrayBuffer | null) { - return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer))); - } - - // Register - api('sw/register', { - endpoint: subscription.endpoint, - auth: encode(subscription.getKey('auth')), - publickey: encode(subscription.getKey('p256dh')) - }); - }) - // When subscribe failed - .catch(async (err: Error) => { - // 通知が許可されていなかったとき - if (err.name === 'NotAllowedError') { - return; - } - - // 違うapplicationServerKey (または gcm_sender_id)のサブスクリプションが - // 既に存在していることが原因でエラーになった可能性があるので、 - // そのサブスクリプションを解除しておく - const subscription = await registration.pushManager.getSubscription(); - if (subscription) subscription.unsubscribe(); - }); - } }); } - -/** - * Convert the URL safe base64 string to a Uint8Array - * @param base64String base64 string - */ -function urlBase64ToUint8Array(base64String: string): Uint8Array { - const padding = '='.repeat((4 - base64String.length % 4) % 4); - const base64 = (base64String + padding) - .replace(/-/g, '+') - .replace(/_/g, '/'); - - const rawData = window.atob(base64); - const outputArray = new Uint8Array(rawData.length); - - for (let i = 0; i < rawData.length; ++i) { - outputArray[i] = rawData.charCodeAt(i); - } - return outputArray; -} -- cgit v1.2.3-freya From b648c4bc4d17c1b0bacd9b98022051242d7a69eb Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 25 Dec 2022 08:27:06 +0900 Subject: chore: fix reporisotry name --- .../backend/src/models/entities/RegistrationTicket.ts | 17 +++++++++++++++++ .../backend/src/models/entities/RegistrationTickets.ts | 17 ----------------- packages/backend/src/models/index.ts | 4 ++-- packages/backend/src/postgre.ts | 2 +- packages/backend/src/server/api/SignupApiService.ts | 4 ++-- .../backend/src/server/api/endpoints/admin/invite.ts | 4 ++-- 6 files changed, 24 insertions(+), 24 deletions(-) create mode 100644 packages/backend/src/models/entities/RegistrationTicket.ts delete mode 100644 packages/backend/src/models/entities/RegistrationTickets.ts (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/models/entities/RegistrationTicket.ts b/packages/backend/src/models/entities/RegistrationTicket.ts new file mode 100644 index 0000000000..139e40f85e --- /dev/null +++ b/packages/backend/src/models/entities/RegistrationTicket.ts @@ -0,0 +1,17 @@ +import { PrimaryColumn, Entity, Index, Column } from 'typeorm'; +import { id } from '../id.js'; + +@Entity() +export class RegistrationTicket { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone') + public createdAt: Date; + + @Index({ unique: true }) + @Column('varchar', { + length: 64, + }) + public code: string; +} diff --git a/packages/backend/src/models/entities/RegistrationTickets.ts b/packages/backend/src/models/entities/RegistrationTickets.ts deleted file mode 100644 index 139e40f85e..0000000000 --- a/packages/backend/src/models/entities/RegistrationTickets.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PrimaryColumn, Entity, Index, Column } from 'typeorm'; -import { id } from '../id.js'; - -@Entity() -export class RegistrationTicket { - @PrimaryColumn(id()) - public id: string; - - @Column('timestamp with time zone') - public createdAt: Date; - - @Index({ unique: true }) - @Column('varchar', { - length: 64, - }) - public code: string; -} diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index 559ed8c45a..c6b4fe11e9 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -40,7 +40,7 @@ import { Poll } from '@/models/entities/Poll.js'; import { PollVote } from '@/models/entities/PollVote.js'; import { PromoNote } from '@/models/entities/PromoNote.js'; import { PromoRead } from '@/models/entities/PromoRead.js'; -import { RegistrationTicket } from '@/models/entities/RegistrationTickets.js'; +import { RegistrationTicket } from '@/models/entities/RegistrationTicket.js'; import { RegistryItem } from '@/models/entities/RegistryItem.js'; import { Relay } from '@/models/entities/Relay.js'; import { Signin } from '@/models/entities/Signin.js'; @@ -171,7 +171,7 @@ export type PollsRepository = Repository; export type PollVotesRepository = Repository; export type PromoNotesRepository = Repository; export type PromoReadsRepository = Repository; -export type RegistrationTicketsRepository = Repository; +export type RegistrationTicketRepository = Repository; export type RegistryItemsRepository = Repository; export type RelaysRepository = Repository; export type SigninsRepository = Repository; diff --git a/packages/backend/src/postgre.ts b/packages/backend/src/postgre.ts index 0f9f7a6a96..829edbd7cf 100644 --- a/packages/backend/src/postgre.ts +++ b/packages/backend/src/postgre.ts @@ -48,7 +48,7 @@ import { Poll } from '@/models/entities/Poll.js'; import { PollVote } from '@/models/entities/PollVote.js'; import { PromoNote } from '@/models/entities/PromoNote.js'; import { PromoRead } from '@/models/entities/PromoRead.js'; -import { RegistrationTicket } from '@/models/entities/RegistrationTickets.js'; +import { RegistrationTicket } from '@/models/entities/RegistrationTicket.js'; import { RegistryItem } from '@/models/entities/RegistryItem.js'; import { Relay } from '@/models/entities/Relay.js'; import { Signin } from '@/models/entities/Signin.js'; diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index bba81250ab..d5acc1a0a6 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import rndstr from 'rndstr'; import bcrypt from 'bcryptjs'; import { DI } from '@/di-symbols.js'; -import type { RegistrationTicketsRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { RegistrationTicketRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; import { CaptchaService } from '@/core/CaptchaService.js'; @@ -32,7 +32,7 @@ export class SignupApiService { private userPendingsRepository: UserPendingsRepository, @Inject(DI.registrationTicketsRepository) - private registrationTicketsRepository: RegistrationTicketsRepository, + private registrationTicketsRepository: RegistrationTicketRepository, private userEntityService: UserEntityService, private idService: IdService, diff --git a/packages/backend/src/server/api/endpoints/admin/invite.ts b/packages/backend/src/server/api/endpoints/admin/invite.ts index bc42bf792a..b0450c9ba5 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite.ts @@ -1,7 +1,7 @@ import rndstr from 'rndstr'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistrationTicketsRepository } from '@/models/index.js'; +import type { RegistrationTicketRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; @@ -37,7 +37,7 @@ export const paramDef = { export default class extends Endpoint { constructor( @Inject(DI.registrationTicketsRepository) - private registrationTicketsRepository: RegistrationTicketsRepository, + private registrationTicketsRepository: RegistrationTicketRepository, private idService: IdService, ) { -- cgit v1.2.3-freya From b11f8b0aae14ba8c2e35ce216a3fbfc60e7e77a2 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 25 Dec 2022 08:30:13 +0900 Subject: chore: fix reporisotry name --- packages/backend/src/models/index.ts | 2 +- packages/backend/src/server/api/SignupApiService.ts | 4 ++-- packages/backend/src/server/api/endpoints/admin/invite.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index c6b4fe11e9..7fde3fbedb 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -171,7 +171,7 @@ export type PollsRepository = Repository; export type PollVotesRepository = Repository; export type PromoNotesRepository = Repository; export type PromoReadsRepository = Repository; -export type RegistrationTicketRepository = Repository; +export type RegistrationTicketsRepository = Repository; export type RegistryItemsRepository = Repository; export type RelaysRepository = Repository; export type SigninsRepository = Repository; diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index d5acc1a0a6..bba81250ab 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import rndstr from 'rndstr'; import bcrypt from 'bcryptjs'; import { DI } from '@/di-symbols.js'; -import type { RegistrationTicketRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { RegistrationTicketsRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; import { CaptchaService } from '@/core/CaptchaService.js'; @@ -32,7 +32,7 @@ export class SignupApiService { private userPendingsRepository: UserPendingsRepository, @Inject(DI.registrationTicketsRepository) - private registrationTicketsRepository: RegistrationTicketRepository, + private registrationTicketsRepository: RegistrationTicketsRepository, private userEntityService: UserEntityService, private idService: IdService, diff --git a/packages/backend/src/server/api/endpoints/admin/invite.ts b/packages/backend/src/server/api/endpoints/admin/invite.ts index b0450c9ba5..bc42bf792a 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite.ts @@ -1,7 +1,7 @@ import rndstr from 'rndstr'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistrationTicketRepository } from '@/models/index.js'; +import type { RegistrationTicketsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; @@ -37,7 +37,7 @@ export const paramDef = { export default class extends Endpoint { constructor( @Inject(DI.registrationTicketsRepository) - private registrationTicketsRepository: RegistrationTicketRepository, + private registrationTicketsRepository: RegistrationTicketsRepository, private idService: IdService, ) { -- cgit v1.2.3-freya From 3e81913b6a161cfc8405bda64b4a00e8e3b1fccd Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 25 Dec 2022 09:09:46 +0900 Subject: feat: introduce retention-rate aggregation --- CHANGELOG.md | 1 + .../1671924750884-RetentionAggregation.js | 13 ++++ .../1671926422832-RetentionAggregation2.js | 15 +++++ packages/backend/src/di-symbols.ts | 1 + packages/backend/src/models/RepositoryModule.ts | 10 ++- .../src/models/entities/RetentionAggregation.ts | 35 ++++++++++ packages/backend/src/models/index.ts | 3 + packages/backend/src/postgre.ts | 2 + packages/backend/src/queue/QueueProcessorModule.ts | 2 + .../backend/src/queue/QueueProcessorService.ts | 8 ++- .../src/queue/SystemQueueProcessorsService.ts | 5 +- .../AggregateRetentionProcessorService.ts | 75 ++++++++++++++++++++++ packages/backend/src/server/api/EndpointsModule.ts | 4 ++ packages/backend/src/server/api/endpoints.ts | 2 + .../backend/src/server/api/endpoints/retention.ts | 47 ++++++++++++++ 15 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 packages/backend/migration/1671924750884-RetentionAggregation.js create mode 100644 packages/backend/migration/1671926422832-RetentionAggregation2.js create mode 100644 packages/backend/src/models/entities/RetentionAggregation.ts create mode 100644 packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts create mode 100644 packages/backend/src/server/api/endpoints/retention.ts (limited to 'packages/backend/src/server/api/endpoints') diff --git a/CHANGELOG.md b/CHANGELOG.md index 4079840ae8..d58ff7691f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ You should also include the user name that made the change. - Push notification of Antenna note @tamaina - AVIF support @tamaina - Add Cloudflare Turnstile CAPTCHA support @CyberRex0 +- Introduce retention-rate aggregation @syuilo - Server: improve syslog performance @syuilo - Server: improve note scoring for featured notes @CyberRex0 - Server: delete outdated notifications regularly to improve db performance @syuilo diff --git a/packages/backend/migration/1671924750884-RetentionAggregation.js b/packages/backend/migration/1671924750884-RetentionAggregation.js new file mode 100644 index 0000000000..ed81a4b5e9 --- /dev/null +++ b/packages/backend/migration/1671924750884-RetentionAggregation.js @@ -0,0 +1,13 @@ +export class RetentionAggregation1671924750884 { + name = 'RetentionAggregation1671924750884' + + async up(queryRunner) { + await queryRunner.query(`CREATE TABLE "retention_aggregation" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userIds" character varying(32) array NOT NULL, "data" jsonb NOT NULL DEFAULT '{}', CONSTRAINT "PK_22aad3e8640b15fb3b90ee02d18" PRIMARY KEY ("id")); COMMENT ON COLUMN "retention_aggregation"."createdAt" IS 'The created date of the Note.'`); + await queryRunner.query(`CREATE INDEX "IDX_09f4e5b9e4a2f268d3e284e4b3" ON "retention_aggregation" ("createdAt") `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_09f4e5b9e4a2f268d3e284e4b3"`); + await queryRunner.query(`DROP TABLE "retention_aggregation"`); + } +} diff --git a/packages/backend/migration/1671926422832-RetentionAggregation2.js b/packages/backend/migration/1671926422832-RetentionAggregation2.js new file mode 100644 index 0000000000..725429e6ef --- /dev/null +++ b/packages/backend/migration/1671926422832-RetentionAggregation2.js @@ -0,0 +1,15 @@ +export class RetentionAggregation21671926422832 { + name = 'RetentionAggregation21671926422832' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "retention_aggregation" ADD "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`COMMENT ON COLUMN "retention_aggregation"."updatedAt" IS 'The updated date of the GalleryPost.'`); + await queryRunner.query(`ALTER TABLE "retention_aggregation" ADD "usersCount" integer NOT NULL`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "retention_aggregation" DROP COLUMN "usersCount"`); + await queryRunner.query(`COMMENT ON COLUMN "retention_aggregation"."updatedAt" IS 'The updated date of the GalleryPost.'`); + await queryRunner.query(`ALTER TABLE "retention_aggregation" DROP COLUMN "updatedAt"`); + } +} diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index cc775a9c8a..d2a361405f 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -68,5 +68,6 @@ export const DI = { webhooksRepository: Symbol('webhooksRepository'), adsRepository: Symbol('adsRepository'), passwordResetRequestsRepository: Symbol('passwordResetRequestsRepository'), + retentionAggregationsRepository: Symbol('retentionAggregationsRepository'), //#endregion }; diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 9edef10e87..e22f0517ca 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserGroup, UserGroupJoining, UserGroupInvitation, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, MessagingMessage, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelNotePining, RegistryItem, Webhook, Ad, PasswordResetRequest } from './index.js'; +import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserGroup, UserGroupJoining, UserGroupInvitation, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, MessagingMessage, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelNotePining, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation } from './index.js'; import type { DataSource } from 'typeorm'; import type { Provider } from '@nestjs/common'; @@ -382,6 +382,12 @@ const $passwordResetRequestsRepository: Provider = { inject: [DI.db], }; +const $retentionAggregationsRepository: Provider = { + provide: DI.retentionAggregationsRepository, + useFactory: (db: DataSource) => db.getRepository(RetentionAggregation), + inject: [DI.db], +}; + @Module({ imports: [ ], @@ -449,6 +455,7 @@ const $passwordResetRequestsRepository: Provider = { $webhooksRepository, $adsRepository, $passwordResetRequestsRepository, + $retentionAggregationsRepository, ], exports: [ $usersRepository, @@ -514,6 +521,7 @@ const $passwordResetRequestsRepository: Provider = { $webhooksRepository, $adsRepository, $passwordResetRequestsRepository, + $retentionAggregationsRepository, ], }) export class RepositoryModule {} diff --git a/packages/backend/src/models/entities/RetentionAggregation.ts b/packages/backend/src/models/entities/RetentionAggregation.ts new file mode 100644 index 0000000000..c79b762d71 --- /dev/null +++ b/packages/backend/src/models/entities/RetentionAggregation.ts @@ -0,0 +1,35 @@ +import { Entity, PrimaryColumn, Index, Column } from 'typeorm'; +import { id } from '../id.js'; +import type { User } from './User.js'; + +@Entity() +export class RetentionAggregation { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the Note.', + }) + public createdAt: Date; + + @Column('timestamp with time zone', { + comment: 'The updated date of the GalleryPost.', + }) + public updatedAt: Date; + + @Column({ + ...id(), + array: true, + }) + public userIds: User['id'][]; + + @Column('integer', { + }) + public usersCount: number; + + @Column('jsonb', { + default: {}, + }) + public data: Record; +} diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index 7fde3fbedb..ca7a7c9e56 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -61,6 +61,7 @@ import { UserPublickey } from '@/models/entities/UserPublickey.js'; import { UserSecurityKey } from '@/models/entities/UserSecurityKey.js'; import { Webhook } from '@/models/entities/Webhook.js'; import { Channel } from '@/models/entities/Channel.js'; +import { RetentionAggregation } from '@/models/entities/RetentionAggregation.js'; import type { Repository } from 'typeorm'; export { @@ -127,6 +128,7 @@ export { UserSecurityKey, Webhook, Channel, + RetentionAggregation, }; export type AbuseUserReportsRepository = Repository; @@ -192,3 +194,4 @@ export type UserPublickeysRepository = Repository; export type UserSecurityKeysRepository = Repository; export type WebhooksRepository = Repository; export type ChannelsRepository = Repository; +export type RetentionAggregationsRepository = Repository; diff --git a/packages/backend/src/postgre.ts b/packages/backend/src/postgre.ts index 829edbd7cf..4b4490a0c3 100644 --- a/packages/backend/src/postgre.ts +++ b/packages/backend/src/postgre.ts @@ -69,6 +69,7 @@ import { UserPublickey } from '@/models/entities/UserPublickey.js'; import { UserSecurityKey } from '@/models/entities/UserSecurityKey.js'; import { Webhook } from '@/models/entities/Webhook.js'; import { Channel } from '@/models/entities/Channel.js'; +import { RetentionAggregation } from '@/models/entities/RetentionAggregation.js'; import { Config } from '@/config.js'; import MisskeyLogger from '@/logger.js'; @@ -182,6 +183,7 @@ export const entities = [ UserPending, Webhook, UserIp, + RetentionAggregation, ...charts, ]; diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index f13dd3ef19..620296498c 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -29,6 +29,7 @@ import { ImportMutingProcessorService } from './processors/ImportMutingProcessor import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js'; import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js'; import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js'; +import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js'; @Module({ imports: [ @@ -63,6 +64,7 @@ import { TickChartsProcessorService } from './processors/TickChartsProcessorServ EndedPollNotificationProcessorService, DeliverProcessorService, InboxProcessorService, + AggregateRetentionProcessorService, QueueProcessorService, ], exports: [ diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 1d2feb5ef8..2123815c4c 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -4,6 +4,7 @@ import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import { QueueService } from '@/core/QueueService.js'; +import { bindThis } from '@/decorators.js'; import { getJobInfo } from './get-job-info.js'; import { SystemQueueProcessorsService } from './SystemQueueProcessorsService.js'; import { ObjectStorageQueueProcessorsService } from './ObjectStorageQueueProcessorsService.js'; @@ -13,7 +14,6 @@ import { EndedPollNotificationProcessorService } from './processors/EndedPollNot import { DeliverProcessorService } from './processors/DeliverProcessorService.js'; import { InboxProcessorService } from './processors/InboxProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class QueueProcessorService { @@ -133,6 +133,12 @@ export class QueueProcessorService { repeat: { cron: '0 0 * * *' }, removeOnComplete: true, }); + + this.queueService.systemQueue.add('aggregateRetention', { + }, { + repeat: { cron: '0 0 * * *' }, + removeOnComplete: true, + }); this.queueService.systemQueue.add('clean', { }, { diff --git a/packages/backend/src/queue/SystemQueueProcessorsService.ts b/packages/backend/src/queue/SystemQueueProcessorsService.ts index 1ce4152b2c..7fb0da4b10 100644 --- a/packages/backend/src/queue/SystemQueueProcessorsService.ts +++ b/packages/backend/src/queue/SystemQueueProcessorsService.ts @@ -1,13 +1,14 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; +import { bindThis } from '@/decorators.js'; import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js'; import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js'; import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js'; import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; +import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js'; import type Bull from 'bull'; -import { bindThis } from '@/decorators.js'; @Injectable() export class SystemQueueProcessorsService { @@ -18,6 +19,7 @@ export class SystemQueueProcessorsService { private tickChartsProcessorService: TickChartsProcessorService, private resyncChartsProcessorService: ResyncChartsProcessorService, private cleanChartsProcessorService: CleanChartsProcessorService, + private aggregateRetentionProcessorService: AggregateRetentionProcessorService, private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService, private cleanProcessorService: CleanProcessorService, ) { @@ -28,6 +30,7 @@ export class SystemQueueProcessorsService { q.process('tickCharts', (job, done) => this.tickChartsProcessorService.process(job, done)); q.process('resyncCharts', (job, done) => this.resyncChartsProcessorService.process(job, done)); q.process('cleanCharts', (job, done) => this.cleanChartsProcessorService.process(job, done)); + q.process('aggregateRetention', (job, done) => this.aggregateRetentionProcessorService.process(job, done)); q.process('checkExpiredMutings', (job, done) => this.checkExpiredMutingsProcessorService.process(job, done)); q.process('clean', (job, done) => this.cleanProcessorService.process(job, done)); } diff --git a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts new file mode 100644 index 0000000000..4650da76bb --- /dev/null +++ b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts @@ -0,0 +1,75 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { In, IsNull, MoreThan } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; +import type { RetentionAggregationsRepository, UsersRepository } from '@/models/index.js'; +import { deepClone } from '@/misc/clone.js'; +import { IdService } from '@/core/IdService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import type Bull from 'bull'; + +@Injectable() +export class AggregateRetentionProcessorService { + private logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.retentionAggregationsRepository) + private retentionAggregationsRepository: RetentionAggregationsRepository, + + private idService: IdService, + private queueLoggerService: QueueLoggerService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('aggregate-retention'); + } + + @bindThis + public async process(job: Bull.Job>, done: () => void): Promise { + this.logger.info('Aggregating retention...'); + + const now = new Date(); + const dateKey = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`; + + // 過去(だいたい)30日分のレコードを取得 + const pastRecords = await this.retentionAggregationsRepository.findBy({ + createdAt: MoreThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 31))), + }); + + // 今日登録したユーザーを全て取得 + const targetUsers = await this.usersRepository.findBy({ + host: IsNull(), + createdAt: MoreThan(new Date(Date.now() - (1000 * 60 * 60 * 24))), + }); + const targetUserIds = targetUsers.map(u => u.id); + + await this.retentionAggregationsRepository.insert({ + id: this.idService.genId(), + createdAt: now, + updatedAt: now, + userIds: targetUserIds, + usersCount: targetUserIds.length, + }); + + for (const record of pastRecords) { + const retention = record.userIds.filter(id => targetUserIds.includes(id)).length; + + const data = deepClone(record.data); + data[dateKey] = retention; + + this.retentionAggregationsRepository.update(record.id, { + updatedAt: now, + data, + }); + } + + this.logger.succ('Retention aggregated.'); + done(); + } +} diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 647f60317a..1f96647e7d 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -315,6 +315,7 @@ import * as ep___users_show from './endpoints/users/show.js'; import * as ep___users_stats from './endpoints/users/stats.js'; import * as ep___fetchRss from './endpoints/fetch-rss.js'; import * as ep___admin_driveCapOverride from './endpoints/admin/drive-capacity-override.js'; +import * as ep___retention from './endpoints/retention.js'; import { GetterService } from './GetterService.js'; import { ApiLoggerService } from './ApiLoggerService.js'; import type { Provider } from '@nestjs/common'; @@ -633,6 +634,7 @@ const $users_show: Provider = { provide: 'ep:users/show', useClass: ep___users_s const $users_stats: Provider = { provide: 'ep:users/stats', useClass: ep___users_stats.default }; const $admin_driveCapOverride: Provider = { provide: 'ep:admin/drive-capacity-override', useClass: ep___admin_driveCapOverride.default }; const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.default }; +const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention.default }; @Module({ imports: [ @@ -955,6 +957,7 @@ const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.d $users_stats, $admin_driveCapOverride, $fetchRss, + $retention, ], exports: [ $admin_meta, @@ -1269,6 +1272,7 @@ const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.d $users_stats, $admin_driveCapOverride, $fetchRss, + $retention, ], }) export class EndpointsModule {} diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 6d10cb8f35..e8dc5abfa1 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -314,6 +314,7 @@ import * as ep___users_show from './endpoints/users/show.js'; import * as ep___users_stats from './endpoints/users/stats.js'; import * as ep___fetchRss from './endpoints/fetch-rss.js'; import * as ep___admin_driveCapOverride from './endpoints/admin/drive-capacity-override.js'; +import * as ep___retention from './endpoints/retention.js'; const eps = [ ['admin/meta', ep___admin_meta], @@ -630,6 +631,7 @@ const eps = [ ['users/stats', ep___users_stats], ['admin/drive-capacity-override', ep___admin_driveCapOverride], ['fetch-rss', ep___fetchRss], + ['retention', ep___retention], ]; export interface IEndpointMeta { diff --git a/packages/backend/src/server/api/endpoints/retention.ts b/packages/backend/src/server/api/endpoints/retention.ts new file mode 100644 index 0000000000..e3c2249cdd --- /dev/null +++ b/packages/backend/src/server/api/endpoints/retention.ts @@ -0,0 +1,47 @@ +import { IsNull } from 'typeorm'; +import { Inject, Injectable } from '@nestjs/common'; +import type { RetentionAggregationsRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; + +export const meta = { + tags: ['users'], + + requireCredential: false, + + res: { + }, + + allowGet: true, + cacheSec: 60 * 60, +} as const; + +export const paramDef = { + type: 'object', + properties: {}, + required: [], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.retentionAggregationsRepository) + private retentionAggregationsRepository: RetentionAggregationsRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const records = await this.retentionAggregationsRepository.find({ + order: { + id: 'DESC', + }, + take: 30, + }); + + return records.map(record => ({ + createdAt: record.createdAt.toISOString(), + users: record.usersCount, + data: record.data, + })); + }); + } +} -- cgit v1.2.3-freya From f44a7e25361b1cee202c744ced4f988e658209b8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 25 Dec 2022 14:22:23 +0900 Subject: refactor --- packages/backend/src/const.ts | 16 ++++++++++++++++ .../src/core/activitypub/models/ApImageService.ts | 4 ++-- packages/backend/src/misc/hard-limits.ts | 14 -------------- packages/backend/src/models/entities/Note.ts | 1 + .../src/server/api/endpoints/admin/update-meta.ts | 2 +- .../src/server/api/endpoints/drive/files/create.ts | 2 +- .../src/server/api/endpoints/drive/files/update.ts | 2 +- .../server/api/endpoints/drive/files/upload-from-url.ts | 2 +- packages/backend/src/server/api/endpoints/meta.ts | 3 +-- 9 files changed, 24 insertions(+), 22 deletions(-) delete mode 100644 packages/backend/src/misc/hard-limits.ts (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts index e2203b6b45..6c7f214214 100644 --- a/packages/backend/src/const.ts +++ b/packages/backend/src/const.ts @@ -3,6 +3,22 @@ export const MAX_NOTE_TEXT_LENGTH = 3000; export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days +//#region hard limits +// If you change DB_* values, you must also change the DB schema. + +/** + * Maximum note text length that can be stored in DB. + * Surrogate pairs count as one + */ +export const DB_MAX_NOTE_TEXT_LENGTH = 8192; + +/** + * Maximum image description length that can be stored in DB. + * Surrogate pairs count as one + */ +export const DB_MAX_IMAGE_COMMENT_LENGTH = 512; +//#endregion + // ブラウザで直接表示することを許可するファイルの種類のリスト // ここに含まれないものは application/octet-stream としてレスポンスされる // SVGはXSSを生むので許可しない diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index 58fcc8cb53..d01817b0de 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -6,12 +6,12 @@ import type { CacheableRemoteUser } from '@/models/entities/User.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import { MetaService } from '@/core/MetaService.js'; import { truncate } from '@/misc/truncate.js'; -import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js'; import { DriveService } from '@/core/DriveService.js'; import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; import { ApResolverService } from '../ApResolverService.js'; import { ApLoggerService } from '../ApLoggerService.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class ApImageService { diff --git a/packages/backend/src/misc/hard-limits.ts b/packages/backend/src/misc/hard-limits.ts deleted file mode 100644 index 1039f7335a..0000000000 --- a/packages/backend/src/misc/hard-limits.ts +++ /dev/null @@ -1,14 +0,0 @@ - -// If you change DB_* values, you must also change the DB schema. - -/** - * Maximum note text length that can be stored in DB. - * Surrogate pairs count as one - */ -export const DB_MAX_NOTE_TEXT_LENGTH = 8192; - -/** - * Maximum image description length that can be stored in DB. - * Surrogate pairs count as one - */ -export const DB_MAX_IMAGE_COMMENT_LENGTH = 512; diff --git a/packages/backend/src/models/entities/Note.ts b/packages/backend/src/models/entities/Note.ts index f1a94bd9ce..82d042f0ce 100644 --- a/packages/backend/src/models/entities/Note.ts +++ b/packages/backend/src/models/entities/Note.ts @@ -53,6 +53,7 @@ export class Note { }) public threadId: string | null; + // TODO: varcharにしたい @Column('text', { nullable: true, }) 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 2a19b1df5f..795b8460f3 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import type { Meta } from '@/models/entities/Meta.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; -import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; +import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; 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 3f4485fc8b..6abbab0270 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import type { DriveFilesRepository } from '@/models/index.js'; -import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index 703f92d8c6..9e2c767277 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js'; -import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index 19ab03a337..a17bca5aba 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,7 +1,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import type { DriveFilesRepository } from '@/models/index.js'; -import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index f2e6e6aea8..05da011979 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -1,8 +1,7 @@ import { IsNull, MoreThan } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import type { AdsRepository, EmojisRepository, UsersRepository } from '@/models/index.js'; -import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; -import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; +import { MAX_NOTE_TEXT_LENGTH, DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; -- cgit v1.2.3-freya From c801434d11a546f5d5375c1798cf9bb981d3f6b2 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 26 Dec 2022 08:50:17 +0900 Subject: fix(server): アンテナの作成数上限を追加 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + .../backend/src/server/api/endpoints/antennas/create.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/CHANGELOG.md b/CHANGELOG.md index 13c2876a82..af3fc75c06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ You should also include the user name that made the change. - Server: Fix peers API returning suspended instances @ineffyble - Server: trim long text of note from ap @syuilo - Server: Ap inboxの最大ペイロードサイズを64kbに制限 @syuilo +- Server: アンテナの作成数上限を追加 @syuilo - Client: case insensitive emoji search @saschanaz - Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina - Client: use proxied image for instance icon @syuilo diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index fe24a10300..2378660ec8 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -26,6 +26,12 @@ export const meta = { code: 'NO_SUCH_USER_GROUP', id: 'aa3c0b9a-8cae-47c0-92ac-202ce5906682', }, + + tooManyAntennas: { + message: 'You cannot create antenna any more.', + code: 'TOO_MANY_ANTENNAS', + id: 'faf47050-e8b5-438c-913c-db2b1576fde4', + }, }, res: { @@ -81,6 +87,13 @@ export default class extends Endpoint { private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { + const currentAntennasCount = await this.antennasRepository.countBy({ + userId: me.id, + }); + if (currentAntennasCount > 5) { + throw new ApiError(meta.errors.tooManyAntennas); + } + let userList; let userGroupJoining; -- cgit v1.2.3-freya From dd4c411d228626126625019c9c7f9f60ea15e562 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 26 Dec 2022 09:22:10 +0900 Subject: ダッシュボードにモデレーター一覧を表示 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/server/api/endpoints/admin/show-users.ts | 4 +- .../client/src/pages/admin/overview.moderators.vue | 52 ++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 packages/client/src/pages/admin/overview.moderators.vue (limited to 'packages/backend/src/server/api/endpoints') 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 5471ef9b4c..33e1be8041 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -26,7 +26,7 @@ export const paramDef = { properties: { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, offset: { type: 'integer', default: 0 }, - sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] }, + sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt', '+lastActiveDate', '-lastActiveDate'] }, state: { type: 'string', enum: ['all', 'alive', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: 'all' }, origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' }, username: { type: 'string', nullable: true, default: null }, @@ -82,6 +82,8 @@ export default class extends Endpoint { 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; + case '+lastActiveDate': query.orderBy('user.lastActiveDate', 'DESC', 'NULLS LAST'); break; + case '-lastActiveDate': query.orderBy('user.lastActiveDate', 'ASC', 'NULLS FIRST'); break; default: query.orderBy('user.id', 'ASC'); break; } diff --git a/packages/client/src/pages/admin/overview.moderators.vue b/packages/client/src/pages/admin/overview.moderators.vue new file mode 100644 index 0000000000..b27477e9f9 --- /dev/null +++ b/packages/client/src/pages/admin/overview.moderators.vue @@ -0,0 +1,52 @@ + + + + + -- cgit v1.2.3-freya From 912791b3ab6a7cb6cbfda2aa9e15d95115770a5e Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 29 Dec 2022 10:14:44 +0900 Subject: refactor: 絵文字URLを引き回すのをやめる (#9423) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/core/entities/EmojiEntityService.ts | 8 ++++-- packages/backend/src/models/schema/emoji.ts | 2 +- packages/backend/src/server/api/endpoints/meta.ts | 3 +- .../backend/src/server/web/ClientServerService.ts | 32 +++++++++++++++++++++- packages/frontend/src/components/MkNote.vue | 6 ++-- .../frontend/src/components/MkNoteDetailed.vue | 6 ++-- packages/frontend/src/components/MkNoteSimple.vue | 2 +- packages/frontend/src/components/MkNoteSub.vue | 2 +- .../frontend/src/components/MkNotification.vue | 14 +++++----- packages/frontend/src/components/MkPoll.vue | 2 +- .../frontend/src/components/MkReactionIcon.vue | 3 +- .../frontend/src/components/MkReactionTooltip.vue | 3 +- .../src/components/MkReactionsViewer.details.vue | 3 +- .../src/components/MkReactionsViewer.reaction.vue | 2 +- .../frontend/src/components/MkSubNoteContent.vue | 2 +- packages/frontend/src/components/MkUserInfo.vue | 2 +- packages/frontend/src/components/MkUserPreview.vue | 2 +- .../frontend/src/components/global/MkEmoji.vue | 16 ++++------- .../global/MkMisskeyFlavoredMarkdown.vue | 3 +- .../frontend/src/components/global/MkUserName.vue | 2 +- packages/frontend/src/components/mfm.ts | 5 ---- packages/frontend/src/instance.ts | 1 + packages/frontend/src/pages/about-misskey.vue | 2 +- packages/frontend/src/pages/follow-requests.vue | 2 +- packages/frontend/src/pages/user/home.vue | 6 ++-- packages/frontend/src/pages/user/reactions.vue | 2 +- packages/frontend/src/pages/welcome.timeline.vue | 2 +- .../src/ui/_common_/statusbar-user-list.vue | 2 +- 28 files changed, 79 insertions(+), 58 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts index 08d83a2753..f9419c5398 100644 --- a/packages/backend/src/core/entities/EmojiEntityService.ts +++ b/packages/backend/src/core/entities/EmojiEntityService.ts @@ -6,8 +6,8 @@ import type { Packed } from '@/misc/schema.js'; import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { Emoji } from '@/models/entities/Emoji.js'; -import { UserEntityService } from './UserEntityService.js'; import { bindThis } from '@/decorators.js'; +import { UserEntityService } from './UserEntityService.js'; @Injectable() export class EmojiEntityService { @@ -22,6 +22,7 @@ export class EmojiEntityService { @bindThis public async pack( src: Emoji['id'] | Emoji, + opts: { omitUrl?: boolean } = {}, ): Promise> { const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src }); @@ -32,15 +33,16 @@ export class EmojiEntityService { category: emoji.category, host: emoji.host, // ?? emoji.originalUrl してるのは後方互換性のため - url: emoji.publicUrl ?? emoji.originalUrl, + url: opts.omitUrl ? undefined : (emoji.publicUrl ?? emoji.originalUrl), }; } @bindThis public packMany( emojis: any[], + opts: { omitUrl?: boolean } = {}, ) { - return Promise.all(emojis.map(x => this.pack(x))); + return Promise.all(emojis.map(x => this.pack(x, opts))); } } diff --git a/packages/backend/src/models/schema/emoji.ts b/packages/backend/src/models/schema/emoji.ts index e97fdd5ef6..9a52609b68 100644 --- a/packages/backend/src/models/schema/emoji.ts +++ b/packages/backend/src/models/schema/emoji.ts @@ -31,7 +31,7 @@ export const packedEmojiSchema = { }, url: { type: 'string', - optional: false, nullable: false, + optional: true, nullable: false, }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 05da011979..66c9f0620a 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -309,6 +309,7 @@ export const paramDef = { type: 'object', properties: { detail: { type: 'boolean', default: true }, + omitEmojiUrl: { type: 'boolean', default: false }, }, required: [], } as const; @@ -390,7 +391,7 @@ export default class extends Endpoint { backgroundImageUrl: instance.backgroundImageUrl, logoImageUrl: instance.logoImageUrl, maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため - emojis: await this.emojiEntityService.packMany(emojis), + emojis: await this.emojiEntityService.packMany(emojis, { omitUrl: ps.omitEmojiUrl }), defaultLightTheme: instance.defaultLightTheme, defaultDarkTheme: instance.defaultDarkTheme, ads: ads.map(ad => ({ diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 2b3f0ce0f5..af1ff91ac9 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -26,7 +26,7 @@ import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; -import type { ChannelsRepository, ClipsRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { ChannelsRepository, ClipsRepository, EmojisRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { deepClone } from '@/misc/clone.js'; import { bindThis } from '@/decorators.js'; import manifest from './manifest.json' assert { type: 'json' }; @@ -70,6 +70,9 @@ export class ClientServerService { @Inject(DI.pagesRepository) private pagesRepository: PagesRepository, + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, private pageEntityService: PageEntityService, @@ -217,6 +220,33 @@ export class ClientServerService { return reply.sendFile('/apple-touch-icon.png', staticAssets); }); + fastify.get<{ Params: { path: string } }>('/emoji/:path(.*)', async (request, reply) => { + const path = request.params.path; + + if (!path.match(/^[a-zA-Z0-9\-_@\.]+?\.webp$/)) { + reply.code(404); + return; + } + + const name = path.split('@')[0].replace('.webp', ''); + const host = path.split('@')[1]?.replace('.webp', ''); + + const emoji = await this.emojisRepository.findOneBy({ + host: host == null ? IsNull() : host, + name: name, + }); + + if (emoji == null) { + reply.code(404); + return; + } + + reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); + + // ?? emoji.originalUrl してるのは後方互換性のため + return await reply.redirect(301, emoji.publicUrl ?? emoji.originalUrl); + }); + fastify.get<{ Params: { path: string } }>('/fluent-emoji/:path(.*)', async (request, reply) => { const path = request.params.path; diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 65e3161c7f..e2e542f514 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -37,20 +37,20 @@

- +

({{ i18n.ts.private }}) - + RN:
{{ $t('translatedFrom', { x: translation.sourceLang }) }}: - +
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index c7b7f49b20..c3c49420c5 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -48,20 +48,20 @@

- +

({{ i18n.ts.private }}) - + RN:
{{ $t('translatedFrom', { x: translation.sourceLang }) }}: - +
diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue index 354de1a614..fc6bf40e9f 100644 --- a/packages/frontend/src/components/MkNoteSimple.vue +++ b/packages/frontend/src/components/MkNoteSimple.vue @@ -5,7 +5,7 @@

- +

diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue index 0dbaae59e4..efaf35f5eb 100644 --- a/packages/frontend/src/components/MkNoteSub.vue +++ b/packages/frontend/src/components/MkNoteSub.vue @@ -6,7 +6,7 @@

- +

diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index cb2f384553..c8b197a850 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -34,31 +34,31 @@
- + - + - + - + - + - + - + {{ i18n.ts.youGotNewFollower }}
diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index a1b927e42a..f2e3a8ee4d 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -5,7 +5,7 @@
- + ({{ $t('_poll.votesCount', { n: choice.votes }) }}) diff --git a/packages/frontend/src/components/MkReactionIcon.vue b/packages/frontend/src/components/MkReactionIcon.vue index 5638c9a816..6e9d2b1a6c 100644 --- a/packages/frontend/src/components/MkReactionIcon.vue +++ b/packages/frontend/src/components/MkReactionIcon.vue @@ -1,5 +1,5 @@ diff --git a/packages/frontend/src/components/MkReactionTooltip.vue b/packages/frontend/src/components/MkReactionTooltip.vue index 310d5954fc..34ebc4da2d 100644 --- a/packages/frontend/src/components/MkReactionTooltip.vue +++ b/packages/frontend/src/components/MkReactionTooltip.vue @@ -1,7 +1,7 @@ diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index 210923be46..a2d1d8ae48 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -4,7 +4,7 @@ ({{ i18n.ts.private }}) ({{ i18n.ts.deleted }}) - + RN: ...
diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue index 036cbea304..6071fef66e 100644 --- a/packages/frontend/src/components/MkUserInfo.vue +++ b/packages/frontend/src/components/MkUserInfo.vue @@ -9,7 +9,7 @@ {{ $ts.followsYou }}
- +
{{ i18n.ts.noAccountDescription }}
diff --git a/packages/frontend/src/components/MkUserPreview.vue b/packages/frontend/src/components/MkUserPreview.vue index 4de2e8baa2..d367d7b5c2 100644 --- a/packages/frontend/src/components/MkUserPreview.vue +++ b/packages/frontend/src/components/MkUserPreview.vue @@ -11,7 +11,7 @@

- +
diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index ce1299a39f..9a8418758d 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -1,5 +1,5 @@ - diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue new file mode 100644 index 0000000000..bc4828f416 --- /dev/null +++ b/packages/frontend/src/pages/flash/flash-index.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue new file mode 100644 index 0000000000..9495206c54 --- /dev/null +++ b/packages/frontend/src/pages/flash/flash.vue @@ -0,0 +1,291 @@ + + + + + + + diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue index e01dae2cd9..11ed8f9f44 100644 --- a/packages/frontend/src/pages/page.vue +++ b/packages/frontend/src/pages/page.vue @@ -18,8 +18,8 @@
@@ -207,20 +207,6 @@ definePageMetadata(computed(() => page ? { padding: 16px 0 0 0; border-top: solid 0.5px var(--divider); - > .like { - > .button { - --accent: rgb(241 97 132); - --X8: rgb(241 92 128); - --buttonBg: rgb(216 71 106 / 5%); - --buttonHoverBg: rgb(216 71 106 / 10%); - color: #ff002f; - - ::v-deep(.count) { - margin-left: 0.5em; - } - } - } - > .other { margin-left: auto; diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue index 9db17efc03..7d097fbaaa 100644 --- a/packages/frontend/src/pages/scratchpad.vue +++ b/packages/frontend/src/pages/scratchpad.vue @@ -1,25 +1,34 @@ - diff --git a/packages/frontend/src/widgets/index.ts b/packages/frontend/src/widgets/index.ts index 39826f13c8..3966649da4 100644 --- a/packages/frontend/src/widgets/index.ts +++ b/packages/frontend/src/widgets/index.ts @@ -22,6 +22,7 @@ export default function(app: App) { app.component('MkwInstanceCloud', defineAsyncComponent(() => import('./instance-cloud.vue'))); app.component('MkwButton', defineAsyncComponent(() => import('./button.vue'))); app.component('MkwAiscript', defineAsyncComponent(() => import('./aiscript.vue'))); + app.component('MkwAiscriptApp', defineAsyncComponent(() => import('./aiscript-app.vue'))); app.component('MkwAichan', defineAsyncComponent(() => import('./aichan.vue'))); app.component('MkwUserList', defineAsyncComponent(() => import('./user-list.vue'))); } @@ -48,6 +49,7 @@ export const widgets = [ 'jobQueue', 'button', 'aiscript', + 'aiscriptApp', 'aichan', 'userList', ]; -- cgit v1.2.3-freya From 6f9aa94e3ae59a5ab8b38a6cb9c179f96033efa8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 5 Jan 2023 16:38:12 +0900 Subject: fix instance sort --- packages/backend/src/server/api/endpoints/federation/instances.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 180887285a..5e2f204661 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -64,8 +64,8 @@ export default class extends Endpoint { 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 '+latestRequestReceivedAt': query.orderBy('instance.latestRequestReceivedAt', 'DESC'); break; - case '-latestRequestReceivedAt': query.orderBy('instance.latestRequestReceivedAt', 'ASC'); break; + case '+latestRequestReceivedAt': query.orderBy('instance.latestRequestReceivedAt', 'DESC', 'NULLS LAST'); break; + case '-latestRequestReceivedAt': query.orderBy('instance.latestRequestReceivedAt', 'ASC', 'NULLS FIRST'); break; default: query.orderBy('instance.id', 'DESC'); break; } -- cgit v1.2.3-freya From bcc3380cfca3ea1faf2a72b30d18f465bdc27634 Mon Sep 17 00:00:00 2001 From: CyberRex Date: Fri, 6 Jan 2023 08:05:02 +0900 Subject: fix(backend): Set correct access control of admin/drive/files (#9472) --- packages/backend/src/server/api/endpoints/admin/drive/files.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src/server/api/endpoints') 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 53a37cb691..8a4498d5fa 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -8,7 +8,7 @@ import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.j export const meta = { tags: ['admin'], - requireCredential: false, + requireCredential: true, requireModerator: true, res: { -- cgit v1.2.3-freya From 2e2ed1385fbae6c102156a08fce2defb642e52c5 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 8 Jan 2023 10:54:45 +0900 Subject: delete pollVote notification --- CHANGELOG.md | 1 + locales/ja-JP.yml | 2 - packages/backend/src/core/PollService.ts | 7 --- .../src/core/entities/NotificationEntityService.ts | 2 +- .../backend/src/models/entities/Notification.ts | 10 ++-- .../src/server/api/endpoints/notes/polls/vote.ts | 7 --- .../frontend/src/components/MkNotification.vue | 12 ---- packages/sw/src/scripts/create-notification.ts | 68 ++++++++++------------ 8 files changed, 37 insertions(+), 72 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/CHANGELOG.md b/CHANGELOG.md index 78caef3252..e437881129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ You should also include the user name that made the change. #### For users - ノートのウォッチ機能が削除されました +- アンケートに投票された際に通知が作成されなくなりました - 新たに動的なPagesを作ることはできなくなりました - 代わりにAiScriptを用いてより柔軟に動的なコンテンツを作成できるMisskey Play機能が実装されています。 - AiScriptが0.12.2にアップデートされました diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index b49d872a0b..3445e58356 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1550,7 +1550,6 @@ _notification: youGotReply: "{name}からのリプライ" youGotQuote: "{name}による引用" youRenoted: "{name}がRenoteしました" - youGotPoll: "{name}が投票しました" youGotMessagingMessageFromUser: "{name}からのチャットがあります" youGotMessagingMessageFromGroup: "{name}のチャットがあります" youWereFollowed: "フォローされました" @@ -1569,7 +1568,6 @@ _notification: renote: "Renote" quote: "引用" reaction: "リアクション" - pollVote: "アンケートに投票された" pollEnded: "アンケートが終了" receiveFollowRequest: "フォロー申請を受け取った" followRequestAccepted: "フォローが受理された" diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index 3cc9b0cc9b..abc598ab76 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -92,13 +92,6 @@ export class PollService { choice: choice, userId: user.id, }); - - // Notify - this.createNotificationService.createNotification(note.userId, 'pollVote', { - notifierId: user.id, - noteId: note.id, - choice: choice, - }); } @bindThis diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index 208d653877..a1c2c9cffb 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -98,7 +98,7 @@ export class NotificationEntityService implements OnModuleInit { }), reaction: notification.reaction, } : {}), - ...(notification.type === 'pollVote' ? { + ...(notification.type === 'pollVote' ? { // TODO: そのうち消す note: this.noteEntityService.pack(notification.note ?? notification.noteId!, { id: notification.notifieeId }, { detail: true, _hint_: options._hintForEachNotes_, diff --git a/packages/backend/src/models/entities/Notification.ts b/packages/backend/src/models/entities/Notification.ts index 53a7dda43a..6679cdb809 100644 --- a/packages/backend/src/models/entities/Notification.ts +++ b/packages/backend/src/models/entities/Notification.ts @@ -55,11 +55,11 @@ export class Notification { * 通知の種類。 * follow - フォローされた * mention - 投稿で自分が言及された - * reply - (自分または自分がWatchしている)投稿が返信された - * renote - (自分または自分がWatchしている)投稿がRenoteされた - * quote - (自分または自分がWatchしている)投稿が引用Renoteされた - * reaction - (自分または自分がWatchしている)投稿にリアクションされた - * pollVote - (自分または自分がWatchしている)投稿のアンケートに投票された + * reply - 投稿に返信された + * renote - 投稿がRenoteされた + * quote - 投稿が引用Renoteされた + * reaction - 投稿にリアクションされた + * pollVote - 投稿のアンケートに投票された (廃止) * pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した * receiveFollowRequest - フォローリクエストされた * followRequestAccepted - 自分の送ったフォローリクエストが承認された 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 793d7c5408..d583dfb936 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -162,13 +162,6 @@ export default class extends Endpoint { userId: me.id, }); - // Notify - this.createNotificationService.createNotification(note.userId, 'pollVote', { - notifierId: me.id, - noteId: note.id, - choice: ps.choice, - }); - // リモート投票の場合リプライ送信 if (note.userHost != null) { const pollOwner = await this.usersRepository.findOneByOrFail({ id: note.userId }) as IRemoteUser; diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index a21a9e12a1..2b9f9a2bb8 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -13,7 +13,6 @@ - - - - - - @@ -239,12 +233,6 @@ useTooltip(reactionRef, (showing) => { pointer-events: none; } - &.pollVote { - padding: 3px; - background: #88a6b7; - pointer-events: none; - } - &.pollEnded { padding: 3px; background: #88a6b7; diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts index a324851fe2..4b006e0f5a 100644 --- a/packages/sw/src/scripts/create-notification.ts +++ b/packages/sw/src/scripts/create-notification.ts @@ -51,8 +51,8 @@ async function composeNotification(data actions: userDetail.isFollowing ? [] : [ { action: 'follow', - title: t('_notification._actions.followBack') - } + title: t('_notification._actions.followBack'), + }, ], }]; } @@ -66,8 +66,8 @@ async function composeNotification(data actions: [ { action: 'reply', - title: t('_notification._actions.reply') - } + title: t('_notification._actions.reply'), + }, ], }]; @@ -80,8 +80,8 @@ async function composeNotification(data actions: [ { action: 'reply', - title: t('_notification._actions.reply') - } + title: t('_notification._actions.reply'), + }, ], }]; @@ -94,8 +94,8 @@ async function composeNotification(data actions: [ { action: 'showUser', - title: getUserName(data.body.user) - } + title: getUserName(data.body.user), + }, ], }]; @@ -108,14 +108,14 @@ async function composeNotification(data actions: [ { action: 'reply', - title: t('_notification._actions.reply') + title: t('_notification._actions.reply'), }, ...((data.body.note.visibility === 'public' || data.body.note.visibility === 'home') ? [ - { - action: 'renote', - title: t('_notification._actions.renote') - } - ] : []) + { + action: 'renote', + title: t('_notification._actions.renote'), + }, + ] : []), ], }]; @@ -141,7 +141,7 @@ async function composeNotification(data const dummy = `${encodeURIComponent(`${u.host}${u.pathname}`)}.png`; badge = `${origin}/proxy/${dummy}?${url.query({ url: u.href, - badge: '1' + badge: '1', })}`; } } @@ -162,20 +162,12 @@ async function composeNotification(data actions: [ { action: 'showUser', - title: getUserName(data.body.user) - } + title: getUserName(data.body.user), + }, ], }]; } - case 'pollVote': - return [t('_notification.youGotPoll', { name: getUserName(data.body.user) }), { - body: data.body.note.text || '', - icon: data.body.user.avatarUrl, - badge: iconUrl('poll-h'), - data, - }]; - case 'pollEnded': return [t('_notification.pollEnded'), { body: data.body.note.text || '', @@ -192,12 +184,12 @@ async function composeNotification(data actions: [ { action: 'accept', - title: t('accept') + title: t('accept'), }, { action: 'reject', - title: t('reject') - } + title: t('reject'), + }, ], }]; @@ -217,21 +209,21 @@ async function composeNotification(data actions: [ { action: 'accept', - title: t('accept') + title: t('accept'), }, { action: 'reject', - title: t('reject') - } + title: t('reject'), + }, ], }]; case 'app': - return [data.body.header || data.body.body, { - body: data.body.header && data.body.body, - icon: data.body.icon, - data - }]; + return [data.body.header || data.body.body, { + body: data.body.header && data.body.body, + icon: data.body.icon, + data, + }]; default: return null; @@ -279,7 +271,7 @@ export async function createEmptyNotification() { silent: true, badge: iconUrl('null'), tag: 'read_notification', - } + }, ); res(); @@ -288,7 +280,7 @@ export async function createEmptyNotification() { for (const n of [ ...(await self.registration.getNotifications({ tag: 'user_visible_auto_notification' })), - ...(await self.registration.getNotifications({ tag: 'read_notification' })) + ...(await self.registration.getNotifications({ tag: 'read_notification' })), ] ) { n.close(); -- cgit v1.2.3-freya From 10e526ba5682fef9488d1d38ba5dfcda38619673 Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Sun, 8 Jan 2023 20:32:17 +0900 Subject: fix: Escape SQL LIKE (#9493) * SQL LIKE escape * CHANGELOG --- CHANGELOG.md | 1 + packages/backend/src/misc/sql-like-escape.ts | 3 +++ .../src/server/api/endpoints/admin/emoji/list-remote.ts | 3 ++- packages/backend/src/server/api/endpoints/admin/emoji/list.ts | 3 ++- packages/backend/src/server/api/endpoints/admin/show-users.ts | 3 ++- .../backend/src/server/api/endpoints/federation/instances.ts | 3 ++- packages/backend/src/server/api/endpoints/hashtags/search.ts | 3 ++- packages/backend/src/server/api/endpoints/notes/search.ts | 3 ++- .../server/api/endpoints/users/search-by-username-and-host.ts | 11 ++++++----- packages/backend/src/server/api/endpoints/users/search.ts | 9 +++++---- 10 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 packages/backend/src/misc/sql-like-escape.ts (limited to 'packages/backend/src/server/api/endpoints') diff --git a/CHANGELOG.md b/CHANGELOG.md index a5db9bc963..d19545c7d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,7 @@ You should also include the user name that made the change. - Server: アンテナの作成数上限を追加 @syuilo - Server: pages/likeのエラーIDが重複しているのを修正 @syuilo - Server: pages/updateのパラメータによってはsummaryの値が更新されないのを修正 @syuilo +- Server: Escape SQL LIKE @mei23 - Client: case insensitive emoji search @saschanaz - Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina - Client: use proxied image for instance icon @syuilo diff --git a/packages/backend/src/misc/sql-like-escape.ts b/packages/backend/src/misc/sql-like-escape.ts new file mode 100644 index 0000000000..8470dca3de --- /dev/null +++ b/packages/backend/src/misc/sql-like-escape.ts @@ -0,0 +1,3 @@ +export function sqlLikeEscape(s: string) { + return s.replace(/([%_])/g, '\\$1'); +} 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 c03d27878c..ed60efd7b4 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 @@ -5,6 +5,7 @@ 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'; +import { sqlLikeEscape } from '@/misc/sql-like-escape'; export const meta = { tags: ['admin'], @@ -92,7 +93,7 @@ export default class extends Endpoint { } if (ps.query) { - q.andWhere('emoji.name like :query', { query: '%' + ps.query + '%' }); + q.andWhere('emoji.name like :query', { query: '%' + sqlLikeEscape(ps.query) + '%' }); } const emojis = await q 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 271b142126..f357e45a52 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -5,6 +5,7 @@ import type { Emoji } from '@/models/entities/Emoji.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +//import { sqlLikeEscape } from '@/misc/sql-like-escape'; export const meta = { tags: ['admin'], @@ -82,7 +83,7 @@ export default class extends Endpoint { let emojis: Emoji[]; if (ps.query) { - //q.andWhere('emoji.name ILIKE :q', { q: `%${ps.query}%` }); + //q.andWhere('emoji.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }); //const emojis = await q.take(ps.limit).getMany(); emojis = await q.getMany(); 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 33e1be8041..722e284dde 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -3,6 +3,7 @@ import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { sqlLikeEscape } from '@/misc/sql-like-escape'; export const meta = { tags: ['admin'], @@ -68,7 +69,7 @@ export default class extends Endpoint { } if (ps.username) { - query.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' }); + query.andWhere('user.usernameLower like :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' }); } if (ps.hostname) { diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 5e2f204661..726979309f 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -4,6 +4,7 @@ import type { InstancesRepository } from '@/models/index.js'; import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; import { MetaService } from '@/core/MetaService.js'; import { DI } from '@/di-symbols.js'; +import { sqlLikeEscape } from '@/misc/sql-like-escape'; export const meta = { tags: ['federation'], @@ -120,7 +121,7 @@ export default class extends Endpoint { } if (ps.host) { - query.andWhere('instance.host like :host', { host: '%' + ps.host.toLowerCase() + '%' }); + query.andWhere('instance.host like :host', { host: '%' + sqlLikeEscape(ps.host.toLowerCase()) + '%' }); } const instances = await query.take(ps.limit).skip(ps.offset).getMany(); diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts index 7f787ea38f..6c56ef5da2 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/search.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { HashtagsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; +import { sqlLikeEscape } from '@/misc/sql-like-escape'; export const meta = { tags: ['hashtags'], @@ -37,7 +38,7 @@ export default class extends Endpoint { ) { super(meta, paramDef, async (ps, me) => { const hashtags = await this.hashtagsRepository.createQueryBuilder('tag') - .where('tag.name like :q', { q: ps.query.toLowerCase() + '%' }) + .where('tag.name like :q', { q: sqlLikeEscape(ps.query.toLowerCase()) + '%' }) .orderBy('tag.count', 'DESC') .groupBy('tag.id') .take(ps.limit) diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index 27b477e141..02701ffe1e 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -6,6 +6,7 @@ import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; +import { sqlLikeEscape } from '@/misc/sql-like-escape'; export const meta = { tags: ['notes'], @@ -70,7 +71,7 @@ export default class extends Endpoint { } query - .andWhere('note.text ILIKE :q', { q: `%${ps.query}%` }) + .andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }) .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('user.avatar', 'avatar') .leftJoinAndSelect('user.banner', 'banner') 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 f13df3ee9d..029b1e91c3 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 @@ -6,6 +6,7 @@ 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'; +import { sqlLikeEscape } from '@/misc/sql-like-escape'; export const meta = { tags: ['users'], @@ -59,10 +60,10 @@ export default class extends Endpoint { if (ps.host) { const q = this.usersRepository.createQueryBuilder('user') .where('user.isSuspended = FALSE') - .andWhere('user.host LIKE :host', { host: ps.host.toLowerCase() + '%' }); + .andWhere('user.host LIKE :host', { host: sqlLikeEscape(ps.host.toLowerCase()) + '%' }); if (ps.username) { - q.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }); + q.andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' }); } q.andWhere('user.updatedAt IS NOT NULL'); @@ -83,7 +84,7 @@ export default class extends Endpoint { .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('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' }) .andWhere(new Brackets(qb => { qb .where('user.updatedAt IS NULL') .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); @@ -101,7 +102,7 @@ export default class extends Endpoint { .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.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' }) .andWhere('user.updatedAt IS NOT NULL'); otherQuery.setParameters(followingQuery.getParameters()); @@ -116,7 +117,7 @@ export default class extends Endpoint { } else { users = await this.usersRepository.createQueryBuilder('user') .where('user.isSuspended = FALSE') - .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) + .andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' }) .andWhere('user.updatedAt IS NOT NULL') .orderBy('user.updatedAt', 'DESC') .take(ps.limit - users.length) diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index ba07714972..25bd621269 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -5,6 +5,7 @@ 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'; +import { sqlLikeEscape } from '@/misc/sql-like-escape'; export const meta = { tags: ['users'], @@ -57,7 +58,7 @@ export default class extends Endpoint { if (isUsername) { const usernameQuery = this.usersRepository.createQueryBuilder('user') - .where('user.usernameLower LIKE :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) + .where('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.query.replace('@', '').toLowerCase()) + '%' }) .andWhere(new Brackets(qb => { qb .where('user.updatedAt IS NULL') .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); @@ -78,11 +79,11 @@ export default class extends Endpoint { } else { const nameQuery = this.usersRepository.createQueryBuilder('user') .where(new Brackets(qb => { - qb.where('user.name ILIKE :query', { query: '%' + ps.query + '%' }); + qb.where('user.name ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' }); // Also search username if it qualifies as username if (this.userEntityService.validateLocalUsername(ps.query)) { - qb.orWhere('user.usernameLower LIKE :username', { username: '%' + ps.query.toLowerCase() + '%' }); + qb.orWhere('user.usernameLower LIKE :username', { username: '%' + sqlLikeEscape(ps.query.toLowerCase()) + '%' }); } })) .andWhere(new Brackets(qb => { qb @@ -106,7 +107,7 @@ export default class extends Endpoint { if (users.length < ps.limit) { const profQuery = this.userProfilesRepository.createQueryBuilder('prof') .select('prof.userId') - .where('prof.description ILIKE :query', { query: '%' + ps.query + '%' }); + .where('prof.description ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' }); if (ps.origin === 'local') { profQuery.andWhere('prof.userHost IS NULL'); -- cgit v1.2.3-freya From 91ced90fb2e724e77afc3ef59456165ea6dbd5c5 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 9 Jan 2023 08:58:16 +0900 Subject: fix imports --- packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts | 2 +- packages/backend/src/server/api/endpoints/admin/emoji/list.ts | 2 +- packages/backend/src/server/api/endpoints/admin/show-users.ts | 2 +- packages/backend/src/server/api/endpoints/federation/instances.ts | 2 +- packages/backend/src/server/api/endpoints/hashtags/search.ts | 2 +- packages/backend/src/server/api/endpoints/notes/search.ts | 2 +- .../src/server/api/endpoints/users/search-by-username-and-host.ts | 2 +- packages/backend/src/server/api/endpoints/users/search.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') 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 ed60efd7b4..08ffc73fea 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 @@ -5,7 +5,7 @@ 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'; -import { sqlLikeEscape } from '@/misc/sql-like-escape'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; export const meta = { tags: ['admin'], 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 f357e45a52..6bcd4973d6 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -5,7 +5,7 @@ import type { Emoji } from '@/models/entities/Emoji.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; -//import { sqlLikeEscape } from '@/misc/sql-like-escape'; +//import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; export const meta = { tags: ['admin'], 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 722e284dde..5a67cf522a 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -3,7 +3,7 @@ import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { sqlLikeEscape } from '@/misc/sql-like-escape'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; export const meta = { tags: ['admin'], diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 726979309f..4c19988eb8 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -4,7 +4,7 @@ import type { InstancesRepository } from '@/models/index.js'; import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; import { MetaService } from '@/core/MetaService.js'; import { DI } from '@/di-symbols.js'; -import { sqlLikeEscape } from '@/misc/sql-like-escape'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; export const meta = { tags: ['federation'], diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts index 6c56ef5da2..4f5f979767 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/search.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { HashtagsRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; -import { sqlLikeEscape } from '@/misc/sql-like-escape'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; export const meta = { tags: ['hashtags'], diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index 02701ffe1e..8eb031dfe3 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -6,7 +6,7 @@ import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; -import { sqlLikeEscape } from '@/misc/sql-like-escape'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; export const meta = { tags: ['notes'], 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 029b1e91c3..95491211bc 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 @@ -6,7 +6,7 @@ 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'; -import { sqlLikeEscape } from '@/misc/sql-like-escape'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; export const meta = { tags: ['users'], diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 25bd621269..d7a60f0437 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -5,7 +5,7 @@ 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'; -import { sqlLikeEscape } from '@/misc/sql-like-escape'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; export const meta = { tags: ['users'], -- cgit v1.2.3-freya From 462acc9eee3f89a926fd4b46ffed0b066519759b Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 9 Jan 2023 15:50:25 +0900 Subject: カスタム絵文字一覧情報をmetaから分離 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 + .../src/core/entities/EmojiEntityService.ts | 8 +- packages/backend/src/models/schema/emoji.ts | 8 +- packages/backend/src/server/api/EndpointsModule.ts | 4 + packages/backend/src/server/api/endpoints.ts | 2 + .../backend/src/server/api/endpoints/emojis.ts | 91 ++++++++++++++++++++++ packages/backend/src/server/api/endpoints/meta.ts | 57 -------------- .../frontend/src/components/MkAutocomplete.vue | 5 +- packages/frontend/src/components/MkEmojiPicker.vue | 9 ++- packages/frontend/src/custom-emojis.ts | 48 ++++++++++++ packages/frontend/src/instance.ts | 24 +----- packages/frontend/src/local-storage.ts | 2 + packages/frontend/src/pages/about.emojis.vue | 87 ++++++++------------- .../frontend/src/pages/admin/emoji-edit-dialog.vue | 4 +- .../frontend/src/pages/admin/overview.stats.vue | 5 +- packages/frontend/src/pages/mfm-cheat-sheet.vue | 5 +- packages/frontend/src/pages/settings/index.vue | 2 + 17 files changed, 212 insertions(+), 151 deletions(-) create mode 100644 packages/backend/src/server/api/endpoints/emojis.ts create mode 100644 packages/frontend/src/custom-emojis.ts (limited to 'packages/backend/src/server/api/endpoints') diff --git a/CHANGELOG.md b/CHANGELOG.md index 269f8b1000..43cf0ef6e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ You should also include the user name that made the change. - Firefox109以下はサポートされなくなりました #### For app developers +- API: metaのレスポンスに`emojis`プロパティが含まれなくなりました + - カスタム絵文字一覧情報を取得するには、`emojis`エンドポイントにリクエストします - API: カスタム絵文字エンティティに`url`プロパティが含まれなくなりました - 絵文字画像を表示するには、`/emoji/.webp`にリクエストすると画像が返ります。 - e.g. `https://p1.a9z.dev/emoji/misskey.webp` diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts index 8a2dc70eda..2a4e09519f 100644 --- a/packages/backend/src/core/entities/EmojiEntityService.ts +++ b/packages/backend/src/core/entities/EmojiEntityService.ts @@ -22,23 +22,25 @@ export class EmojiEntityService { @bindThis public async pack( src: Emoji['id'] | Emoji, + opts: { omitHost?: boolean; omitId?: boolean; } = {}, ): Promise> { const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src }); return { - id: emoji.id, + id: opts.omitId ? undefined : emoji.id, aliases: emoji.aliases, name: emoji.name, category: emoji.category, - host: emoji.host, + host: opts.omitHost ? undefined : emoji.host, }; } @bindThis public packMany( emojis: any[], + opts: { omitHost?: boolean; omitId?: boolean; } = {}, ) { - return Promise.all(emojis.map(x => this.pack(x))); + return Promise.all(emojis.map(x => this.pack(x, opts))); } } diff --git a/packages/backend/src/models/schema/emoji.ts b/packages/backend/src/models/schema/emoji.ts index 9a52609b68..d897a0fc05 100644 --- a/packages/backend/src/models/schema/emoji.ts +++ b/packages/backend/src/models/schema/emoji.ts @@ -3,7 +3,7 @@ export const packedEmojiSchema = { properties: { id: { type: 'string', - optional: false, nullable: false, + optional: true, nullable: false, format: 'id', example: 'xxxxxxxxxx', }, @@ -26,12 +26,8 @@ export const packedEmojiSchema = { }, host: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, description: 'The local host is represented with `null`.', }, - url: { - type: 'string', - optional: true, nullable: false, - }, }, } as const; diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 60beca4f47..ab9349966d 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -220,6 +220,7 @@ import * as ep___messaging_messages_create from './endpoints/messaging/messages/ import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js'; import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js'; import * as ep___meta from './endpoints/meta.js'; +import * as ep___emojis from './endpoints/emojis.js'; import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js'; import * as ep___mute_create from './endpoints/mute/create.js'; import * as ep___mute_delete from './endpoints/mute/delete.js'; @@ -550,6 +551,7 @@ const $messaging_messages_create: Provider = { provide: 'ep:messaging/messages/c const $messaging_messages_delete: Provider = { provide: 'ep:messaging/messages/delete', useClass: ep___messaging_messages_delete.default }; const $messaging_messages_read: Provider = { provide: 'ep:messaging/messages/read', useClass: ep___messaging_messages_read.default }; const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default }; +const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default }; const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default }; const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default }; const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default }; @@ -884,6 +886,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $messaging_messages_delete, $messaging_messages_read, $meta, + $emojis, $miauth_genToken, $mute_create, $mute_delete, @@ -1212,6 +1215,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $messaging_messages_delete, $messaging_messages_read, $meta, + $emojis, $miauth_genToken, $mute_create, $mute_delete, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index d4f8be5b85..f9749ad660 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -219,6 +219,7 @@ import * as ep___messaging_messages_create from './endpoints/messaging/messages/ import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js'; import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js'; import * as ep___meta from './endpoints/meta.js'; +import * as ep___emojis from './endpoints/emojis.js'; import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js'; import * as ep___mute_create from './endpoints/mute/create.js'; import * as ep___mute_delete from './endpoints/mute/delete.js'; @@ -547,6 +548,7 @@ const eps = [ ['messaging/messages/delete', ep___messaging_messages_delete], ['messaging/messages/read', ep___messaging_messages_read], ['meta', ep___meta], + ['emojis', ep___emojis], ['miauth/gen-token', ep___miauth_genToken], ['mute/create', ep___mute_create], ['mute/delete', ep___mute_delete], diff --git a/packages/backend/src/server/api/endpoints/emojis.ts b/packages/backend/src/server/api/endpoints/emojis.ts new file mode 100644 index 0000000000..0a16268229 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/emojis.ts @@ -0,0 +1,91 @@ +import { IsNull, MoreThan } from 'typeorm'; +import { Inject, Injectable } from '@nestjs/common'; +import type { EmojisRepository } from '@/models/index.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import type { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; + +export const meta = { + tags: ['meta'], + + requireCredential: false, + + res: { + type: 'object', + optional: false, nullable: false, + properties: { + emojis: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + aliases: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + category: { + type: 'string', + optional: false, nullable: true, + }, + }, + }, + }, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + }, + required: [], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.emojisRepository) + private emojisRepository: EmojisRepository, + + private emojiEntityService: EmojiEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const emojis = await this.emojisRepository.find({ + where: { + host: IsNull(), + }, + order: { + category: 'ASC', + name: 'ASC', + }, + cache: { + id: 'meta_emojis', + milliseconds: 3600000, // 1 hour + }, + }); + + return { + emojis: await this.emojiEntityService.packMany(emojis, { + omitId: true, + omitHost: true, + }), + }; + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 05da011979..c44d63d64b 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -4,7 +4,6 @@ import type { AdsRepository, EmojisRepository, UsersRepository } from '@/models/ import { MAX_NOTE_TEXT_LENGTH, DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { MetaService } from '@/core/MetaService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; @@ -152,43 +151,6 @@ export const meta = { type: 'number', optional: false, nullable: false, }, - emojis: { - type: 'array', - optional: false, nullable: false, - items: { - type: 'object', - optional: false, nullable: false, - properties: { - id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - }, - aliases: { - type: 'array', - optional: false, nullable: false, - items: { - type: 'string', - optional: false, nullable: false, - }, - }, - category: { - type: 'string', - optional: false, nullable: true, - }, - host: { - type: 'string', - optional: false, nullable: true, - description: 'The local host is represented with `null`.', - }, - url: { - type: 'string', - optional: false, nullable: false, - format: 'url', - }, - }, - }, - }, ads: { type: 'array', optional: false, nullable: false, @@ -326,30 +288,12 @@ export default class extends Endpoint { @Inject(DI.adsRepository) private adsRepository: AdsRepository, - @Inject(DI.emojisRepository) - private emojisRepository: EmojisRepository, - private userEntityService: UserEntityService, - private emojiEntityService: EmojiEntityService, private metaService: MetaService, ) { super(meta, paramDef, async (ps, me) => { const instance = await this.metaService.fetch(true); - const emojis = await this.emojisRepository.find({ - where: { - host: IsNull(), - }, - order: { - category: 'ASC', - name: 'ASC', - }, - cache: { - id: 'meta_emojis', - milliseconds: 3600000, // 1 hour - }, - }); - const ads = await this.adsRepository.find({ where: { expiresAt: MoreThan(new Date()), @@ -390,7 +334,6 @@ export default class extends Endpoint { 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 => ({ diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 8ed60bc5dc..0d0c5edbae 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -47,6 +47,9 @@ import { emojilist } from '@/scripts/emojilist'; import { instance } from '@/instance'; import { i18n } from '@/i18n'; import { miLocalStorage } from '@/local-storage'; +import { getCustomEmojis } from '@/custom-emojis'; + +const customEmojis = await getCustomEmojis(); type EmojiDef = { emoji: string; @@ -86,7 +89,6 @@ for (const x of lib) { emjdb.sort((a, b) => a.name.length - b.name.length); //#region Construct Emoji DB -const customEmojis = instance.emojis; const emojiDefinitions: EmojiDef[] = []; for (const x of customEmojis) { @@ -117,7 +119,6 @@ export default { emojiDb, emojiDefinitions, emojilist, - customEmojis, }; diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index f3b3ac0b50..8df01f6c25 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -6,7 +6,7 @@
@@ -28,8 +28,8 @@
- diff --git a/packages/frontend/src/components/MkUserCardMini.vue b/packages/frontend/src/components/MkUserCardMini.vue index 1a4c494987..be8a4c408e 100644 --- a/packages/frontend/src/components/MkUserCardMini.vue +++ b/packages/frontend/src/components/MkUserCardMini.vue @@ -1,5 +1,5 @@ + - - - - - - - - - + + +
@@ -180,12 +181,16 @@ import { definePageMetadata } from '@/scripts/page-metadata'; import { i18n } from '@/i18n'; import { iAmAdmin, iAmModerator } from '@/account'; import { instance } from '@/instance'; +import MkRolePreview from '@/components/MkRolePreview.vue'; -const props = defineProps<{ +const props = withDefaults(defineProps<{ userId: string; -}>(); + initialTab?: string; +}>(), { + initialTab: 'overview', +}); -let tab = $ref('overview'); +let tab = $ref(props.initialTab); let chartSrc = $ref('per-user-notes'); let user = $ref(); let init = $ref>(); @@ -195,7 +200,6 @@ let ap = $ref(null); let moderator = $ref(false); let silenced = $ref(false); let suspended = $ref(false); -let driveCapacityOverrideMb: number | null = $ref(0); let moderationNote = $ref(''); const filesPagination = { endpoint: 'admin/drive/files' as const, @@ -220,7 +224,6 @@ function createFetcher() { moderator = info.isModerator; silenced = info.isSilenced; suspended = info.isSuspended; - driveCapacityOverrideMb = user.driveCapacityOverrideMb; moderationNote = info.moderationNote; watch($$(moderationNote), async () => { @@ -257,19 +260,6 @@ async function resetPassword() { }); } -async function toggleSilence(v) { - const confirm = await os.confirm({ - type: 'warning', - text: v ? i18n.ts.silenceConfirm : i18n.ts.unsilenceConfirm, - }); - if (confirm.canceled) { - silenced = !v; - } else { - await os.api(v ? 'admin/silence-user' : 'admin/unsilence-user', { userId: user.id }); - await refreshUser(); - } -} - async function toggleSuspend(v) { const confirm = await os.confirm({ type: 'warning', @@ -283,11 +273,6 @@ async function toggleSuspend(v) { } } -async function toggleModerator(v) { - await os.api(v ? 'admin/moderators/add' : 'admin/moderators/remove', { userId: user.id }); - await refreshUser(); -} - async function deleteAllFiles() { const confirm = await os.confirm({ type: 'warning', @@ -307,22 +292,6 @@ async function deleteAllFiles() { await refreshUser(); } -async function applyDriveCapacityOverride() { - let driveCapOrMb = driveCapacityOverrideMb; - if (driveCapacityOverrideMb && driveCapacityOverrideMb < 0) { - driveCapOrMb = null; - } - try { - await os.apiWithDialog('admin/drive-capacity-override', { userId: user.id, overrideMb: driveCapOrMb }); - await refreshUser(); - } catch (err) { - os.alert({ - type: 'error', - text: err.toString(), - }); - } -} - async function deleteAccount() { const confirm = await os.confirm({ type: 'warning', @@ -347,6 +316,31 @@ async function deleteAccount() { } } +async function assignRole() { + const roles = await os.api('admin/roles/list'); + + const { canceled, result: roleId } = await os.select({ + title: i18n.ts._role.chooseRoleToAssign, + items: roles.map(r => ({ text: r.name, value: r.id })), + }); + if (canceled) return; + + await os.apiWithDialog('admin/roles/assign', { roleId, userId: user.id }); + refreshUser(); +} + +async function unassignRole(role, ev) { + os.popupMenu([{ + text: i18n.ts.unassign, + icon: 'ti ti-x', + danger: true, + action: async () => { + await os.apiWithDialog('admin/roles/unassign', { roleId: role.id, userId: user.id }); + refreshUser(); + }, + }], ev.currentTarget ?? ev.target); +} + watch(() => props.userId, () => { init = createFetcher(); }, { @@ -484,4 +478,19 @@ definePageMetadata(computed(() => ({ margin-left: auto; } } + +.roleItem { + display: flex; +} + +.role { + flex: 1; +} + +.roleUnassign { + width: 32px; + height: 32px; + margin-left: 8px; + align-self: center; +} diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 07d34a794d..eea4d20094 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -18,7 +18,6 @@
-
@@ -35,7 +34,6 @@
-
@@ -189,7 +187,7 @@ onMounted(() => { const bd = parseInt(props.user.birthday.split('-')[2]); if (m === bm && d === bd) { confetti({ - duration: 1000 * 4 + duration: 1000 * 4, }); } } diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 4b9f49f8fd..05dcd7806e 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -37,6 +37,7 @@ export const routes = [{ }, { path: '/user-info/:userId', component: page(() => import('./pages/user-info.vue')), + hash: 'initialTab', }, { path: '/instance-info/:host', component: page(() => import('./pages/instance-info.vue')), @@ -351,6 +352,22 @@ export const routes = [{ path: '/ads', name: 'ads', component: page(() => import('./pages/admin/ads.vue')), + }, { + path: '/roles/:id/edit', + name: 'roles', + component: page(() => import('./pages/admin/roles.edit.vue')), + }, { + path: '/roles/new', + name: 'roles', + component: page(() => import('./pages/admin/roles.edit.vue')), + }, { + path: '/roles/:id', + name: 'roles', + component: page(() => import('./pages/admin/roles.role.vue')), + }, { + path: '/roles', + name: 'roles', + component: page(() => import('./pages/admin/roles.vue')), }, { path: '/database', name: 'database', diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts index 7ede64c327..74bd61fd78 100644 --- a/packages/frontend/src/scripts/get-user-menu.ts +++ b/packages/frontend/src/scripts/get-user-menu.ts @@ -108,26 +108,6 @@ export function getUserMenu(user, router: Router = mainRouter) { }); } - async function toggleSilence() { - if (!await getConfirmed(i18n.t(user.isSilenced ? 'unsilenceConfirm' : 'silenceConfirm'))) return; - - os.apiWithDialog(user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', { - userId: user.id, - }).then(() => { - user.isSilenced = !user.isSilenced; - }); - } - - async function toggleSuspend() { - if (!await getConfirmed(i18n.t(user.isSuspended ? 'unsuspendConfirm' : 'suspendConfirm'))) return; - - os.apiWithDialog(user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', { - userId: user.id, - }).then(() => { - user.isSuspended = !user.isSuspended; - }); - } - function reportAbuse() { os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), { user: user, @@ -218,13 +198,11 @@ export function getUserMenu(user, router: Router = mainRouter) { if (iAmModerator) { menu = menu.concat([null, { - icon: 'ti ti-microphone-2-off', - text: user.isSilenced ? i18n.ts.unsilence : i18n.ts.silence, - action: toggleSilence, - }, { - icon: 'ti ti-snowflake', - text: user.isSuspended ? i18n.ts.unsuspend : i18n.ts.suspend, - action: toggleSuspend, + icon: 'ti ti-user-exclamation', + text: i18n.ts.moderation, + action: () => { + router.push('/user-info/' + user.id + '#moderation'); + }, }]); } } diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue index f75e526939..b8a0a504a3 100644 --- a/packages/frontend/src/ui/deck/tl-column.vue +++ b/packages/frontend/src/ui/deck/tl-column.vue @@ -45,9 +45,7 @@ onMounted(() => { if (props.column.tl == null) { setType(); } else if ($i) { - disabled = !$i.isModerator && !$i.isAdmin && ( - instance.disableLocalTimeline && ['local', 'social'].includes(props.column.tl) || - instance.disableGlobalTimeline && ['global'].includes(props.column.tl)); + disabled = false; // TODO } }); -- cgit v1.2.3-freya From 978a9bbb3beafb7322aaf81d097430de6dbf527e Mon Sep 17 00:00:00 2001 From: tamaina Date: Thu, 12 Jan 2023 21:03:02 +0900 Subject: perf(backend): Use undici instead of node-fetch and got (#9459) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement? HttpFetchService * :v: * remove node-fetch * fix * refactor * fix * gateway timeout * UndiciFetcherクラスを追加 (仮コミット, ビルドもstartもさせていない) * fix * add logger and fix url preview * fix ip check * enhance logger and error handling * fix * fix * clean up * Use custom fetcher for ApRequest / ApResolver * bypassProxyはproxyBypassHostsに判断を委譲するように * set maxRedirections (default 3, ApRequest/ApResolver: 0) * fix comment * handle error s3 upload * add debug message * no return await * Revert "no return await" This reverts commit b5b0dc58a342393d260492e3a6f58304372f53b2. * reduce maxSockets * apResolverのUndiciFetcherを廃止しapRequestのものを使う、 add ap logger * Revert "apResolverのUndiciFetcherを廃止しapRequestのものを使う、 add ap logger" This reverts commit 997243915c8e1f8472da64f607f88c36cb1d5cb4. * add logger * fix * change logger name * safe * デフォルトでUser-Agentを設定 --- .config/example.yml | 10 +- packages/backend/package.json | 3 +- packages/backend/src/core/CaptchaService.ts | 26 +- packages/backend/src/core/DownloadService.ts | 90 ++---- packages/backend/src/core/DriveService.ts | 17 +- .../src/core/FetchInstanceMetadataService.ts | 7 +- packages/backend/src/core/HttpRequestService.ts | 345 ++++++++++++++++----- packages/backend/src/core/S3Service.ts | 2 +- packages/backend/src/core/WebfingerService.ts | 2 +- .../src/core/activitypub/ApRequestService.ts | 36 ++- .../src/core/activitypub/ApResolverService.ts | 15 +- .../src/core/activitypub/LdSignatureService.ts | 20 +- .../processors/WebhookDeliverProcessorService.ts | 38 +-- .../backend/src/server/api/endpoints/fetch-rss.ts | 19 +- .../src/server/api/endpoints/notes/translate.ts | 35 ++- .../server/api/integration/DiscordServerService.ts | 4 +- .../server/api/integration/GithubServerService.ts | 4 +- .../backend/src/server/web/UrlPreviewService.ts | 3 +- yarn.lock | 17 +- 19 files changed, 441 insertions(+), 252 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/.config/example.yml b/.config/example.yml index 93dea1f254..cabf167fba 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -122,10 +122,12 @@ id: 'aid' # Proxy for HTTP/HTTPS #proxy: http://127.0.0.1:3128 -#proxyBypassHosts: [ -# 'example.com', -# '192.0.2.8' -#] +proxyBypassHosts: + - api.deepl.com + - api-free.deepl.com + - www.recaptcha.net + - hcaptcha.com + - challenges.cloudflare.com # Proxy for SMTP/SMTPS #proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT diff --git a/packages/backend/package.json b/packages/backend/package.json index 293b630ad6..fbce696a3e 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -77,7 +77,6 @@ "misskey-js": "0.0.14", "ms": "3.0.0-canary.1", "nested-property": "4.0.0", - "node-fetch": "3.3.0", "nodemailer": "6.8.0", "nsfwjs": "2.4.2", "oauth": "^0.10.0", @@ -118,6 +117,7 @@ "twemoji-parser": "14.0.0", "typeorm": "0.3.11", "ulid": "2.3.0", + "undici": "^5.14.0", "unzipper": "0.10.11", "uuid": "9.0.0", "vary": "1.1.2", @@ -180,6 +180,7 @@ "execa": "6.1.0", "jest": "29.3.1", "jest-mock": "^29.3.1", + "node-fetch": "3.3.0", "typescript": "4.9.4" } } diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index 0207cf58a0..1e98914052 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -1,7 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { DI } from '@/di-symbols.js'; -import type { UsersRepository } from '@/models/index.js'; -import type { Config } from '@/config.js'; +import { Injectable } from '@nestjs/common'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { bindThis } from '@/decorators.js'; @@ -13,9 +10,6 @@ type CaptchaResponse = { @Injectable() export class CaptchaService { constructor( - @Inject(DI.config) - private config: Config, - private httpRequestService: HttpRequestService, ) { } @@ -27,16 +21,16 @@ export class CaptchaService { response, }); - const res = await fetch(url, { - method: 'POST', - body: params, - headers: { - 'User-Agent': this.config.userAgent, + const res = await this.httpRequestService.fetch( + url, + { + method: 'POST', + body: params, }, - // TODO - //timeout: 10 * 1000, - agent: (url, bypassProxy) => this.httpRequestService.getAgentByUrl(url, bypassProxy), - }).catch(err => { + { + noOkError: true, + } + ).catch(err => { throw `${err.message ?? err}`; }); diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts index 62123246a7..a3078bff45 100644 --- a/packages/backend/src/core/DownloadService.ts +++ b/packages/backend/src/core/DownloadService.ts @@ -8,11 +8,12 @@ import got, * as Got from 'got'; import chalk from 'chalk'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; -import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { HttpRequestService, UndiciFetcher } from '@/core/HttpRequestService.js'; import { createTemp } from '@/misc/create-temp.js'; import { StatusError } from '@/misc/status-error.js'; import { LoggerService } from '@/core/LoggerService.js'; import type Logger from '@/logger.js'; +import { buildConnector } from 'undici'; const pipeline = util.promisify(stream.pipeline); import { bindThis } from '@/decorators.js'; @@ -20,6 +21,7 @@ import { bindThis } from '@/decorators.js'; @Injectable() export class DownloadService { private logger: Logger; + private undiciFetcher: UndiciFetcher; constructor( @Inject(DI.config) @@ -29,70 +31,42 @@ export class DownloadService { private loggerService: LoggerService, ) { this.logger = this.loggerService.getLogger('download'); + + this.undiciFetcher = new UndiciFetcher(this.httpRequestService.getStandardUndiciFetcherOption( + { + connect: process.env.NODE_ENV === 'development' ? + this.httpRequestService.clientDefaults.connect + : + this.httpRequestService.getConnectorWithIpCheck( + buildConnector({ + ...this.httpRequestService.clientDefaults.connect, + }), + (ip) => !this.isPrivateIp(ip) + ), + bodyTimeout: 30 * 1000, + }, + { + connect: this.httpRequestService.clientDefaults.connect, + } + ), this.logger); } @bindThis public async downloadUrl(url: string, path: string): Promise { this.logger.info(`Downloading ${chalk.cyan(url)} to ${chalk.cyanBright(path)} ...`); - + const timeout = 30 * 1000; const operationTimeout = 60 * 1000; const maxSize = this.config.maxFileSize ?? 262144000; - - const req = got.stream(url, { - headers: { - 'User-Agent': this.config.userAgent, - }, - timeout: { - lookup: timeout, - connect: timeout, - secureConnect: timeout, - socket: timeout, // read timeout - response: timeout, - send: timeout, - request: operationTimeout, // whole operation timeout - }, - agent: { - http: this.httpRequestService.httpAgent, - https: this.httpRequestService.httpsAgent, - }, - http2: false, // default - retry: { - limit: 0, - }, - }).on('response', (res: Got.Response) => { - if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !this.config.proxy && res.ip) { - if (this.isPrivateIp(res.ip)) { - this.logger.warn(`Blocked address: ${res.ip}`); - req.destroy(); - } - } - - const contentLength = res.headers['content-length']; - if (contentLength != null) { - const size = Number(contentLength); - if (size > maxSize) { - this.logger.warn(`maxSize exceeded (${size} > ${maxSize}) on response`); - req.destroy(); - } - } - }).on('downloadProgress', (progress: Got.Progress) => { - if (progress.transferred > maxSize) { - this.logger.warn(`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`); - req.destroy(); - } - }); - - try { - await pipeline(req, fs.createWriteStream(path)); - } catch (e) { - if (e instanceof Got.HTTPError) { - throw new StatusError(`${e.response.statusCode} ${e.response.statusMessage}`, e.response.statusCode, e.response.statusMessage); - } else { - throw e; - } + + const response = await this.undiciFetcher.fetch(url); + + if (response.body === null) { + throw new StatusError('No body', 400, 'No body'); } - + + await pipeline(stream.Readable.fromWeb(response.body), fs.createWriteStream(path)); + this.logger.succ(`Download finished: ${chalk.cyan(url)}`); } @@ -114,7 +88,7 @@ export class DownloadService { cleanup(); } } - + @bindThis private isPrivateIp(ip: string): boolean { for (const net of this.config.allowedPrivateNetworks ?? []) { @@ -124,6 +98,6 @@ export class DownloadService { } } - return PrivateIp(ip); + return PrivateIp(ip) ?? false; } } diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 5954abba91..747dfd6130 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -375,8 +375,19 @@ export class DriveService { partSize: s3.endpoint.hostname === 'storage.googleapis.com' ? 500 * 1024 * 1024 : 8 * 1024 * 1024, }); - const result = await upload.promise(); - if (result) this.registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`); + await upload.promise() + .then( + result => { + if (result) { + this.registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`); + } else { + this.registerLogger.error(`Upload Result Empty: key = ${key}, filename = ${filename}`); + } + }, + err => { + this.registerLogger.error(`Upload Failed: key = ${key}, filename = ${filename}`, err); + } + ); } @bindThis @@ -462,6 +473,8 @@ export class DriveService { } } + this.registerLogger.debug(`ADD DRIVE FILE: user ${user?.id ?? 'not set'}, name ${detectedName}, tmp ${path}`); + //#region Check drive usage if (user && !isLink) { const usage = await this.driveFileEntityService.calcDriveUsageOf(user); diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 7eea45200e..cb9d099a22 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -1,7 +1,6 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { JSDOM } from 'jsdom'; -import fetch from 'node-fetch'; import tinycolor from 'tinycolor2'; import type { Instance } from '@/models/entities/Instance.js'; import type { InstancesRepository } from '@/models/index.js'; @@ -191,11 +190,7 @@ export class FetchInstanceMetadataService { const faviconUrl = url + '/favicon.ico'; - const favicon = await fetch(faviconUrl, { - // TODO - //timeout: 10000, - agent: url => this.httpRequestService.getAgentByUrl(url), - }); + const favicon = await this.httpRequestService.fetch(faviconUrl, {}, { noOkError: true }); if (favicon.ok) { return faviconUrl; diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index 49b28ae523..8639b5713d 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -1,67 +1,257 @@ import * as http from 'node:http'; import * as https from 'node:https'; import CacheableLookup from 'cacheable-lookup'; -import fetch from 'node-fetch'; import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { StatusError } from '@/misc/status-error.js'; import { bindThis } from '@/decorators.js'; -import type { Response } from 'node-fetch'; -import type { URL } from 'node:url'; +import * as undici from 'undici'; +import { LookupFunction } from 'node:net'; +import { LoggerService } from '@/core/LoggerService.js'; +import type Logger from '@/logger.js'; -@Injectable() -export class HttpRequestService { +// true to allow, false to deny +export type IpChecker = (ip: string) => boolean; + +/* + * Child class to create and save Agent for fetch. + * You should construct this when you want + * to change timeout, size limit, socket connect function, etc. + */ +export class UndiciFetcher { /** - * Get http non-proxy agent + * Get http non-proxy agent (undici) */ - private http: http.Agent; + public nonProxiedAgent: undici.Agent; /** - * Get https non-proxy agent + * Get http proxy or non-proxy agent (undici) */ - private https: https.Agent; + public agent: undici.ProxyAgent | undici.Agent; + + private proxyBypassHosts: string[]; + private userAgent: string | undefined; + + private logger: Logger | undefined; + + constructor( + args: { + agentOptions: undici.Agent.Options; + proxy?: { + uri: string; + options?: undici.Agent.Options; // Override of agentOptions + }, + proxyBypassHosts?: string[]; + userAgent?: string; + }, + logger?: Logger, + ) { + this.logger = logger; + this.logger?.debug('UndiciFetcher constructor', args); + + this.proxyBypassHosts = args.proxyBypassHosts ?? []; + this.userAgent = args.userAgent; + + this.nonProxiedAgent = new undici.Agent({ + ...args.agentOptions, + connect: (process.env.NODE_ENV !== 'production' && typeof args.agentOptions.connect !== 'function') + ? (options, cb) => { + // Custom connector for debug + undici.buildConnector(args.agentOptions.connect as undici.buildConnector.BuildOptions)(options, (err, socket) => { + this.logger?.debug('Socket connector called', socket); + if (err) { + this.logger?.debug(`Socket error`, err); + cb(new Error(`Error while socket connecting\n${err}`), null); + return; + } + this.logger?.debug(`Socket connected: port ${socket.localPort} => remote ${socket.remoteAddress}`); + cb(null, socket); + }); + } : args.agentOptions.connect, + }); + + this.agent = args.proxy + ? new undici.ProxyAgent({ + ...args.agentOptions, + ...args.proxy.options, + + uri: args.proxy.uri, + + connect: (process.env.NODE_ENV !== 'production' && typeof (args.proxy?.options?.connect ?? args.agentOptions.connect) !== 'function') + ? (options, cb) => { + // Custom connector for debug + undici.buildConnector((args.proxy?.options?.connect ?? args.agentOptions.connect) as undici.buildConnector.BuildOptions)(options, (err, socket) => { + this.logger?.debug('Socket connector called (secure)', socket); + if (err) { + this.logger?.debug(`Socket error`, err); + cb(new Error(`Error while socket connecting\n${err}`), null); + return; + } + this.logger?.debug(`Socket connected (secure): port ${socket.localPort} => remote ${socket.remoteAddress}`); + cb(null, socket); + }); + } : (args.proxy?.options?.connect ?? args.agentOptions.connect), + }) + : this.nonProxiedAgent; + } /** - * Get http proxy or non-proxy agent + * Get agent by URL + * @param url URL + * @param bypassProxy Allways bypass proxy */ + @bindThis + public getAgentByUrl(url: URL, bypassProxy = false): undici.Agent | undici.ProxyAgent { + if (bypassProxy || this.proxyBypassHosts.includes(url.hostname)) { + return this.nonProxiedAgent; + } else { + return this.agent; + } + } + + @bindThis + public async fetch( + url: string | URL, + options: undici.RequestInit = {}, + privateOptions: { noOkError?: boolean; bypassProxy?: boolean; } = { noOkError: false, bypassProxy: false } + ): Promise { + const res = await undici.fetch(url, { + dispatcher: this.getAgentByUrl(new URL(url), privateOptions.bypassProxy), + ...options, + headers: { + 'User-Agent': this.userAgent ?? '', + ...(options.headers ?? {}), + }, + }).catch((err) => { + this.logger?.error('fetch error', err); + throw new StatusError('Resource Unreachable', 500, 'Resource Unreachable'); + }); + if (!res.ok && !privateOptions.noOkError) { + throw new StatusError(`${res.status} ${res.statusText}`, res.status, res.statusText); + } + return res; + } + + @bindThis + public async getJson(url: string, accept = 'application/json, */*', headers?: Record): Promise { + const res = await this.fetch( + url, + { + headers: Object.assign({ + Accept: accept, + }, headers ?? {}), + } + ); + + return await res.json() as T; + } + + @bindThis + public async getHtml(url: string, accept = 'text/html, */*', headers?: Record): Promise { + const res = await this.fetch( + url, + { + headers: Object.assign({ + Accept: accept, + }, headers ?? {}), + } + ); + + return await res.text(); + } +} + +@Injectable() +export class HttpRequestService { + public defaultFetcher: UndiciFetcher; + public fetch: UndiciFetcher['fetch']; + public getHtml: UndiciFetcher['getHtml']; + public defaultJsonFetcher: UndiciFetcher; + public getJson: UndiciFetcher['getJson']; + + //#region for old http/https, only used in S3Service + // http non-proxy agent + private http: http.Agent; + + // https non-proxy agent + private https: https.Agent; + + // http proxy or non-proxy agent public httpAgent: http.Agent; - /** - * Get https proxy or non-proxy agent - */ + // https proxy or non-proxy agent public httpsAgent: https.Agent; + //#endregion + + public readonly dnsCache: CacheableLookup; + public readonly clientDefaults: undici.Agent.Options; + private maxSockets: number; + + private logger: Logger; constructor( @Inject(DI.config) private config: Config, + private loggerService: LoggerService, ) { - const cache = new CacheableLookup({ + this.logger = this.loggerService.getLogger('http-request'); + + this.dnsCache = new CacheableLookup({ maxTtl: 3600, // 1hours errorTtl: 30, // 30secs lookup: false, // nativeのdns.lookupにfallbackしない }); - + + this.clientDefaults = { + keepAliveTimeout: 30 * 1000, + keepAliveMaxTimeout: 10 * 60 * 1000, + keepAliveTimeoutThreshold: 1 * 1000, + strictContentLength: true, + headersTimeout: 10 * 1000, + bodyTimeout: 10 * 1000, + maxHeaderSize: 16364, // default + maxResponseSize: 10 * 1024 * 1024, + maxRedirections: 3, + connect: { + timeout: 10 * 1000, // コネクションが確立するまでのタイムアウト + maxCachedSessions: 300, // TLSセッションのキャッシュ数 https://github.com/nodejs/undici/blob/v5.14.0/lib/core/connect.js#L80 + lookup: this.dnsCache.lookup as LookupFunction, // https://github.com/nodejs/undici/blob/v5.14.0/lib/core/connect.js#L98 + }, + } + + this.maxSockets = Math.max(64, this.config.deliverJobConcurrency ?? 128); + + this.defaultFetcher = new UndiciFetcher(this.getStandardUndiciFetcherOption(), this.logger); + + this.fetch = this.defaultFetcher.fetch; + this.getHtml = this.defaultFetcher.getHtml; + + this.defaultJsonFetcher = new UndiciFetcher(this.getStandardUndiciFetcherOption({ + maxResponseSize: 1024 * 256, + }), this.logger); + + this.getJson = this.defaultJsonFetcher.getJson; + + //#region for old http/https, only used in S3Service this.http = new http.Agent({ keepAlive: true, keepAliveMsecs: 30 * 1000, - lookup: cache.lookup, + lookup: this.dnsCache.lookup, } as http.AgentOptions); this.https = new https.Agent({ keepAlive: true, keepAliveMsecs: 30 * 1000, - lookup: cache.lookup, + lookup: this.dnsCache.lookup, } as https.AgentOptions); - - const maxSockets = Math.max(256, config.deliverJobConcurrency ?? 128); - + this.httpAgent = config.proxy ? new HttpProxyAgent({ keepAlive: true, keepAliveMsecs: 30 * 1000, - maxSockets, + maxSockets: this.maxSockets, maxFreeSockets: 256, scheduling: 'lifo', proxy: config.proxy, @@ -72,21 +262,42 @@ export class HttpRequestService { ? new HttpsProxyAgent({ keepAlive: true, keepAliveMsecs: 30 * 1000, - maxSockets, + maxSockets: this.maxSockets, maxFreeSockets: 256, scheduling: 'lifo', proxy: config.proxy, }) : this.https; + //#endregion + } + + @bindThis + public getStandardUndiciFetcherOption(opts: undici.Agent.Options = {}, proxyOpts: undici.Agent.Options = {}) { + return { + agentOptions: { + ...this.clientDefaults, + ...opts, + }, + ...(this.config.proxy ? { + proxy: { + uri: this.config.proxy, + options: { + connections: this.maxSockets, + ...proxyOpts, + } + } + } : {}), + userAgent: this.config.userAgent, + } } /** - * Get agent by URL + * Get http agent by URL * @param url URL * @param bypassProxy Allways bypass proxy */ @bindThis - public getAgentByUrl(url: URL, bypassProxy = false): http.Agent | https.Agent { + public getHttpAgentByUrl(url: URL, bypassProxy = false): http.Agent | https.Agent { if (bypassProxy || (this.config.proxyBypassHosts || []).includes(url.hostname)) { return url.protocol === 'http:' ? this.http : this.https; } else { @@ -94,67 +305,37 @@ export class HttpRequestService { } } + /** + * check ip + */ @bindThis - public async getJson(url: string, accept = 'application/json, */*', timeout = 10000, headers?: Record): Promise { - const res = await this.getResponse({ - url, - method: 'GET', - headers: Object.assign({ - 'User-Agent': this.config.userAgent, - Accept: accept, - }, headers ?? {}), - timeout, - size: 1024 * 256, - }); - - return await res.json(); - } - - @bindThis - public async getHtml(url: string, accept = 'text/html, */*', timeout = 10000, headers?: Record): Promise { - const res = await this.getResponse({ - url, - method: 'GET', - headers: Object.assign({ - 'User-Agent': this.config.userAgent, - Accept: accept, - }, headers ?? {}), - timeout, - }); + public getConnectorWithIpCheck(connector: undici.buildConnector.connector, checkIp: IpChecker): undici.buildConnector.connectorAsync { + return (options, cb) => { + connector(options, (err, socket) => { + this.logger.debug('Socket connector (with ip checker) called', socket); + if (err) { + this.logger.error(`Socket error`, err) + cb(new Error(`Error while socket connecting\n${err}`), null); + return; + } - return await res.text(); - } + if (socket.remoteAddress == undefined) { + this.logger.error(`Socket error: remoteAddress is undefined`); + cb(new Error('remoteAddress is undefined (maybe socket destroyed)'), null); + return; + } - @bindThis - public async getResponse(args: { - url: string, - method: string, - body?: string, - headers: Record, - timeout?: number, - size?: number, - }): Promise { - const timeout = args.timeout ?? 10 * 1000; - - const controller = new AbortController(); - setTimeout(() => { - controller.abort(); - }, timeout * 6); - - const res = await fetch(args.url, { - method: args.method, - headers: args.headers, - body: args.body, - timeout, - size: args.size ?? 10 * 1024 * 1024, - agent: (url) => this.getAgentByUrl(url), - signal: controller.signal, - }); + // allow + if (checkIp(socket.remoteAddress)) { + this.logger.debug(`Socket connected (ip ok): ${socket.localPort} => ${socket.remoteAddress}`); + cb(null, socket); + return; + } - if (!res.ok) { - throw new StatusError(`${res.status} ${res.statusText}`, res.status, res.statusText); - } - - return res; + this.logger.error('IP is not allowed', socket); + cb(new StatusError('IP is not allowed', 403, 'IP is not allowed'), null); + socket.destroy(); + }); + }; } } diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts index 0ce69aaa74..930188ce6e 100644 --- a/packages/backend/src/core/S3Service.ts +++ b/packages/backend/src/core/S3Service.ts @@ -33,7 +33,7 @@ export class S3Service { ? false : meta.objectStorageS3ForcePathStyle, httpOptions: { - agent: this.httpRequestService.getAgentByUrl(new URL(u), !meta.objectStorageUseProxy), + agent: this.httpRequestService.getHttpAgentByUrl(new URL(u), !meta.objectStorageUseProxy), }, }); } diff --git a/packages/backend/src/core/WebfingerService.ts b/packages/backend/src/core/WebfingerService.ts index 4c91ab8438..69df2d0c1b 100644 --- a/packages/backend/src/core/WebfingerService.ts +++ b/packages/backend/src/core/WebfingerService.ts @@ -30,7 +30,7 @@ export class WebfingerService { public async webfinger(query: string): Promise { const url = this.genUrl(query); - return await this.httpRequestService.getJson(url, 'application/jrd+json, application/json') as IWebFinger; + return await this.httpRequestService.getJson(url, 'application/jrd+json, application/json'); } @bindThis diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index d1edd579fa..d44d06a442 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -5,8 +5,10 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import type { User } from '@/models/entities/User.js'; import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; -import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { HttpRequestService, UndiciFetcher } from '@/core/HttpRequestService.js'; +import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; +import type Logger from '@/logger.js'; type Request = { url: string; @@ -28,13 +30,21 @@ type PrivateKey = { @Injectable() export class ApRequestService { + private undiciFetcher: UndiciFetcher; + private logger: Logger; + constructor( @Inject(DI.config) private config: Config, private userKeypairStoreService: UserKeypairStoreService, private httpRequestService: HttpRequestService, + private loggerService: LoggerService, ) { + this.logger = this.loggerService?.getLogger('ap-request'); // なぜか TypeError: Cannot read properties of undefined (reading 'getLogger') と言われる + this.undiciFetcher = new UndiciFetcher(this.httpRequestService.getStandardUndiciFetcherOption({ + maxRedirections: 0, + }), this.logger ); } @bindThis @@ -148,16 +158,17 @@ export class ApRequestService { url, body, additionalHeaders: { - 'User-Agent': this.config.userAgent, }, }); - await this.httpRequestService.getResponse({ + await this.undiciFetcher.fetch( url, - method: req.request.method, - headers: req.request.headers, - body, - }); + { + method: req.request.method, + headers: req.request.headers, + body, + } + ); } /** @@ -176,15 +187,16 @@ export class ApRequestService { }, url, additionalHeaders: { - 'User-Agent': this.config.userAgent, }, }); - const res = await this.httpRequestService.getResponse({ + const res = await this.httpRequestService.fetch( url, - method: req.request.method, - headers: req.request.headers, - }); + { + method: req.request.method, + headers: req.request.headers, + } + ); return await res.json(); } diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index e96c84f148..dc84f06a6a 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -4,7 +4,7 @@ import { InstanceActorService } from '@/core/InstanceActorService.js'; import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; -import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { HttpRequestService, UndiciFetcher } from '@/core/HttpRequestService.js'; import { DI } from '@/di-symbols.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; @@ -12,11 +12,15 @@ import { isCollectionOrOrderedCollection } from './type.js'; import { ApDbResolverService } from './ApDbResolverService.js'; import { ApRendererService } from './ApRendererService.js'; import { ApRequestService } from './ApRequestService.js'; +import { LoggerService } from '@/core/LoggerService.js'; import type { IObject, ICollection, IOrderedCollection } from './type.js'; +import type Logger from '@/logger.js'; export class Resolver { private history: Set; private user?: ILocalUser; + private undiciFetcher: UndiciFetcher; + private logger: Logger; constructor( private config: Config, @@ -31,9 +35,14 @@ export class Resolver { private httpRequestService: HttpRequestService, private apRendererService: ApRendererService, private apDbResolverService: ApDbResolverService, + private loggerService: LoggerService, private recursionLimit = 100, ) { this.history = new Set(); + this.logger = this.loggerService?.getLogger('ap-resolve'); // なぜか TypeError: Cannot read properties of undefined (reading 'getLogger') と言われる + this.undiciFetcher = new UndiciFetcher(this.httpRequestService.getStandardUndiciFetcherOption({ + maxRedirections: 0, + }), this.logger); } @bindThis @@ -96,8 +105,8 @@ export class Resolver { } const object = (this.user - ? await this.apRequestService.signedGet(value, this.user) - : await this.httpRequestService.getJson(value, 'application/activity+json, application/ld+json')) as IObject; + ? await this.apRequestService.signedGet(value, this.user) as IObject + : await this.undiciFetcher.getJson(value, 'application/activity+json, application/ld+json')); if (object == null || ( Array.isArray(object['@context']) ? diff --git a/packages/backend/src/core/activitypub/LdSignatureService.ts b/packages/backend/src/core/activitypub/LdSignatureService.ts index b71320ed0b..4e4b7dce2d 100644 --- a/packages/backend/src/core/activitypub/LdSignatureService.ts +++ b/packages/backend/src/core/activitypub/LdSignatureService.ts @@ -1,6 +1,5 @@ import * as crypto from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; -import fetch from 'node-fetch'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { bindThis } from '@/decorators.js'; import { CONTEXTS } from './misc/contexts.js'; @@ -116,14 +115,19 @@ class LdSignature { @bindThis private async fetchDocument(url: string) { - const json = await fetch(url, { - headers: { - Accept: 'application/ld+json, application/json', + const json = await this.httpRequestService.fetch( + url, + { + headers: { + Accept: 'application/ld+json, application/json', + }, + // TODO + //timeout: this.loderTimeout, }, - // TODO - //timeout: this.loderTimeout, - agent: u => u.protocol === 'http:' ? this.httpRequestService.httpAgent : this.httpRequestService.httpsAgent, - }).then(res => { + { + noOkError: true, + } + ).then(res => { if (!res.ok) { throw `${res.status} ${res.statusText}`; } else { diff --git a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts index 183ef07477..f0543a5ed1 100644 --- a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts @@ -33,24 +33,26 @@ export class WebhookDeliverProcessorService { try { this.logger.debug(`delivering ${job.data.webhookId}`); - const res = await this.httpRequestService.getResponse({ - url: job.data.to, - method: 'POST', - headers: { - 'User-Agent': 'Misskey-Hooks', - 'X-Misskey-Host': this.config.host, - 'X-Misskey-Hook-Id': job.data.webhookId, - 'X-Misskey-Hook-Secret': job.data.secret, - }, - body: JSON.stringify({ - hookId: job.data.webhookId, - userId: job.data.userId, - eventId: job.data.eventId, - createdAt: job.data.createdAt, - type: job.data.type, - body: job.data.content, - }), - }); + const res = await this.httpRequestService.fetch( + job.data.to, + { + method: 'POST', + headers: { + 'User-Agent': 'Misskey-Hooks', + 'X-Misskey-Host': this.config.host, + 'X-Misskey-Hook-Id': job.data.webhookId, + 'X-Misskey-Hook-Secret': job.data.secret, + }, + body: JSON.stringify({ + hookId: job.data.webhookId, + userId: job.data.userId, + eventId: job.data.eventId, + createdAt: job.data.createdAt, + type: job.data.type, + body: job.data.content, + }), + } + ); this.webhooksRepository.update({ id: job.data.webhookId }, { latestSentAt: new Date(), diff --git a/packages/backend/src/server/api/endpoints/fetch-rss.ts b/packages/backend/src/server/api/endpoints/fetch-rss.ts index 58fa01ac48..ae6a87513d 100644 --- a/packages/backend/src/server/api/endpoints/fetch-rss.ts +++ b/packages/backend/src/server/api/endpoints/fetch-rss.ts @@ -33,15 +33,16 @@ export default class extends Endpoint { private httpRequestService: HttpRequestService, ) { super(meta, paramDef, async (ps, me) => { - const res = await this.httpRequestService.getResponse({ - url: ps.url, - method: 'GET', - headers: Object.assign({ - 'User-Agent': config.userAgent, - Accept: 'application/rss+xml, */*', - }), - timeout: 5000, - }); + const res = await this.httpRequestService.fetch( + ps.url, + { + method: 'GET', + headers: { + Accept: 'application/rss+xml, */*', + }, + // timeout: 5000, + } + ); const text = await res.text(); diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index ec16965998..ab19771676 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -1,5 +1,4 @@ import { URLSearchParams } from 'node:url'; -import fetch from 'node-fetch'; import { Inject, Injectable } from '@nestjs/common'; import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -84,25 +83,27 @@ export default class extends Endpoint { const endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate'; - const res = await fetch(endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': config.userAgent, - Accept: 'application/json, */*', + const res = await this.httpRequestService.fetch( + endpoint, + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: 'application/json, */*', + }, + body: params.toString(), }, - body: params, - // TODO - //timeout: 10000, - agent: (url) => this.httpRequestService.getAgentByUrl(url), - }); + { + noOkError: false, + } + ); const json = (await res.json()) as { - translations: { - detected_source_language: string; - text: string; - }[]; - }; + translations: { + detected_source_language: string; + text: string; + }[]; + }; return { sourceLang: json.translations[0].detected_source_language, diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts index 805056da8b..0ac2733817 100644 --- a/packages/backend/src/server/api/integration/DiscordServerService.ts +++ b/packages/backend/src/server/api/integration/DiscordServerService.ts @@ -181,7 +181,7 @@ export class DiscordServerService { } })); - const { id, username, discriminator } = (await this.httpRequestService.getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { + const { id, username, discriminator } = (await this.httpRequestService.getJson('https://discord.com/api/users/@me', '*/*', { 'Authorization': `Bearer ${accessToken}`, })) as Record; @@ -249,7 +249,7 @@ export class DiscordServerService { } })); - const { id, username, discriminator } = (await this.httpRequestService.getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { + const { id, username, discriminator } = (await this.httpRequestService.getJson('https://discord.com/api/users/@me', '*/*', { 'Authorization': `Bearer ${accessToken}`, })) as Record; if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') { diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts index 6f38c262a1..a8c745d2dc 100644 --- a/packages/backend/src/server/api/integration/GithubServerService.ts +++ b/packages/backend/src/server/api/integration/GithubServerService.ts @@ -174,7 +174,7 @@ export class GithubServerService { } })); - const { login, id } = (await this.httpRequestService.getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { + const { login, id } = (await this.httpRequestService.getJson('https://api.github.com/user', 'application/vnd.github.v3+json', { 'Authorization': `bearer ${accessToken}`, })) as Record; if (typeof login !== 'string' || typeof id !== 'string') { @@ -223,7 +223,7 @@ export class GithubServerService { } })); - const { login, id } = (await this.httpRequestService.getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { + const { login, id } = (await this.httpRequestService.getJson('https://api.github.com/user', 'application/vnd.github.v3+json', { 'Authorization': `bearer ${accessToken}`, })) as Record; diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index baef8fa993..802b404ce6 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -63,9 +63,8 @@ export class UrlPreviewService { this.logger.info(meta.summalyProxy ? `(Proxy) Getting preview of ${url}@${lang} ...` : `Getting preview of ${url}@${lang} ...`); - try { - const summary = meta.summalyProxy ? await this.httpRequestService.getJson(`${meta.summalyProxy}?${query({ + const summary = meta.summalyProxy ? await this.httpRequestService.getJson>(`${meta.summalyProxy}?${query({ url: url, lang: lang ?? 'ja-JP', })}`) : await summaly.default(url, { diff --git a/yarn.lock b/yarn.lock index 2e45bc1440..cdf2a19e1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4272,6 +4272,7 @@ __metadata: typeorm: 0.3.11 typescript: 4.9.4 ulid: 2.3.0 + undici: ^5.14.0 unzipper: 0.10.11 uuid: 9.0.0 vary: 1.1.2 @@ -16665,21 +16666,21 @@ __metadata: languageName: node linkType: hard -"undici@npm:^5.2.0": - version: 5.13.0 - resolution: "undici@npm:5.13.0" +"undici@npm:^5.14.0, undici@npm:^5.5.1": + version: 5.14.0 + resolution: "undici@npm:5.14.0" dependencies: busboy: ^1.6.0 - checksum: 15b8f7df4bd8c9bf9d777456d16b51eb621f25d3ee60f575a8693192a647b3a8b33671e984386636e8df42085488f86a52d0d47db1b8c22edcc591134892a424 + checksum: 7a076e44d84b25844b4eb657034437b8b9bb91f17d347de474fdea1d4263ce7ae9406db79cd30de5642519277b4893f43073258bcc8fed420b295da3fdd11b26 languageName: node linkType: hard -"undici@npm:^5.5.1": - version: 5.14.0 - resolution: "undici@npm:5.14.0" +"undici@npm:^5.2.0": + version: 5.13.0 + resolution: "undici@npm:5.13.0" dependencies: busboy: ^1.6.0 - checksum: 7a076e44d84b25844b4eb657034437b8b9bb91f17d347de474fdea1d4263ce7ae9406db79cd30de5642519277b4893f43073258bcc8fed420b295da3fdd11b26 + checksum: 15b8f7df4bd8c9bf9d777456d16b51eb621f25d3ee60f575a8693192a647b3a8b33671e984386636e8df42085488f86a52d0d47db1b8c22edcc591134892a424 languageName: node linkType: hard -- cgit v1.2.3-freya From c5c40a73b7b81c71d62100945ea33fd90d78a518 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 13 Jan 2023 11:03:54 +0900 Subject: feat: conditional role Resolve #9539 --- locales/ja-JP.yml | 15 ++- .../migration/1673570377815-RoleConditional.js | 15 +++ packages/backend/src/core/RoleService.ts | 44 ++++++- .../backend/src/core/entities/RoleEntityService.ts | 2 + packages/backend/src/models/entities/Role.ts | 53 +++++++++ .../src/server/api/endpoints/admin/roles/create.ts | 6 + .../src/server/api/endpoints/admin/roles/update.ts | 6 + .../src/pages/admin/RolesEditorFormula.vue | 129 +++++++++++++++++++++ packages/frontend/src/pages/admin/roles.editor.vue | 30 ++++- 9 files changed, 296 insertions(+), 4 deletions(-) create mode 100644 packages/backend/migration/1673570377815-RoleConditional.js create mode 100644 packages/frontend/src/pages/admin/RolesEditorFormula.vue (limited to 'packages/backend/src/server/api/endpoints') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 933bb285ca..dc2d4bd237 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -938,7 +938,12 @@ _role: name: "ロール名" description: "ロールの説明" permission: "ロールの権限" - descriptionOfType: "モデレーターは基本的なモデレーションに関する操作を行えます。\n管理者はインスタンスの全ての設定を変更できます。" + descriptionOfPermission: "モデレーターは基本的なモデレーションに関する操作を行えます。\n管理者はインスタンスの全ての設定を変更できます。" + assignTarget: "アサインターゲット" + descriptionOfAssignTarget: "マニュアルは誰がこのロールに含まれるかを手動で管理します。\nコンディショナルは条件を設定し、それに合致するユーザーが自動で含まれるようになります。" + manual: "マニュアル" + conditional: "コンディショナル" + condition: "条件" isPublic: "ロールを公開" descriptionOfIsPublic: "ロールにアサインされたユーザーを誰でも見ることができます。また、ユーザーのプロフィールでこのロールが表示されます。" options: "オプション" @@ -953,6 +958,14 @@ _role: canPublicNote: "パブリック投稿の許可" driveCapacity: "ドライブ容量" antennaMax: "アンテナの作成可能数" + _condition: + isLocal: "ローカルユーザー" + isRemote: "リモートユーザー" + createdLessThan: "アカウント作成から~以内" + createdMoreThan: "アカウント作成から~経過" + and: "~かつ~" + or: "~または~" + not: "~ではない" _sensitiveMediaDetection: description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" diff --git a/packages/backend/migration/1673570377815-RoleConditional.js b/packages/backend/migration/1673570377815-RoleConditional.js new file mode 100644 index 0000000000..11ae4f00c6 --- /dev/null +++ b/packages/backend/migration/1673570377815-RoleConditional.js @@ -0,0 +1,15 @@ +export class RoleConditional1673570377815 { + name = 'RoleConditional1673570377815' + + async up(queryRunner) { + await queryRunner.query(`CREATE TYPE "public"."role_target_enum" AS ENUM('manual', 'conditional')`); + await queryRunner.query(`ALTER TABLE "role" ADD "target" "public"."role_target_enum" NOT NULL DEFAULT 'manual'`); + await queryRunner.query(`ALTER TABLE "role" ADD "condFormula" jsonb NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "condFormula"`); + await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "target"`); + await queryRunner.query(`DROP TYPE "public"."role_target_enum"`); + } +} diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 6ce7f431ca..3183adb369 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -7,6 +7,9 @@ import type { CacheableLocalUser, CacheableUser, ILocalUser, User } from '@/mode import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { MetaService } from '@/core/MetaService.js'; +import { UserCacheService } from '@/core/UserCacheService.js'; +import { RoleCondFormulaValue } from '@/models/entities/Role.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; export type RoleOptions = { @@ -44,6 +47,8 @@ export class RoleService implements OnApplicationShutdown { private roleAssignmentsRepository: RoleAssignmentsRepository, private metaService: MetaService, + private userCacheService: UserCacheService, + private userEntityService: UserEntityService, ) { //this.onMessage = this.onMessage.bind(this); @@ -111,12 +116,49 @@ export class RoleService implements OnApplicationShutdown { } } + @bindThis + private evalCond(user: User, value: RoleCondFormulaValue): boolean { + try { + switch (value.type) { + case 'and': { + return value.values.every(v => this.evalCond(user, v)); + } + case 'or': { + return value.values.some(v => this.evalCond(user, v)); + } + case 'not': { + return !this.evalCond(user, value.value); + } + case 'isLocal': { + return this.userEntityService.isLocalUser(user); + } + case 'isRemote': { + return this.userEntityService.isRemoteUser(user); + } + case 'createdLessThan': { + return user.createdAt.getTime() > (Date.now() - (value.sec * 1000)); + } + case 'createdMoreThan': { + return user.createdAt.getTime() < (Date.now() - (value.sec * 1000)); + } + default: + return false; + } + } catch (err) { + // TODO: log error + return false; + } + } + @bindThis public async getUserRoles(userId: User['id']) { const assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId })); const assignedRoleIds = assigns.map(x => x.roleId); const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({})); - return roles.filter(r => assignedRoleIds.includes(r.id)); + const assignedRoles = roles.filter(r => assignedRoleIds.includes(r.id)); + const user = roles.some(r => r.target === 'conditional') ? await this.userCacheService.findById(userId) : null; + const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula)); + return [...assignedRoles, ...matchedCondRoles]; } @bindThis diff --git a/packages/backend/src/core/entities/RoleEntityService.ts b/packages/backend/src/core/entities/RoleEntityService.ts index 22c4cdff81..27e34a649a 100644 --- a/packages/backend/src/core/entities/RoleEntityService.ts +++ b/packages/backend/src/core/entities/RoleEntityService.ts @@ -55,6 +55,8 @@ export class RoleEntityService { name: role.name, description: role.description, color: role.color, + target: role.target, + condFormula: role.condFormula, isPublic: role.isPublic, isAdministrator: role.isAdministrator, isModerator: role.isModerator, diff --git a/packages/backend/src/models/entities/Role.ts b/packages/backend/src/models/entities/Role.ts index 34dbc2ce41..f7b4edc9e7 100644 --- a/packages/backend/src/models/entities/Role.ts +++ b/packages/backend/src/models/entities/Role.ts @@ -1,6 +1,48 @@ import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; import { id } from '../id.js'; +type CondFormulaValueAnd = { + type: 'and'; + values: RoleCondFormulaValue[]; +}; + +type CondFormulaValueOr = { + type: 'or'; + values: RoleCondFormulaValue[]; +}; + +type CondFormulaValueNot = { + type: 'not'; + value: RoleCondFormulaValue; +}; + +type CondFormulaValueIsLocal = { + type: 'isLocal'; +}; + +type CondFormulaValueIsRemote = { + type: 'isRemote'; +}; + +type CondFormulaValueCreatedLessThan = { + type: 'createdLessThan'; + sec: number; +}; + +type CondFormulaValueCreatedMoreThan = { + type: 'createdMoreThan'; + sec: number; +}; + +export type RoleCondFormulaValue = + CondFormulaValueAnd | + CondFormulaValueOr | + CondFormulaValueNot | + CondFormulaValueIsLocal | + CondFormulaValueIsRemote | + CondFormulaValueCreatedLessThan | + CondFormulaValueCreatedMoreThan; + @Entity() export class Role { @PrimaryColumn(id()) @@ -36,6 +78,17 @@ export class Role { }) public color: string | null; + @Column('enum', { + enum: ['manual', 'conditional'], + default: 'manual', + }) + public target: 'manual' | 'conditional'; + + @Column('jsonb', { + default: { }, + }) + public condFormula: RoleCondFormulaValue; + @Column('boolean', { default: false, }) diff --git a/packages/backend/src/server/api/endpoints/admin/roles/create.ts b/packages/backend/src/server/api/endpoints/admin/roles/create.ts index b04188fac6..a9216a6386 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/create.ts @@ -19,6 +19,8 @@ export const paramDef = { name: { type: 'string' }, description: { type: 'string' }, color: { type: 'string', nullable: true }, + target: { type: 'string' }, + condFormula: { type: 'object' }, isPublic: { type: 'boolean' }, isModerator: { type: 'boolean' }, isAdministrator: { type: 'boolean' }, @@ -31,6 +33,8 @@ export const paramDef = { 'name', 'description', 'color', + 'target', + 'condFormula', 'isPublic', 'isModerator', 'isAdministrator', @@ -60,6 +64,8 @@ export default class extends Endpoint { name: ps.name, description: ps.description, color: ps.color, + target: ps.target, + condFormula: ps.condFormula, isPublic: ps.isPublic, isAdministrator: ps.isAdministrator, isModerator: ps.isModerator, diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update.ts b/packages/backend/src/server/api/endpoints/admin/roles/update.ts index 7d97d68e14..4ca5124eda 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts @@ -27,6 +27,8 @@ export const paramDef = { name: { type: 'string' }, description: { type: 'string' }, color: { type: 'string', nullable: true }, + target: { type: 'string' }, + condFormula: { type: 'object' }, isPublic: { type: 'boolean' }, isModerator: { type: 'boolean' }, isAdministrator: { type: 'boolean' }, @@ -40,6 +42,8 @@ export const paramDef = { 'name', 'description', 'color', + 'target', + 'condFormula', 'isPublic', 'isModerator', 'isAdministrator', @@ -69,6 +73,8 @@ export default class extends Endpoint { name: ps.name, description: ps.description, color: ps.color, + target: ps.target, + condFormula: ps.condFormula, isPublic: ps.isPublic, isModerator: ps.isModerator, isAdministrator: ps.isAdministrator, diff --git a/packages/frontend/src/pages/admin/RolesEditorFormula.vue b/packages/frontend/src/pages/admin/RolesEditorFormula.vue new file mode 100644 index 0000000000..76ba639277 --- /dev/null +++ b/packages/frontend/src/pages/admin/RolesEditorFormula.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index b8e45cda50..f584c5c8bf 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -15,12 +15,26 @@ - + + + + + + + + + + +
+ +
+
+
@@ -107,7 +121,9 @@ - - diff --git a/packages/frontend/src/pages/admin/emojis.vue b/packages/frontend/src/pages/admin/emojis.vue deleted file mode 100644 index 49f130d75b..0000000000 --- a/packages/frontend/src/pages/admin/emojis.vue +++ /dev/null @@ -1,398 +0,0 @@ - - - - - diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 1d83ae3244..b66c967e6a 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -90,6 +90,19 @@
+ + + +
+ + + + + + +
+
+ @@ -175,6 +188,8 @@ let options_canPublicNote_useDefault = $ref(role?.options?.canPublicNote?.useDef let options_canPublicNote_value = $ref(role?.options?.canPublicNote?.value ?? false); let options_canInvite_useDefault = $ref(role?.options?.canInvite?.useDefault ?? true); let options_canInvite_value = $ref(role?.options?.canInvite?.value ?? false); +let options_canManageCustomEmojis_useDefault = $ref(role?.options?.canManageCustomEmojis?.useDefault ?? true); +let options_canManageCustomEmojis_value = $ref(role?.options?.canManageCustomEmojis?.value ?? false); let options_driveCapacityMb_useDefault = $ref(role?.options?.driveCapacityMb?.useDefault ?? true); let options_driveCapacityMb_value = $ref(role?.options?.driveCapacityMb?.value ?? 0); let options_antennaLimit_useDefault = $ref(role?.options?.antennaLimit?.useDefault ?? true); @@ -192,6 +207,7 @@ function getOptions() { ltlAvailable: { useDefault: options_ltlAvailable_useDefault, value: options_ltlAvailable_value }, canPublicNote: { useDefault: options_canPublicNote_useDefault, value: options_canPublicNote_value }, canInvite: { useDefault: options_canInvite_useDefault, value: options_canInvite_value }, + canManageCustomEmojis: { useDefault: options_canManageCustomEmojis_useDefault, value: options_canManageCustomEmojis_value }, driveCapacityMb: { useDefault: options_driveCapacityMb_useDefault, value: options_driveCapacityMb_value }, antennaLimit: { useDefault: options_antennaLimit_useDefault, value: options_antennaLimit_value }, }; diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index 6495074bb7..16c12f9928 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -40,6 +40,14 @@ + + + + + + + + @@ -90,6 +98,7 @@ let options_gtlAvailable = $ref(instance.baseRole.gtlAvailable); let options_ltlAvailable = $ref(instance.baseRole.ltlAvailable); let options_canPublicNote = $ref(instance.baseRole.canPublicNote); let options_canInvite = $ref(instance.baseRole.canInvite); +let options_canManageCustomEmojis = $ref(instance.baseRole.canManageCustomEmojis); let options_driveCapacityMb = $ref(instance.baseRole.driveCapacityMb); let options_antennaLimit = $ref(instance.baseRole.antennaLimit); @@ -100,6 +109,7 @@ async function updateBaseRole() { ltlAvailable: options_ltlAvailable, canPublicNote: options_canPublicNote, canInvite: options_canInvite, + canManageCustomEmojis: options_canManageCustomEmojis, driveCapacityMb: options_driveCapacityMb, antennaLimit: options_antennaLimit, }, diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue new file mode 100644 index 0000000000..87d205ed78 --- /dev/null +++ b/packages/frontend/src/pages/custom-emojis-manager.vue @@ -0,0 +1,397 @@ + + + + + diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue new file mode 100644 index 0000000000..b2880b60b1 --- /dev/null +++ b/packages/frontend/src/pages/emoji-edit-dialog.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 05dcd7806e..26c73c610f 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -305,6 +305,9 @@ export const routes = [{ }, { path: '/channels', component: page(() => import('./pages/channels.vue')), +}, { + path: '/custom-emojis-manager', + component: page(() => import('./pages/custom-emojis-manager.vue')), }, { path: '/registry/keys/system/:path(*)?', component: page(() => import('./pages/registry.keys.vue')), @@ -331,7 +334,7 @@ export const routes = [{ }, { path: '/emojis', name: 'emojis', - component: page(() => import('./pages/admin/emojis.vue')), + component: page(() => import('./pages/custom-emojis-manager.vue')), }, { path: '/queue', name: 'queue', diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index 079b31489e..cc152f9c1c 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -47,7 +47,7 @@ export function openInstanceMenu(ev: MouseEvent) { to: '/clicker', text: '🍪👈', icon: 'ti ti-cookie', - }, ($i && ($i.isRoot || $i.role.canInvite) && instance.disableRegistration) ? { + }, ($i && ($i.isAdmin || $i.role.canInvite) && instance.disableRegistration) ? { text: i18n.ts.invite, icon: 'ti ti-user-plus', action: () => { @@ -63,6 +63,11 @@ export function openInstanceMenu(ev: MouseEvent) { }); }); }, + } : undefined, ($i && ($i.isAdmin || $i.role.canManageCustomEmojis)) ? { + type: 'link', + to: '/custom-emojis-manager', + text: i18n.ts.manageCustomEmojis, + icon: 'ti ti-icons', } : undefined], }, null, { text: i18n.ts.help, -- cgit v1.2.3-freya From bc0b8afb1f955b2d487673d8b90ea6a2b427116a Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 13 Jan 2023 15:15:31 +0900 Subject: fix ads/update api --- packages/backend/src/server/api/endpoints/admin/ad/update.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src/server/api/endpoints') 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 195300666e..08e3c96ca9 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -38,7 +38,7 @@ export const paramDef = { @Injectable() export default class extends Endpoint { constructor( - @Inject(DI.usersRepository) + @Inject(DI.adsRepository) private adsRepository: AdsRepository, ) { super(meta, paramDef, async (ps, me) => { -- cgit v1.2.3-freya From 2654936c1746a69459f8066ca785efd722e527fa Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 13 Jan 2023 17:42:12 +0900 Subject: fix --- packages/backend/src/server/api/endpoints/i/webhooks/update.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'packages/backend/src/server/api/endpoints') 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 50098f96e7..8ec308eda7 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts @@ -67,7 +67,11 @@ export default class extends Endpoint { active: ps.active, }); - this.globalEventService.publishInternalEvent('webhookUpdated', webhook); + const updated = await this.webhooksRepository.findOneByOrFail({ + id: ps.webhookId, + }); + + this.globalEventService.publishInternalEvent('webhookUpdated', updated); }); } } -- cgit v1.2.3-freya From 303519a1bd93710938ab19335974608bd9bacabd Mon Sep 17 00:00:00 2001 From: tamaina Date: Fri, 13 Jan 2023 18:21:07 +0900 Subject: enhance: Judge instance block by endsWith (#9263) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * TypeScriptでendsWith * fix * SQL? * バ〜カアホ * Update packages/backend/src/core/UtilityService.ts Co-authored-by: Acid Chicken (硫酸鶏) * add comment * add description * Update packages/backend/src/core/UtilityService.ts Co-authored-by: syuilo * Update packages/backend/src/core/chart/charts/federation.ts Co-authored-by: Acid Chicken (硫酸鶏) * remove comment * fix * fix? * add changelog * ILIKE, ARRAY Co-authored-by: Acid Chicken (硫酸鶏) Co-authored-by: syuilo --- CHANGELOG.md | 1 + locales/ja-JP.yml | 2 +- packages/backend/src/core/UtilityService.ts | 6 ++++++ packages/backend/src/core/activitypub/ApInboxService.ts | 2 +- packages/backend/src/core/activitypub/ApResolverService.ts | 2 +- packages/backend/src/core/activitypub/models/ApNoteService.ts | 2 +- packages/backend/src/core/chart/charts/federation.ts | 10 +++++----- packages/backend/src/core/entities/InstanceEntityService.ts | 6 ++++-- .../backend/src/queue/processors/DeliverProcessorService.ts | 2 +- packages/backend/src/queue/processors/InboxProcessorService.ts | 4 ++-- packages/backend/src/server/api/endpoints/admin/update-meta.ts | 2 +- packages/backend/src/server/api/endpoints/ap/show.ts | 2 +- 12 files changed, 25 insertions(+), 16 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/CHANGELOG.md b/CHANGELOG.md index f705bae062..ac0e270a03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ You should also include the user name that made the change. - 代わりに今後任意の検索プロバイダを設定できる仕組みを構想しています。その仕組みを使えば今まで通りElasticsearchも利用できます - Migrate to Yarn Berry (v3.2.1) @ThatOneCalculator - You may have to `yarn run clean-all`, `sudo corepack enable` and `yarn set version berry` before running `yarn install` if you're still on yarn classic +- インスタンスブロックはサブドメインにも適用されるようになります - ロールの導入に伴い、いくつかの機能がロールと統合されました - モデレーターはロールに統合されました。今までのモデレーター情報は失われるため、予めモデレーター一覧を記録しておき、アップデート後にモデレーターロールを作りアサインし直してください。 - サイレンスはロールに統合されました。今までのユーザーは恩赦されるため、予めサイレンス一覧を記録しておくのをおすすめします。 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 934ee0615e..daeacf7690 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -193,7 +193,7 @@ clearQueueConfirmText: "未配達の投稿は配送されなくなります。 clearCachedFiles: "キャッシュをクリア" clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?" blockedInstances: "ブロックしたインスタンス" -blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定します。ブロックされたインスタンスは、このインスタンスとやり取りできなくなります。" +blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定します。ブロックされたインスタンスは、このインスタンスとやり取りできなくなります。サブドメインもブロックされます。" muteAndBlock: "ミュートとブロック" mutedUsers: "ミュートしたユーザー" blockedUsers: "ブロックしたユーザー" diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index 1412e6e9aa..d00708a442 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -24,6 +24,12 @@ export class UtilityService { return this.toPuny(this.config.host) === this.toPuny(host); } + @bindThis + public isBlockedHost(blockedHosts: string[], host: string | null): boolean { + if (host == null) return false; + return blockedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`)); + } + @bindThis public extractDbHost(uri: string): string { const url = new URL(uri); diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 79a917426a..76c8bf68df 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -291,7 +291,7 @@ export class ApInboxService { // アナウンス先をブロックしてたら中断 const meta = await this.metaService.fetch(); - if (meta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) return; + if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) return; const unlock = await this.appLockService.getApLock(uri); diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index dc84f06a6a..e51ae37954 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -96,7 +96,7 @@ export class Resolver { } const meta = await this.metaService.fetch(); - if (meta.blockedHosts.includes(host)) { + if (this.utilityService.isBlockedHost(meta.blockedHosts, host)) { throw new Error('Instance is blocked'); } diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index e1d93a08b0..c9192f53b7 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -324,7 +324,7 @@ export class ApNoteService { // ブロックしてたら中断 const meta = await this.metaService.fetch(); - if (meta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) throw { statusCode: 451 }; + if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) throw { statusCode: 451 }; const unlock = await this.appLockService.getApLock(uri); diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts index b8012809f7..ae4eb6e48d 100644 --- a/packages/backend/src/core/chart/charts/federation.ts +++ b/packages/backend/src/core/chart/charts/federation.ts @@ -61,21 +61,21 @@ export default class FederationChart extends Chart { this.followingsRepository.createQueryBuilder('following') .select('COUNT(DISTINCT following.followeeHost)') .where('following.followeeHost IS NOT NULL') - .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT IN (:...blocked)', { blocked: meta.blockedHosts }) + .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) .andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`) .getRawOne() .then(x => parseInt(x.count, 10)), this.followingsRepository.createQueryBuilder('following') .select('COUNT(DISTINCT following.followerHost)') .where('following.followerHost IS NOT NULL') - .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT IN (:...blocked)', { blocked: meta.blockedHosts }) + .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) .andWhere(`following.followerHost NOT IN (${ suspendedInstancesQuery.getQuery() })`) .getRawOne() .then(x => parseInt(x.count, 10)), this.followingsRepository.createQueryBuilder('following') .select('COUNT(DISTINCT following.followeeHost)') .where('following.followeeHost IS NOT NULL') - .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT IN (:...blocked)', { blocked: meta.blockedHosts }) + .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) .andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`) .andWhere(`following.followeeHost IN (${ pubsubSubQuery.getQuery() })`) .setParameters(pubsubSubQuery.getParameters()) @@ -84,7 +84,7 @@ export default class FederationChart extends Chart { this.instancesRepository.createQueryBuilder('instance') .select('COUNT(instance.id)') .where(`instance.host IN (${ subInstancesQuery.getQuery() })`) - .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocked)', { blocked: meta.blockedHosts }) + .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) .andWhere('instance.isSuspended = false') .andWhere('instance.isNotResponding = false') .getRawOne() @@ -92,7 +92,7 @@ export default class FederationChart extends Chart { this.instancesRepository.createQueryBuilder('instance') .select('COUNT(instance.id)') .where(`instance.host IN (${ pubInstancesQuery.getQuery() })`) - .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocked)', { blocked: meta.blockedHosts }) + .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) .andWhere('instance.isSuspended = false') .andWhere('instance.isNotResponding = false') .getRawOne() diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index 81d02bb331..c8bb74debf 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -7,8 +7,8 @@ import type { } from '@/models/entities/Blocking.js'; import type { User } from '@/models/entities/User.js'; import type { Instance } from '@/models/entities/Instance.js'; import { MetaService } from '@/core/MetaService.js'; +import { UtilityService } from '../UtilityService.js'; import { bindThis } from '@/decorators.js'; -import { UserEntityService } from './UserEntityService.js'; @Injectable() export class InstanceEntityService { @@ -17,6 +17,8 @@ export class InstanceEntityService { private instancesRepository: InstancesRepository, private metaService: MetaService, + + private utilityService: UtilityService, ) { } @@ -35,7 +37,7 @@ export class InstanceEntityService { followersCount: instance.followersCount, isNotResponding: instance.isNotResponding, isSuspended: instance.isSuspended, - isBlocked: meta.blockedHosts.includes(instance.host), + isBlocked: this.utilityService.isBlockedHost(meta.blockedHosts, instance.host), softwareName: instance.softwareName, softwareVersion: instance.softwareVersion, openRegistrations: instance.openRegistrations, diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index c5e4a66517..10fcb5684f 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -56,7 +56,7 @@ export class DeliverProcessorService { // ブロックしてたら中断 const meta = await this.metaService.fetch(); - if (meta.blockedHosts.includes(this.utilityService.toPuny(host))) { + if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.toPuny(host))) { return 'skip (blocked)'; } diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index d033637849..f814368a7a 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -76,7 +76,7 @@ export class InboxProcessorService { // ブロックしてたら中断 const meta = await this.metaService.fetch(); - if (meta.blockedHosts.includes(host)) { + if (this.utilityService.isBlockedHost(meta.blockedHosts, host)) { return `Blocked request: ${host}`; } @@ -158,7 +158,7 @@ export class InboxProcessorService { // ブロックしてたら中断 const ldHost = this.utilityService.extractDbHost(authUser.user.uri); - if (meta.blockedHosts.includes(ldHost)) { + if (this.utilityService.isBlockedHost(meta.blockedHosts, ldHost)) { return `Blocked request: ${ldHost}`; } } else { 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 c5b44b6b86..aacd634ed8 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -139,7 +139,7 @@ export default class extends Endpoint { } if (Array.isArray(ps.blockedHosts)) { - set.blockedHosts = ps.blockedHosts.filter(Boolean); + set.blockedHosts = ps.blockedHosts.filter(Boolean).map(x => x.toLowerCase()); } if (ps.themeColor !== undefined) { diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 1068a2eec7..9470dd3cbb 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -117,7 +117,7 @@ export default class extends Endpoint { private async fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise | null> { // ブロックしてたら中断 const fetchedMeta = await this.metaService.fetch(); - if (fetchedMeta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) return null; + if (this.utilityService.isBlockedHost(fetchedMeta.blockedHosts, this.utilityService.extractDbHost(uri))) return null; let local = await this.mergePack(me, ...await Promise.all([ this.apDbResolverService.getUserFromApId(uri), -- cgit v1.2.3-freya From 98469117bf4bbf04f25b9d4db02561f32a493a83 Mon Sep 17 00:00:00 2001 From: こけっち <50144466+sim1222@users.noreply.github.com> Date: Sat, 14 Jan 2023 06:18:58 +0900 Subject: fix: follow request list api pagination (#9548) * fix: follow request list api pagination * fix: improve alias Co-authored-by: syuilo Co-authored-by: syuilo --- .../server/api/endpoints/following/requests/list.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') 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 5b11633e6f..f581023e2e 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,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; import type { FollowRequestsRepository } from '@/models/index.js'; import { FollowRequestEntityService } from '@/core/entities/FollowRequestEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -40,7 +41,11 @@ export const meta = { export const paramDef = { type: 'object', - properties: {}, + properties: { + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + }, required: [], } as const; @@ -52,13 +57,16 @@ export default class extends Endpoint { private followRequestsRepository: FollowRequestsRepository, private followRequestEntityService: FollowRequestEntityService, + private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const reqs = await this.followRequestsRepository.findBy({ - followeeId: me.id, - }); + const query = this.queryService.makePaginationQuery(this.followRequestsRepository.createQueryBuilder('request'), ps.sinceId, ps.untilId); + + const requests = await query + .take(ps.limit) + .getMany(); - return await Promise.all(reqs.map(req => this.followRequestEntityService.pack(req))); + return await Promise.all(requests.map(req => this.followRequestEntityService.pack(req))); }); } } -- cgit v1.2.3-freya From d1807ee5dc9d7677cf60ad5139b490b6307807ce Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 14 Jan 2023 08:04:38 +0900 Subject: enhance: ハードワードミュートの最大文字数を設定可能に MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolve #7574 --- CHANGELOG.md | 5 +++-- locales/ja-JP.yml | 1 + packages/backend/src/core/RoleService.ts | 3 +++ packages/backend/src/server/api/endpoints/i/update.ts | 14 ++++++++++++++ packages/frontend/src/pages/admin/roles.editor.vue | 16 ++++++++++++++++ packages/frontend/src/pages/admin/roles.vue | 11 +++++++++++ 6 files changed, 48 insertions(+), 2 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/CHANGELOG.md b/CHANGELOG.md index ac9d4b835a..7ca44f87f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,8 +73,9 @@ You should also include the user name that made the change. - Push notification of Antenna note @tamaina - AVIF support @tamaina - Add Cloudflare Turnstile CAPTCHA support @CyberRex0 -- 非モデレーターでも、権限を持つロールをアサインされたユーザーはインスタンスの招待コードを発行できるように -- 非モデレーターでも、権限を持つロールをアサインされたユーザーはカスタム絵文字の追加、編集、削除を行えるように +- 非モデレーターでも、権限を持つロールをアサインされたユーザーはインスタンスの招待コードを発行できるように @syuilo +- 非モデレーターでも、権限を持つロールをアサインされたユーザーはカスタム絵文字の追加、編集、削除を行えるように @syuilo +- ハードワードミュートの最大文字数を設定可能に @syuilo - Server: signToActivityPubGet is set to true by default @syuilo - Server: improve syslog performance @syuilo - Server: Use undici instead of node-fetch and got @tamaina diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index daeacf7690..943168061c 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -962,6 +962,7 @@ _role: canManageCustomEmojis: "カスタム絵文字の管理" driveCapacity: "ドライブ容量" antennaMax: "アンテナの作成可能数" + wordMuteMax: "ワードミュートの最大文字数" _condition: isLocal: "ローカルユーザー" isRemote: "リモートユーザー" diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 15ba900802..d2056709e1 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -20,6 +20,7 @@ export type RoleOptions = { canManageCustomEmojis: boolean; driveCapacityMb: number; antennaLimit: number; + wordMuteLimit: number; }; export const DEFAULT_ROLE: RoleOptions = { @@ -30,6 +31,7 @@ export const DEFAULT_ROLE: RoleOptions = { canManageCustomEmojis: false, driveCapacityMb: 100, antennaLimit: 5, + wordMuteLimit: 200, }; @Injectable() @@ -187,6 +189,7 @@ export class RoleService implements OnApplicationShutdown { canManageCustomEmojis: getOptionValues('canManageCustomEmojis').some(x => x === true), driveCapacityMb: Math.max(...getOptionValues('driveCapacityMb')), antennaLimit: Math.max(...getOptionValues('antennaLimit')), + wordMuteLimit: Math.max(...getOptionValues('wordMuteLimit')), }; } diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index c2231f5f75..ef5ab16bc4 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -17,6 +17,7 @@ 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 { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -62,6 +63,12 @@ export const meta = { code: 'INVALID_REGEXP', id: '0d786918-10df-41cd-8f33-8dec7d9a89a5', }, + + tooManyMutedWords: { + message: 'Too many muted words.', + code: 'TOO_MANY_MUTED_WORDS', + id: '010665b1-a211-42d2-bc64-8f6609d79785', + }, }, res: { @@ -144,6 +151,7 @@ export default class extends Endpoint { private userFollowingService: UserFollowingService, private accountUpdateService: AccountUpdateService, private hashtagService: HashtagService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, _user, token) => { const user = await this.usersRepository.findOneByOrFail({ id: _user.id }); @@ -163,6 +171,12 @@ export default class extends Endpoint { if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId; if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId; if (ps.mutedWords !== undefined) { + // TODO: ちゃんと数える + const length = JSON.stringify(ps.mutedWords).length; + if (length > (await this.roleService.getUserRoleOptions(user.id)).antennaLimit) { + throw new ApiError(meta.errors.tooManyMutedWords); + } + // validate regular expression syntax ps.mutedWords.filter(x => !Array.isArray(x)).forEach(x => { const regexp = x.match(/^\/(.+)\/(.*)$/); diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index b66c967e6a..30bc6c238e 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -127,6 +127,19 @@
+ + + + +
+ + + + + + +
+
@@ -194,6 +207,8 @@ let options_driveCapacityMb_useDefault = $ref(role?.options?.driveCapacityMb?.us let options_driveCapacityMb_value = $ref(role?.options?.driveCapacityMb?.value ?? 0); let options_antennaLimit_useDefault = $ref(role?.options?.antennaLimit?.useDefault ?? true); let options_antennaLimit_value = $ref(role?.options?.antennaLimit?.value ?? 0); +let options_wordMuteLimit_useDefault = $ref(role?.options?.wordMuteLimit?.useDefault ?? true); +let options_wordMuteLimit_value = $ref(role?.options?.wordMuteLimit?.value ?? 0); if (_DEV_) { watch($$(condFormula), () => { @@ -210,6 +225,7 @@ function getOptions() { canManageCustomEmojis: { useDefault: options_canManageCustomEmojis_useDefault, value: options_canManageCustomEmojis_value }, driveCapacityMb: { useDefault: options_driveCapacityMb_useDefault, value: options_driveCapacityMb_value }, antennaLimit: { useDefault: options_antennaLimit_useDefault, value: options_antennaLimit_value }, + wordMuteLimit: { useDefault: options_wordMuteLimit_useDefault, value: options_wordMuteLimit_value }, }; } diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index 16c12f9928..001800ea26 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -62,6 +62,15 @@ + + + + + + + + + {{ i18n.ts.save }}
@@ -101,6 +110,7 @@ let options_canInvite = $ref(instance.baseRole.canInvite); let options_canManageCustomEmojis = $ref(instance.baseRole.canManageCustomEmojis); let options_driveCapacityMb = $ref(instance.baseRole.driveCapacityMb); let options_antennaLimit = $ref(instance.baseRole.antennaLimit); +let options_wordMuteLimit = $ref(instance.baseRole.wordMuteLimit); async function updateBaseRole() { await os.apiWithDialog('admin/roles/update-default-role-override', { @@ -112,6 +122,7 @@ async function updateBaseRole() { canManageCustomEmojis: options_canManageCustomEmojis, driveCapacityMb: options_driveCapacityMb, antennaLimit: options_antennaLimit, + wordMuteLimit: options_wordMuteLimit, }, }); } -- cgit v1.2.3-freya From f45059b7b1decbb5719eb18cffbc02db2a3bcea7 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 14 Jan 2023 10:46:40 +0900 Subject: fix --- packages/backend/src/server/api/endpoints/i/update.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index ef5ab16bc4..fe09eca674 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -173,7 +173,7 @@ export default class extends Endpoint { if (ps.mutedWords !== undefined) { // TODO: ちゃんと数える const length = JSON.stringify(ps.mutedWords).length; - if (length > (await this.roleService.getUserRoleOptions(user.id)).antennaLimit) { + if (length > (await this.roleService.getUserRoleOptions(user.id)).wordMuteLimit) { throw new ApiError(meta.errors.tooManyMutedWords); } -- cgit v1.2.3-freya From bcb5182e8686dcb517defe56a18324fb0ec72027 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 14 Jan 2023 10:48:11 +0900 Subject: Webhookの作成可能数を設定可能に MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + locales/ja-JP.yml | 1 + packages/backend/src/core/RoleService.ts | 3 +++ .../src/server/api/endpoints/i/webhooks/create.ts | 17 +++++++++++++++++ packages/frontend/src/pages/admin/roles.editor.vue | 15 +++++++++++++++ packages/frontend/src/pages/admin/roles.vue | 9 +++++++++ 6 files changed, 46 insertions(+) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/CHANGELOG.md b/CHANGELOG.md index c25b98d27a..0872e875f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,7 @@ You should also include the user name that made the change. - 非モデレーターでも、権限を持つロールをアサインされたユーザーはインスタンスの招待コードを発行できるように @syuilo - 非モデレーターでも、権限を持つロールをアサインされたユーザーはカスタム絵文字の追加、編集、削除を行えるように @syuilo - ハードワードミュートの最大文字数を設定可能に @syuilo +- Webhookの作成可能数を設定可能に @syuilo - Server: signToActivityPubGet is set to true by default @syuilo - Server: improve syslog performance @syuilo - Server: Use undici instead of node-fetch and got @tamaina diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1982681aed..f0291db54c 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -963,6 +963,7 @@ _role: driveCapacity: "ドライブ容量" antennaMax: "アンテナの作成可能数" wordMuteMax: "ワードミュートの最大文字数" + webhookMax: "Webhookの作成可能数" _condition: isLocal: "ローカルユーザー" isRemote: "リモートユーザー" diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index e7821ebd78..c639786ec6 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -22,6 +22,7 @@ export type RoleOptions = { driveCapacityMb: number; antennaLimit: number; wordMuteLimit: number; + webhookLimit: number; }; export const DEFAULT_ROLE: RoleOptions = { @@ -33,6 +34,7 @@ export const DEFAULT_ROLE: RoleOptions = { driveCapacityMb: 100, antennaLimit: 5, wordMuteLimit: 200, + webhookLimit: 3, }; @Injectable() @@ -203,6 +205,7 @@ export class RoleService implements OnApplicationShutdown { driveCapacityMb: Math.max(...getOptionValues('driveCapacityMb')), antennaLimit: Math.max(...getOptionValues('antennaLimit')), wordMuteLimit: Math.max(...getOptionValues('wordMuteLimit')), + webhookLimit: Math.max(...getOptionValues('webhookLimit')), }; } 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 584c2ba6a4..45cfd8161c 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -5,6 +5,7 @@ import type { WebhooksRepository } from '@/models/index.js'; import { webhookEventTypes } from '@/models/entities/Webhook.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; +import { RoleService } from '@/core/RoleService.js'; export const meta = { tags: ['webhooks'], @@ -12,6 +13,14 @@ export const meta = { requireCredential: true, kind: 'write:account', + + errors: { + tooManyWebhooks: { + message: 'You cannot create webhook any more.', + code: 'TOO_MANY_WEBHOOKS', + id: '87a9bb19-111e-4e37-81d3-a3e7426453b0', + }, + }, } as const; export const paramDef = { @@ -38,8 +47,16 @@ export default class extends Endpoint { private idService: IdService, private globalEventService: GlobalEventService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { + const currentWebhooksCount = await this.webhooksRepository.countBy({ + userId: me.id, + }); + if (currentWebhooksCount > (await this.roleService.getUserRoleOptions(me.id)).webhookLimit) { + throw new ApiError(meta.errors.tooManyWebhooks); + } + const webhook = await this.webhooksRepository.insert({ id: this.idService.genId(), createdAt: new Date(), diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 30bc6c238e..9d26707423 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -140,6 +140,18 @@ + + + + +
+ + + + + +
+
@@ -209,6 +221,8 @@ let options_antennaLimit_useDefault = $ref(role?.options?.antennaLimit?.useDefau let options_antennaLimit_value = $ref(role?.options?.antennaLimit?.value ?? 0); let options_wordMuteLimit_useDefault = $ref(role?.options?.wordMuteLimit?.useDefault ?? true); let options_wordMuteLimit_value = $ref(role?.options?.wordMuteLimit?.value ?? 0); +let options_webhookLimit_useDefault = $ref(role?.options?.webhookLimit?.useDefault ?? true); +let options_webhookLimit_value = $ref(role?.options?.webhookLimit?.value ?? 0); if (_DEV_) { watch($$(condFormula), () => { @@ -226,6 +240,7 @@ function getOptions() { driveCapacityMb: { useDefault: options_driveCapacityMb_useDefault, value: options_driveCapacityMb_value }, antennaLimit: { useDefault: options_antennaLimit_useDefault, value: options_antennaLimit_value }, wordMuteLimit: { useDefault: options_wordMuteLimit_useDefault, value: options_wordMuteLimit_value }, + webhookLimit: { useDefault: options_webhookLimit_useDefault, value: options_webhookLimit_value }, }; } diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index 001800ea26..cde5142a63 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -71,6 +71,13 @@ + + + + + + + {{ i18n.ts.save }} @@ -111,6 +118,7 @@ let options_canManageCustomEmojis = $ref(instance.baseRole.canManageCustomEmojis let options_driveCapacityMb = $ref(instance.baseRole.driveCapacityMb); let options_antennaLimit = $ref(instance.baseRole.antennaLimit); let options_wordMuteLimit = $ref(instance.baseRole.wordMuteLimit); +let options_webhookLimit = $ref(instance.baseRole.webhookLimit); async function updateBaseRole() { await os.apiWithDialog('admin/roles/update-default-role-override', { @@ -123,6 +131,7 @@ async function updateBaseRole() { driveCapacityMb: options_driveCapacityMb, antennaLimit: options_antennaLimit, wordMuteLimit: options_wordMuteLimit, + webhookLimit: options_webhookLimit, }, }); } -- cgit v1.2.3-freya From c730973294872f261279d84a05d679ab335b4f9c Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 14 Jan 2023 13:36:18 +0900 Subject: 多分 fix #9551 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/server/api/endpoints/following/requests/list.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages/backend/src/server/api/endpoints') 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 f581023e2e..d68248fab9 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/list.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts @@ -60,7 +60,8 @@ export default class extends Endpoint { private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.followRequestsRepository.createQueryBuilder('request'), ps.sinceId, ps.untilId); + const query = this.queryService.makePaginationQuery(this.followRequestsRepository.createQueryBuilder('request'), ps.sinceId, ps.untilId) + .andWhere('request.followeeId = :meId', { meId: me.id }); const requests = await query .take(ps.limit) -- cgit v1.2.3-freya From d52f0617a1ffd91d486d999fa004eb6f500e6cdd Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 14 Jan 2023 13:41:53 +0900 Subject: fix(server): ドライブ容量超過時のエラーが適切にレスポンスされない問題を修正 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #9550 --- CHANGELOG.md | 1 + packages/backend/src/server/api/endpoints/drive/files/create.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/CHANGELOG.md b/CHANGELOG.md index 0872e875f4..c13d11b0ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -133,6 +133,7 @@ You should also include the user name that made the change. - Server: 非公開のクリップのURLでOGPレンダリングされる問題を修正 @syuilo - Server: アンテナタイムライン(ストリーミング)が、フォローしていないユーザーの鍵投稿も拾ってしまう @syuilo - Server: follow request list api pagination @sim1222 +- Server: ドライブ容量超過時のエラーが適切にレスポンスされない問題を修正 @syuilo - Client: パスワードマネージャーなどでユーザー名がオートコンプリートされない問題を修正 @massongit - Client: 日付形式の文字列などがカスタム絵文字として表示されるのを修正 @syuilo - Client: case insensitive emoji search @saschanaz 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 6abbab0270..b3bdef41d3 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -90,7 +90,7 @@ export default class extends Endpoint { } } - const meta = await this.metaService.fetch(); + const instance = await this.metaService.fetch(); try { // Create file @@ -102,8 +102,8 @@ export default class extends Endpoint { folderId: ps.folderId, force: ps.force, sensitive: ps.isSensitive, - requestIp: meta.enableIpLogging ? ip : null, - requestHeaders: meta.enableIpLogging ? headers : null, + requestIp: instance.enableIpLogging ? ip : null, + requestHeaders: instance.enableIpLogging ? headers : null, }); return await this.driveFileEntityService.pack(driveFile, { self: true }); } catch (err) { @@ -116,7 +116,7 @@ export default class extends Endpoint { } throw new ApiError(); } finally { - cleanup!(); + cleanup!(); } }); } -- cgit v1.2.3-freya From 7df3ca7388954ee43db28725092e3260f1f23d98 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 14 Jan 2023 15:59:15 +0900 Subject: enhance(server): add rate limits for some endpoints --- CHANGELOG.md | 1 + packages/backend/src/server/api/endpoints/blocking/create.ts | 4 ++-- packages/backend/src/server/api/endpoints/channels/create.ts | 6 ++++++ packages/backend/src/server/api/endpoints/clips/add-note.ts | 8 +++++++- packages/backend/src/server/api/endpoints/mute/create.ts | 8 +++++++- .../backend/src/server/api/endpoints/notes/favorites/create.ts | 6 ++++++ packages/backend/src/server/api/endpoints/users/lists/push.ts | 6 ++++++ 7 files changed, 35 insertions(+), 4 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/CHANGELOG.md b/CHANGELOG.md index c13d11b0ad..48dc6ff92d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,7 @@ You should also include the user name that made the change. - Server: Judge instance block by endsWith @tamaina - Server: improve note scoring for featured notes @CyberRex0 - Server: アンケート選択肢の文字数制限を緩和 @syuilo +- Server: add rate limits for some endpoints @syuilo - Server: improve stats api performance @syuilo - Server: improve nodeinfo performance @syuilo - Server: delete outdated notifications regularly to improve db performance @syuilo diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index c468010bce..d9ba99f209 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -5,15 +5,15 @@ import type { UsersRepository, BlockingsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../error.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['account'], limit: { duration: ms('1hour'), - max: 100, + max: 20, }, requireCredential: true, diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index 10f8b24629..dff8a9d10d 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { ChannelsRepository, DriveFilesRepository } from '@/models/index.js'; import type { Channel } from '@/models/entities/Channel.js'; @@ -14,6 +15,11 @@ export const meta = { kind: 'write:channels', + limit: { + duration: ms('1hour'), + max: 10, + }, + res: { type: 'object', optional: false, nullable: false, 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 a242124e6a..c5ac4f22d7 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -1,10 +1,11 @@ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; -import { ApiError } from '../../error.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['account', 'notes', 'clips'], @@ -13,6 +14,11 @@ export const meta = { kind: 'write:account', + limit: { + duration: ms('1hour'), + max: 20, + }, + errors: { noSuchClip: { message: 'No such clip.', diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 5ead470314..9099eea52e 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -1,12 +1,13 @@ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; import type { MutingsRepository } from '@/models/index.js'; import type { Muting } from '@/models/entities/Muting.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../error.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['account'], @@ -15,6 +16,11 @@ export const meta = { kind: 'write:mutes', + limit: { + duration: ms('1hour'), + max: 20, + }, + errors: { noSuchUser: { message: 'No such user.', 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 e742c1bb35..acf22a5ad4 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import type { NoteFavoritesRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -13,6 +14,11 @@ export const meta = { kind: 'write:favorites', + limit: { + duration: ms('1hour'), + max: 20, + }, + errors: { noSuchNote: { message: 'No such note.', 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 c3a1308286..96be7e11e8 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; @@ -15,6 +16,11 @@ export const meta = { description: 'Add a user to an existing list.', + limit: { + duration: ms('1hour'), + max: 30, + }, + errors: { noSuchList: { message: 'No such list.', -- cgit v1.2.3-freya From 46d2a8726e4ea95ae1107d1179e71c07d3c4672c Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 14 Jan 2023 16:04:13 +0900 Subject: fix missing import --- packages/backend/src/server/api/endpoints/i/webhooks/create.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages/backend/src/server/api/endpoints') 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 45cfd8161c..3d89b77a7b 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -6,6 +6,7 @@ import { webhookEventTypes } from '@/models/entities/Webhook.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; +import { ApiError } from '@/server/api/error.js'; export const meta = { tags: ['webhooks'], -- cgit v1.2.3-freya From c2009acb2d3a505f8d140827471cf953f6d922e6 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 14 Jan 2023 16:14:24 +0900 Subject: enhance: クリップおよびクリップ内のノートの作成可能数を設定可能に MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + locales/ja-JP.yml | 2 ++ packages/backend/src/core/RoleService.ts | 6 +++++ .../src/server/api/endpoints/clips/add-note.ts | 15 +++++++++++ .../src/server/api/endpoints/clips/create.ts | 18 +++++++++++++ packages/frontend/src/pages/admin/roles.editor.vue | 30 ++++++++++++++++++++++ packages/frontend/src/pages/admin/roles.vue | 18 +++++++++++++ 7 files changed, 90 insertions(+) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/CHANGELOG.md b/CHANGELOG.md index 48dc6ff92d..2901872465 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ You should also include the user name that made the change. - Add Cloudflare Turnstile CAPTCHA support @CyberRex0 - 非モデレーターでも、権限を持つロールをアサインされたユーザーはインスタンスの招待コードを発行できるように @syuilo - 非モデレーターでも、権限を持つロールをアサインされたユーザーはカスタム絵文字の追加、編集、削除を行えるように @syuilo +- クリップおよびクリップ内のノートの作成可能数を設定可能に @syuilo - ハードワードミュートの最大文字数を設定可能に @syuilo - Webhookの作成可能数を設定可能に @syuilo - Server: signToActivityPubGet is set to true by default @syuilo diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index f0291db54c..fbb7633a1e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -964,6 +964,8 @@ _role: antennaMax: "アンテナの作成可能数" wordMuteMax: "ワードミュートの最大文字数" webhookMax: "Webhookの作成可能数" + clipMax: "クリップの作成可能数" + noteEachClipsMax: "クリップ内のノートの最大数" _condition: isLocal: "ローカルユーザー" isRemote: "リモートユーザー" diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index c639786ec6..39413e2a55 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -23,6 +23,8 @@ export type RoleOptions = { antennaLimit: number; wordMuteLimit: number; webhookLimit: number; + clipLimit: number; + noteEachClipsLimit: number; }; export const DEFAULT_ROLE: RoleOptions = { @@ -35,6 +37,8 @@ export const DEFAULT_ROLE: RoleOptions = { antennaLimit: 5, wordMuteLimit: 200, webhookLimit: 3, + clipLimit: 10, + noteEachClipsLimit: 200, }; @Injectable() @@ -206,6 +210,8 @@ export class RoleService implements OnApplicationShutdown { antennaLimit: Math.max(...getOptionValues('antennaLimit')), wordMuteLimit: Math.max(...getOptionValues('wordMuteLimit')), webhookLimit: Math.max(...getOptionValues('webhookLimit')), + clipLimit: Math.max(...getOptionValues('clipLimit')), + noteEachClipsLimit: Math.max(...getOptionValues('noteEachClipsLimit')), }; } 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 c5ac4f22d7..3cf096c242 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -5,6 +5,7 @@ import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -37,6 +38,12 @@ export const meta = { code: 'ALREADY_CLIPPED', id: '734806c4-542c-463a-9311-15c512803965', }, + + tooManyClipNotes: { + message: 'You cannot add notes to the clip any more.', + code: 'TOO_MANY_CLIP_NOTES', + id: 'f0dba960-ff73-4615-8df4-d6ac5d9dc118', + }, }, } as const; @@ -60,6 +67,7 @@ export default class extends Endpoint { private clipNotesRepository: ClipNotesRepository, private idService: IdService, + private roleService: RoleService, private getterService: GetterService, ) { super(meta, paramDef, async (ps, me) => { @@ -86,6 +94,13 @@ export default class extends Endpoint { throw new ApiError(meta.errors.alreadyClipped); } + const currentCount = await this.clipNotesRepository.countBy({ + clipId: clip.id, + }); + if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).noteEachClipsLimit) { + throw new ApiError(meta.errors.tooManyClipNotes); + } + await this.clipNotesRepository.insert({ id: this.idService.genId(), noteId: note.id, diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index d300203a21..abc0288c89 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -4,6 +4,8 @@ import { IdService } from '@/core/IdService.js'; import type { ClipsRepository } from '@/models/index.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; +import { RoleService } from '@/core/RoleService.js'; +import { ApiError } from '@/server/api/error.js'; export const meta = { tags: ['clips'], @@ -17,6 +19,14 @@ export const meta = { optional: false, nullable: false, ref: 'Clip', }, + + errors: { + tooManyClips: { + message: 'You cannot create clip any more.', + code: 'TOO_MANY_CLIPS', + id: '920f7c2d-6208-4b76-8082-e632020f5883', + }, + }, } as const; export const paramDef = { @@ -37,9 +47,17 @@ export default class extends Endpoint { private clipsRepository: ClipsRepository, private clipEntityService: ClipEntityService, + private roleService: RoleService, private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { + const currentCount = await this.clipsRepository.countBy({ + userId: me.id, + }); + if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).clipLimit) { + throw new ApiError(meta.errors.tooManyClips); + } + const clip = await this.clipsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 9d26707423..2fceaf9ce2 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -152,6 +152,30 @@ + + + + +
+ + + + + +
+
+ + + + +
+ + + + + +
+
@@ -223,6 +247,10 @@ let options_wordMuteLimit_useDefault = $ref(role?.options?.wordMuteLimit?.useDef let options_wordMuteLimit_value = $ref(role?.options?.wordMuteLimit?.value ?? 0); let options_webhookLimit_useDefault = $ref(role?.options?.webhookLimit?.useDefault ?? true); let options_webhookLimit_value = $ref(role?.options?.webhookLimit?.value ?? 0); +let options_clipLimit_useDefault = $ref(role?.options?.clipLimit?.useDefault ?? true); +let options_clipLimit_value = $ref(role?.options?.clipLimit?.value ?? 0); +let options_noteEachClipsLimit_useDefault = $ref(role?.options?.noteEachClipsLimit?.useDefault ?? true); +let options_noteEachClipsLimit_value = $ref(role?.options?.noteEachClipsLimit?.value ?? 0); if (_DEV_) { watch($$(condFormula), () => { @@ -241,6 +269,8 @@ function getOptions() { antennaLimit: { useDefault: options_antennaLimit_useDefault, value: options_antennaLimit_value }, wordMuteLimit: { useDefault: options_wordMuteLimit_useDefault, value: options_wordMuteLimit_value }, webhookLimit: { useDefault: options_webhookLimit_useDefault, value: options_webhookLimit_value }, + clipLimit: { useDefault: options_clipLimit_useDefault, value: options_clipLimit_value }, + noteEachClipsLimit: { useDefault: options_noteEachClipsLimit_useDefault, value: options_noteEachClipsLimit_value }, }; } diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index cde5142a63..13b893ba46 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -78,6 +78,20 @@ + + + + + + + + + + + + + + {{ i18n.ts.save }} @@ -119,6 +133,8 @@ let options_driveCapacityMb = $ref(instance.baseRole.driveCapacityMb); let options_antennaLimit = $ref(instance.baseRole.antennaLimit); let options_wordMuteLimit = $ref(instance.baseRole.wordMuteLimit); let options_webhookLimit = $ref(instance.baseRole.webhookLimit); +let options_clipLimit = $ref(instance.baseRole.clipLimit); +let options_noteEachClipsLimit = $ref(instance.baseRole.noteEachClipsLimit); async function updateBaseRole() { await os.apiWithDialog('admin/roles/update-default-role-override', { @@ -132,6 +148,8 @@ async function updateBaseRole() { antennaLimit: options_antennaLimit, wordMuteLimit: options_wordMuteLimit, webhookLimit: options_webhookLimit, + clipLimit: options_clipLimit, + noteEachClipsLimit: options_noteEachClipsLimit, }, }); } -- cgit v1.2.3-freya From 76c049522e2fb506915f9fad0cd763236245f45c Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 14 Jan 2023 17:38:16 +0900 Subject: enhance: ユーザーリストおよびユーザーリスト内のユーザーの作成可能数を設定可能に MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + locales/ja-JP.yml | 2 ++ packages/backend/src/core/RoleService.ts | 6 +++++ packages/backend/src/core/UserListService.ts | 11 +++++++- .../processors/ImportUserListsProcessorService.ts | 4 +-- .../src/server/api/endpoints/users/lists/create.ts | 18 +++++++++++++ .../src/server/api/endpoints/users/lists/push.ts | 2 +- packages/frontend/src/pages/admin/roles.editor.vue | 30 ++++++++++++++++++++++ packages/frontend/src/pages/admin/roles.vue | 18 +++++++++++++ 9 files changed, 88 insertions(+), 4 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') diff --git a/CHANGELOG.md b/CHANGELOG.md index b7b3260cc7..d679a4c01f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ You should also include the user name that made the change. - 非モデレーターでも、権限を持つロールをアサインされたユーザーはインスタンスの招待コードを発行できるように @syuilo - 非モデレーターでも、権限を持つロールをアサインされたユーザーはカスタム絵文字の追加、編集、削除を行えるように @syuilo - クリップおよびクリップ内のノートの作成可能数を設定可能に @syuilo +- ユーザーリストおよびユーザーリスト内のユーザーの作成可能数を設定可能に @syuilo - ハードワードミュートの最大文字数を設定可能に @syuilo - Webhookの作成可能数を設定可能に @syuilo - Server: signToActivityPubGet is set to true by default @syuilo diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index fbb7633a1e..d4fba36746 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -966,6 +966,8 @@ _role: webhookMax: "Webhookの作成可能数" clipMax: "クリップの作成可能数" noteEachClipsMax: "クリップ内のノートの最大数" + userListMax: "ユーザーリストの作成可能数" + userEachUserListsMax: "ユーザーリスト内のユーザーの最大数" _condition: isLocal: "ローカルユーザー" isRemote: "リモートユーザー" diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 39413e2a55..0ddcb44ca1 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -25,6 +25,8 @@ export type RoleOptions = { webhookLimit: number; clipLimit: number; noteEachClipsLimit: number; + userListLimit: number; + userEachUserListsLimit: number; }; export const DEFAULT_ROLE: RoleOptions = { @@ -39,6 +41,8 @@ export const DEFAULT_ROLE: RoleOptions = { webhookLimit: 3, clipLimit: 10, noteEachClipsLimit: 200, + userListLimit: 10, + userEachUserListsLimit: 50, }; @Injectable() @@ -212,6 +216,8 @@ export class RoleService implements OnApplicationShutdown { webhookLimit: Math.max(...getOptionValues('webhookLimit')), clipLimit: Math.max(...getOptionValues('clipLimit')), noteEachClipsLimit: Math.max(...getOptionValues('noteEachClipsLimit')), + userListLimit: Math.max(...getOptionValues('userListLimit')), + userEachUserListsLimit: Math.max(...getOptionValues('userEachUserListsLimit')), }; } diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 054387ff8e..18c9787fa8 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -10,6 +10,7 @@ import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ProxyAccountService } from '@/core/ProxyAccountService.js'; import { bindThis } from '@/decorators.js'; +import { RoleService } from '@/core/RoleService.js'; @Injectable() export class UserListService { @@ -23,13 +24,21 @@ export class UserListService { private userEntityService: UserEntityService, private idService: IdService, private userFollowingService: UserFollowingService, + private roleService: RoleService, private globalEventServie: GlobalEventService, private proxyAccountService: ProxyAccountService, ) { } @bindThis - public async push(target: User, list: UserList) { + public async push(target: User, list: UserList, me: User) { + const currentCount = await this.userListJoiningsRepository.countBy({ + userListId: list.id, + }); + if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).userEachUserListsLimit) { + throw new Error('Too many users'); + } + await this.userListJoiningsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index 1bec77b837..a9672250c8 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -10,10 +10,10 @@ import { DownloadService } from '@/core/DownloadService.js'; import { UserListService } from '@/core/UserListService.js'; import { IdService } from '@/core/IdService.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserImportJobData } from '../types.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class ImportUserListsProcessorService { @@ -102,7 +102,7 @@ export class ImportUserListsProcessorService { if (await this.userListJoiningsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue; - this.userListService.push(target, list!); + this.userListService.push(target, list!, user); } catch (e) { this.logger.warn(`Error in line:${linenum} ${e}`); } 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 99f0751ea8..810c9909b2 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -5,6 +5,8 @@ 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'; +import { ApiError } from '@/server/api/error'; +import { RoleService } from '@/core/RoleService.js'; export const meta = { tags: ['lists'], @@ -20,6 +22,14 @@ export const meta = { optional: false, nullable: false, ref: 'UserList', }, + + errors: { + tooManyUserLists: { + message: 'You cannot create user list any more.', + code: 'TOO_MANY_USERLISTS', + id: '0cf21a28-7715-4f39-a20d-777bfdb8d138', + }, + }, } as const; export const paramDef = { @@ -39,8 +49,16 @@ export default class extends Endpoint { private userListEntityService: UserListEntityService, private idService: IdService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { + const currentCount = await this.userListsRepository.countBy({ + userId: me.id, + }); + if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).userListLimit) { + throw new ApiError(meta.errors.tooManyUserLists); + } + const userList = await this.userListsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), 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 96be7e11e8..3a079ee1ab 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -111,7 +111,7 @@ export default class extends Endpoint { } // Push the user - await this.userListService.push(user, userList); + await this.userListService.push(user, userList, me); }); } } diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 2fceaf9ce2..f836b46202 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -176,6 +176,30 @@ + + + + +
+ + + + + +
+
+ + + + +
+ + + + + +
+
@@ -251,6 +275,10 @@ let options_clipLimit_useDefault = $ref(role?.options?.clipLimit?.useDefault ?? let options_clipLimit_value = $ref(role?.options?.clipLimit?.value ?? 0); let options_noteEachClipsLimit_useDefault = $ref(role?.options?.noteEachClipsLimit?.useDefault ?? true); let options_noteEachClipsLimit_value = $ref(role?.options?.noteEachClipsLimit?.value ?? 0); +let options_userListLimit_useDefault = $ref(role?.options?.userListLimit?.useDefault ?? true); +let options_userListLimit_value = $ref(role?.options?.userListLimit?.value ?? 0); +let options_userEachUserListsLimit_useDefault = $ref(role?.options?.userEachUserListsLimit?.useDefault ?? true); +let options_userEachUserListsLimit_value = $ref(role?.options?.userEachUserListsLimit?.value ?? 0); if (_DEV_) { watch($$(condFormula), () => { @@ -271,6 +299,8 @@ function getOptions() { webhookLimit: { useDefault: options_webhookLimit_useDefault, value: options_webhookLimit_value }, clipLimit: { useDefault: options_clipLimit_useDefault, value: options_clipLimit_value }, noteEachClipsLimit: { useDefault: options_noteEachClipsLimit_useDefault, value: options_noteEachClipsLimit_value }, + userListLimit: { useDefault: options_userListLimit_useDefault, value: options_userListLimit_value }, + userEachUserListsLimit: { useDefault: options_userEachUserListsLimit_useDefault, value: options_userEachUserListsLimit_value }, }; } diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index 13b893ba46..7a2f269310 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -92,6 +92,20 @@ + + + + + + + + + + + + + + {{ i18n.ts.save }} @@ -135,6 +149,8 @@ let options_wordMuteLimit = $ref(instance.baseRole.wordMuteLimit); let options_webhookLimit = $ref(instance.baseRole.webhookLimit); let options_clipLimit = $ref(instance.baseRole.clipLimit); let options_noteEachClipsLimit = $ref(instance.baseRole.noteEachClipsLimit); +let options_userListLimit = $ref(instance.baseRole.userListLimit); +let options_userEachUserListsLimit = $ref(instance.baseRole.userEachUserListsLimit); async function updateBaseRole() { await os.apiWithDialog('admin/roles/update-default-role-override', { @@ -150,6 +166,8 @@ async function updateBaseRole() { webhookLimit: options_webhookLimit, clipLimit: options_clipLimit, noteEachClipsLimit: options_noteEachClipsLimit, + userListLimit: options_userListLimit, + userEachUserListsLimit: options_userEachUserListsLimit, }, }); } -- cgit v1.2.3-freya From 6cb9612943d34ce5399c4e040d221a7535b48bc1 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 14 Jan 2023 17:40:51 +0900 Subject: fix import --- packages/backend/src/server/api/endpoints/users/lists/create.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src/server/api/endpoints') 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 810c9909b2..22e5e3ce78 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -5,7 +5,7 @@ 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'; -import { ApiError } from '@/server/api/error'; +import { ApiError } from '@/server/api/error.js'; import { RoleService } from '@/core/RoleService.js'; export const meta = { -- cgit v1.2.3-freya From 20474492943853a5d0d9d3f6c8f8321e19f3ced0 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 14 Jan 2023 20:21:03 +0900 Subject: enhance(server): add rate limits for some endpoints --- packages/backend/src/server/api/endpoints/drive/folders/create.ts | 6 ++++++ packages/backend/src/server/api/endpoints/following/create.ts | 4 ++-- packages/backend/src/server/api/endpoints/gallery/posts/create.ts | 2 +- .../backend/src/server/api/endpoints/messaging/messages/create.ts | 6 ++++++ .../backend/src/server/api/endpoints/notes/thread-muting/create.ts | 6 ++++++ packages/backend/src/server/api/endpoints/pages/create.ts | 2 +- packages/backend/src/server/api/endpoints/users/groups/create.ts | 6 ++++++ 7 files changed, 28 insertions(+), 4 deletions(-) (limited to 'packages/backend/src/server/api/endpoints') 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 e7c11a8c13..39c9c6bc58 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { DriveFoldersRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; @@ -14,6 +15,11 @@ export const meta = { kind: 'write:drive', + limit: { + duration: ms('1hour'), + max: 10, + }, + errors: { noSuchFolder: { message: 'No such folder.', diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index f879429372..411c39110a 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -6,15 +6,15 @@ 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 '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['following', 'users'], limit: { duration: ms('1hour'), - max: 100, + max: 50, }, requireCredential: true, 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 2842308510..3d9d471502 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -18,7 +18,7 @@ export const meta = { limit: { duration: ms('1hour'), - max: 300, + max: 20, }, res: { 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 00e65b4875..e9ffc7a9eb 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { BlockingsRepository, UserGroupJoiningsRepository, DriveFilesRepository, UserGroupsRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; @@ -15,6 +16,11 @@ export const meta = { kind: 'write:messaging', + limit: { + duration: ms('1hour'), + max: 120, + }, + res: { type: 'object', optional: false, nullable: false, 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 140614d36e..abea069da8 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,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import type { NotesRepository, NoteThreadMutingsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -14,6 +15,11 @@ export const meta = { kind: 'write:account', + limit: { + duration: ms('1hour'), + max: 10, + }, + errors: { noSuchNote: { message: 'No such note.', diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index eae8f18403..4015bf1f29 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -17,7 +17,7 @@ export const meta = { limit: { duration: ms('1hour'), - max: 300, + max: 10, }, res: { 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 c1f4f48445..24dbf5ca3c 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import type { UserGroup } from '@/models/entities/UserGroup.js'; @@ -16,6 +17,11 @@ export const meta = { description: 'Create a new group.', + limit: { + duration: ms('1hour'), + max: 10, + }, + res: { type: 'object', optional: false, nullable: false, -- cgit v1.2.3-freya From 81f11d8f860803cf01fbb2cfd106bd3344db98f2 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 15 Jan 2023 20:52:53 +0900 Subject: refactor: rename role.options -> role.policies --- locales/ja-JP.yml | 1 + .../backend/migration/1673783015567-Policies.js | 13 ++ packages/backend/src/core/DriveService.ts | 4 +- packages/backend/src/core/NoteCreateService.ts | 2 +- packages/backend/src/core/NotePiningService.ts | 2 +- packages/backend/src/core/RoleService.ts | 26 ++-- packages/backend/src/core/UserListService.ts | 2 +- .../backend/src/core/entities/RoleEntityService.ts | 10 +- .../backend/src/core/entities/UserEntityService.ts | 4 +- packages/backend/src/models/entities/Meta.ts | 2 +- packages/backend/src/models/entities/Role.ts | 2 +- .../backend/src/server/NodeinfoServerService.ts | 8 +- packages/backend/src/server/api/ApiCallService.ts | 8 +- packages/backend/src/server/api/EndpointsModule.ts | 8 +- packages/backend/src/server/api/endpoints.ts | 6 +- .../api/endpoints/admin/emoji/add-aliases-bulk.ts | 2 +- .../src/server/api/endpoints/admin/emoji/add.ts | 2 +- .../src/server/api/endpoints/admin/emoji/copy.ts | 2 +- .../api/endpoints/admin/emoji/delete-bulk.ts | 2 +- .../src/server/api/endpoints/admin/emoji/delete.ts | 2 +- .../server/api/endpoints/admin/emoji/import-zip.ts | 2 +- .../api/endpoints/admin/emoji/list-remote.ts | 2 +- .../src/server/api/endpoints/admin/emoji/list.ts | 2 +- .../endpoints/admin/emoji/remove-aliases-bulk.ts | 2 +- .../api/endpoints/admin/emoji/set-aliases-bulk.ts | 2 +- .../api/endpoints/admin/emoji/set-category-bulk.ts | 2 +- .../src/server/api/endpoints/admin/emoji/update.ts | 2 +- .../backend/src/server/api/endpoints/admin/meta.ts | 4 +- .../src/server/api/endpoints/admin/roles/create.ts | 6 +- .../admin/roles/update-default-policies.ts | 42 ++++++ .../admin/roles/update-default-role-override.ts | 42 ------ .../src/server/api/endpoints/admin/roles/update.ts | 6 +- .../src/server/api/endpoints/admin/show-user.ts | 3 +- .../src/server/api/endpoints/antennas/create.ts | 2 +- .../src/server/api/endpoints/clips/add-note.ts | 2 +- .../src/server/api/endpoints/clips/create.ts | 2 +- packages/backend/src/server/api/endpoints/drive.ts | 4 +- .../backend/src/server/api/endpoints/i/update.ts | 2 +- .../src/server/api/endpoints/i/webhooks/create.ts | 2 +- .../backend/src/server/api/endpoints/invite.ts | 2 +- packages/backend/src/server/api/endpoints/meta.ts | 4 +- .../server/api/endpoints/notes/global-timeline.ts | 4 +- .../server/api/endpoints/notes/hybrid-timeline.ts | 4 +- .../server/api/endpoints/notes/local-timeline.ts | 4 +- .../src/server/api/endpoints/users/lists/create.ts | 2 +- .../server/api/stream/channels/global-timeline.ts | 4 +- .../server/api/stream/channels/hybrid-timeline.ts | 4 +- .../server/api/stream/channels/local-timeline.ts | 4 +- packages/backend/src/server/api/stream/types.ts | 2 +- packages/frontend/src/pages/admin/roles.editor.vue | 142 ++++++++++----------- packages/frontend/src/pages/admin/roles.vue | 119 ++++++++--------- packages/frontend/src/pages/timeline.vue | 4 +- packages/frontend/src/pages/user-info.vue | 20 ++- packages/frontend/src/ui/_common_/common.ts | 4 +- 54 files changed, 292 insertions(+), 270 deletions(-) create mode 100644 packages/backend/migration/1673783015567-Policies.js create mode 100644 packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts delete mode 100644 packages/backend/src/server/api/endpoints/admin/roles/update-default-role-override.ts (limited to 'packages/backend/src/server/api/endpoints') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 79856447a0..786d41ca41 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -952,6 +952,7 @@ _role: isPublic: "ロールを公開" descriptionOfIsPublic: "ロールにアサインされたユーザーを誰でも見ることができます。また、ユーザーのプロフィールでこのロールが表示されます。" options: "オプション" + policies: "ポリシー" baseRole: "ベースロール" useBaseValue: "ベースロールの値を使用" chooseRoleToAssign: "アサインするロールを選択" diff --git a/packages/backend/migration/1673783015567-Policies.js b/packages/backend/migration/1673783015567-Policies.js new file mode 100644 index 0000000000..8b36921d41 --- /dev/null +++ b/packages/backend/migration/1673783015567-Policies.js @@ -0,0 +1,13 @@ +export class Policies1673783015567 { + name = 'Policies1673783015567' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "role" RENAME COLUMN "options" TO "policies"`); + await queryRunner.query(`ALTER TABLE "meta" RENAME COLUMN "defaultRoleOverride" TO "policies"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" RENAME COLUMN "policies" TO "defaultRoleOverride"`); + await queryRunner.query(`ALTER TABLE "role" RENAME COLUMN "policies" TO "options"`); + } +} diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 9002c96a65..598a457e83 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -479,8 +479,8 @@ export class DriveService { if (user && !isLink) { const usage = await this.driveFileEntityService.calcDriveUsageOf(user); - const role = await this.roleService.getUserRoleOptions(user.id); - const driveCapacity = 1024 * 1024 * role.driveCapacityMb; + const policies = await this.roleService.getUserPolicies(user.id); + const driveCapacity = 1024 * 1024 * policies.driveCapacityMb; this.registerLogger.debug('drive capacity override applied'); this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`); diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 112b84fdf9..3dc44a25fe 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -226,7 +226,7 @@ export class NoteCreateService { if (data.channel != null) data.localOnly = true; if (data.visibility === 'public' && data.channel == null) { - if ((await this.roleService.getUserRoleOptions(user.id)).canPublicNote === false) { + if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) { data.visibility = 'home'; } } diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts index bc038e17a7..bb6def1edb 100644 --- a/packages/backend/src/core/NotePiningService.ts +++ b/packages/backend/src/core/NotePiningService.ts @@ -57,7 +57,7 @@ export class NotePiningService { const pinings = await this.userNotePiningsRepository.findBy({ userId: user.id }); - if (pinings.length >= (await this.roleService.getUserRoleOptions(user.id)).pinLimit) { + if (pinings.length >= (await this.roleService.getUserPolicies(user.id)).pinLimit) { throw new IdentifiableError('15a018eb-58e5-4da1-93be-330fcc5e4e1a', 'You can not pin notes any more.'); } diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 42b477d9ed..abad058303 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -13,7 +13,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { StreamMessages } from '@/server/api/stream/types.js'; import type { OnApplicationShutdown } from '@nestjs/common'; -export type RoleOptions = { +export type RolePolicies = { gtlAvailable: boolean; ltlAvailable: boolean; canPublicNote: boolean; @@ -31,7 +31,7 @@ export type RoleOptions = { rateLimitFactor: number; }; -export const DEFAULT_ROLE: RoleOptions = { +export const DEFAULT_POLICIES: RolePolicies = { gtlAvailable: true, ltlAvailable: true, canPublicNote: true, @@ -195,26 +195,26 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - public async getUserRoleOptions(userId: User['id'] | null): Promise { + public async getUserPolicies(userId: User['id'] | null): Promise { const meta = await this.metaService.fetch(); - const baseRoleOptions = { ...DEFAULT_ROLE, ...meta.defaultRoleOverride }; + const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies }; - if (userId == null) return baseRoleOptions; + if (userId == null) return basePolicies; const roles = await this.getUserRoles(userId); - function calc(name: T, aggregate: (values: RoleOptions[T][]) => RoleOptions[T]) { - if (roles.length === 0) return baseRoleOptions[name]; + function calc(name: T, aggregate: (values: RolePolicies[T][]) => RolePolicies[T]) { + if (roles.length === 0) return basePolicies[name]; - const options = roles.map(role => role.options[name] ?? { priority: 0, useDefault: true }); + const policies = roles.map(role => role.policies[name] ?? { priority: 0, useDefault: true }); - const p2 = options.filter(option => option.priority === 2); - if (p2.length > 0) return aggregate(p2.map(option => option.useDefault ? baseRoleOptions[name] : option.value)); + const p2 = policies.filter(policy => policy.priority === 2); + if (p2.length > 0) return aggregate(p2.map(policy => policy.useDefault ? basePolicies[name] : policy.value)); - const p1 = options.filter(option => option.priority === 1); - if (p1.length > 0) return aggregate(p2.map(option => option.useDefault ? baseRoleOptions[name] : option.value)); + const p1 = policies.filter(policy => policy.priority === 1); + if (p1.length > 0) return aggregate(p2.map(policy => policy.useDefault ? basePolicies[name] : policy.value)); - return aggregate(options.map(option => option.useDefault ? baseRoleOptions[name] : option.value)); + return aggregate(policies.map(policy => policy.useDefault ? basePolicies[name] : policy.value)); } return { diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 18c9787fa8..fc48738307 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -35,7 +35,7 @@ export class UserListService { const currentCount = await this.userListJoiningsRepository.countBy({ userListId: list.id, }); - if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).userEachUserListsLimit) { + if (currentCount > (await this.roleService.getUserPolicies(me.id)).userEachUserListsLimit) { throw new Error('Too many users'); } diff --git a/packages/backend/src/core/entities/RoleEntityService.ts b/packages/backend/src/core/entities/RoleEntityService.ts index 6a14775653..52f3374468 100644 --- a/packages/backend/src/core/entities/RoleEntityService.ts +++ b/packages/backend/src/core/entities/RoleEntityService.ts @@ -6,7 +6,7 @@ import type { Packed } from '@/misc/schema.js'; import type { User } from '@/models/entities/User.js'; import type { Role } from '@/models/entities/Role.js'; import { bindThis } from '@/decorators.js'; -import { DEFAULT_ROLE } from '@/core/RoleService.js'; +import { DEFAULT_POLICIES } from '@/core/RoleService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -40,9 +40,9 @@ export class RoleEntityService { roleId: role.id, }); - const roleOptions = { ...role.options }; - for (const [k, v] of Object.entries(DEFAULT_ROLE)) { - if (roleOptions[k] == null) roleOptions[k] = { + const policies = { ...role.policies }; + for (const [k, v] of Object.entries(DEFAULT_POLICIES)) { + if (policies[k] == null) policies[k] = { useDefault: true, priority: 0, value: v, @@ -62,7 +62,7 @@ export class RoleEntityService { isAdministrator: role.isAdministrator, isModerator: role.isModerator, canEditMembersByModerator: role.canEditMembersByModerator, - options: roleOptions, + policies: policies, usersCount: assigns.length, ...(opts.detail ? { users: this.userEntityService.packMany(assigns.map(x => x.userId), me), diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 6b754150cf..bf6f6f4553 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -423,7 +423,7 @@ export class UserEntityService implements OnModuleInit { bannerUrl: user.banner ? this.driveFileEntityService.getPublicUrl(user.banner, false) : null, bannerBlurhash: user.banner?.blurhash ?? null, isLocked: user.isLocked, - isSilenced: this.roleService.getUserRoleOptions(user.id).then(r => !r.canPublicNote), + isSilenced: this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote), isSuspended: user.isSuspended ?? falsy, description: profile!.description, location: profile!.location, @@ -496,7 +496,7 @@ export class UserEntityService implements OnModuleInit { } : {}), ...(opts.includeSecrets ? { - role: this.roleService.getUserRoleOptions(user.id), + policies: this.roleService.getUserPolicies(user.id), email: profile!.email, emailVerified: profile!.emailVerified, securityKeysList: profile!.twoFactorEnabled diff --git a/packages/backend/src/models/entities/Meta.ts b/packages/backend/src/models/entities/Meta.ts index e724ba9a49..5d222a6da1 100644 --- a/packages/backend/src/models/entities/Meta.ts +++ b/packages/backend/src/models/entities/Meta.ts @@ -458,5 +458,5 @@ export class Meta { @Column('jsonb', { default: { }, }) - public defaultRoleOverride: Record; + public policies: Record; } diff --git a/packages/backend/src/models/entities/Role.ts b/packages/backend/src/models/entities/Role.ts index d8d203493b..abd5f864a2 100644 --- a/packages/backend/src/models/entities/Role.ts +++ b/packages/backend/src/models/entities/Role.ts @@ -136,7 +136,7 @@ export class Role { @Column('jsonb', { default: { }, }) - public options: Record null) : null; - const baseRoleOptions = { ...DEFAULT_ROLE, ...meta.defaultRoleOverride }; + const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies }; return { software: { @@ -105,8 +105,8 @@ export class NodeinfoServerService { repositoryUrl: meta.repositoryUrl, feedbackUrl: meta.feedbackUrl, disableRegistration: meta.disableRegistration, - disableLocalTimeline: !baseRoleOptions.ltlAvailable, - disableGlobalTimeline: !baseRoleOptions.gtlAvailable, + disableLocalTimeline: !basePolicies.ltlAvailable, + disableGlobalTimeline: !basePolicies.gtlAvailable, emailRequiredForSignup: meta.emailRequiredForSignup, enableHcaptcha: meta.enableHcaptcha, enableRecaptcha: meta.enableRecaptcha, diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index dcc9342a82..395a1c468a 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -225,7 +225,7 @@ export class ApiCallService implements OnApplicationShutdown { } // TODO: 毎リクエスト計算するのもあれだしキャッシュしたい - const factor = user ? (await this.roleService.getUserRoleOptions(user.id)).rateLimitFactor : 1; + const factor = user ? (await this.roleService.getUserPolicies(user.id)).rateLimitFactor : 1; // Rate limit await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable }, limitActor, factor).catch(err => { @@ -274,9 +274,9 @@ export class ApiCallService implements OnApplicationShutdown { } } - if (ep.meta.requireRoleOption != null && !user!.isRoot) { - const myRole = await this.roleService.getUserRoleOptions(user!.id); - if (!myRole[ep.meta.requireRoleOption]) { + if (ep.meta.requireRolePolicy != null && !user!.isRoot) { + const policies = await this.roleService.getUserPolicies(user!.id); + if (!policies[ep.meta.requireRolePolicy]) { throw new ApiError({ message: 'You are not assigned to a required role.', code: 'ROLE_PERMISSION_DENIED', diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index aa88a9dd13..14927da7d6 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -65,7 +65,7 @@ import * as ep___admin_roles_show from './endpoints/admin/roles/show.js'; import * as ep___admin_roles_update from './endpoints/admin/roles/update.js'; import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js'; import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js'; -import * as ep___admin_roles_updateDefaultRoleOverride from './endpoints/admin/roles/update-default-role-override.js'; +import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js'; import * as ep___announcements from './endpoints/announcements.js'; import * as ep___antennas_create from './endpoints/antennas/create.js'; import * as ep___antennas_delete from './endpoints/antennas/delete.js'; @@ -399,7 +399,7 @@ const $admin_roles_show: Provider = { provide: 'ep:admin/roles/show', useClass: const $admin_roles_update: Provider = { provide: 'ep:admin/roles/update', useClass: ep___admin_roles_update.default }; const $admin_roles_assign: Provider = { provide: 'ep:admin/roles/assign', useClass: ep___admin_roles_assign.default }; const $admin_roles_unassign: Provider = { provide: 'ep:admin/roles/unassign', useClass: ep___admin_roles_unassign.default }; -const $admin_roles_updateDefaultRoleOverride: Provider = { provide: 'ep:admin/roles/update-default-role-override', useClass: ep___admin_roles_updateDefaultRoleOverride.default }; +const $admin_roles_updateDefaultPolicies: Provider = { provide: 'ep:admin/roles/update-default-policies', useClass: ep___admin_roles_updateDefaultPolicies.default }; const $announcements: Provider = { provide: 'ep:announcements', useClass: ep___announcements.default }; const $antennas_create: Provider = { provide: 'ep:antennas/create', useClass: ep___antennas_create.default }; const $antennas_delete: Provider = { provide: 'ep:antennas/delete', useClass: ep___antennas_delete.default }; @@ -737,7 +737,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $admin_roles_update, $admin_roles_assign, $admin_roles_unassign, - $admin_roles_updateDefaultRoleOverride, + $admin_roles_updateDefaultPolicies, $announcements, $antennas_create, $antennas_delete, @@ -1069,7 +1069,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $admin_roles_update, $admin_roles_assign, $admin_roles_unassign, - $admin_roles_updateDefaultRoleOverride, + $admin_roles_updateDefaultPolicies, $announcements, $antennas_create, $antennas_delete, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index f50a3b5dd2..54c4206ea4 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -64,7 +64,7 @@ import * as ep___admin_roles_show from './endpoints/admin/roles/show.js'; import * as ep___admin_roles_update from './endpoints/admin/roles/update.js'; import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js'; import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js'; -import * as ep___admin_roles_updateDefaultRoleOverride from './endpoints/admin/roles/update-default-role-override.js'; +import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js'; import * as ep___announcements from './endpoints/announcements.js'; import * as ep___antennas_create from './endpoints/antennas/create.js'; import * as ep___antennas_delete from './endpoints/antennas/delete.js'; @@ -396,7 +396,7 @@ const eps = [ ['admin/roles/update', ep___admin_roles_update], ['admin/roles/assign', ep___admin_roles_assign], ['admin/roles/unassign', ep___admin_roles_unassign], - ['admin/roles/update-default-role-override', ep___admin_roles_updateDefaultRoleOverride], + ['admin/roles/update-default-policies', ep___admin_roles_updateDefaultPolicies], ['announcements', ep___announcements], ['antennas/create', ep___antennas_create], ['antennas/delete', ep___antennas_delete], @@ -695,7 +695,7 @@ export interface IEndpointMeta { */ readonly requireAdmin?: boolean; - readonly requireRoleOption?: string; + readonly requireRolePolicy?: string; /** * エンドポイントのリミテーションに関するやつ 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 d114fd3d55..9b6c774f0c 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 @@ -8,7 +8,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireRoleOption: 'canManageCustomEmojis', + requireRolePolicy: 'canManageCustomEmojis', } as const; export const paramDef = { 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 52ccb74447..abca1d169d 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -14,7 +14,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireRoleOption: 'canManageCustomEmojis', + requireRolePolicy: 'canManageCustomEmojis', errors: { noSuchFile: { 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 4d1fdd989d..b4fc7fd6f5 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -14,7 +14,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireRoleOption: 'canManageCustomEmojis', + requireRolePolicy: 'canManageCustomEmojis', errors: { noSuchEmoji: { 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 27aa4fb1b1..ae45105b28 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 @@ -9,7 +9,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireRoleOption: 'canManageCustomEmojis', + requireRolePolicy: 'canManageCustomEmojis', } as const; export const paramDef = { 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 2531246569..e237d87d34 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -10,7 +10,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireRoleOption: 'canManageCustomEmojis', + requireRolePolicy: 'canManageCustomEmojis', errors: { noSuchEmoji: { 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 15f468c180..b4a07324bb 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 @@ -5,7 +5,7 @@ import { QueueService } from '@/core/QueueService.js'; export const meta = { secure: true, requireCredential: true, - requireRoleOption: 'canManageCustomEmojis', + requireRolePolicy: 'canManageCustomEmojis', } as const; export const paramDef = { 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 131c9ef223..d9ce97194a 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 @@ -11,7 +11,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireRoleOption: 'canManageCustomEmojis', + requireRolePolicy: 'canManageCustomEmojis', res: { type: 'array', 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 ef2bc936c3..1a6096f36f 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -11,7 +11,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireRoleOption: 'canManageCustomEmojis', + requireRolePolicy: 'canManageCustomEmojis', res: { type: 'array', 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 a70cd8d787..5fc9e024bf 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 @@ -8,7 +8,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireRoleOption: 'canManageCustomEmojis', + requireRolePolicy: 'canManageCustomEmojis', } as const; export const paramDef = { 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 b33e5662bb..8b5ba8fbf4 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 @@ -8,7 +8,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireRoleOption: 'canManageCustomEmojis', + requireRolePolicy: 'canManageCustomEmojis', } as const; export const paramDef = { 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 05834bc572..827b5ace7a 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 @@ -8,7 +8,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireRoleOption: 'canManageCustomEmojis', + requireRolePolicy: 'canManageCustomEmojis', } as const; export const paramDef = { 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 19645cb515..fb0ef12878 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -9,7 +9,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireRoleOption: 'canManageCustomEmojis', + requireRolePolicy: 'canManageCustomEmojis', errors: { noSuchEmoji: { diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index fd08a5f847..b393827054 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -4,7 +4,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { MetaService } from '@/core/MetaService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; -import { DEFAULT_ROLE } from '@/core/RoleService.js'; +import { DEFAULT_POLICIES } from '@/core/RoleService.js'; export const meta = { tags: ['meta'], @@ -440,7 +440,7 @@ export default class extends Endpoint { deeplIsPro: instance.deeplIsPro, enableIpLogging: instance.enableIpLogging, enableActiveEmailValidation: instance.enableActiveEmailValidation, - baseRole: { ...DEFAULT_ROLE, ...instance.defaultRoleOverride }, + policies: { ...DEFAULT_POLICIES, ...instance.policies }, }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/roles/create.ts b/packages/backend/src/server/api/endpoints/admin/roles/create.ts index a9216a6386..f136c6d624 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/create.ts @@ -25,7 +25,7 @@ export const paramDef = { isModerator: { type: 'boolean' }, isAdministrator: { type: 'boolean' }, canEditMembersByModerator: { type: 'boolean' }, - options: { + policies: { type: 'object', }, }, @@ -39,7 +39,7 @@ export const paramDef = { 'isModerator', 'isAdministrator', 'canEditMembersByModerator', - 'options', + 'policies', ], } as const; @@ -70,7 +70,7 @@ export default class extends Endpoint { isAdministrator: ps.isAdministrator, isModerator: ps.isModerator, canEditMembersByModerator: ps.canEditMembersByModerator, - options: ps.options, + policies: ps.policies, }).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0])); this.globalEventService.publishInternalEvent('roleCreated', created); diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts b/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts new file mode 100644 index 0000000000..6006816bcb --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts @@ -0,0 +1,42 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { RolesRepository } from '@/models/index.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '@/server/api/error.js'; +import { MetaService } from '@/core/MetaService.js'; + +export const meta = { + tags: ['admin', 'role'], + + requireCredential: true, + requireAdmin: true, +} as const; + +export const paramDef = { + type: 'object', + properties: { + policies: { + type: 'object', + }, + }, + required: [ + 'policies', + ], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + private metaService: MetaService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps) => { + await this.metaService.update({ + policies: ps.policies, + }); + this.globalEventService.publishInternalEvent('policiesUpdated', ps.policies); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update-default-role-override.ts b/packages/backend/src/server/api/endpoints/admin/roles/update-default-role-override.ts deleted file mode 100644 index 35da04efd2..0000000000 --- a/packages/backend/src/server/api/endpoints/admin/roles/update-default-role-override.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RolesRepository } from '@/models/index.js'; -import { GlobalEventService } from '@/core/GlobalEventService.js'; -import { DI } from '@/di-symbols.js'; -import { ApiError } from '@/server/api/error.js'; -import { MetaService } from '@/core/MetaService.js'; - -export const meta = { - tags: ['admin', 'role'], - - requireCredential: true, - requireAdmin: true, -} as const; - -export const paramDef = { - type: 'object', - properties: { - options: { - type: 'object', - }, - }, - required: [ - 'options', - ], -} as const; - -// eslint-disable-next-line import/no-default-export -@Injectable() -export default class extends Endpoint { - constructor( - private metaService: MetaService, - private globalEventService: GlobalEventService, - ) { - super(meta, paramDef, async (ps) => { - await this.metaService.update({ - defaultRoleOverride: ps.options, - }); - this.globalEventService.publishInternalEvent('defaultRoleOverrideUpdated', ps.options); - }); - } -} diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update.ts b/packages/backend/src/server/api/endpoints/admin/roles/update.ts index 4ca5124eda..fc4c3d8f11 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts @@ -33,7 +33,7 @@ export const paramDef = { isModerator: { type: 'boolean' }, isAdministrator: { type: 'boolean' }, canEditMembersByModerator: { type: 'boolean' }, - options: { + policies: { type: 'object', }, }, @@ -48,7 +48,7 @@ export const paramDef = { 'isModerator', 'isAdministrator', 'canEditMembersByModerator', - 'options', + 'policies', ], } as const; @@ -79,7 +79,7 @@ export default class extends Endpoint { isModerator: ps.isModerator, isAdministrator: ps.isAdministrator, canEditMembersByModerator: ps.canEditMembersByModerator, - options: ps.options, + policies: ps.policies, }); const updated = await this.rolesRepository.findOneByOrFail({ id: ps.roleId }); this.globalEventService.publishInternalEvent('roleUpdated', updated); 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 3f4ec299af..94603cc91a 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -52,7 +52,7 @@ export default class extends Endpoint { } const isModerator = await this.roleService.isModerator(user); - const isSilenced = !(await this.roleService.getUserRoleOptions(user.id)).canPublicNote; + const isSilenced = !(await this.roleService.getUserPolicies(user.id)).canPublicNote; const _me = await this.usersRepository.findOneByOrFail({ id: me.id }); if (!await this.roleService.isAdministrator(_me) && await this.roleService.isAdministrator(user)) { @@ -94,6 +94,7 @@ export default class extends Endpoint { lastActiveDate: user.lastActiveDate, moderationNote: profile.moderationNote, signins, + policies: await this.roleService.getUserPolicies(user.id), roles: await this.roleEntityService.packMany(roles, me, { detail: false }), }; }); diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 08625250c8..a1553b6a80 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -92,7 +92,7 @@ export default class extends Endpoint { const currentAntennasCount = await this.antennasRepository.countBy({ userId: me.id, }); - if (currentAntennasCount > (await this.roleService.getUserRoleOptions(me.id)).antennaLimit) { + if (currentAntennasCount > (await this.roleService.getUserPolicies(me.id)).antennaLimit) { throw new ApiError(meta.errors.tooManyAntennas); } 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 3cf096c242..f3f9c3477f 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -97,7 +97,7 @@ export default class extends Endpoint { const currentCount = await this.clipNotesRepository.countBy({ clipId: clip.id, }); - if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).noteEachClipsLimit) { + if (currentCount > (await this.roleService.getUserPolicies(me.id)).noteEachClipsLimit) { throw new ApiError(meta.errors.tooManyClipNotes); } diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index abc0288c89..c095de702c 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -54,7 +54,7 @@ export default class extends Endpoint { const currentCount = await this.clipsRepository.countBy({ userId: me.id, }); - if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).clipLimit) { + if (currentCount > (await this.roleService.getUserPolicies(me.id)).clipLimit) { throw new ApiError(meta.errors.tooManyClips); } diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts index 2a06792dcf..e5bbfecbcf 100644 --- a/packages/backend/src/server/api/endpoints/drive.ts +++ b/packages/backend/src/server/api/endpoints/drive.ts @@ -47,10 +47,10 @@ export default class extends Endpoint { // Calculate drive usage const usage = await this.driveFileEntityService.calcDriveUsageOf(me.id); - const myRole = await this.roleService.getUserRoleOptions(me.id); + const policies = await this.roleService.getUserPolicies(me.id); return { - capacity: 1024 * 1024 * myRole.driveCapacityMb, + capacity: 1024 * 1024 * policies.driveCapacityMb, usage: usage, }; }); diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index fe09eca674..b1eaab3908 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -173,7 +173,7 @@ export default class extends Endpoint { if (ps.mutedWords !== undefined) { // TODO: ちゃんと数える const length = JSON.stringify(ps.mutedWords).length; - if (length > (await this.roleService.getUserRoleOptions(user.id)).wordMuteLimit) { + if (length > (await this.roleService.getUserPolicies(user.id)).wordMuteLimit) { throw new ApiError(meta.errors.tooManyMutedWords); } 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 3d89b77a7b..51fcce6cf0 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -54,7 +54,7 @@ export default class extends Endpoint { const currentWebhooksCount = await this.webhooksRepository.countBy({ userId: me.id, }); - if (currentWebhooksCount > (await this.roleService.getUserRoleOptions(me.id)).webhookLimit) { + if (currentWebhooksCount > (await this.roleService.getUserPolicies(me.id)).webhookLimit) { throw new ApiError(meta.errors.tooManyWebhooks); } diff --git a/packages/backend/src/server/api/endpoints/invite.ts b/packages/backend/src/server/api/endpoints/invite.ts index 9b03cf4bb6..5d2c479e79 100644 --- a/packages/backend/src/server/api/endpoints/invite.ts +++ b/packages/backend/src/server/api/endpoints/invite.ts @@ -9,7 +9,7 @@ export const meta = { tags: ['meta'], requireCredential: true, - requireRoleOption: 'canInvite', + requireRolePolicy: 'canInvite', res: { type: 'object', diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index f46a32dfe7..89fa503173 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -7,7 +7,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { MetaService } from '@/core/MetaService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; -import { DEFAULT_ROLE } from '@/core/RoleService.js'; +import { DEFAULT_POLICIES } from '@/core/RoleService.js'; export const meta = { tags: ['meta'], @@ -334,7 +334,7 @@ export default class extends Endpoint { translatorAvailable: instance.deeplAuthKey != null, - baseRole: { ...DEFAULT_ROLE, ...instance.defaultRoleOverride }, + policies: { ...DEFAULT_POLICIES, ...instance.policies }, ...(ps.detail ? { pinnedPages: instance.pinnedPages, 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 081563493d..5d0cdc3fca 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -62,8 +62,8 @@ export default class extends Endpoint { private activeUsersChart: ActiveUsersChart, ) { super(meta, paramDef, async (ps, me) => { - const role = await this.roleService.getUserRoleOptions(me ? me.id : null); - if (!role.gtlAvailable) { + const policies = await this.roleService.getUserPolicies(me ? me.id : null); + if (!policies.gtlAvailable) { throw new ApiError(meta.errors.gtlDisabled); } 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 b2c504448e..2819abb125 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -71,8 +71,8 @@ export default class extends Endpoint { private activeUsersChart: ActiveUsersChart, ) { super(meta, paramDef, async (ps, me) => { - const role = await this.roleService.getUserRoleOptions(me.id); - if (!role.ltlAvailable) { + const policies = await this.roleService.getUserPolicies(me.id); + if (!policies.ltlAvailable) { throw new ApiError(meta.errors.stlDisabled); } 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 6361edc310..f396f7e584 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -67,8 +67,8 @@ export default class extends Endpoint { private activeUsersChart: ActiveUsersChart, ) { super(meta, paramDef, async (ps, me) => { - const role = await this.roleService.getUserRoleOptions(me ? me.id : null); - if (!role.ltlAvailable) { + const policies = await this.roleService.getUserPolicies(me ? me.id : null); + if (!policies.ltlAvailable) { throw new ApiError(meta.errors.ltlDisabled); } 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 22e5e3ce78..a840c1a04e 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -55,7 +55,7 @@ export default class extends Endpoint { const currentCount = await this.userListsRepository.countBy({ userId: me.id, }); - if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).userListLimit) { + if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) { throw new ApiError(meta.errors.tooManyUserLists); } diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index 185c813869..43d8907fc9 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -29,8 +29,8 @@ class GlobalTimelineChannel extends Channel { @bindThis public async init(params: any) { - const role = await this.roleService.getUserRoleOptions(this.user ? this.user.id : null); - if (!role.gtlAvailable) return; + const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); + if (!policies.gtlAvailable) return; // Subscribe events this.subscriber.on('notesStream', this.onNote); diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index a0f75f202c..340f677815 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -30,8 +30,8 @@ class HybridTimelineChannel extends Channel { @bindThis public async init(params: any): Promise { - const role = await this.roleService.getUserRoleOptions(this.user ? this.user.id : null); - if (!role.ltlAvailable) return; + const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); + if (!policies.ltlAvailable) return; // Subscribe events this.subscriber.on('notesStream', this.onNote); diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index 7d76f42fe7..ea29e30d63 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -28,8 +28,8 @@ class LocalTimelineChannel extends Channel { @bindThis public async init(params: any) { - const role = await this.roleService.getUserRoleOptions(this.user ? this.user.id : null); - if (!role.ltlAvailable) return; + const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); + if (!policies.ltlAvailable) return; // Subscribe events this.subscriber.on('notesStream', this.onNote); diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 03837baefb..a442529bb3 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -30,7 +30,7 @@ export interface InternalStreamTypes { remoteUserUpdated: Serialized<{ id: User['id']; }>; follow: Serialized<{ followerId: User['id']; followeeId: User['id']; }>; unfollow: Serialized<{ followerId: User['id']; followeeId: User['id']; }>; - defaultRoleOverrideUpdated: Serialized; + policiesUpdated: Serialized; roleCreated: Serialized; roleDeleted: Serialized; roleUpdated: Serialized; diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 0f67cec0b2..d0be0a8c39 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -36,20 +36,20 @@ - +
- +
- + - + - +
@@ -57,15 +57,15 @@ - +
- + - + - +
@@ -73,15 +73,15 @@ - +
- + - + - +
@@ -89,15 +89,15 @@ - +
- + - + - +
@@ -105,15 +105,15 @@ - +
- + - + - +
@@ -121,15 +121,15 @@ - +
- + - + - +
@@ -137,15 +137,15 @@ - +
- + - + - +
@@ -153,14 +153,14 @@ - +
- + - + - +
@@ -168,14 +168,14 @@ - +
- + - + - +
@@ -183,15 +183,15 @@ - +
- + - + - +
@@ -199,14 +199,14 @@ - +
- + - + - +
@@ -214,14 +214,14 @@ - +
- + - + - +
@@ -229,14 +229,14 @@ - +
- + - + - +
@@ -244,14 +244,14 @@ - +
- + - + - +
@@ -259,14 +259,14 @@ - +
- + - + - +
@@ -306,7 +306,7 @@ import * as os from '@/os'; import { i18n } from '@/i18n'; import { instance } from '@/instance'; -const ROLE_OPTIONS = [ +const ROLE_POLICIES = [ 'gtlAvailable', 'ltlAvailable', 'canPublicNote', @@ -345,13 +345,13 @@ let condFormula = $ref(role?.condFormula ?? { id: uuid(), type: 'isRemote' }); let isPublic = $ref(role?.isPublic ?? false); let canEditMembersByModerator = $ref(role?.canEditMembersByModerator ?? false); -const options = reactive>({}); -for (const ROLE_OPTION of ROLE_OPTIONS) { - const _options = role?.options ?? {}; - options[ROLE_OPTION] = { - useDefault: _options[ROLE_OPTION]?.useDefault ?? true, - priority: _options[ROLE_OPTION]?.priority ?? 0, - value: _options[ROLE_OPTION]?.value ?? instance.baseRole[ROLE_OPTION], +const policies = reactive>({}); +for (const ROLE_POLICY of ROLE_POLICIES) { + const _policies = role?.policies ?? {}; + policies[ROLE_POLICY] = { + useDefault: _policies[ROLE_POLICY]?.useDefault ?? true, + priority: _policies[ROLE_POLICY]?.priority ?? 0, + value: _policies[ROLE_POLICY]?.value ?? instance.policies[ROLE_POLICY], }; } @@ -381,7 +381,7 @@ async function save() { isModerator: rolePermission === 'moderator', isPublic, canEditMembersByModerator, - options, + policies, }); emit('updated'); } else { @@ -395,7 +395,7 @@ async function save() { isModerator: rolePermission === 'moderator', isPublic, canEditMembersByModerator, - options, + policies, }); emit('created', created); } diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index 1ceead8b6c..f074069df0 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -10,114 +10,114 @@
- - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + @@ -134,7 +134,7 @@